mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-09 07:28:14 -07:00
Init repo
This commit is contained in:
commit
764b6034e3
566 changed files with 212335 additions and 0 deletions
202
lib/widgets/shared/fladder_slider.dart
Normal file
202
lib/widgets/shared/fladder_slider.dart
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
import 'package:fladder/util/num_extension.dart';
|
||||
import 'package:fladder/widgets/gapped_container_shape.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
double normalize(double min, double max, double value) {
|
||||
return (value - min) / (max - min);
|
||||
}
|
||||
|
||||
class FladderSlider extends StatefulWidget {
|
||||
final double value;
|
||||
final double min;
|
||||
final double max;
|
||||
final int? divisions;
|
||||
final double thumbWidth;
|
||||
final bool showThumb;
|
||||
final Duration animation;
|
||||
final Function(double value)? onChanged;
|
||||
final Function(double value)? onChangeStart;
|
||||
final Function(double value)? onChangeEnd;
|
||||
|
||||
const FladderSlider({
|
||||
required this.value,
|
||||
this.min = 0.0,
|
||||
this.max = 1.0,
|
||||
this.divisions,
|
||||
this.onChanged,
|
||||
this.thumbWidth = 6.5,
|
||||
this.showThumb = true,
|
||||
this.animation = const Duration(milliseconds: 100),
|
||||
this.onChangeStart,
|
||||
this.onChangeEnd,
|
||||
super.key,
|
||||
}) : assert(value >= min || value <= max);
|
||||
|
||||
@override
|
||||
FladderSliderState createState() => FladderSliderState();
|
||||
}
|
||||
|
||||
class FladderSliderState extends State<FladderSlider> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
double _currentValue = 0.0;
|
||||
bool hovering = false;
|
||||
bool dragging = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentValue = widget.value;
|
||||
_controller = AnimationController(vsync: this, duration: widget.animation);
|
||||
_animation = Tween<double>(begin: widget.value, end: widget.value).animate(_controller);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant FladderSlider oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.value != widget.value || oldWidget.divisions != widget.divisions) {
|
||||
double newValue = widget.value;
|
||||
|
||||
if (widget.divisions != null) {
|
||||
final stepSize = (widget.max - widget.min) / widget.divisions!;
|
||||
newValue = ((newValue - widget.min) / stepSize).round() * stepSize + widget.min;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_currentValue = newValue;
|
||||
});
|
||||
|
||||
_animation = Tween<double>(begin: _animation.value, end: _currentValue).animate(_controller);
|
||||
_controller.forward(from: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
double normalize(double min, double max, double value) {
|
||||
return (value - min) / (max - min);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final thumbWidth = widget.thumbWidth;
|
||||
final height = Theme.of(context).sliderTheme.trackHeight ?? 24.0;
|
||||
|
||||
double calculateChange(double offset, double width) {
|
||||
double relativeOffset = (offset / width).clamp(0.0, 1.0);
|
||||
double newValue = (widget.max - widget.min) * relativeOffset + widget.min;
|
||||
|
||||
if (widget.divisions != null) {
|
||||
final stepSize = (widget.max - widget.min) / widget.divisions!;
|
||||
newValue = ((newValue - widget.min) / stepSize).round() * stepSize + widget.min;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_currentValue = newValue.clamp(widget.min, widget.max);
|
||||
});
|
||||
|
||||
return _currentValue.roundTo(2);
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: height * 4,
|
||||
color: Colors.transparent,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth;
|
||||
final divisionSize = 5.0 * 0.95;
|
||||
final stepSize = constraints.maxWidth / (widget.divisions ?? 1);
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (event) => setState(() => hovering = true),
|
||||
onExit: (event) => setState(() => hovering = false),
|
||||
child: GestureDetector(
|
||||
onTapUp: (details) => widget.onChangeEnd?.call(calculateChange(details.localPosition.dx, width)),
|
||||
onTapDown: (details) => widget.onChanged?.call(calculateChange(details.localPosition.dx, width)),
|
||||
onHorizontalDragStart: (details) {
|
||||
setState(() {
|
||||
dragging = true;
|
||||
});
|
||||
widget.onChangeStart?.call(calculateChange(details.localPosition.dx, width));
|
||||
},
|
||||
onHorizontalDragEnd: (details) {
|
||||
setState(() {
|
||||
dragging = false;
|
||||
});
|
||||
widget.onChangeEnd?.call(calculateChange(details.localPosition.dx, width));
|
||||
},
|
||||
onHorizontalDragUpdate: (details) =>
|
||||
widget.onChanged?.call(calculateChange(details.localPosition.dx, width)),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) {
|
||||
final relativeValue = normalize(widget.min, widget.max, _animation.value);
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: height,
|
||||
width: constraints.maxWidth,
|
||||
child: GappedContainerShape(
|
||||
thumbPosition: relativeValue,
|
||||
),
|
||||
),
|
||||
if (widget.divisions != null && stepSize > divisionSize * 3)
|
||||
...List.generate(
|
||||
widget.divisions! + 1,
|
||||
(index) {
|
||||
final offset = (stepSize * index)
|
||||
.clamp(divisionSize / 1.2, constraints.maxWidth - divisionSize / 1.2);
|
||||
final active = (1.0 / widget.divisions!) * index > relativeValue;
|
||||
return Positioned(
|
||||
left: offset - divisionSize / 2,
|
||||
child: Container(
|
||||
width: divisionSize,
|
||||
height: divisionSize,
|
||||
decoration: BoxDecoration(
|
||||
color: active
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onPrimary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// Thumb
|
||||
if (widget.showThumb)
|
||||
Positioned(
|
||||
left:
|
||||
(width * relativeValue).clamp(thumbWidth / 2, width - thumbWidth / 2) - thumbWidth / 2,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 125),
|
||||
height: (hovering || dragging) ? height * 3 : height,
|
||||
width: thumbWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: (hovering || dragging)
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue