diff --git a/modules/ensemble/lib/util/utils.dart b/modules/ensemble/lib/util/utils.dart index 0ee09e761..7e0bb1829 100644 --- a/modules/ensemble/lib/util/utils.dart +++ b/modules/ensemble/lib/util/utils.dart @@ -1,8 +1,6 @@ import 'dart:io'; import 'dart:math'; -import 'dart:ui'; import 'package:ensemble/ensemble.dart'; -import 'package:ensemble/ensemble_app.dart'; import 'package:ensemble/framework/stub/location_manager.dart'; import 'package:ensemble/framework/theme/theme_manager.dart'; import 'package:ensemble_ts_interpreter/invokables/UserLocale.dart'; @@ -11,7 +9,6 @@ import 'package:path/path.dart' as p; import 'package:ensemble/framework/error_handling.dart'; import 'package:ensemble/framework/extensions.dart'; import 'package:ensemble/framework/model.dart'; -import 'package:ensemble/framework/scope.dart'; import 'package:ensemble/widget/helpers/controllers.dart'; import 'package:ensemble_ts_interpreter/invokables/invokableprimitives.dart'; import 'package:flutter/foundation.dart'; @@ -77,6 +74,32 @@ class Utils { return rtn; } + /// Expects a number or a String containing 2 numbers values separated by whitespace + /// (e.g. 1 or "1 4") + static RangeValues? getRangeValues(dynamic value) { + if (value is num) { + final start = optionalDouble(value); + if (start != null) { + return RangeValues(start, start); + } + } else if (value is String) { + final List valuesList = value.split(RegExp('\\s+')); + if (valuesList.length == 1) { + final start = optionalDouble(valuesList[0]); + if (start != null) { + return RangeValues(start, start); + } + } else if (valuesList.length == 2) { + final start = optionalDouble(valuesList[0]); + final end = optionalDouble(valuesList[1]); + if (start != null && end != null) { + return RangeValues(start, end); + } + } + } + return null; + } + /// expect a value in seconds static Duration? getDuration(dynamic value) { double? number = optionalDouble(value, min: 0); @@ -549,7 +572,8 @@ class Utils { decorationStyle: TextDecorationStyle.values.from(style['decorationStyle']), decorationColor: Utils.getColor(style['decorationColor']), - decorationThickness: Utils.optionalDouble(style['decorationThickness']), + decorationThickness: + Utils.optionalDouble(style['decorationThickness']), overflow: TextOverflow.values.from(style['overflow']), letterSpacing: Utils.optionalDouble(style['letterSpacing']), wordSpacing: Utils.optionalDouble(style['wordSpacing'])); diff --git a/modules/ensemble/lib/widget/input/slider/composites/thumb_style.dart b/modules/ensemble/lib/widget/input/slider/composites/thumb_style.dart index 73fd3f649..cbb07cc75 100644 --- a/modules/ensemble/lib/widget/input/slider/composites/thumb_style.dart +++ b/modules/ensemble/lib/widget/input/slider/composites/thumb_style.dart @@ -81,8 +81,7 @@ class ThumbStyleComposite extends WidgetCompositeProperty { Utils.getDouble(payload['elevation'], fallback: 1.0); composite.pressedElevation = Utils.getDouble(payload['pressedElevation'], fallback: 2.0); - composite.thumbColor = - Utils.getColor(payload['thumbColor']); + composite.thumbColor = Utils.getColor(payload['thumbColor']); composite.disabledThumbColor = Utils.getColor(payload['disabledThumbColor']); composite.borderWidth = @@ -94,14 +93,12 @@ class ThumbStyleComposite extends WidgetCompositeProperty { @override Map setters() => { - 'radius': (value) => - radius = Utils.getDouble(value, fallback: 10.0), + 'radius': (value) => radius = Utils.getDouble(value, fallback: 10.0), 'elevation': (value) => elevation = Utils.getDouble(value, fallback: 1.0), 'pressedElevation': (value) => pressedElevation = Utils.getDouble(value, fallback: 2.0), - 'thumbColor': (value) => - thumbColor = Utils.getColor(value), + 'thumbColor': (value) => thumbColor = Utils.getColor(value), 'disabledThumbColor': (value) => disabledThumbColor = Utils.getColor(value), 'borderWidth': (value) => @@ -132,4 +129,12 @@ class ThumbStyleComposite extends WidgetCompositeProperty { borderColor: borderColor, ); } -} \ No newline at end of file + + RangeSliderThumbShape getRangeThumbShape() { + return RoundRangeSliderThumbShape( + enabledThumbRadius: radius, + elevation: elevation, + pressedElevation: pressedElevation, + ); + } +} diff --git a/modules/ensemble/lib/widget/input/slider/composites/track_style.dart b/modules/ensemble/lib/widget/input/slider/composites/track_style.dart index c93a6c7c6..51cf672b8 100644 --- a/modules/ensemble/lib/widget/input/slider/composites/track_style.dart +++ b/modules/ensemble/lib/widget/input/slider/composites/track_style.dart @@ -86,4 +86,15 @@ class TrackStyleComposite extends WidgetCompositeProperty { return RectangularSliderTrackShape(); } } -} \ No newline at end of file + + RangeSliderTrackShape getRangeTrackShape() { + switch (shape) { + case 'rectangular': + return RectangularRangeSliderTrackShape(); + case 'circle': + return RoundedRectRangeSliderTrackShape(); + default: + return RectangularRangeSliderTrackShape(); + } + } +} diff --git a/modules/ensemble/lib/widget/input/slider/composites/value_indicator_style.dart b/modules/ensemble/lib/widget/input/slider/composites/value_indicator_style.dart index af6af9e3b..1347072aa 100644 --- a/modules/ensemble/lib/widget/input/slider/composites/value_indicator_style.dart +++ b/modules/ensemble/lib/widget/input/slider/composites/value_indicator_style.dart @@ -8,20 +8,22 @@ class ValueIndicatorStyleComposite extends WidgetCompositeProperty { : super(widgetController); ShowValueIndicator visibility = ShowValueIndicator.onlyForDiscrete; - ValueIndicatorShape shape = ValueIndicatorShape.drop; + ValueIndicatorShape shape = ValueIndicatorShape.drop; Color? color; TextStyle? textStyle; factory ValueIndicatorStyleComposite.from( ChangeNotifier widgetController, dynamic payload) { - ValueIndicatorStyleComposite composite = ValueIndicatorStyleComposite(widgetController); + ValueIndicatorStyleComposite composite = + ValueIndicatorStyleComposite(widgetController); if (payload is Map) { - composite.visibility = ShowValueIndicator.values.from(payload['visibility']) ?? - ShowValueIndicator.onlyForDiscrete; - - composite.shape = ValueIndicatorShape.values.from(payload['shape']) ?? - ValueIndicatorShape.drop; - + composite.visibility = + ShowValueIndicator.values.from(payload['visibility']) ?? + ShowValueIndicator.onlyForDiscrete; + + composite.shape = ValueIndicatorShape.values.from(payload['shape']) ?? + ValueIndicatorShape.drop; + composite.color = Utils.getColor(payload['color']); composite.textStyle = Utils.getTextStyle(payload['textStyle']); } @@ -30,12 +32,11 @@ class ValueIndicatorStyleComposite extends WidgetCompositeProperty { @override Map setters() => { - 'visibility': (value) => - visibility = ShowValueIndicator.values.from(value) ?? + 'visibility': (value) => visibility = + ShowValueIndicator.values.from(value) ?? ShowValueIndicator.onlyForDiscrete, - 'shape': (value) => - shape = ValueIndicatorShape.values.from(value) ?? - ValueIndicatorShape.drop, + 'shape': (value) => shape = + ValueIndicatorShape.values.from(value) ?? ValueIndicatorShape.drop, 'color': (value) => color = Utils.getColor(value), 'textStyle': (value) => textStyle = Utils.getTextStyle(value), }; @@ -58,15 +59,22 @@ class ValueIndicatorStyleComposite extends WidgetCompositeProperty { case ValueIndicatorShape.paddle: return PaddleSliderValueIndicatorShape(); case ValueIndicatorShape.drop: - return DropSliderValueIndicatorShape(); + return DropSliderValueIndicatorShape(); + default: + return DropSliderValueIndicatorShape(); + } + } + + RangeSliderValueIndicatorShape? getRangeIndicatorShape() { + switch (shape) { + case ValueIndicatorShape.rectangular: + return RectangularRangeSliderValueIndicatorShape(); + case ValueIndicatorShape.paddle: + return PaddleRangeSliderValueIndicatorShape(); default: - return DropSliderValueIndicatorShape(); + return RectangularRangeSliderValueIndicatorShape(); } } } -enum ValueIndicatorShape { - drop, - paddle, - rectangular -} \ No newline at end of file +enum ValueIndicatorShape { drop, paddle, rectangular } diff --git a/modules/ensemble/lib/widget/input/slider/slider.dart b/modules/ensemble/lib/widget/input/slider/slider.dart index b9aa81198..2690d99d8 100644 --- a/modules/ensemble/lib/widget/input/slider/slider.dart +++ b/modules/ensemble/lib/widget/input/slider/slider.dart @@ -47,8 +47,8 @@ class EnsembleSlider extends StatefulWidget Map setters() { return { // Basic Properties - 'initialValue': (value) => - _controller.value = Utils.optionalDouble(value) ?? 0, + 'initialValue': (value) => _controller.value = + Utils.getRangeValues(value) ?? const RangeValues(0, 0), 'min': (value) => _controller.minValue = Utils.getDouble(value, fallback: 0.0), 'max': (value) => @@ -60,11 +60,11 @@ class EnsembleSlider extends StatefulWidget _controller.trackStyle = TrackStyleComposite.from(_controller, value), 'tickMarkStyle': (value) => _controller.tickMarkStyle = TickMarkStyleComposite.from(_controller, value), - 'thumbStyle': (value) => _controller.thumbStyle = - ThumbStyleComposite.from(_controller, value), - 'overlayStyle': (value) => _controller.overlayStyle = + 'thumbStyle': (value) => + _controller.thumbStyle = ThumbStyleComposite.from(_controller, value), + 'overlayStyle': (value) => _controller.overlayStyle = OverlayStyleComposite.from(_controller, value), - 'valueIndicatorStyle': (value) => _controller.valueIndicatorStyle = + 'valueIndicatorStyle': (value) => _controller.valueIndicatorStyle = ValueIndicatorStyleComposite.from(_controller, value), // @deprecated properties @@ -85,6 +85,9 @@ class EnsembleSlider extends StatefulWidget // Event Handler 'onChange': (definition) => _controller.onChange = EnsembleAction.from(definition, initiator: this), + + 'isRange': (value) => + _controller.isRange = Utils.getBool(value, fallback: false), }; } -} \ No newline at end of file +} diff --git a/modules/ensemble/lib/widget/input/slider/slider_controller.dart b/modules/ensemble/lib/widget/input/slider/slider_controller.dart index c54febba8..1df7bfa72 100644 --- a/modules/ensemble/lib/widget/input/slider/slider_controller.dart +++ b/modules/ensemble/lib/widget/input/slider/slider_controller.dart @@ -9,7 +9,7 @@ import 'composites/value_indicator_style.dart'; class SliderController extends FormFieldController { // Basic Values - double value = 0.0; + RangeValues value = const RangeValues(0.0, 1.0); double minValue = 0.0; double maxValue = 1.0; int? divisions; @@ -26,7 +26,7 @@ class SliderController extends FormFieldController { set tickMarkStyle(TickMarkStyleComposite value) => _tickMarkStyle = value; ThumbStyleComposite? _thumbStyle; - ThumbStyleComposite get thumbStyle => + ThumbStyleComposite get thumbStyle => _thumbStyle ??= ThumbStyleComposite(this); set thumbStyle(ThumbStyleComposite value) => _thumbStyle = value; @@ -38,7 +38,7 @@ class SliderController extends FormFieldController { ValueIndicatorStyleComposite? _valueIndicatorStyle; ValueIndicatorStyleComposite get valueIndicatorStyle => _valueIndicatorStyle ??= ValueIndicatorStyleComposite(this); - set valueIndicatorStyle(ValueIndicatorStyleComposite value) => + set valueIndicatorStyle(ValueIndicatorStyleComposite value) => _valueIndicatorStyle = value; // @deprecated. backward compatibility @@ -52,4 +52,7 @@ class SliderController extends FormFieldController { // Event Handler EnsembleAction? onChange; -} \ No newline at end of file + + // Optional property to determine if the slider is a range slider + bool isRange = false; +} diff --git a/modules/ensemble/lib/widget/input/slider/slider_state.dart b/modules/ensemble/lib/widget/input/slider/slider_state.dart index 1a4248eb3..9b04e9a1c 100644 --- a/modules/ensemble/lib/widget/input/slider/slider_state.dart +++ b/modules/ensemble/lib/widget/input/slider/slider_state.dart @@ -79,37 +79,64 @@ class SliderState extends FormFieldWidgetState { widget.controller.valueIndicatorStyle.textStyle, valueIndicatorShape: widget.controller.valueIndicatorStyle.getIndicatorShape(), + + // Range Slider Properties + rangeValueIndicatorShape: + widget.controller.valueIndicatorStyle.getRangeIndicatorShape(), + rangeThumbShape: widget.controller.thumbStyle.getRangeThumbShape(), + rangeTrackShape: widget.controller.trackStyle.getRangeTrackShape(), ); return SliderTheme( data: themeData, - child: Slider( - value: widget.controller.value, - min: widget.controller.minValue, - max: widget.controller.maxValue, - divisions: widget.controller.divisions, - label: widget.controller.value.toStringAsFixed(decimalPlaces), - onChanged: isEnabled() - ? (value) { - setState(() { - widget.controller.value = value; - }); - if (widget.controller.onChange != null) { - ScreenController().executeAction( - context, - widget.controller.onChange!, - event: EnsembleEvent(widget), - ); - } - } - : null, - ), + child: widget.controller.isRange + ? RangeSlider( + labels: RangeLabels( + widget.controller.value.start + .toStringAsFixed(decimalPlaces), + widget.controller.value.end + .toStringAsFixed(decimalPlaces), + ), + min: widget.controller.minValue, + max: widget.controller.maxValue, + values: widget.controller.value, + divisions: widget.controller.divisions, + onChanged: _onChanged, + ) + : Slider( + value: widget.controller.value.start, + min: widget.controller.minValue, + max: widget.controller.maxValue, + divisions: widget.controller.divisions, + label: widget.controller.value.start + .toStringAsFixed(decimalPlaces), + onChanged: _onChanged, + ), ); }, ), ); } + void _onChanged(dynamic value) { + if (!isEnabled()) return; + + setState(() { + if (value is RangeValues) { + widget.controller.value = value; + } else if (value is double) { + widget.controller.value = RangeValues(value, value); + } + }); + if (widget.controller.onChange != null) { + ScreenController().executeAction( + context, + widget.controller.onChange!, + event: EnsembleEvent(widget), + ); + } + } + int calculateDecimalPlaces(double min, double max, int? divisions) { if (divisions == null) return 1; double interval = (max - min) / divisions;