mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-14 03:32:23 +01:00
Made build portable
This commit is contained in:
855
dependencies/fluent_ui-3.12.0/lib/src/controls/inputs/slider.dart
vendored
Normal file
855
dependencies/fluent_ui-3.12.0/lib/src/controls/inputs/slider.dart
vendored
Normal file
@@ -0,0 +1,855 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/material.dart' as m;
|
||||
|
||||
/// A slider is a control that lets the user select from a
|
||||
/// range of values by moving a thumb control along a track.
|
||||
///
|
||||
/// A slider is a good choice when you know that users think
|
||||
/// of the value as a relative quantity, not a numeric value.
|
||||
/// For example, users think about setting their audio volume
|
||||
/// to low or medium—not about setting the value to 2 or 5.
|
||||
///
|
||||
/// 
|
||||
///
|
||||
/// See also:
|
||||
/// - [RatingBar]
|
||||
class Slider extends StatefulWidget {
|
||||
const Slider({
|
||||
Key? key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.onChangeStart,
|
||||
this.onChangeEnd,
|
||||
this.min = 0.0,
|
||||
this.max = 100.0,
|
||||
this.divisions,
|
||||
this.style,
|
||||
this.label,
|
||||
this.focusNode,
|
||||
this.vertical = false,
|
||||
this.autofocus = false,
|
||||
this.mouseCursor = MouseCursor.defer,
|
||||
}) : assert(value >= min && value <= max),
|
||||
assert(divisions == null || divisions > 0),
|
||||
super(key: key);
|
||||
|
||||
/// The currently selected value for this slider.
|
||||
///
|
||||
/// The slider's thumb is drawn at a position that corresponds to this value.
|
||||
final double value;
|
||||
|
||||
/// Called during a drag when the user is selecting a new value for the slider
|
||||
/// by dragging.
|
||||
///
|
||||
/// The slider passes the new value to the callback but does not actually
|
||||
/// change state until the parent widget rebuilds the slider with the new
|
||||
/// value.
|
||||
///
|
||||
/// If null, the slider will be displayed as disabled.
|
||||
///
|
||||
/// The callback provided to onChanged should update the state of the parent
|
||||
/// [StatefulWidget] using the [State.setState] method, so that the parent
|
||||
/// gets rebuilt; for example:
|
||||
///
|
||||
/// {@tool snippet}
|
||||
///
|
||||
/// ```dart
|
||||
/// Slider(
|
||||
/// value: _duelCommandment.toDouble(),
|
||||
/// min: 1.0,
|
||||
/// max: 10.0,
|
||||
/// divisions: 10,
|
||||
/// label: '$_duelCommandment',
|
||||
/// onChanged: (double newValue) {
|
||||
/// setState(() {
|
||||
/// _duelCommandment = newValue.round();
|
||||
/// });
|
||||
/// },
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [onChangeStart] for a callback that is called when the user starts
|
||||
/// changing the value.
|
||||
/// * [onChangeEnd] for a callback that is called when the user stops
|
||||
/// changing the value.
|
||||
final ValueChanged<double>? onChanged;
|
||||
|
||||
/// Called when the user starts selecting a new value for the slider.
|
||||
///
|
||||
/// This callback shouldn't be used to update the slider [value] (use
|
||||
/// [onChanged] for that), but rather to be notified when the user has started
|
||||
/// selecting a new value by starting a drag or with a tap.
|
||||
///
|
||||
/// The value passed will be the last [value] that the slider had before the
|
||||
/// change began.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
///
|
||||
/// ```dart
|
||||
/// Slider(
|
||||
/// value: _duelCommandment.toDouble(),
|
||||
/// min: 1.0,
|
||||
/// max: 10.0,
|
||||
/// divisions: 10,
|
||||
/// label: '$_duelCommandment',
|
||||
/// onChanged: (double newValue) {
|
||||
/// setState(() {
|
||||
/// _duelCommandment = newValue.round();
|
||||
/// });
|
||||
/// },
|
||||
/// onChangeStart: (double startValue) {
|
||||
/// print('Started change at $startValue');
|
||||
/// },
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [onChangeEnd] for a callback that is called when the value change is
|
||||
/// complete.
|
||||
final ValueChanged<double>? onChangeStart;
|
||||
|
||||
/// Called when the user is done selecting a new value for the slider.
|
||||
///
|
||||
/// This callback shouldn't be used to update the slider [value] (use
|
||||
/// [onChanged] for that), but rather to know when the user has completed
|
||||
/// selecting a new [value] by ending a drag or a click.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
///
|
||||
/// ```dart
|
||||
/// Slider(
|
||||
/// value: _duelCommandment.toDouble(),
|
||||
/// min: 1.0,
|
||||
/// max: 10.0,
|
||||
/// divisions: 10,
|
||||
/// label: '$_duelCommandment',
|
||||
/// onChanged: (double newValue) {
|
||||
/// setState(() {
|
||||
/// _duelCommandment = newValue.round();
|
||||
/// });
|
||||
/// },
|
||||
/// onChangeEnd: (double newValue) {
|
||||
/// print('Ended change on $newValue');
|
||||
/// },
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [onChangeStart] for a callback that is called when a value change
|
||||
/// begins.
|
||||
final ValueChanged<double>? onChangeEnd;
|
||||
|
||||
/// The maximum value the user can select.
|
||||
///
|
||||
/// Defaults to 1.0. Must be greater than or equal to [min].
|
||||
///
|
||||
/// If the [max] is equal to the [min], then the slider is disabled.
|
||||
final double min;
|
||||
|
||||
/// The minimum value the user can select.
|
||||
///
|
||||
/// Defaults to 0.0. Must be less than or equal to [max].
|
||||
///
|
||||
/// If the [max] is equal to the [min], then the slider is disabled.
|
||||
final double max;
|
||||
|
||||
/// The number of discrete divisions.
|
||||
///
|
||||
/// Typically used with [label] to show the current discrete value.
|
||||
///
|
||||
/// If null, the slider is continuous.
|
||||
final int? divisions;
|
||||
|
||||
/// The style used in this slider. It's mescled with [ThemeData.sliderThemeData]
|
||||
final SliderThemeData? style;
|
||||
|
||||
/// A label to show above the slider, or at the left
|
||||
/// of the slider if [vertical] is `true` when the slider is active.
|
||||
final String? label;
|
||||
|
||||
/// {@macro flutter.widgets.Focus.focusNode}
|
||||
final FocusNode? focusNode;
|
||||
|
||||
/// {@macro flutter.widgets.Focus.autofocus}
|
||||
final bool autofocus;
|
||||
|
||||
/// Whether the slider is vertical or not
|
||||
///
|
||||
/// Use a vertical slider if the slider represents a
|
||||
/// real-world value that is normally shown vertically
|
||||
/// (such as temperature).
|
||||
final bool vertical;
|
||||
|
||||
/// {@macro fluent_ui.controls.inputs.HoverButton.mouseCursor}
|
||||
final MouseCursor mouseCursor;
|
||||
|
||||
@override
|
||||
_SliderState createState() => _SliderState();
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DoubleProperty('value', value))
|
||||
..add(DoubleProperty('min', min))
|
||||
..add(DoubleProperty('max', max))
|
||||
..add(IntProperty('divisions', divisions))
|
||||
..add(StringProperty('label', label))
|
||||
..add(ObjectFlagProperty<FocusNode>.has('focusNode', focusNode))
|
||||
..add(DiagnosticsProperty<SliderThemeData>('style', style))
|
||||
..add(FlagProperty('vertical', value: vertical, ifFalse: 'horizontal'));
|
||||
}
|
||||
}
|
||||
|
||||
class _SliderState extends m.State<Slider> {
|
||||
bool _showFocusHighlight = false;
|
||||
|
||||
late FocusNode _focusNode;
|
||||
bool _sliding = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode = widget.focusNode ?? FocusNode();
|
||||
_focusNode.addListener(_handleFocusChanged);
|
||||
}
|
||||
|
||||
void _handleFocusChanged() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.removeListener(_handleFocusChanged);
|
||||
// Only dispose the focus node manually created
|
||||
if (widget.focusNode == null) _focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasFluentTheme(context));
|
||||
assert(debugCheckHasDirectionality(context));
|
||||
final style = SliderTheme.of(context).merge(widget.style);
|
||||
final direction = Directionality.of(context);
|
||||
Widget child = HoverButton(
|
||||
onPressed: widget.onChanged == null ? null : () {},
|
||||
margin: style.margin ?? EdgeInsets.zero,
|
||||
cursor: widget.mouseCursor,
|
||||
builder: (context, states) => m.Material(
|
||||
type: m.MaterialType.transparency,
|
||||
child: TweenAnimationBuilder<double>(
|
||||
duration: FluentTheme.of(context).fastAnimationDuration,
|
||||
tween: Tween<double>(
|
||||
begin: 1.0,
|
||||
end: states.isPressing || _sliding
|
||||
? 0.45
|
||||
: states.isHovering
|
||||
? 0.66
|
||||
: 0.5,
|
||||
),
|
||||
builder: (context, innerFactor, child) => m.SliderTheme(
|
||||
data: m.SliderThemeData(
|
||||
showValueIndicator: m.ShowValueIndicator.always,
|
||||
thumbColor: style.thumbColor ?? style.activeColor,
|
||||
overlayShape: const m.RoundSliderOverlayShape(overlayRadius: 0),
|
||||
thumbShape: SliderThumbShape(
|
||||
elevation: 0,
|
||||
pressedElevation: 0,
|
||||
useBall: style.useThumbBall ?? true,
|
||||
innerFactor: innerFactor,
|
||||
brightness: FluentTheme.of(context).brightness,
|
||||
),
|
||||
valueIndicatorShape: _RectangularSliderValueIndicatorShape(
|
||||
backgroundColor: style.labelBackgroundColor,
|
||||
vertical: widget.vertical,
|
||||
ltr: direction == TextDirection.ltr,
|
||||
),
|
||||
trackHeight: 1.75,
|
||||
trackShape: _CustomTrackShape(),
|
||||
disabledThumbColor: style.disabledThumbColor,
|
||||
disabledInactiveTrackColor: style.disabledInactiveColor,
|
||||
disabledActiveTrackColor: style.disabledActiveColor,
|
||||
),
|
||||
child: child!,
|
||||
),
|
||||
child: m.Slider(
|
||||
value: widget.value,
|
||||
max: widget.max,
|
||||
min: widget.min,
|
||||
onChanged: widget.onChanged,
|
||||
onChangeEnd: (v) {
|
||||
widget.onChangeEnd?.call(v);
|
||||
setState(() => _sliding = false);
|
||||
},
|
||||
onChangeStart: (v) {
|
||||
widget.onChangeStart?.call(v);
|
||||
setState(() => _sliding = true);
|
||||
},
|
||||
activeColor: style.activeColor,
|
||||
inactiveColor: style.inactiveColor,
|
||||
divisions: widget.divisions,
|
||||
label: widget.label,
|
||||
focusNode: _focusNode,
|
||||
autofocus: widget.autofocus,
|
||||
mouseCursor: MouseCursor.defer,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
child = FocusableActionDetector(
|
||||
onShowFocusHighlight: (v) => setState(() => _showFocusHighlight = v),
|
||||
child: FocusBorder(
|
||||
focused: _showFocusHighlight && (_focusNode.hasPrimaryFocus),
|
||||
useStackApproach: true,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
if (widget.vertical) {
|
||||
return RotatedBox(
|
||||
quarterTurns: direction == TextDirection.ltr ? 3 : 5,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
/// This is used to remove the padding the Material Slider adds automatically
|
||||
class _CustomTrackShape extends m.RoundedRectSliderTrackShape {
|
||||
@override
|
||||
Rect getPreferredRect({
|
||||
required RenderBox parentBox,
|
||||
Offset offset = Offset.zero,
|
||||
required m.SliderThemeData sliderTheme,
|
||||
bool isEnabled = false,
|
||||
bool isDiscrete = false,
|
||||
}) {
|
||||
final double trackHeight = sliderTheme.trackHeight!;
|
||||
final double trackLeft = offset.dx;
|
||||
final double trackTop =
|
||||
offset.dy + (parentBox.size.height - trackHeight) / 2;
|
||||
final double trackWidth = parentBox.size.width;
|
||||
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/// The default shape of a [Slider]'s thumb.
|
||||
///
|
||||
/// There is a shadow for the resting, pressed, hovered, and focused state.
|
||||
class SliderThumbShape extends m.SliderComponentShape {
|
||||
/// Create a fluent-styled slider thumb;
|
||||
const SliderThumbShape({
|
||||
this.enabledThumbRadius = 10.0,
|
||||
this.disabledThumbRadius,
|
||||
this.elevation = 1.0,
|
||||
this.pressedElevation = 6.0,
|
||||
this.useBall = true,
|
||||
this.innerFactor = 1.0,
|
||||
this.brightness = Brightness.light,
|
||||
});
|
||||
|
||||
final double innerFactor;
|
||||
|
||||
final Brightness brightness;
|
||||
|
||||
/// Whether to draw a ball instead of a line
|
||||
final bool useBall;
|
||||
|
||||
/// The preferred radius of the round thumb shape when the slider is enabled.
|
||||
///
|
||||
/// If it is not provided, then the material default of 10 is used.
|
||||
final double enabledThumbRadius;
|
||||
|
||||
/// The preferred radius of the round thumb shape when the slider is disabled.
|
||||
///
|
||||
/// If no disabledRadius is provided, then it is equal to the
|
||||
/// [enabledThumbRadius]
|
||||
final double? disabledThumbRadius;
|
||||
double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
|
||||
|
||||
/// The resting elevation adds shadow to the unpressed thumb.
|
||||
///
|
||||
/// The default is 1.
|
||||
///
|
||||
/// Use 0 for no shadow. The higher the value, the larger the shadow. For
|
||||
/// example, a value of 12 will create a very large shadow.
|
||||
///
|
||||
final double elevation;
|
||||
|
||||
/// The pressed elevation adds shadow to the pressed thumb.
|
||||
///
|
||||
/// The default is 6.
|
||||
///
|
||||
/// Use 0 for no shadow. The higher the value, the larger the shadow. For
|
||||
/// example, a value of 12 will create a very large shadow.
|
||||
final double pressedElevation;
|
||||
|
||||
@override
|
||||
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
|
||||
return Size.fromRadius(
|
||||
isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset center, {
|
||||
required Animation<double> activationAnimation,
|
||||
required Animation<double> enableAnimation,
|
||||
required bool isDiscrete,
|
||||
required TextPainter labelPainter,
|
||||
required RenderBox parentBox,
|
||||
required m.SliderThemeData sliderTheme,
|
||||
required TextDirection textDirection,
|
||||
required double value,
|
||||
required double textScaleFactor,
|
||||
required Size sizeWithOverflow,
|
||||
}) {
|
||||
assert(sliderTheme.disabledThumbColor != null);
|
||||
assert(sliderTheme.thumbColor != null);
|
||||
|
||||
final Canvas canvas = context.canvas;
|
||||
final Tween<double> radiusTween = Tween<double>(
|
||||
begin: _disabledThumbRadius,
|
||||
end: enabledThumbRadius,
|
||||
);
|
||||
final ColorTween colorTween = ColorTween(
|
||||
begin: sliderTheme.disabledThumbColor,
|
||||
end: sliderTheme.thumbColor,
|
||||
);
|
||||
|
||||
final Color color = colorTween.evaluate(enableAnimation)!;
|
||||
final double radius = radiusTween.evaluate(enableAnimation);
|
||||
|
||||
if (!useBall) {
|
||||
canvas.drawLine(
|
||||
center - const Offset(0, 6),
|
||||
center + const Offset(0, 6),
|
||||
Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeJoin = StrokeJoin.round
|
||||
..strokeCap = StrokeCap.round
|
||||
..strokeWidth = 8.0,
|
||||
);
|
||||
} else {
|
||||
final Tween<double> elevationTween = Tween<double>(
|
||||
begin: elevation,
|
||||
end: pressedElevation,
|
||||
);
|
||||
final double evaluatedElevation =
|
||||
elevationTween.evaluate(activationAnimation);
|
||||
final Path path = Path()
|
||||
..addArc(
|
||||
Rect.fromCenter(
|
||||
center: center, width: 2 * radius, height: 2 * radius),
|
||||
0,
|
||||
math.pi * 2);
|
||||
canvas.drawShadow(path, Colors.black, evaluatedElevation, true);
|
||||
canvas.drawCircle(
|
||||
center,
|
||||
radius,
|
||||
Paint()
|
||||
..color = brightness == Brightness.light
|
||||
? Colors.white
|
||||
: const Color(0xFF454545),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
center,
|
||||
radius * innerFactor,
|
||||
Paint()..color = color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An inherited widget that defines the configuration for
|
||||
/// [Slider]s in this widget's subtree.
|
||||
///
|
||||
/// Values specified here are used for [Slider] properties that are not
|
||||
/// given an explicit non-null value.
|
||||
class SliderTheme extends InheritedTheme {
|
||||
/// Creates a slider theme that controls the configurations for
|
||||
/// [Slider].
|
||||
const SliderTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The properties for descendant [Slider] widgets.
|
||||
final SliderThemeData data;
|
||||
|
||||
/// Creates a button theme that controls how descendant [Slider]s should
|
||||
/// look like, and merges in the current slider theme, if any.
|
||||
static Widget merge({
|
||||
Key? key,
|
||||
required SliderThemeData data,
|
||||
required Widget child,
|
||||
}) {
|
||||
return Builder(builder: (BuildContext context) {
|
||||
return SliderTheme(
|
||||
key: key,
|
||||
data: _getInheritedThemeData(context).merge(data),
|
||||
child: child,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static SliderThemeData _getInheritedThemeData(BuildContext context) {
|
||||
final theme = context.dependOnInheritedWidgetOfExactType<SliderTheme>();
|
||||
return theme?.data ?? FluentTheme.of(context).sliderTheme;
|
||||
}
|
||||
|
||||
/// Returns the [data] from the closest [SliderTheme] ancestor. If there is
|
||||
/// no ancestor, it returns [ThemeData.sliderTheme]. Applications can assume
|
||||
/// that the returned value will not be null.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// SliderThemeData theme = SliderTheme.of(context);
|
||||
/// ```
|
||||
static SliderThemeData of(BuildContext context) {
|
||||
return SliderThemeData.standard(FluentTheme.of(context)).merge(
|
||||
_getInheritedThemeData(context),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
return SliderTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(SliderTheme oldWidget) => data != oldWidget.data;
|
||||
}
|
||||
|
||||
@immutable
|
||||
class SliderThemeData with Diagnosticable {
|
||||
final Color? thumbColor;
|
||||
final Color? disabledThumbColor;
|
||||
final Color? labelBackgroundColor;
|
||||
|
||||
final bool? useThumbBall;
|
||||
|
||||
final Color? activeColor;
|
||||
final Color? inactiveColor;
|
||||
|
||||
final Color? disabledActiveColor;
|
||||
final Color? disabledInactiveColor;
|
||||
|
||||
final EdgeInsetsGeometry? margin;
|
||||
|
||||
const SliderThemeData({
|
||||
this.margin,
|
||||
this.thumbColor,
|
||||
this.disabledThumbColor,
|
||||
this.activeColor,
|
||||
this.disabledActiveColor,
|
||||
this.inactiveColor,
|
||||
this.disabledInactiveColor,
|
||||
this.labelBackgroundColor,
|
||||
this.useThumbBall,
|
||||
});
|
||||
|
||||
factory SliderThemeData.standard(ThemeData? style) {
|
||||
final def = SliderThemeData(
|
||||
thumbColor: style?.accentColor,
|
||||
activeColor: style?.accentColor,
|
||||
inactiveColor: style?.disabledColor.withOpacity(1),
|
||||
margin: EdgeInsets.zero,
|
||||
disabledActiveColor: style?.disabledColor.withOpacity(1),
|
||||
disabledThumbColor: style?.disabledColor.withOpacity(1),
|
||||
disabledInactiveColor: style?.disabledColor,
|
||||
useThumbBall: true,
|
||||
);
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
static SliderThemeData lerp(
|
||||
SliderThemeData? a, SliderThemeData? b, double t) {
|
||||
return SliderThemeData(
|
||||
margin: EdgeInsetsGeometry.lerp(a?.margin, b?.margin, t),
|
||||
thumbColor: Color.lerp(a?.thumbColor, b?.thumbColor, t),
|
||||
activeColor: Color.lerp(a?.activeColor, b?.activeColor, t),
|
||||
inactiveColor: Color.lerp(a?.inactiveColor, b?.inactiveColor, t),
|
||||
disabledActiveColor:
|
||||
Color.lerp(a?.disabledActiveColor, b?.disabledActiveColor, t),
|
||||
disabledInactiveColor:
|
||||
Color.lerp(a?.disabledInactiveColor, b?.disabledInactiveColor, t),
|
||||
disabledThumbColor:
|
||||
Color.lerp(a?.disabledThumbColor, b?.disabledThumbColor, t),
|
||||
labelBackgroundColor:
|
||||
Color.lerp(a?.labelBackgroundColor, b?.labelBackgroundColor, t),
|
||||
useThumbBall: t < 0.5 ? a?.useThumbBall : b?.useThumbBall,
|
||||
);
|
||||
}
|
||||
|
||||
SliderThemeData merge(SliderThemeData? style) {
|
||||
return SliderThemeData(
|
||||
margin: style?.margin ?? margin,
|
||||
thumbColor: style?.thumbColor ?? thumbColor,
|
||||
activeColor: style?.activeColor ?? activeColor,
|
||||
inactiveColor: style?.inactiveColor ?? inactiveColor,
|
||||
disabledActiveColor: style?.disabledActiveColor ?? disabledActiveColor,
|
||||
disabledInactiveColor:
|
||||
style?.disabledInactiveColor ?? disabledInactiveColor,
|
||||
disabledThumbColor: style?.disabledThumbColor ?? disabledThumbColor,
|
||||
labelBackgroundColor: style?.labelBackgroundColor ?? labelBackgroundColor,
|
||||
useThumbBall: style?.useThumbBall ?? useThumbBall,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry?>('margin', margin));
|
||||
properties.add(ColorProperty('thumbColor', thumbColor));
|
||||
properties.add(ColorProperty('activeColor', activeColor));
|
||||
properties.add(ColorProperty('inactiveColor', inactiveColor));
|
||||
properties.add(ColorProperty('disabledActiveColor', disabledActiveColor));
|
||||
properties
|
||||
.add(ColorProperty('disabledInactiveColor', disabledInactiveColor));
|
||||
properties.add(ColorProperty('disabledThumbColor', disabledThumbColor));
|
||||
properties.add(ColorProperty('labelBackgroundColor', labelBackgroundColor));
|
||||
}
|
||||
}
|
||||
|
||||
class _RectangularSliderValueIndicatorShape extends m.SliderComponentShape {
|
||||
final Color? backgroundColor;
|
||||
final bool vertical;
|
||||
final bool ltr;
|
||||
|
||||
/// Create a slider value indicator that resembles a rectangular tooltip.
|
||||
const _RectangularSliderValueIndicatorShape({
|
||||
this.backgroundColor,
|
||||
this.vertical = false,
|
||||
this.ltr = false,
|
||||
});
|
||||
|
||||
get _pathPainter => _RectangularSliderValueIndicatorPathPainter(
|
||||
vertical,
|
||||
ltr,
|
||||
);
|
||||
|
||||
@override
|
||||
Size getPreferredSize(
|
||||
bool isEnabled,
|
||||
bool isDiscrete, {
|
||||
TextPainter? labelPainter,
|
||||
double? textScaleFactor,
|
||||
}) {
|
||||
assert(labelPainter != null);
|
||||
assert(textScaleFactor != null && textScaleFactor >= 0);
|
||||
return _pathPainter.getPreferredSize(labelPainter!, textScaleFactor!);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset center, {
|
||||
required Animation<double> activationAnimation,
|
||||
required Animation<double> enableAnimation,
|
||||
required bool isDiscrete,
|
||||
required TextPainter labelPainter,
|
||||
required RenderBox parentBox,
|
||||
required m.SliderThemeData sliderTheme,
|
||||
required TextDirection textDirection,
|
||||
required double value,
|
||||
required double textScaleFactor,
|
||||
required Size sizeWithOverflow,
|
||||
}) {
|
||||
final Canvas canvas = context.canvas;
|
||||
final double scale = activationAnimation.value;
|
||||
_pathPainter.paint(
|
||||
parentBox: parentBox,
|
||||
canvas: canvas,
|
||||
center: center,
|
||||
scale: scale,
|
||||
labelPainter: labelPainter,
|
||||
textScaleFactor: textScaleFactor,
|
||||
sizeWithOverflow: sizeWithOverflow,
|
||||
backgroundPaintColor: backgroundColor ?? sliderTheme.valueIndicatorColor!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RectangularSliderValueIndicatorPathPainter {
|
||||
final bool vertical;
|
||||
|
||||
/// Whether the current [Directionality] is [TextDirection.ltr]
|
||||
final bool ltr;
|
||||
|
||||
const _RectangularSliderValueIndicatorPathPainter([
|
||||
this.vertical = false,
|
||||
this.ltr = false,
|
||||
]);
|
||||
|
||||
static const double _triangleHeight = 8.0;
|
||||
static const double _labelPadding = 8.0;
|
||||
static const double _preferredHeight = 32.0;
|
||||
static const double _minLabelWidth = 16.0;
|
||||
static const double _bottomTipYOffset = 14.0;
|
||||
static const double _preferredHalfHeight = _preferredHeight / 2;
|
||||
static const double _upperRectRadius = 4;
|
||||
|
||||
Size getPreferredSize(TextPainter labelPainter, double textScaleFactor) {
|
||||
return Size(
|
||||
_upperRectangleWidth(labelPainter, 1, textScaleFactor),
|
||||
labelPainter.height + _labelPadding,
|
||||
);
|
||||
}
|
||||
|
||||
double getHorizontalShift({
|
||||
required RenderBox parentBox,
|
||||
required Offset center,
|
||||
required TextPainter labelPainter,
|
||||
required double textScaleFactor,
|
||||
required Size sizeWithOverflow,
|
||||
required double scale,
|
||||
}) {
|
||||
assert(!sizeWithOverflow.isEmpty);
|
||||
|
||||
const double edgePadding = 8.0;
|
||||
final double rectangleWidth =
|
||||
_upperRectangleWidth(labelPainter, scale, textScaleFactor);
|
||||
|
||||
/// Value indicator draws on the Overlay and by using the global Offset
|
||||
/// we are making sure we use the bounds of the Overlay instead of the Slider.
|
||||
final Offset globalCenter = parentBox.localToGlobal(center);
|
||||
|
||||
// The rectangle must be shifted towards the center so that it minimizes the
|
||||
// chance of it rendering outside the bounds of the render box. If the shift
|
||||
// is negative, then the lobe is shifted from right to left, and if it is
|
||||
// positive, then the lobe is shifted from left to right.
|
||||
final double overflowLeft =
|
||||
math.max(0, rectangleWidth / 2 - globalCenter.dx + edgePadding);
|
||||
final double overflowRight = math.max(
|
||||
0,
|
||||
rectangleWidth / 2 -
|
||||
(sizeWithOverflow.width - globalCenter.dx - edgePadding));
|
||||
|
||||
if (rectangleWidth < sizeWithOverflow.width) {
|
||||
return overflowLeft - overflowRight;
|
||||
} else if (overflowLeft - overflowRight > 0) {
|
||||
return overflowLeft - (edgePadding * textScaleFactor);
|
||||
} else {
|
||||
return -overflowRight + (edgePadding * textScaleFactor);
|
||||
}
|
||||
}
|
||||
|
||||
double _upperRectangleWidth(
|
||||
TextPainter labelPainter,
|
||||
double scale,
|
||||
double textScaleFactor,
|
||||
) {
|
||||
final double unscaledWidth =
|
||||
math.max(_minLabelWidth * textScaleFactor, labelPainter.width) +
|
||||
_labelPadding;
|
||||
return unscaledWidth * scale;
|
||||
}
|
||||
|
||||
void paint({
|
||||
required RenderBox parentBox,
|
||||
required Canvas canvas,
|
||||
required Offset center,
|
||||
required double scale,
|
||||
required TextPainter labelPainter,
|
||||
required double textScaleFactor,
|
||||
required Size sizeWithOverflow,
|
||||
required Color backgroundPaintColor,
|
||||
Color? strokePaintColor,
|
||||
}) {
|
||||
if (scale == 0.0) {
|
||||
// Zero scale essentially means "do not draw anything", so it's safe to just return.
|
||||
return;
|
||||
}
|
||||
assert(!sizeWithOverflow.isEmpty);
|
||||
|
||||
final double rectangleWidth = _upperRectangleWidth(
|
||||
labelPainter,
|
||||
scale,
|
||||
textScaleFactor,
|
||||
);
|
||||
final double horizontalShift = getHorizontalShift(
|
||||
parentBox: parentBox,
|
||||
center: center,
|
||||
labelPainter: labelPainter,
|
||||
textScaleFactor: textScaleFactor,
|
||||
sizeWithOverflow: sizeWithOverflow,
|
||||
scale: scale,
|
||||
);
|
||||
|
||||
final double rectHeight = labelPainter.height + _labelPadding;
|
||||
final Rect upperRect = Rect.fromLTWH(
|
||||
-rectangleWidth / 2 + horizontalShift,
|
||||
-_triangleHeight - rectHeight,
|
||||
rectangleWidth,
|
||||
rectHeight,
|
||||
);
|
||||
|
||||
final Path trianglePath = Path()..close();
|
||||
final Paint fillPaint = Paint()..color = backgroundPaintColor;
|
||||
final RRect upperRRect = RRect.fromRectAndRadius(
|
||||
upperRect,
|
||||
const Radius.circular(_upperRectRadius),
|
||||
);
|
||||
trianglePath.addRRect(upperRRect);
|
||||
canvas.save();
|
||||
// Prepare the canvas for the base of the tooltip, which is relative to the
|
||||
// center of the thumb.
|
||||
final double verticalFactor = ltr ? 20.0 : 10.0;
|
||||
canvas.translate(
|
||||
center.dx +
|
||||
(vertical
|
||||
? ltr
|
||||
? -verticalFactor
|
||||
: verticalFactor * 2
|
||||
: 0),
|
||||
center.dy -
|
||||
_bottomTipYOffset +
|
||||
(vertical
|
||||
? ltr
|
||||
? -verticalFactor
|
||||
: -verticalFactor * 2
|
||||
: 0),
|
||||
);
|
||||
canvas.scale(scale, scale);
|
||||
// Rotate the label if it's vertical
|
||||
if (vertical) canvas.rotate((ltr ? 1 : -1) * math.pi / 2);
|
||||
if (strokePaintColor != null) {
|
||||
final Paint strokePaint = Paint()
|
||||
..color = strokePaintColor
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke;
|
||||
canvas.drawPath(trianglePath, strokePaint);
|
||||
}
|
||||
canvas.drawPath(trianglePath, fillPaint);
|
||||
|
||||
// The label text is centered within the value indicator.
|
||||
final double bottomTipToUpperRectTranslateY =
|
||||
-_preferredHalfHeight / 2 - upperRect.height;
|
||||
canvas.translate(0, bottomTipToUpperRectTranslateY);
|
||||
final Offset boxCenter = Offset(horizontalShift, upperRect.height / 2);
|
||||
final Offset halfLabelPainterOffset =
|
||||
Offset(labelPainter.width / 2, labelPainter.height / 2);
|
||||
final Offset labelOffset = boxCenter - halfLabelPainterOffset;
|
||||
labelPainter.paint(canvas, labelOffset);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user