diff --git a/example/lib/widgets/components/widgets/slider_example.dart b/example/lib/widgets/components/widgets/slider_example.dart index bf2cdc82..41c00473 100644 --- a/example/lib/widgets/components/widgets/slider_example.dart +++ b/example/lib/widgets/components/widgets/slider_example.dart @@ -67,6 +67,9 @@ GSSlider( isDisabled: $isDisabled, isReversed: $isReversed, orientation: $selectedOrientationOption, + defaultValue: 3, + minValue: 0, + maxValue: 10, onChanged: (value) { setState(() { currentValue = value; @@ -87,9 +90,11 @@ GSSlider( isDisabled: isDisabled, isReversed: isReversed, orientation: selectedOrientationOption, + defaultValue: 3, + minValue: 0, + maxValue: 10, onChanged: (value) { - setState(() { - }); + setState(() {}); }, ), controls: Column( diff --git a/example/lib/widgets/storybook/widgets/slider_story.dart b/example/lib/widgets/storybook/widgets/slider_story.dart index 438c7ef3..707c6713 100644 --- a/example/lib/widgets/storybook/widgets/slider_story.dart +++ b/example/lib/widgets/storybook/widgets/slider_story.dart @@ -13,19 +13,17 @@ final class SliderStory extends StoryWidget { return Story( name: storyName, builder: (context) => GSSlider( - size: GSSliderSizes.values[context.knobs - .options(label: 'Size', initial: 0, options: sizeOptions)], - orientation: GSOrientations.values[context.knobs.options( - label: 'Orientation', - initial: 0, - options: orientationOptions)], - isDisabled: - context.knobs.boolean(label: "isDisabled", initial: false), - - isReversed: - context.knobs.boolean(label: "isReversed", initial: false), - // style: GSStyle(width: 30), - )); + size: GSSliderSizes.values[context.knobs + .options(label: 'Size', initial: 0, options: sizeOptions)], + orientation: GSOrientations.values[context.knobs.options( + label: 'Orientation', initial: 0, options: orientationOptions)], + isDisabled: + context.knobs.boolean(label: "isDisabled", initial: false), + isReversed: + context.knobs.boolean(label: "isReversed", initial: false), + minValue: 0, + maxValue: 10, + defaultValue: 3)); } @override diff --git a/lib/src/widgets/gs_app/gs_app.dart b/lib/src/widgets/gs_app/gs_app.dart index fab61f54..7c047e05 100644 --- a/lib/src/widgets/gs_app/gs_app.dart +++ b/lib/src/widgets/gs_app/gs_app.dart @@ -284,8 +284,8 @@ class _GSAppState extends State { localeListResolutionCallback: widget.localeListResolutionCallback, supportedLocales: widget.supportedLocales, showPerformanceOverlay: widget.showPerformanceOverlay, - checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, - checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, + // checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, + // checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, showSemanticsDebugger: widget.showSemanticsDebugger, debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, shortcuts: widget.shortcuts, @@ -310,8 +310,8 @@ class _GSAppState extends State { localeResolutionCallback: widget.localeResolutionCallback, supportedLocales: widget.supportedLocales, showPerformanceOverlay: widget.showPerformanceOverlay, - checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, - checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, + // checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, + // checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, showSemanticsDebugger: widget.showSemanticsDebugger, debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, shortcuts: widget.shortcuts, diff --git a/lib/src/widgets/gs_form_control/gs_form_control.dart b/lib/src/widgets/gs_form_control/gs_form_control.dart index bd4de956..fe5c19da 100644 --- a/lib/src/widgets/gs_form_control/gs_form_control.dart +++ b/lib/src/widgets/gs_form_control/gs_form_control.dart @@ -16,7 +16,7 @@ Form Control Compatible Components: class GSFormControl extends StatefulWidget { final GlobalKey formKey; final Widget child; - final PopInvokedCallback? onPopInvoked; + // final PopInvokedCallback? onPopInvoked; final VoidCallback? onChanged; final AutovalidateMode autovalidateMode; final bool? canPop; @@ -32,7 +32,7 @@ class GSFormControl extends StatefulWidget { const GSFormControl({ super.key, required this.child, - this.onPopInvoked, + // this.onPopInvoked, this.onChanged, this.autovalidateMode = AutovalidateMode.disabled, this.canPop, @@ -84,7 +84,7 @@ class _GSFormControlState extends State { key: widget.formKey, canPop: widget.canPop, onChanged: widget.onChanged, - onPopInvoked: widget.onPopInvoked, + // onPopInvoked: widget.onPopInvoked, autovalidateMode: widget.autovalidateMode, child: widget.child, ), diff --git a/lib/src/widgets/gs_radio/gs_radio_icon.dart b/lib/src/widgets/gs_radio/gs_radio_icon.dart index 9689cb76..6e8c1e58 100644 --- a/lib/src/widgets/gs_radio/gs_radio_icon.dart +++ b/lib/src/widgets/gs_radio/gs_radio_icon.dart @@ -2,8 +2,6 @@ import 'package:gluestack_ui/src/style/gs_config_style_internal.dart'; import 'package:gluestack_ui/src/style/style_resolver.dart'; import 'package:gluestack_ui/src/widgets/gs_radio/gs_radio_icon_style.dart'; -import 'package:gluestack_ui/src/widgets/gs_radio/gs_radio_raw.dart'; - class GSRadioIcon extends StatelessWidget { final Color? activeColor; final bool autofocus; diff --git a/lib/src/widgets/gs_radio/gs_radio_raw.dart b/lib/src/widgets/gs_radio/gs_radio_raw.dart index 4e29b440..00a078b5 100644 --- a/lib/src/widgets/gs_radio/gs_radio_raw.dart +++ b/lib/src/widgets/gs_radio/gs_radio_raw.dart @@ -1,7 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/cupertino.dart'; import 'package:gluestack_ui/gluestack_ui.dart'; -import 'gs_toggleable.dart'; const Size _size = Size(18.0, 18.0); const double _kOuterRadius = 8.0; diff --git a/lib/src/widgets/gs_radio/public.dart b/lib/src/widgets/gs_radio/public.dart index cec54ad8..35d80cfe 100644 --- a/lib/src/widgets/gs_radio/public.dart +++ b/lib/src/widgets/gs_radio/public.dart @@ -2,3 +2,4 @@ export 'gs_radio_icon.dart'; export 'gs_radio_provider.dart'; export 'gs_radio_text.dart'; export 'gs_radio.dart'; +export 'gs_radio_raw.dart'; diff --git a/lib/src/widgets/gs_slider/gs_slider.dart b/lib/src/widgets/gs_slider/gs_slider.dart index b6c7a778..dc89fc82 100644 --- a/lib/src/widgets/gs_slider/gs_slider.dart +++ b/lib/src/widgets/gs_slider/gs_slider.dart @@ -1,7 +1,8 @@ -import 'package:flutter/material.dart'; -import 'package:gluestack_ui/src/style/gs_config_style_internal.dart'; +import 'package:flutter/services.dart'; +import 'package:gluestack_ui/gluestack_ui.dart'; import 'package:gluestack_ui/src/style/style_resolver.dart'; import 'package:gluestack_ui/src/widgets/gs_slider/gs_slider_filled_track_style.dart'; +import 'package:gluestack_ui/src/widgets/gs_slider/gs_slider_painter.dart'; import 'package:gluestack_ui/src/widgets/gs_slider/gs_slider_style.dart'; import 'package:gluestack_ui/src/widgets/gs_slider/gs_slider_thumb_style.dart'; import 'package:gluestack_ui/src/widgets/gs_slider/gs_slider_track_style.dart'; @@ -10,33 +11,47 @@ import 'package:gluestack_ui/src/widgets/gs_style_builder/gs_style_builder.dart' class GSSlider extends StatefulWidget { final bool isReversed; final bool isDisabled; - final double max; - final double min; + final double maxValue; + final double minValue; final GSSliderSizes? size; final GSOrientations? orientation; final GSStyle? style; final ValueChanged? onChanged; + final int? step; + final double? defaultValue; + const GSSlider( {super.key, this.style, this.size, - this.min = 0, - this.max = 10, + this.minValue = 0, + this.maxValue = 10, this.isReversed = false, this.isDisabled = false, - this.orientation, - this.onChanged}); + this.orientation = GSOrientations.horizontal, + this.onChanged, + this.step, + this.defaultValue}); @override State createState() => _GSSliderState(); } class _GSSliderState extends State { - late double _sliderValue; + late double _currentValue; + late FocusNode _focusNode; + @override void initState() { super.initState(); - _sliderValue = widget.min; + _currentValue = widget.defaultValue ?? widget.minValue; + _focusNode = FocusNode(); + } + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); } @override @@ -46,8 +61,9 @@ class _GSSliderState extends State { widget.orientation ?? sliderStyle.props?.orientation; return GSStyleBuilder( - isDisabled: widget.isDisabled, - child: Builder(builder: (context) { + isDisabled: widget.isDisabled, + child: Builder( + builder: (context) { GSConfigStyle styler = resolveStyles( context: context, styles: [ @@ -60,9 +76,10 @@ class _GSSliderState extends State { ); GSConfigStyle thumbStyler = resolveStyles( - context: context, - styles: [sliderThumbStyle], - inlineStyle: widget.style); + context: context, + styles: [sliderThumbStyle], + inlineStyle: widget.style, + ); GSConfigStyle trackStyler = resolveStyles( context: context, @@ -75,12 +92,67 @@ class _GSSliderState extends State { styles: [sliderFilledTrackStyle], inlineStyle: widget.style, ); - int quarterTurns; - if (widget.orientation == GSOrientations.horizontal) { - quarterTurns = 0; - } else { - quarterTurns = widget.isReversed ? 1 : 3; + + void updateValue(double newValue) { + setState(() { + if (widget.step != null) { + final int step = widget.step!; + final double divisionWidth = + (widget.maxValue - widget.minValue) / step; + _currentValue = + ((newValue / divisionWidth).round() * divisionWidth) + .clamp(widget.minValue, widget.maxValue); + } else { + _currentValue = + newValue.clamp(widget.minValue, widget.maxValue); + } + if (widget.onChanged != null) { + widget.onChanged!(_currentValue); + } + }); + } + + void handleKeyEvent(KeyEvent event) { + if (event is KeyDownEvent) { + final double step = widget.step != null + ? (widget.maxValue - widget.minValue) / widget.step! + : (widget.maxValue - widget.minValue) / 100; + if (event.logicalKey == LogicalKeyboardKey.arrowRight || + event.logicalKey == LogicalKeyboardKey.arrowUp) { + updateValue(_currentValue + (widget.isReversed ? -step : step)); + } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft || + event.logicalKey == LogicalKeyboardKey.arrowDown) { + updateValue(_currentValue + (widget.isReversed ? step : -step)); + } + } + } + + void handleDragUpdate(DragUpdateDetails details, double length) { + double delta = widget.orientation == GSOrientations.horizontal + ? details.localPosition.dx + : details.localPosition.dy; + if (widget.isReversed) { + delta = length - delta; + } + final double newValue = + (delta / length) * (widget.maxValue - widget.minValue) + + widget.minValue; + updateValue(newValue); + } + + void handleTapDown(TapDownDetails details, double length) { + double delta = widget.orientation == GSOrientations.horizontal + ? details.localPosition.dx + : details.localPosition.dy; + if (widget.isReversed) { + delta = length - delta; + } + final double newValue = + (delta / length) * (widget.maxValue - widget.minValue) + + widget.minValue; + updateValue(newValue); } + return GSAncestor( decedentStyles: styler.descendantStyles, child: Directionality( @@ -88,46 +160,81 @@ class _GSSliderState extends State { widget.orientation == GSOrientations.horizontal ? TextDirection.rtl : TextDirection.ltr, - child: RotatedBox( - quarterTurns: quarterTurns, - child: Opacity( - opacity: widget.isDisabled ? 0.6 : 1, - child: SliderTheme( - data: SliderTheme.of(context).copyWith( - activeTrackColor: filledTrackStyler.bg?.getColor(context), - inactiveTrackColor: trackStyler.bg?.getColor(context), - thumbColor: thumbStyler.bg?.getColor(context), - trackHeight: - widget.orientation == GSOrientations.horizontal - ? styler.trackHeight - : styler.trackWidth ?? 10, - trackShape: const RoundedRectSliderTrackShape(), - thumbShape: RoundSliderThumbShape( - enabledThumbRadius: (styler.thumbHeight ?? 40) / 2), - overlayShape: RoundSliderOverlayShape( - overlayRadius: (styler.thumbHeight ?? 40) / 2), - ), - child: Slider( - value: _sliderValue, - min: widget.min, - max: widget.max, - label: _sliderValue.round().toString(), - onChanged: widget.isDisabled - ? (value) {} - : (double value) { - setState(() { - _sliderValue = value; - }); - if (widget.onChanged != null) { - widget.onChanged!(value); - } - }, - ), - ), + child: Opacity( + opacity: widget.isDisabled ? 0.6 : 1, + child: LayoutBuilder( + builder: (context, constraints) { + double length = + widget.orientation == GSOrientations.horizontal + ? (styler.trackWidth ?? constraints.maxWidth) + .clamp(0.0, constraints.maxWidth) + : (styler.trackHeight ?? constraints.maxHeight) + .clamp(0.0, constraints.maxHeight); + if (length == double.infinity) { + length = styler.trackHeight ?? 200; + } + final double thickness = styler.thumbHeight ?? 20.00; + return Focus( + focusNode: _focusNode, + onKeyEvent: (FocusNode node, KeyEvent event) { + handleKeyEvent(event); + return KeyEventResult.handled; + }, + child: GestureDetector( + onHorizontalDragUpdate: widget.isDisabled || + widget.orientation == GSOrientations.vertical + ? null + : (details) { + _focusNode.requestFocus(); + handleDragUpdate(details, length); + }, + onVerticalDragUpdate: widget.isDisabled || + widget.orientation == GSOrientations.horizontal + ? null + : (details) { + _focusNode.requestFocus(); + handleDragUpdate(details, length); + }, + onTapDown: widget.isDisabled + ? null + : (details) { + _focusNode.requestFocus(); + handleTapDown(details, length); + }, + child: CustomPaint( + size: widget.orientation == GSOrientations.horizontal + ? Size(length, thickness) + : Size(thickness, length), + painter: SliderPainter( + thumbHeight: styler.thumbHeight, + context: context, + value: _currentValue, + minValue: widget.minValue, + maxValue: widget.maxValue, + trackWidth: widget.orientation == + GSOrientations.horizontal + ? styler.trackHeight + : styler.trackWidth ?? 5, + step: widget.step, + reverse: widget.isReversed, + length: length, + thickness: thickness, + orientation: widget.orientation ?? + GSOrientations.horizontal, + styler: styler, + thumbStyler: thumbStyler, + trackStyler: trackStyler, + filledTrackStyler: filledTrackStyler), + ), + ), + ); + }, ), ), ), ); - })); + }, + ), + ); } } diff --git a/lib/src/widgets/gs_slider/gs_slider_painter.dart b/lib/src/widgets/gs_slider/gs_slider_painter.dart new file mode 100644 index 00000000..e2094410 --- /dev/null +++ b/lib/src/widgets/gs_slider/gs_slider_painter.dart @@ -0,0 +1,154 @@ +import 'package:gluestack_ui/gluestack_ui.dart'; + +class SliderPainter extends CustomPainter { + final double value; + final double minValue; + final double maxValue; + final int? step; + final bool reverse; + final double length; + final double thickness; + final GSOrientations orientation; + final double? trackWidth; + final double? thumbHeight; + final GSConfigStyle? filledTrackStyler; + final GSConfigStyle? trackStyler; + final BuildContext context; + final GSConfigStyle? thumbStyler; + final GSConfigStyle styler; + + SliderPainter({ + required this.value, + required this.minValue, + required this.maxValue, + this.step, + required this.reverse, + required this.length, + required this.thickness, + required this.orientation, + required this.trackWidth, + this.filledTrackStyler, + required this.context, + this.trackStyler, + this.thumbStyler, + required this.styler, + required this.thumbHeight, + }); + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; + + @override + void paint(Canvas canvas, Size size) { + final Paint filledTrackPaint = Paint() + ..color = (styler.trackColorTrue?.getColor(context) ?? + filledTrackStyler?.bg?.getColor(context) ?? + GSTheme.of(context).background100)! + ..strokeWidth = trackWidth ?? 5.0 + ..strokeCap = StrokeCap.round; + + final Paint unfilledTrackPaint = Paint() + ..color = styler.trackColorFalse?.getColor(context) ?? + trackStyler?.bg?.getColor(context) ?? + GSTheme.of(context).background100! + ..strokeWidth = trackWidth ?? 5.0 + ..strokeCap = StrokeCap.round; + + final Paint thumbPaint = Paint() + ..color = styler.thumbColor?.getColor(context) ?? + thumbStyler?.bg?.getColor(context) ?? + GSTheme.of(context).background600! + ..style = PaintingStyle.fill; + + // Calculate thumb radius + final double thumbRadius = (thumbHeight ?? 20.0) / 2; + + // Adjust trackStart and trackEnd to accommodate the thumb radius + final double trackStart = thumbRadius; + final double trackEnd = length - thumbRadius; + + // Calculate the thumb position on the track + final double thumbPos = + ((value - minValue) / (maxValue - minValue)) * (trackEnd - trackStart) + trackStart; + + // Adjust the thumb position if the slider is reversed + final double adjustedThumbPos = + reverse ? trackEnd - (thumbPos - trackStart) : thumbPos; + + if (orientation == GSOrientations.horizontal) { + if (reverse) { + // Draw the filled track + canvas.drawLine( + Offset(adjustedThumbPos, thickness / 2), + Offset(trackEnd, thickness / 2), + filledTrackPaint, + ); + + // Draw the unfilled track + canvas.drawLine( + Offset(trackStart, thickness / 2), + Offset(adjustedThumbPos, thickness / 2), + unfilledTrackPaint, + ); + } else { + // Draw the filled track + canvas.drawLine( + Offset(trackStart, thickness / 2), + Offset(adjustedThumbPos, thickness / 2), + filledTrackPaint, + ); + + // Draw the unfilled track + canvas.drawLine( + Offset(adjustedThumbPos, thickness / 2), + Offset(trackEnd, thickness / 2), + unfilledTrackPaint, + ); + } + + // Draw the thumb + canvas.drawCircle( + Offset(adjustedThumbPos, thickness / 2), + thumbRadius, + thumbPaint, + ); + } else { + if (reverse) { + // Draw the filled track + canvas.drawLine( + Offset(thickness / 2, adjustedThumbPos), + Offset(thickness / 2, trackEnd), + filledTrackPaint, + ); + + // Draw the unfilled track + canvas.drawLine( + Offset(thickness / 2, trackStart), + Offset(thickness / 2, adjustedThumbPos), + unfilledTrackPaint, + ); + } else { + // Draw the filled track + canvas.drawLine( + Offset(thickness / 2, trackStart), + Offset(thickness / 2, adjustedThumbPos), + filledTrackPaint, + ); + + // Draw the unfilled track + canvas.drawLine( + Offset(thickness / 2, adjustedThumbPos), + Offset(thickness / 2, trackEnd), + unfilledTrackPaint, + ); + } + + // Draw the thumb + canvas.drawCircle( + Offset(thickness / 2, adjustedThumbPos), + thumbRadius, + thumbPaint, + ); + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 65e53471..781ef7a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^3.0.0 + flutter_lints: ^4.0.0 flutter: