1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'color_scheme.dart';
6/// @docImport 'range_slider.dart';
7/// @docImport 'text_theme.dart';
8library;
9
10import 'dart:math' as math;
11import 'dart:ui' show Path, lerpDouble;
12
13import 'package:flutter/foundation.dart';
14import 'package:flutter/rendering.dart';
15import 'package:flutter/widgets.dart';
16
17import 'colors.dart';
18import 'material_state.dart';
19import 'slider.dart';
20import 'theme.dart';
21
22/// Applies a slider theme to descendant [Slider] widgets.
23///
24/// A slider theme describes the colors and shape choices of the slider
25/// components.
26///
27/// Descendant widgets obtain the current theme's [SliderThemeData] object using
28/// [SliderTheme.of]. When a widget uses [SliderTheme.of], it is automatically
29/// rebuilt if the theme later changes.
30///
31/// The slider is as big as the largest of
32/// the [SliderComponentShape.getPreferredSize] of the thumb shape,
33/// the [SliderComponentShape.getPreferredSize] of the overlay shape,
34/// and the [SliderTickMarkShape.getPreferredSize] of the tick mark shape.
35///
36/// See also:
37///
38/// * [SliderThemeData], which describes the actual configuration of a slider
39/// theme.
40/// * [SliderComponentShape], which can be used to create custom shapes for
41/// the [Slider]'s thumb, overlay, and value indicator and the
42/// [RangeSlider]'s overlay.
43/// * [SliderTrackShape], which can be used to create custom shapes for the
44/// [Slider]'s track.
45/// * [SliderTickMarkShape], which can be used to create custom shapes for the
46/// [Slider]'s tick marks.
47/// * [RangeSliderThumbShape], which can be used to create custom shapes for
48/// the [RangeSlider]'s thumb.
49/// * [RangeSliderValueIndicatorShape], which can be used to create custom
50/// shapes for the [RangeSlider]'s value indicator.
51/// * [RangeSliderTrackShape], which can be used to create custom shapes for
52/// the [RangeSlider]'s track.
53/// * [RangeSliderTickMarkShape], which can be used to create custom shapes for
54/// the [RangeSlider]'s tick marks.
55class SliderTheme extends InheritedTheme {
56 /// Applies the given theme [data] to [child].
57 const SliderTheme({super.key, required this.data, required super.child});
58
59 /// Specifies the color and shape values for descendant slider widgets.
60 final SliderThemeData data;
61
62 /// Returns the data from the closest [SliderTheme] instance that encloses
63 /// the given context.
64 ///
65 /// Defaults to the ambient [ThemeData.sliderTheme] if there is no
66 /// [SliderTheme] in the given build context.
67 ///
68 /// {@tool snippet}
69 ///
70 /// ```dart
71 /// class Launch extends StatefulWidget {
72 /// const Launch({super.key});
73 ///
74 /// @override
75 /// State createState() => LaunchState();
76 /// }
77 ///
78 /// class LaunchState extends State<Launch> {
79 /// double _rocketThrust = 0;
80 ///
81 /// @override
82 /// Widget build(BuildContext context) {
83 /// return SliderTheme(
84 /// data: SliderTheme.of(context).copyWith(activeTrackColor: const Color(0xff804040)),
85 /// child: Slider(
86 /// onChanged: (double value) { setState(() { _rocketThrust = value; }); },
87 /// value: _rocketThrust,
88 /// ),
89 /// );
90 /// }
91 /// }
92 /// ```
93 /// {@end-tool}
94 ///
95 /// See also:
96 ///
97 /// * [SliderThemeData], which describes the actual configuration of a slider
98 /// theme.
99 static SliderThemeData of(BuildContext context) {
100 final SliderTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<SliderTheme>();
101 return inheritedTheme != null ? inheritedTheme.data : Theme.of(context).sliderTheme;
102 }
103
104 @override
105 Widget wrap(BuildContext context, Widget child) {
106 return SliderTheme(data: data, child: child);
107 }
108
109 @override
110 bool updateShouldNotify(SliderTheme oldWidget) => data != oldWidget.data;
111}
112
113/// Describes the conditions under which the value indicator on a [Slider]
114/// will be shown. Used with [SliderThemeData.showValueIndicator].
115///
116/// See also:
117///
118/// * [Slider], a Material Design slider widget.
119/// * [SliderThemeData], which describes the actual configuration of a slider
120/// theme.
121enum ShowValueIndicator {
122 /// The value indicator will only be shown for discrete sliders (sliders
123 /// where [Slider.divisions] is non-null).
124 onlyForDiscrete,
125
126 /// The value indicator will only be shown for continuous sliders (sliders
127 /// where [Slider.divisions] is null).
128 onlyForContinuous,
129
130 /// The value indicator will be shown for all types of sliders.
131 always,
132
133 /// The value indicator will never be shown.
134 never,
135}
136
137/// Identifier for a thumb.
138///
139/// There are 2 thumbs in a [RangeSlider], [start] and [end].
140///
141/// For [TextDirection.ltr], the [start] thumb is the left-most thumb and the
142/// [end] thumb is the right-most thumb. For [TextDirection.rtl] the [start]
143/// thumb is the right-most thumb, and the [end] thumb is the left-most thumb.
144enum Thumb {
145 /// Left-most thumb for [TextDirection.ltr], otherwise, right-most thumb.
146 start,
147
148 /// Right-most thumb for [TextDirection.ltr], otherwise, left-most thumb.
149 end,
150}
151
152/// Holds the color, shape, and typography values for a Material Design slider
153/// theme.
154///
155/// Use this class to configure a [SliderTheme] widget, or to set the
156/// [ThemeData.sliderTheme] for a [Theme] widget.
157///
158/// To obtain the current ambient slider theme, use [SliderTheme.of].
159///
160/// This theme is for both the [Slider] and the [RangeSlider]. The properties
161/// that are only for the [Slider] are: [tickMarkShape], [thumbShape],
162/// [trackShape], and [valueIndicatorShape]. The properties that are only for
163/// the [RangeSlider] are [rangeTickMarkShape], [rangeThumbShape],
164/// [rangeTrackShape], [rangeValueIndicatorShape],
165/// [overlappingShapeStrokeColor], [minThumbSeparation], and [thumbSelector].
166/// All other properties are used by both the [Slider] and the [RangeSlider].
167///
168/// The parts of a slider are:
169///
170/// * The "thumb", which is a shape that slides horizontally when the user
171/// drags it.
172/// * The "track", which is the line that the slider thumb slides along.
173/// * The "tick marks", which are regularly spaced marks that are drawn when
174/// using discrete divisions.
175/// * The "value indicator", which appears when the user is dragging the thumb
176/// to indicate the value being selected.
177/// * The "overlay", which appears around the thumb, and is shown when the
178/// thumb is pressed, focused, or hovered. It is painted underneath the
179/// thumb, so it must extend beyond the bounds of the thumb itself to
180/// actually be visible.
181/// * The "active" side of the slider is the side between the thumb and the
182/// minimum value.
183/// * The "inactive" side of the slider is the side between the thumb and the
184/// maximum value.
185/// * The [Slider] is disabled when it is not accepting user input. See
186/// [Slider] for details on when this happens.
187///
188/// The thumb, track, tick marks, value indicator, and overlay can be customized
189/// by creating subclasses of [SliderTrackShape],
190/// [SliderComponentShape], and/or [SliderTickMarkShape]. See
191/// [RoundSliderThumbShape], [RectangularSliderTrackShape],
192/// [RoundSliderTickMarkShape], [RectangularSliderValueIndicatorShape], and
193/// [RoundSliderOverlayShape] for examples.
194///
195/// The track painting can be skipped by specifying 0 for [trackHeight].
196/// The thumb painting can be skipped by specifying
197/// [SliderComponentShape.noThumb] for [SliderThemeData.thumbShape].
198/// The overlay painting can be skipped by specifying
199/// [SliderComponentShape.noOverlay] for [SliderThemeData.overlayShape].
200/// The tick mark painting can be skipped by specifying
201/// [SliderTickMarkShape.noTickMark] for [SliderThemeData.tickMarkShape].
202/// The value indicator painting can be skipped by specifying the
203/// appropriate [ShowValueIndicator] for [SliderThemeData.showValueIndicator].
204///
205/// See also:
206///
207/// * [SliderTheme] widget, which can override the slider theme of its
208/// children.
209/// * [Theme] widget, which performs a similar function to [SliderTheme],
210/// but for overall themes.
211/// * [ThemeData], which has a default [SliderThemeData].
212/// * [SliderComponentShape], which can be used to create custom shapes for
213/// the [Slider]'s thumb, overlay, and value indicator and the
214/// [RangeSlider]'s overlay.
215/// * [SliderTrackShape], which can be used to create custom shapes for the
216/// [Slider]'s track.
217/// * [SliderTickMarkShape], which can be used to create custom shapes for the
218/// [Slider]'s tick marks.
219/// * [RangeSliderThumbShape], which can be used to create custom shapes for
220/// the [RangeSlider]'s thumb.
221/// * [RangeSliderValueIndicatorShape], which can be used to create custom
222/// shapes for the [RangeSlider]'s value indicator.
223/// * [RangeSliderTrackShape], which can be used to create custom shapes for
224/// the [RangeSlider]'s track.
225/// * [RangeSliderTickMarkShape], which can be used to create custom shapes for
226/// the [RangeSlider]'s tick marks.
227@immutable
228class SliderThemeData with Diagnosticable {
229 /// Create a [SliderThemeData] given a set of exact values.
230 ///
231 /// This will rarely be used directly. It is used by [lerp] to
232 /// create intermediate themes based on two themes.
233 ///
234 /// The simplest way to create a SliderThemeData is to use
235 /// [copyWith] on the one you get from [SliderTheme.of], or create an
236 /// entirely new one with [SliderThemeData.fromPrimaryColors].
237 ///
238 /// {@tool snippet}
239 ///
240 /// ```dart
241 /// class Blissful extends StatefulWidget {
242 /// const Blissful({super.key});
243 ///
244 /// @override
245 /// State createState() => BlissfulState();
246 /// }
247 ///
248 /// class BlissfulState extends State<Blissful> {
249 /// double _bliss = 0;
250 ///
251 /// @override
252 /// Widget build(BuildContext context) {
253 /// return SliderTheme(
254 /// data: SliderTheme.of(context).copyWith(activeTrackColor: const Color(0xff404080)),
255 /// child: Slider(
256 /// onChanged: (double value) { setState(() { _bliss = value; }); },
257 /// value: _bliss,
258 /// ),
259 /// );
260 /// }
261 /// }
262 /// ```
263 /// {@end-tool}
264 const SliderThemeData({
265 this.trackHeight,
266 this.activeTrackColor,
267 this.inactiveTrackColor,
268 this.secondaryActiveTrackColor,
269 this.disabledActiveTrackColor,
270 this.disabledInactiveTrackColor,
271 this.disabledSecondaryActiveTrackColor,
272 this.activeTickMarkColor,
273 this.inactiveTickMarkColor,
274 this.disabledActiveTickMarkColor,
275 this.disabledInactiveTickMarkColor,
276 this.thumbColor,
277 this.overlappingShapeStrokeColor,
278 this.disabledThumbColor,
279 this.overlayColor,
280 this.valueIndicatorColor,
281 this.valueIndicatorStrokeColor,
282 this.overlayShape,
283 this.tickMarkShape,
284 this.thumbShape,
285 this.trackShape,
286 this.valueIndicatorShape,
287 this.rangeTickMarkShape,
288 this.rangeThumbShape,
289 this.rangeTrackShape,
290 this.rangeValueIndicatorShape,
291 this.showValueIndicator,
292 this.valueIndicatorTextStyle,
293 this.minThumbSeparation,
294 this.thumbSelector,
295 this.mouseCursor,
296 this.allowedInteraction,
297 this.padding,
298 this.thumbSize,
299 this.trackGap,
300 @Deprecated(
301 'Set this flag to false to opt into the 2024 slider appearance. Defaults to true. '
302 'In the future, this flag will default to false. Use SliderThemeData to customize individual properties. '
303 'This feature was deprecated after v3.27.0-0.2.pre.',
304 )
305 this.year2023,
306 });
307
308 /// Generates a SliderThemeData from three main colors.
309 ///
310 /// Usually these are the primary, dark and light colors from
311 /// a [ThemeData].
312 ///
313 /// The opacities of these colors will be overridden with the Material Design
314 /// defaults when assigning them to the slider theme component colors.
315 ///
316 /// This is used to generate the default slider theme for a [ThemeData].
317 factory SliderThemeData.fromPrimaryColors({
318 required Color primaryColor,
319 required Color primaryColorDark,
320 required Color primaryColorLight,
321 required TextStyle valueIndicatorTextStyle,
322 }) {
323 // These are Material Design defaults, and are used to derive
324 // component Colors (with opacity) from base colors.
325 const int activeTrackAlpha = 0xff;
326 const int inactiveTrackAlpha = 0x3d; // 24% opacity
327 const int secondaryActiveTrackAlpha = 0x8a; // 54% opacity
328 const int disabledActiveTrackAlpha = 0x52; // 32% opacity
329 const int disabledInactiveTrackAlpha = 0x1f; // 12% opacity
330 const int disabledSecondaryActiveTrackAlpha = 0x1f; // 12% opacity
331 const int activeTickMarkAlpha = 0x8a; // 54% opacity
332 const int inactiveTickMarkAlpha = 0x8a; // 54% opacity
333 const int disabledActiveTickMarkAlpha = 0x1f; // 12% opacity
334 const int disabledInactiveTickMarkAlpha = 0x1f; // 12% opacity
335 const int thumbAlpha = 0xff;
336 const int disabledThumbAlpha = 0x52; // 32% opacity
337 const int overlayAlpha = 0x1f; // 12% opacity
338 const int valueIndicatorAlpha = 0xff;
339
340 return SliderThemeData(
341 trackHeight: 2.0,
342 activeTrackColor: primaryColor.withAlpha(activeTrackAlpha),
343 inactiveTrackColor: primaryColor.withAlpha(inactiveTrackAlpha),
344 secondaryActiveTrackColor: primaryColor.withAlpha(secondaryActiveTrackAlpha),
345 disabledActiveTrackColor: primaryColorDark.withAlpha(disabledActiveTrackAlpha),
346 disabledInactiveTrackColor: primaryColorDark.withAlpha(disabledInactiveTrackAlpha),
347 disabledSecondaryActiveTrackColor: primaryColorDark.withAlpha(
348 disabledSecondaryActiveTrackAlpha,
349 ),
350 activeTickMarkColor: primaryColorLight.withAlpha(activeTickMarkAlpha),
351 inactiveTickMarkColor: primaryColor.withAlpha(inactiveTickMarkAlpha),
352 disabledActiveTickMarkColor: primaryColorLight.withAlpha(disabledActiveTickMarkAlpha),
353 disabledInactiveTickMarkColor: primaryColorDark.withAlpha(disabledInactiveTickMarkAlpha),
354 thumbColor: primaryColor.withAlpha(thumbAlpha),
355 overlappingShapeStrokeColor: Colors.white,
356 disabledThumbColor: primaryColorDark.withAlpha(disabledThumbAlpha),
357 overlayColor: primaryColor.withAlpha(overlayAlpha),
358 valueIndicatorColor: primaryColor.withAlpha(valueIndicatorAlpha),
359 valueIndicatorStrokeColor: primaryColor.withAlpha(valueIndicatorAlpha),
360 overlayShape: const RoundSliderOverlayShape(),
361 tickMarkShape: const RoundSliderTickMarkShape(),
362 thumbShape: const RoundSliderThumbShape(),
363 trackShape: const RoundedRectSliderTrackShape(),
364 valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
365 rangeTickMarkShape: const RoundRangeSliderTickMarkShape(),
366 rangeThumbShape: const RoundRangeSliderThumbShape(),
367 rangeTrackShape: const RoundedRectRangeSliderTrackShape(),
368 rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
369 valueIndicatorTextStyle: valueIndicatorTextStyle,
370 showValueIndicator: ShowValueIndicator.onlyForDiscrete,
371 );
372 }
373
374 /// The height of the [Slider] track.
375 final double? trackHeight;
376
377 /// The color of the [Slider] track between the [Slider.min] position and the
378 /// current thumb position.
379 final Color? activeTrackColor;
380
381 /// The color of the [Slider] track between the current thumb position and the
382 /// [Slider.max] position.
383 final Color? inactiveTrackColor;
384
385 /// The color of the [Slider] track between the current thumb position and the
386 /// [Slider.secondaryTrackValue] position.
387 final Color? secondaryActiveTrackColor;
388
389 /// The color of the [Slider] track between the [Slider.min] position and the
390 /// current thumb position when the [Slider] is disabled.
391 final Color? disabledActiveTrackColor;
392
393 /// The color of the [Slider] track between the current thumb position and the
394 /// [Slider.secondaryTrackValue] position when the [Slider] is disabled.
395 final Color? disabledSecondaryActiveTrackColor;
396
397 /// The color of the [Slider] track between the current thumb position and the
398 /// [Slider.max] position when the [Slider] is disabled.
399 final Color? disabledInactiveTrackColor;
400
401 /// The color of the track's tick marks that are drawn between the [Slider.min]
402 /// position and the current thumb position.
403 final Color? activeTickMarkColor;
404
405 /// The color of the track's tick marks that are drawn between the current
406 /// thumb position and the [Slider.max] position.
407 final Color? inactiveTickMarkColor;
408
409 /// The color of the track's tick marks that are drawn between the [Slider.min]
410 /// position and the current thumb position when the [Slider] is disabled.
411 final Color? disabledActiveTickMarkColor;
412
413 /// The color of the track's tick marks that are drawn between the current
414 /// thumb position and the [Slider.max] position when the [Slider] is
415 /// disabled.
416 final Color? disabledInactiveTickMarkColor;
417
418 /// The color given to the [thumbShape] to draw itself with.
419 final Color? thumbColor;
420
421 /// The color given to the perimeter of the top [rangeThumbShape] when the
422 /// thumbs are overlapping and the top [rangeValueIndicatorShape] when the
423 /// value indicators are overlapping.
424 final Color? overlappingShapeStrokeColor;
425
426 /// The color given to the [thumbShape] to draw itself with when the
427 /// [Slider] is disabled.
428 final Color? disabledThumbColor;
429
430 /// The color of the overlay drawn around the slider thumb when it is
431 /// pressed, focused, or hovered.
432 ///
433 /// This is typically a semi-transparent color.
434 final Color? overlayColor;
435
436 /// The color given to the [valueIndicatorShape] to draw itself with.
437 final Color? valueIndicatorColor;
438
439 /// The color given to the [valueIndicatorShape] stroke.
440 final Color? valueIndicatorStrokeColor;
441
442 /// The shape that will be used to draw the [Slider]'s overlay.
443 ///
444 /// Both the [overlayColor] and a non default [overlayShape] may be specified.
445 /// The default [overlayShape] refers to the [overlayColor].
446 ///
447 /// The default value is [RoundSliderOverlayShape].
448 final SliderComponentShape? overlayShape;
449
450 /// The shape that will be used to draw the [Slider]'s tick marks.
451 ///
452 /// The [SliderTickMarkShape.getPreferredSize] is used to help determine the
453 /// location of each tick mark on the track. The slider's minimum size will
454 /// be at least this big.
455 ///
456 /// The default value is [RoundSliderTickMarkShape].
457 ///
458 /// See also:
459 ///
460 /// * [RoundRangeSliderTickMarkShape], which is the default tick mark
461 /// shape for the range slider.
462 final SliderTickMarkShape? tickMarkShape;
463
464 /// The shape that will be used to draw the [Slider]'s thumb.
465 ///
466 /// The default value is [RoundSliderThumbShape].
467 ///
468 /// See also:
469 ///
470 /// * [RoundRangeSliderThumbShape], which is the default thumb shape for
471 /// the [RangeSlider].
472 final SliderComponentShape? thumbShape;
473
474 /// The shape that will be used to draw the [Slider]'s track.
475 ///
476 /// The [SliderTrackShape.getPreferredRect] method is used to map
477 /// slider-relative gesture coordinates to the correct thumb position on the
478 /// track. It is also used to horizontally position tick marks, when the
479 /// slider is discrete.
480 ///
481 /// The default value is [RoundedRectSliderTrackShape].
482 ///
483 /// See also:
484 ///
485 /// * [RoundedRectRangeSliderTrackShape], which is the default track
486 /// shape for the [RangeSlider].
487 final SliderTrackShape? trackShape;
488
489 /// The shape that will be used to draw the [Slider]'s value
490 /// indicator.
491 ///
492 /// The default value is [PaddleSliderValueIndicatorShape].
493 ///
494 /// See also:
495 ///
496 /// * [PaddleRangeSliderValueIndicatorShape], which is the default value
497 /// indicator shape for the [RangeSlider].
498 final SliderComponentShape? valueIndicatorShape;
499
500 /// The shape that will be used to draw the [RangeSlider]'s tick marks.
501 ///
502 /// The [RangeSliderTickMarkShape.getPreferredSize] is used to help determine
503 /// the location of each tick mark on the track. The slider's minimum size
504 /// will be at least this big.
505 ///
506 /// The default value is [RoundRangeSliderTickMarkShape].
507 ///
508 /// See also:
509 ///
510 /// * [RoundSliderTickMarkShape], which is the default tick mark shape
511 /// for the [Slider].
512 final RangeSliderTickMarkShape? rangeTickMarkShape;
513
514 /// The shape that will be used for the [RangeSlider]'s thumbs.
515 ///
516 /// By default the same shape is used for both thumbs, but strokes the top
517 /// thumb when it overlaps the bottom thumb. The top thumb is always the last
518 /// selected thumb.
519 ///
520 /// The default value is [RoundRangeSliderThumbShape].
521 ///
522 /// See also:
523 ///
524 /// * [RoundSliderThumbShape], which is the default thumb shape for the
525 /// [Slider].
526 final RangeSliderThumbShape? rangeThumbShape;
527
528 /// The shape that will be used to draw the [RangeSlider]'s track.
529 ///
530 /// The [SliderTrackShape.getPreferredRect] method is used to map
531 /// slider-relative gesture coordinates to the correct thumb position on the
532 /// track. It is also used to horizontally position the tick marks, when the
533 /// slider is discrete.
534 ///
535 /// The default value is [RoundedRectRangeSliderTrackShape].
536 ///
537 /// See also:
538 ///
539 /// * [RoundedRectSliderTrackShape], which is the default track
540 /// shape for the [Slider].
541 final RangeSliderTrackShape? rangeTrackShape;
542
543 /// The shape that will be used for the [RangeSlider]'s value indicators.
544 ///
545 /// The default shape uses the same value indicator for each thumb, but
546 /// strokes the top value indicator when it overlaps the bottom value
547 /// indicator. The top indicator corresponds to the top thumb, which is always
548 /// the most recently selected thumb.
549 ///
550 /// The default value is [PaddleRangeSliderValueIndicatorShape].
551 ///
552 /// See also:
553 ///
554 /// * [PaddleSliderValueIndicatorShape], which is the default value
555 /// indicator shape for the [Slider].
556 final RangeSliderValueIndicatorShape? rangeValueIndicatorShape;
557
558 /// Whether the value indicator should be shown for different types of
559 /// sliders.
560 ///
561 /// By default, [showValueIndicator] is set to
562 /// [ShowValueIndicator.onlyForDiscrete]. The value indicator is only shown
563 /// when the thumb is being touched.
564 final ShowValueIndicator? showValueIndicator;
565
566 /// The text style for the text on the value indicator.
567 final TextStyle? valueIndicatorTextStyle;
568
569 /// Limits the thumb's separation distance.
570 ///
571 /// Use this only if you want to control the visual appearance of the thumbs
572 /// in terms of a logical pixel value. This can be done when you want a
573 /// specific look for thumbs when they are close together. To limit with the
574 /// real values, rather than logical pixels, the values can be restricted by
575 /// the parent.
576 final double? minThumbSeparation;
577
578 /// Determines which thumb should be selected when the slider is interacted
579 /// with.
580 ///
581 /// If null, the default thumb selector finds the closest thumb, excluding
582 /// taps that are between the thumbs and not within any one touch target.
583 /// When the selection is within the touch target bounds of both thumbs, no
584 /// thumb is selected until the selection is moved.
585 ///
586 /// Override this for custom thumb selection.
587 final RangeThumbSelector? thumbSelector;
588
589 /// {@macro flutter.material.slider.mouseCursor}
590 ///
591 /// If specified, overrides the default value of [Slider.mouseCursor].
592 final MaterialStateProperty<MouseCursor?>? mouseCursor;
593
594 /// Allowed way for the user to interact with the [Slider].
595 ///
596 /// If specified, overrides the default value of [Slider.allowedInteraction].
597 final SliderInteraction? allowedInteraction;
598
599 /// Determines the padding around the [Slider].
600 ///
601 /// If specified, this padding overrides the default vertical padding of
602 /// the [Slider], defaults to the height of the overlay shape, and the
603 /// horizontal padding, defaults to the width of the thumb shape or
604 /// overlay shape, whichever is larger.
605 final EdgeInsetsGeometry? padding;
606
607 /// The size of the [HandleThumbShape] thumb.
608 ///
609 /// If [SliderThemeData.thumbShape] is [HandleThumbShape], this property is used to
610 /// set the size of the thumb. Otherwise, the default thumb size is 4 pixels for the
611 /// width and 44 pixels for the height.
612 final MaterialStateProperty<Size?>? thumbSize;
613
614 /// The size of the gap between the active and inactive tracks of the [GappedSliderTrackShape].
615 ///
616 /// If [SliderThemeData.trackShape] is [GappedSliderTrackShape], this property
617 /// is used to set the gap between the active and inactive tracks. Otherwise,
618 /// the default gap size is 6.0 pixels.
619 ///
620 /// The Slider defaults to [GappedSliderTrackShape] when the track shape is
621 /// not specified, and the [trackGap] can be used to adjust the gap size.
622 ///
623 /// If [Slider.year2023] is false or [ThemeData.useMaterial3] is false, then
624 /// the Slider track shape defaults to [RoundedRectSliderTrackShape] and the
625 /// [trackGap] is ignored. In this case, set the track shape to
626 /// [GappedSliderTrackShape] to use the [trackGap].
627 ///
628 /// Defaults to 6.0 pixels of gap between the active and inactive tracks.
629 final double? trackGap;
630
631 /// Overrides the default value of [Slider.year2023].
632 ///
633 /// When true, the [Slider] will use the 2023 Material Design 3 appearance.
634 /// Defaults to true.
635 ///
636 /// If this is set to false, the [Slider] will use the latest Material Design 3
637 /// appearance, which was introduced in December 2023.
638 ///
639 /// If [ThemeData.useMaterial3] is false, then this property is ignored.
640 @Deprecated(
641 'Set this flag to false to opt into the 2024 slider appearance. Defaults to true. '
642 'In the future, this flag will default to false. Use SliderThemeData to customize individual properties. '
643 'This feature was deprecated after v3.27.0-0.2.pre.',
644 )
645 final bool? year2023;
646
647 /// Creates a copy of this object but with the given fields replaced with the
648 /// new values.
649 SliderThemeData copyWith({
650 double? trackHeight,
651 Color? activeTrackColor,
652 Color? inactiveTrackColor,
653 Color? secondaryActiveTrackColor,
654 Color? disabledActiveTrackColor,
655 Color? disabledInactiveTrackColor,
656 Color? disabledSecondaryActiveTrackColor,
657 Color? activeTickMarkColor,
658 Color? inactiveTickMarkColor,
659 Color? disabledActiveTickMarkColor,
660 Color? disabledInactiveTickMarkColor,
661 Color? thumbColor,
662 Color? overlappingShapeStrokeColor,
663 Color? disabledThumbColor,
664 Color? overlayColor,
665 Color? valueIndicatorColor,
666 Color? valueIndicatorStrokeColor,
667 SliderComponentShape? overlayShape,
668 SliderTickMarkShape? tickMarkShape,
669 SliderComponentShape? thumbShape,
670 SliderTrackShape? trackShape,
671 SliderComponentShape? valueIndicatorShape,
672 RangeSliderTickMarkShape? rangeTickMarkShape,
673 RangeSliderThumbShape? rangeThumbShape,
674 RangeSliderTrackShape? rangeTrackShape,
675 RangeSliderValueIndicatorShape? rangeValueIndicatorShape,
676 ShowValueIndicator? showValueIndicator,
677 TextStyle? valueIndicatorTextStyle,
678 double? minThumbSeparation,
679 RangeThumbSelector? thumbSelector,
680 MaterialStateProperty<MouseCursor?>? mouseCursor,
681 SliderInteraction? allowedInteraction,
682 EdgeInsetsGeometry? padding,
683 MaterialStateProperty<Size?>? thumbSize,
684 double? trackGap,
685 bool? year2023,
686 }) {
687 return SliderThemeData(
688 trackHeight: trackHeight ?? this.trackHeight,
689 activeTrackColor: activeTrackColor ?? this.activeTrackColor,
690 inactiveTrackColor: inactiveTrackColor ?? this.inactiveTrackColor,
691 secondaryActiveTrackColor: secondaryActiveTrackColor ?? this.secondaryActiveTrackColor,
692 disabledActiveTrackColor: disabledActiveTrackColor ?? this.disabledActiveTrackColor,
693 disabledInactiveTrackColor: disabledInactiveTrackColor ?? this.disabledInactiveTrackColor,
694 disabledSecondaryActiveTrackColor:
695 disabledSecondaryActiveTrackColor ?? this.disabledSecondaryActiveTrackColor,
696 activeTickMarkColor: activeTickMarkColor ?? this.activeTickMarkColor,
697 inactiveTickMarkColor: inactiveTickMarkColor ?? this.inactiveTickMarkColor,
698 disabledActiveTickMarkColor: disabledActiveTickMarkColor ?? this.disabledActiveTickMarkColor,
699 disabledInactiveTickMarkColor:
700 disabledInactiveTickMarkColor ?? this.disabledInactiveTickMarkColor,
701 thumbColor: thumbColor ?? this.thumbColor,
702 overlappingShapeStrokeColor: overlappingShapeStrokeColor ?? this.overlappingShapeStrokeColor,
703 disabledThumbColor: disabledThumbColor ?? this.disabledThumbColor,
704 overlayColor: overlayColor ?? this.overlayColor,
705 valueIndicatorColor: valueIndicatorColor ?? this.valueIndicatorColor,
706 valueIndicatorStrokeColor: valueIndicatorStrokeColor ?? this.valueIndicatorStrokeColor,
707 overlayShape: overlayShape ?? this.overlayShape,
708 tickMarkShape: tickMarkShape ?? this.tickMarkShape,
709 thumbShape: thumbShape ?? this.thumbShape,
710 trackShape: trackShape ?? this.trackShape,
711 valueIndicatorShape: valueIndicatorShape ?? this.valueIndicatorShape,
712 rangeTickMarkShape: rangeTickMarkShape ?? this.rangeTickMarkShape,
713 rangeThumbShape: rangeThumbShape ?? this.rangeThumbShape,
714 rangeTrackShape: rangeTrackShape ?? this.rangeTrackShape,
715 rangeValueIndicatorShape: rangeValueIndicatorShape ?? this.rangeValueIndicatorShape,
716 showValueIndicator: showValueIndicator ?? this.showValueIndicator,
717 valueIndicatorTextStyle: valueIndicatorTextStyle ?? this.valueIndicatorTextStyle,
718 minThumbSeparation: minThumbSeparation ?? this.minThumbSeparation,
719 thumbSelector: thumbSelector ?? this.thumbSelector,
720 mouseCursor: mouseCursor ?? this.mouseCursor,
721 allowedInteraction: allowedInteraction ?? this.allowedInteraction,
722 padding: padding ?? this.padding,
723 thumbSize: thumbSize ?? this.thumbSize,
724 trackGap: trackGap ?? this.trackGap,
725 year2023: year2023 ?? this.year2023,
726 );
727 }
728
729 /// Linearly interpolate between two slider themes.
730 ///
731 /// {@macro dart.ui.shadow.lerp}
732 static SliderThemeData lerp(SliderThemeData a, SliderThemeData b, double t) {
733 if (identical(a, b)) {
734 return a;
735 }
736 return SliderThemeData(
737 trackHeight: lerpDouble(a.trackHeight, b.trackHeight, t),
738 activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t),
739 inactiveTrackColor: Color.lerp(a.inactiveTrackColor, b.inactiveTrackColor, t),
740 secondaryActiveTrackColor: Color.lerp(
741 a.secondaryActiveTrackColor,
742 b.secondaryActiveTrackColor,
743 t,
744 ),
745 disabledActiveTrackColor: Color.lerp(
746 a.disabledActiveTrackColor,
747 b.disabledActiveTrackColor,
748 t,
749 ),
750 disabledInactiveTrackColor: Color.lerp(
751 a.disabledInactiveTrackColor,
752 b.disabledInactiveTrackColor,
753 t,
754 ),
755 disabledSecondaryActiveTrackColor: Color.lerp(
756 a.disabledSecondaryActiveTrackColor,
757 b.disabledSecondaryActiveTrackColor,
758 t,
759 ),
760 activeTickMarkColor: Color.lerp(a.activeTickMarkColor, b.activeTickMarkColor, t),
761 inactiveTickMarkColor: Color.lerp(a.inactiveTickMarkColor, b.inactiveTickMarkColor, t),
762 disabledActiveTickMarkColor: Color.lerp(
763 a.disabledActiveTickMarkColor,
764 b.disabledActiveTickMarkColor,
765 t,
766 ),
767 disabledInactiveTickMarkColor: Color.lerp(
768 a.disabledInactiveTickMarkColor,
769 b.disabledInactiveTickMarkColor,
770 t,
771 ),
772 thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t),
773 overlappingShapeStrokeColor: Color.lerp(
774 a.overlappingShapeStrokeColor,
775 b.overlappingShapeStrokeColor,
776 t,
777 ),
778 disabledThumbColor: Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t),
779 overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t),
780 valueIndicatorColor: Color.lerp(a.valueIndicatorColor, b.valueIndicatorColor, t),
781 valueIndicatorStrokeColor: Color.lerp(
782 a.valueIndicatorStrokeColor,
783 b.valueIndicatorStrokeColor,
784 t,
785 ),
786 overlayShape: t < 0.5 ? a.overlayShape : b.overlayShape,
787 tickMarkShape: t < 0.5 ? a.tickMarkShape : b.tickMarkShape,
788 thumbShape: t < 0.5 ? a.thumbShape : b.thumbShape,
789 trackShape: t < 0.5 ? a.trackShape : b.trackShape,
790 valueIndicatorShape: t < 0.5 ? a.valueIndicatorShape : b.valueIndicatorShape,
791 rangeTickMarkShape: t < 0.5 ? a.rangeTickMarkShape : b.rangeTickMarkShape,
792 rangeThumbShape: t < 0.5 ? a.rangeThumbShape : b.rangeThumbShape,
793 rangeTrackShape: t < 0.5 ? a.rangeTrackShape : b.rangeTrackShape,
794 rangeValueIndicatorShape: t < 0.5 ? a.rangeValueIndicatorShape : b.rangeValueIndicatorShape,
795 showValueIndicator: t < 0.5 ? a.showValueIndicator : b.showValueIndicator,
796 valueIndicatorTextStyle: TextStyle.lerp(
797 a.valueIndicatorTextStyle,
798 b.valueIndicatorTextStyle,
799 t,
800 ),
801 minThumbSeparation: lerpDouble(a.minThumbSeparation, b.minThumbSeparation, t),
802 thumbSelector: t < 0.5 ? a.thumbSelector : b.thumbSelector,
803 mouseCursor: t < 0.5 ? a.mouseCursor : b.mouseCursor,
804 allowedInteraction: t < 0.5 ? a.allowedInteraction : b.allowedInteraction,
805 padding: EdgeInsetsGeometry.lerp(a.padding, b.padding, t),
806 thumbSize: MaterialStateProperty.lerp<Size?>(a.thumbSize, b.thumbSize, t, Size.lerp),
807 trackGap: lerpDouble(a.trackGap, b.trackGap, t),
808 year2023: t < 0.5 ? a.year2023 : b.year2023,
809 );
810 }
811
812 @override
813 int get hashCode => Object.hash(
814 trackHeight,
815 activeTrackColor,
816 inactiveTrackColor,
817 secondaryActiveTrackColor,
818 disabledActiveTrackColor,
819 disabledInactiveTrackColor,
820 disabledSecondaryActiveTrackColor,
821 activeTickMarkColor,
822 inactiveTickMarkColor,
823 disabledActiveTickMarkColor,
824 disabledInactiveTickMarkColor,
825 thumbColor,
826 overlappingShapeStrokeColor,
827 disabledThumbColor,
828 overlayColor,
829 valueIndicatorColor,
830 overlayShape,
831 tickMarkShape,
832 thumbShape,
833 Object.hash(
834 trackShape,
835 valueIndicatorShape,
836 rangeTickMarkShape,
837 rangeThumbShape,
838 rangeTrackShape,
839 rangeValueIndicatorShape,
840 showValueIndicator,
841 valueIndicatorTextStyle,
842 minThumbSeparation,
843 thumbSelector,
844 mouseCursor,
845 allowedInteraction,
846 padding,
847 thumbSize,
848 trackGap,
849 year2023,
850 ),
851 );
852
853 @override
854 bool operator ==(Object other) {
855 if (identical(this, other)) {
856 return true;
857 }
858 if (other.runtimeType != runtimeType) {
859 return false;
860 }
861 return other is SliderThemeData &&
862 other.trackHeight == trackHeight &&
863 other.activeTrackColor == activeTrackColor &&
864 other.inactiveTrackColor == inactiveTrackColor &&
865 other.secondaryActiveTrackColor == secondaryActiveTrackColor &&
866 other.disabledActiveTrackColor == disabledActiveTrackColor &&
867 other.disabledInactiveTrackColor == disabledInactiveTrackColor &&
868 other.disabledSecondaryActiveTrackColor == disabledSecondaryActiveTrackColor &&
869 other.activeTickMarkColor == activeTickMarkColor &&
870 other.inactiveTickMarkColor == inactiveTickMarkColor &&
871 other.disabledActiveTickMarkColor == disabledActiveTickMarkColor &&
872 other.disabledInactiveTickMarkColor == disabledInactiveTickMarkColor &&
873 other.thumbColor == thumbColor &&
874 other.overlappingShapeStrokeColor == overlappingShapeStrokeColor &&
875 other.disabledThumbColor == disabledThumbColor &&
876 other.overlayColor == overlayColor &&
877 other.valueIndicatorColor == valueIndicatorColor &&
878 other.valueIndicatorStrokeColor == valueIndicatorStrokeColor &&
879 other.overlayShape == overlayShape &&
880 other.tickMarkShape == tickMarkShape &&
881 other.thumbShape == thumbShape &&
882 other.trackShape == trackShape &&
883 other.valueIndicatorShape == valueIndicatorShape &&
884 other.rangeTickMarkShape == rangeTickMarkShape &&
885 other.rangeThumbShape == rangeThumbShape &&
886 other.rangeTrackShape == rangeTrackShape &&
887 other.rangeValueIndicatorShape == rangeValueIndicatorShape &&
888 other.showValueIndicator == showValueIndicator &&
889 other.valueIndicatorTextStyle == valueIndicatorTextStyle &&
890 other.minThumbSeparation == minThumbSeparation &&
891 other.thumbSelector == thumbSelector &&
892 other.mouseCursor == mouseCursor &&
893 other.allowedInteraction == allowedInteraction &&
894 other.padding == padding &&
895 other.thumbSize == thumbSize &&
896 other.trackGap == trackGap &&
897 other.year2023 == year2023;
898 }
899
900 @override
901 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
902 super.debugFillProperties(properties);
903 const SliderThemeData defaultData = SliderThemeData();
904 properties.add(
905 DoubleProperty('trackHeight', trackHeight, defaultValue: defaultData.trackHeight),
906 );
907 properties.add(
908 ColorProperty(
909 'activeTrackColor',
910 activeTrackColor,
911 defaultValue: defaultData.activeTrackColor,
912 ),
913 );
914 properties.add(
915 ColorProperty(
916 'inactiveTrackColor',
917 inactiveTrackColor,
918 defaultValue: defaultData.inactiveTrackColor,
919 ),
920 );
921 properties.add(
922 ColorProperty(
923 'secondaryActiveTrackColor',
924 secondaryActiveTrackColor,
925 defaultValue: defaultData.secondaryActiveTrackColor,
926 ),
927 );
928 properties.add(
929 ColorProperty(
930 'disabledActiveTrackColor',
931 disabledActiveTrackColor,
932 defaultValue: defaultData.disabledActiveTrackColor,
933 ),
934 );
935 properties.add(
936 ColorProperty(
937 'disabledInactiveTrackColor',
938 disabledInactiveTrackColor,
939 defaultValue: defaultData.disabledInactiveTrackColor,
940 ),
941 );
942 properties.add(
943 ColorProperty(
944 'disabledSecondaryActiveTrackColor',
945 disabledSecondaryActiveTrackColor,
946 defaultValue: defaultData.disabledSecondaryActiveTrackColor,
947 ),
948 );
949 properties.add(
950 ColorProperty(
951 'activeTickMarkColor',
952 activeTickMarkColor,
953 defaultValue: defaultData.activeTickMarkColor,
954 ),
955 );
956 properties.add(
957 ColorProperty(
958 'inactiveTickMarkColor',
959 inactiveTickMarkColor,
960 defaultValue: defaultData.inactiveTickMarkColor,
961 ),
962 );
963 properties.add(
964 ColorProperty(
965 'disabledActiveTickMarkColor',
966 disabledActiveTickMarkColor,
967 defaultValue: defaultData.disabledActiveTickMarkColor,
968 ),
969 );
970 properties.add(
971 ColorProperty(
972 'disabledInactiveTickMarkColor',
973 disabledInactiveTickMarkColor,
974 defaultValue: defaultData.disabledInactiveTickMarkColor,
975 ),
976 );
977 properties.add(ColorProperty('thumbColor', thumbColor, defaultValue: defaultData.thumbColor));
978 properties.add(
979 ColorProperty(
980 'overlappingShapeStrokeColor',
981 overlappingShapeStrokeColor,
982 defaultValue: defaultData.overlappingShapeStrokeColor,
983 ),
984 );
985 properties.add(
986 ColorProperty(
987 'disabledThumbColor',
988 disabledThumbColor,
989 defaultValue: defaultData.disabledThumbColor,
990 ),
991 );
992 properties.add(
993 ColorProperty('overlayColor', overlayColor, defaultValue: defaultData.overlayColor),
994 );
995 properties.add(
996 ColorProperty(
997 'valueIndicatorColor',
998 valueIndicatorColor,
999 defaultValue: defaultData.valueIndicatorColor,
1000 ),
1001 );
1002 properties.add(
1003 ColorProperty(
1004 'valueIndicatorStrokeColor',
1005 valueIndicatorStrokeColor,
1006 defaultValue: defaultData.valueIndicatorStrokeColor,
1007 ),
1008 );
1009 properties.add(
1010 DiagnosticsProperty<SliderComponentShape>(
1011 'overlayShape',
1012 overlayShape,
1013 defaultValue: defaultData.overlayShape,
1014 ),
1015 );
1016 properties.add(
1017 DiagnosticsProperty<SliderTickMarkShape>(
1018 'tickMarkShape',
1019 tickMarkShape,
1020 defaultValue: defaultData.tickMarkShape,
1021 ),
1022 );
1023 properties.add(
1024 DiagnosticsProperty<SliderComponentShape>(
1025 'thumbShape',
1026 thumbShape,
1027 defaultValue: defaultData.thumbShape,
1028 ),
1029 );
1030 properties.add(
1031 DiagnosticsProperty<SliderTrackShape>(
1032 'trackShape',
1033 trackShape,
1034 defaultValue: defaultData.trackShape,
1035 ),
1036 );
1037 properties.add(
1038 DiagnosticsProperty<SliderComponentShape>(
1039 'valueIndicatorShape',
1040 valueIndicatorShape,
1041 defaultValue: defaultData.valueIndicatorShape,
1042 ),
1043 );
1044 properties.add(
1045 DiagnosticsProperty<RangeSliderTickMarkShape>(
1046 'rangeTickMarkShape',
1047 rangeTickMarkShape,
1048 defaultValue: defaultData.rangeTickMarkShape,
1049 ),
1050 );
1051 properties.add(
1052 DiagnosticsProperty<RangeSliderThumbShape>(
1053 'rangeThumbShape',
1054 rangeThumbShape,
1055 defaultValue: defaultData.rangeThumbShape,
1056 ),
1057 );
1058 properties.add(
1059 DiagnosticsProperty<RangeSliderTrackShape>(
1060 'rangeTrackShape',
1061 rangeTrackShape,
1062 defaultValue: defaultData.rangeTrackShape,
1063 ),
1064 );
1065 properties.add(
1066 DiagnosticsProperty<RangeSliderValueIndicatorShape>(
1067 'rangeValueIndicatorShape',
1068 rangeValueIndicatorShape,
1069 defaultValue: defaultData.rangeValueIndicatorShape,
1070 ),
1071 );
1072 properties.add(
1073 EnumProperty<ShowValueIndicator>(
1074 'showValueIndicator',
1075 showValueIndicator,
1076 defaultValue: defaultData.showValueIndicator,
1077 ),
1078 );
1079 properties.add(
1080 DiagnosticsProperty<TextStyle>(
1081 'valueIndicatorTextStyle',
1082 valueIndicatorTextStyle,
1083 defaultValue: defaultData.valueIndicatorTextStyle,
1084 ),
1085 );
1086 properties.add(
1087 DoubleProperty(
1088 'minThumbSeparation',
1089 minThumbSeparation,
1090 defaultValue: defaultData.minThumbSeparation,
1091 ),
1092 );
1093 properties.add(
1094 DiagnosticsProperty<RangeThumbSelector>(
1095 'thumbSelector',
1096 thumbSelector,
1097 defaultValue: defaultData.thumbSelector,
1098 ),
1099 );
1100 properties.add(
1101 DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>(
1102 'mouseCursor',
1103 mouseCursor,
1104 defaultValue: defaultData.mouseCursor,
1105 ),
1106 );
1107 properties.add(
1108 EnumProperty<SliderInteraction>(
1109 'allowedInteraction',
1110 allowedInteraction,
1111 defaultValue: defaultData.allowedInteraction,
1112 ),
1113 );
1114 properties.add(
1115 DiagnosticsProperty<EdgeInsetsGeometry>(
1116 'padding',
1117 padding,
1118 defaultValue: defaultData.padding,
1119 ),
1120 );
1121 properties.add(
1122 DiagnosticsProperty<MaterialStateProperty<Size?>>(
1123 'thumbSize',
1124 thumbSize,
1125 defaultValue: defaultData.thumbSize,
1126 ),
1127 );
1128 properties.add(DoubleProperty('trackGap', trackGap, defaultValue: defaultData.trackGap));
1129 properties.add(
1130 DiagnosticsProperty<bool>('year2023', year2023, defaultValue: defaultData.year2023),
1131 );
1132 }
1133}
1134
1135/// Base class for slider thumb, thumb overlay, and value indicator shapes.
1136///
1137/// Create a subclass of this if you would like a custom shape.
1138///
1139/// All shapes are painted to the same canvas and ordering is important.
1140/// The overlay is painted first, then the value indicator, then the thumb.
1141///
1142/// The thumb painting can be skipped by specifying [noThumb] for
1143/// [SliderThemeData.thumbShape].
1144///
1145/// The overlay painting can be skipped by specifying [noOverlay] for
1146/// [SliderThemeData.overlayShape].
1147///
1148/// See also:
1149///
1150/// * [RoundSliderThumbShape], which is the default [Slider]'s thumb shape that
1151/// paints a solid circle.
1152/// * [RoundSliderOverlayShape], which is the default [Slider] and
1153/// [RangeSlider]'s overlay shape that paints a transparent circle.
1154/// * [PaddleSliderValueIndicatorShape], which is the default [Slider]'s value
1155/// indicator shape that paints a custom path with text in it.
1156abstract class SliderComponentShape {
1157 /// This abstract const constructor enables subclasses to provide
1158 /// const constructors so that they can be used in const expressions.
1159 const SliderComponentShape();
1160
1161 /// Returns the preferred size of the shape, based on the given conditions.
1162 Size getPreferredSize(bool isEnabled, bool isDiscrete);
1163
1164 /// Paints the shape, taking into account the state passed to it.
1165 ///
1166 /// {@template flutter.material.SliderComponentShape.paint.context}
1167 /// The `context` argument is the same as the one that includes the [Slider]'s
1168 /// render box.
1169 /// {@endtemplate}
1170 ///
1171 /// {@template flutter.material.SliderComponentShape.paint.center}
1172 /// The `center` argument is the offset for where this shape's center should be
1173 /// painted. This offset is relative to the origin of the [context] canvas.
1174 /// {@endtemplate}
1175 ///
1176 /// The `activationAnimation` argument is an animation triggered when the user
1177 /// begins to interact with the slider. It reverses when the user stops interacting
1178 /// with the slider.
1179 ///
1180 /// {@template flutter.material.SliderComponentShape.paint.enableAnimation}
1181 /// The `enableAnimation` argument is an animation triggered when the [Slider]
1182 /// is enabled, and it reverses when the slider is disabled. The [Slider] is
1183 /// enabled when [Slider.onChanged] is not null.Use this to paint intermediate
1184 /// frames for this shape when the slider changes enabled state.
1185 /// {@endtemplate}
1186 ///
1187 /// {@template flutter.material.SliderComponentShape.paint.isDiscrete}
1188 /// The `isDiscrete` argument is true if [Slider.divisions] is non-null. When
1189 /// true, the slider will render tick marks on top of the track.
1190 /// {@endtemplate}
1191 ///
1192 /// If the `labelPainter` argument is non-null, then [TextPainter.paint]
1193 /// should be called on the `labelPainter` with the location that the label
1194 /// should appear. If the `labelPainter` argument is null, then no label was
1195 /// supplied to the [Slider].
1196 ///
1197 /// {@template flutter.material.SliderComponentShape.paint.parentBox}
1198 /// The `parentBox` argument is the [RenderBox] of the [Slider]. Its attributes,
1199 /// such as size, can be used to assist in painting this shape.
1200 /// {@endtemplate}
1201 ///
1202 /// {@template flutter.material.SliderComponentShape.paint.sliderTheme}
1203 /// the `sliderTheme` argument is the theme assigned to the [Slider] that this
1204 /// shape belongs to.
1205 /// {@endtemplate}
1206 ///
1207 /// The `textDirection` argument can be used to determine how any extra text
1208 /// or graphics (besides the text painted by the `labelPainter`) should be
1209 /// positioned. The `labelPainter` already has the [textDirection] set.
1210 ///
1211 /// The `value` argument is the current parametric value (from 0.0 to 1.0) of
1212 /// the slider.
1213 ///
1214 /// {@template flutter.material.SliderComponentShape.paint.textScaleFactor}
1215 /// The `textScaleFactor` argument can be used to determine whether the
1216 /// component should paint larger or smaller, depending on whether
1217 /// [textScaleFactor] is greater than 1 for larger, and between 0 and 1 for
1218 /// smaller. It's usually computed from [MediaQueryData.textScaler].
1219 /// {@endtemplate}
1220 ///
1221 /// {@template flutter.material.SliderComponentShape.paint.sizeWithOverflow}
1222 /// The `sizeWithOverflow` argument can be used to determine the bounds the
1223 /// drawing of the components that are outside of the regular slider bounds.
1224 /// It's the size of the box, whose center is aligned with the slider's
1225 /// bounds, that the value indicators must be drawn within. Typically, it is
1226 /// bigger than the slider.
1227 /// {@endtemplate}
1228 void paint(
1229 PaintingContext context,
1230 Offset center, {
1231 required Animation<double> activationAnimation,
1232 required Animation<double> enableAnimation,
1233 required bool isDiscrete,
1234 required TextPainter labelPainter,
1235 required RenderBox parentBox,
1236 required SliderThemeData sliderTheme,
1237 required TextDirection textDirection,
1238 required double value,
1239 required double textScaleFactor,
1240 required Size sizeWithOverflow,
1241 });
1242
1243 /// Special instance of [SliderComponentShape] to skip the thumb drawing.
1244 ///
1245 /// See also:
1246 ///
1247 /// * [SliderThemeData.thumbShape], which is the shape that the [Slider]
1248 /// uses when painting the thumb.
1249 static final SliderComponentShape noThumb = _EmptySliderComponentShape();
1250
1251 /// Special instance of [SliderComponentShape] to skip the overlay drawing.
1252 ///
1253 /// See also:
1254 ///
1255 /// * [SliderThemeData.overlayShape], which is the shape that the [Slider]
1256 /// uses when painting the overlay.
1257 static final SliderComponentShape noOverlay = _EmptySliderComponentShape();
1258}
1259
1260/// Base class for [Slider] tick mark shapes.
1261///
1262/// Create a subclass of this if you would like a custom slider tick mark shape.
1263///
1264/// The tick mark painting can be skipped by specifying [noTickMark] for
1265/// [SliderThemeData.tickMarkShape].
1266///
1267/// See also:
1268///
1269/// * [RoundSliderTickMarkShape], which is the default [Slider]'s tick mark
1270/// shape that paints a solid circle.
1271/// * [SliderTrackShape], which can be used to create custom shapes for the
1272/// [Slider]'s track.
1273/// * [SliderComponentShape], which can be used to create custom shapes for
1274/// the [Slider]'s thumb, overlay, and value indicator and the
1275/// [RangeSlider]'s overlay.
1276abstract class SliderTickMarkShape {
1277 /// This abstract const constructor enables subclasses to provide
1278 /// const constructors so that they can be used in const expressions.
1279 const SliderTickMarkShape();
1280
1281 /// Returns the preferred size of the shape.
1282 ///
1283 /// It is used to help position the tick marks within the slider.
1284 ///
1285 /// {@macro flutter.material.SliderComponentShape.paint.sliderTheme}
1286 ///
1287 /// {@template flutter.material.SliderTickMarkShape.getPreferredSize.isEnabled}
1288 /// The `isEnabled` argument is false when [Slider.onChanged] is null and true
1289 /// otherwise. When true, the slider will respond to input.
1290 /// {@endtemplate}
1291 Size getPreferredSize({required SliderThemeData sliderTheme, required bool isEnabled});
1292
1293 /// Paints the slider track.
1294 ///
1295 /// {@macro flutter.material.SliderComponentShape.paint.context}
1296 ///
1297 /// {@macro flutter.material.SliderComponentShape.paint.center}
1298 ///
1299 /// {@macro flutter.material.SliderComponentShape.paint.parentBox}
1300 ///
1301 /// {@macro flutter.material.SliderComponentShape.paint.sliderTheme}
1302 ///
1303 /// {@macro flutter.material.SliderComponentShape.paint.enableAnimation}
1304 ///
1305 /// {@macro flutter.material.SliderTickMarkShape.getPreferredSize.isEnabled}
1306 ///
1307 /// The `textDirection` argument can be used to determine how the tick marks
1308 /// are painting depending on whether they are on an active track segment or
1309 /// not. The track segment between the start of the slider and the thumb is
1310 /// the active track segment. The track segment between the thumb and the end
1311 /// of the slider is the inactive track segment. In LTR text direction, the
1312 /// start of the slider is on the left, and in RTL text direction, the start
1313 /// of the slider is on the right.
1314 void paint(
1315 PaintingContext context,
1316 Offset center, {
1317 required RenderBox parentBox,
1318 required SliderThemeData sliderTheme,
1319 required Animation<double> enableAnimation,
1320 required Offset thumbCenter,
1321 required bool isEnabled,
1322 required TextDirection textDirection,
1323 });
1324
1325 /// Special instance of [SliderTickMarkShape] to skip the tick mark painting.
1326 ///
1327 /// See also:
1328 ///
1329 /// * [SliderThemeData.tickMarkShape], which is the shape that the [Slider]
1330 /// uses when painting tick marks.
1331 static final SliderTickMarkShape noTickMark = _EmptySliderTickMarkShape();
1332}
1333
1334/// Base class for slider track shapes.
1335///
1336/// The slider's thumb moves along the track. A discrete slider's tick marks
1337/// are drawn after the track, but before the thumb, and are aligned with the
1338/// track.
1339///
1340/// The [getPreferredRect] helps position the slider thumb and tick marks
1341/// relative to the track.
1342///
1343/// See also:
1344///
1345/// * [RoundedRectSliderTrackShape] for the default [Slider]'s track shape that
1346/// paints a stadium-like track.
1347/// * [SliderTickMarkShape], which can be used to create custom shapes for the
1348/// [Slider]'s tick marks.
1349/// * [SliderComponentShape], which can be used to create custom shapes for
1350/// the [Slider]'s thumb, overlay, and value indicator and the
1351/// [RangeSlider]'s overlay.
1352abstract class SliderTrackShape {
1353 /// This abstract const constructor enables subclasses to provide
1354 /// const constructors so that they can be used in const expressions.
1355 const SliderTrackShape();
1356
1357 /// Returns the preferred bounds of the shape.
1358 ///
1359 /// It is used to provide horizontal boundaries for the thumb's position, and
1360 /// to help position the slider thumb and tick marks relative to the track.
1361 ///
1362 /// The `parentBox` argument can be used to help determine the preferredRect relative to
1363 /// attributes of the render box of the slider itself, such as size.
1364 ///
1365 /// The `offset` argument is relative to the caller's bounding box. It can be used to
1366 /// convert gesture coordinates from global to slider-relative coordinates.
1367 ///
1368 /// {@macro flutter.material.SliderComponentShape.paint.sliderTheme}
1369 ///
1370 /// {@macro flutter.material.SliderTickMarkShape.getPreferredSize.isEnabled}
1371 ///
1372 /// {@macro flutter.material.SliderComponentShape.paint.isDiscrete}
1373 Rect getPreferredRect({
1374 required RenderBox parentBox,
1375 Offset offset = Offset.zero,
1376 required SliderThemeData sliderTheme,
1377 bool isEnabled,
1378 bool isDiscrete,
1379 });
1380
1381 /// Paints the track shape based on the state passed to it.
1382 ///
1383 /// {@macro flutter.material.SliderComponentShape.paint.context}
1384 ///
1385 /// The `offset` argument the offset of the origin of the `parentBox` to the
1386 /// origin of its `context` canvas. This shape must be painted relative to
1387 /// this offset. See [PaintingContextCallback].
1388 ///
1389 /// {@macro flutter.material.SliderComponentShape.paint.parentBox}
1390 ///
1391 /// {@macro flutter.material.SliderComponentShape.paint.sliderTheme}
1392 ///
1393 /// {@macro flutter.material.SliderComponentShape.paint.enableAnimation}
1394 ///
1395 /// The `thumbCenter` argument is the offset of the center of the thumb
1396 /// relative to the origin of the [PaintingContext.canvas]. It can be used as
1397 /// the point that divides the track into 2 segments.
1398 ///
1399 /// The `secondaryOffset` argument is the offset of the secondary value
1400 /// relative to the origin of the [PaintingContext.canvas].
1401 ///
1402 /// If not null, the track is divided into 3 segments.
1403 ///
1404 /// {@macro flutter.material.SliderTickMarkShape.getPreferredSize.isEnabled}
1405 ///
1406 /// {@macro flutter.material.SliderComponentShape.paint.isDiscrete}
1407 ///
1408 /// The `textDirection` argument can be used to determine how the track
1409 /// segments are painted depending on whether they are active or not.
1410 ///
1411 /// {@template flutter.material.SliderTrackShape.paint.trackSegment}
1412 /// The track segment between the start of the slider and the thumb is the
1413 /// active track segment. The track segment between the thumb and the end of the
1414 /// slider is the inactive track segment. In [TextDirection.ltr], the start of
1415 /// the slider is on the left, and in [TextDirection.rtl], the start of the
1416 /// slider is on the right.
1417 /// {@endtemplate}
1418 void paint(
1419 PaintingContext context,
1420 Offset offset, {
1421 required RenderBox parentBox,
1422 required SliderThemeData sliderTheme,
1423 required Animation<double> enableAnimation,
1424 required Offset thumbCenter,
1425 Offset? secondaryOffset,
1426 bool isEnabled,
1427 bool isDiscrete,
1428 required TextDirection textDirection,
1429 });
1430
1431 /// Whether the track shape is rounded.
1432 ///
1433 /// This is used to determine the correct position of the thumb in relation to the track.
1434 bool get isRounded => false;
1435}
1436
1437/// Base class for [RangeSlider] thumb shapes.
1438///
1439/// See also:
1440///
1441/// * [RoundRangeSliderThumbShape] for the default [RangeSlider]'s thumb shape
1442/// that paints a solid circle.
1443/// * [RangeSliderTickMarkShape], which can be used to create custom shapes for
1444/// the [RangeSlider]'s tick marks.
1445/// * [RangeSliderTrackShape], which can be used to create custom shapes for
1446/// the [RangeSlider]'s track.
1447/// * [RangeSliderValueIndicatorShape], which can be used to create custom
1448/// shapes for the [RangeSlider]'s value indicator.
1449/// * [SliderComponentShape], which can be used to create custom shapes for
1450/// the [Slider]'s thumb, overlay, and value indicator and the
1451/// [RangeSlider]'s overlay.
1452abstract class RangeSliderThumbShape {
1453 /// This abstract const constructor enables subclasses to provide
1454 /// const constructors so that they can be used in const expressions.
1455 const RangeSliderThumbShape();
1456
1457 /// Returns the preferred size of the shape, based on the given conditions.
1458 ///
1459 /// {@template flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
1460 /// The `isDiscrete` argument is true if [RangeSlider.divisions] is non-null.
1461 /// When true, the slider will render tick marks on top of the track.
1462 /// {@endtemplate}
1463 ///
1464 /// {@template flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
1465 /// The `isEnabled` argument is false when [RangeSlider.onChanged] is null and
1466 /// true otherwise. When true, the slider will respond to input.
1467 /// {@endtemplate}
1468 Size getPreferredSize(bool isEnabled, bool isDiscrete);
1469
1470 /// Paints the thumb shape based on the state passed to it.
1471 ///
1472 /// {@template flutter.material.RangeSliderThumbShape.paint.context}
1473 /// The `context` argument represents the [RangeSlider]'s render box.
1474 /// {@endtemplate}
1475 ///
1476 /// {@macro flutter.material.SliderComponentShape.paint.center}
1477 ///
1478 /// {@template flutter.material.RangeSliderThumbShape.paint.activationAnimation}
1479 /// The `activationAnimation` argument is an animation triggered when the user
1480 /// begins to interact with the [RangeSlider]. It reverses when the user stops
1481 /// interacting with the slider.
1482 /// {@endtemplate}
1483 ///
1484 /// {@template flutter.material.RangeSliderThumbShape.paint.enableAnimation}
1485 /// The `enableAnimation` argument is an animation triggered when the
1486 /// [RangeSlider] is enabled, and it reverses when the slider is disabled. The
1487 /// [RangeSlider] is enabled when [RangeSlider.onChanged] is not null. Use
1488 /// this to paint intermediate frames for this shape when the slider changes
1489 /// enabled state.
1490 /// {@endtemplate}
1491 ///
1492 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
1493 ///
1494 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
1495 ///
1496 /// If the `isOnTop` argument is true, this thumb is painted on top of the
1497 /// other slider thumb because this thumb is the one that was most recently
1498 /// selected.
1499 ///
1500 /// {@template flutter.material.RangeSliderThumbShape.paint.sliderTheme}
1501 /// The `sliderTheme` argument is the theme assigned to the [RangeSlider] that
1502 /// this shape belongs to.
1503 /// {@endtemplate}
1504 ///
1505 /// The `textDirection` argument can be used to determine how the orientation
1506 /// of either slider thumb should be changed, such as drawing different
1507 /// shapes for the left and right thumb.
1508 ///
1509 /// {@template flutter.material.RangeSliderThumbShape.paint.thumb}
1510 /// The `thumb` argument is the specifier for which of the two thumbs this
1511 /// method should paint (start or end).
1512 /// {@endtemplate}
1513 ///
1514 /// The `isPressed` argument can be used to give the selected thumb
1515 /// additional selected or pressed state visual feedback, such as a larger
1516 /// shadow.
1517 void paint(
1518 PaintingContext context,
1519 Offset center, {
1520 required Animation<double> activationAnimation,
1521 required Animation<double> enableAnimation,
1522 bool isDiscrete,
1523 bool isEnabled,
1524 bool isOnTop,
1525 TextDirection textDirection,
1526 required SliderThemeData sliderTheme,
1527 Thumb thumb,
1528 bool isPressed,
1529 });
1530}
1531
1532/// Base class for [RangeSlider] value indicator shapes.
1533///
1534/// See also:
1535///
1536/// * [PaddleRangeSliderValueIndicatorShape] for the default [RangeSlider]'s
1537/// value indicator shape that paints a custom path with text in it.
1538/// * [RangeSliderTickMarkShape], which can be used to create custom shapes for
1539/// the [RangeSlider]'s tick marks.
1540/// * [RangeSliderThumbShape], which can be used to create custom shapes for
1541/// the [RangeSlider]'s thumb.
1542/// * [RangeSliderTrackShape], which can be used to create custom shapes for
1543/// the [RangeSlider]'s track.
1544/// * [SliderComponentShape], which can be used to create custom shapes for
1545/// the [Slider]'s thumb, overlay, and value indicator and the
1546/// [RangeSlider]'s overlay.
1547abstract class RangeSliderValueIndicatorShape {
1548 /// This abstract const constructor enables subclasses to provide
1549 /// const constructors so that they can be used in const expressions.
1550 const RangeSliderValueIndicatorShape();
1551
1552 /// Returns the preferred size of the shape, based on the given conditions.
1553 ///
1554 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
1555 ///
1556 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
1557 ///
1558 /// The `labelPainter` argument helps determine the width of the shape. It is
1559 /// variable width because it is derived from a formatted string.
1560 ///
1561 /// {@macro flutter.material.SliderComponentShape.paint.textScaleFactor}
1562 Size getPreferredSize(
1563 bool isEnabled,
1564 bool isDiscrete, {
1565 required TextPainter labelPainter,
1566 required double textScaleFactor,
1567 });
1568
1569 /// Determines the best offset to keep this shape on the screen.
1570 ///
1571 /// Override this method when the center of the value indicator should be
1572 /// shifted from the vertical center of the thumb.
1573 double getHorizontalShift({
1574 RenderBox? parentBox,
1575 Offset? center,
1576 TextPainter? labelPainter,
1577 Animation<double>? activationAnimation,
1578 double? textScaleFactor,
1579 Size? sizeWithOverflow,
1580 }) {
1581 return 0;
1582 }
1583
1584 /// Paints the value indicator shape based on the state passed to it.
1585 ///
1586 /// {@macro flutter.material.RangeSliderThumbShape.paint.context}
1587 ///
1588 /// {@macro flutter.material.SliderComponentShape.paint.center}
1589 ///
1590 /// {@macro flutter.material.RangeSliderThumbShape.paint.activationAnimation}
1591 ///
1592 /// {@macro flutter.material.RangeSliderThumbShape.paint.enableAnimation}
1593 ///
1594 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
1595 ///
1596 /// The `isOnTop` argument is the top-most value indicator between the two value
1597 /// indicators, which is always the indicator for the most recently selected thumb. In
1598 /// the default case, this is used to paint a stroke around the top indicator
1599 /// for better visibility between the two indicators.
1600 ///
1601 /// {@macro flutter.material.SliderComponentShape.paint.textScaleFactor}
1602 ///
1603 /// {@macro flutter.material.SliderComponentShape.paint.sizeWithOverflow}
1604 ///
1605 /// {@template flutter.material.RangeSliderValueIndicatorShape.paint.parentBox}
1606 /// The `parentBox` argument is the [RenderBox] of the [RangeSlider]. Its
1607 /// attributes, such as size, can be used to assist in painting this shape.
1608 /// {@endtemplate}
1609 ///
1610 /// {@macro flutter.material.RangeSliderThumbShape.paint.sliderTheme}
1611 ///
1612 /// The `textDirection` argument can be used to determine how any extra text
1613 /// or graphics, besides the text painted by the [labelPainter] should be
1614 /// positioned. The `labelPainter` argument already has the `textDirection`
1615 /// set.
1616 ///
1617 /// The `value` argument is the current parametric value (from 0.0 to 1.0) of
1618 /// the slider.
1619 ///
1620 /// {@macro flutter.material.RangeSliderThumbShape.paint.thumb}
1621 void paint(
1622 PaintingContext context,
1623 Offset center, {
1624 required Animation<double> activationAnimation,
1625 required Animation<double> enableAnimation,
1626 bool isDiscrete,
1627 bool isOnTop,
1628 required TextPainter labelPainter,
1629 double textScaleFactor,
1630 Size sizeWithOverflow,
1631 required RenderBox parentBox,
1632 required SliderThemeData sliderTheme,
1633 TextDirection textDirection,
1634 double value,
1635 Thumb thumb,
1636 });
1637}
1638
1639/// Base class for [RangeSlider] tick mark shapes.
1640///
1641/// This is a simplified version of [SliderComponentShape] with a
1642/// [SliderThemeData] passed when getting the preferred size.
1643///
1644/// See also:
1645///
1646/// * [RoundRangeSliderTickMarkShape] for the default [RangeSlider]'s tick mark
1647/// shape that paints a solid circle.
1648/// * [RangeSliderThumbShape], which can be used to create custom shapes for
1649/// the [RangeSlider]'s thumb.
1650/// * [RangeSliderTrackShape], which can be used to create custom shapes for
1651/// the [RangeSlider]'s track.
1652/// * [RangeSliderValueIndicatorShape], which can be used to create custom
1653/// shapes for the [RangeSlider]'s value indicator.
1654/// * [SliderComponentShape], which can be used to create custom shapes for
1655/// the [Slider]'s thumb, overlay, and value indicator and the
1656/// [RangeSlider]'s overlay.
1657abstract class RangeSliderTickMarkShape {
1658 /// This abstract const constructor enables subclasses to provide
1659 /// const constructors so that they can be used in const expressions.
1660 const RangeSliderTickMarkShape();
1661
1662 /// Returns the preferred size of the shape.
1663 ///
1664 /// It is used to help position the tick marks within the slider.
1665 ///
1666 /// {@macro flutter.material.RangeSliderThumbShape.paint.sliderTheme}
1667 ///
1668 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
1669 Size getPreferredSize({required SliderThemeData sliderTheme, bool isEnabled});
1670
1671 /// Paints the slider track.
1672 ///
1673 /// {@macro flutter.material.RangeSliderThumbShape.paint.context}
1674 ///
1675 /// {@macro flutter.material.SliderComponentShape.paint.center}
1676 ///
1677 /// {@macro flutter.material.RangeSliderValueIndicatorShape.paint.parentBox}
1678 ///
1679 /// {@macro flutter.material.RangeSliderThumbShape.paint.sliderTheme}
1680 ///
1681 /// {@macro flutter.material.RangeSliderThumbShape.paint.enableAnimation}
1682 ///
1683 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
1684 ///
1685 /// The `textDirection` argument can be used to determine how the tick marks
1686 /// are painted depending on whether they are on an active track segment or not.
1687 ///
1688 /// {@template flutter.material.RangeSliderTickMarkShape.paint.trackSegment}
1689 /// The track segment between the two thumbs is the active track segment. The
1690 /// track segments between the thumb and each end of the slider are the inactive
1691 /// track segments. In [TextDirection.ltr], the start of the slider is on the
1692 /// left, and in [TextDirection.rtl], the start of the slider is on the right.
1693 /// {@endtemplate}
1694 void paint(
1695 PaintingContext context,
1696 Offset center, {
1697 required RenderBox parentBox,
1698 required SliderThemeData sliderTheme,
1699 required Animation<double> enableAnimation,
1700 required Offset startThumbCenter,
1701 required Offset endThumbCenter,
1702 bool isEnabled,
1703 required TextDirection textDirection,
1704 });
1705}
1706
1707/// Base class for [RangeSlider] track shapes.
1708///
1709/// The slider's thumbs move along the track. A discrete slider's tick marks
1710/// are drawn after the track, but before the thumb, and are aligned with the
1711/// track.
1712///
1713/// The [getPreferredRect] helps position the slider thumbs and tick marks
1714/// relative to the track.
1715///
1716/// See also:
1717///
1718/// * [RoundedRectRangeSliderTrackShape] for the default [RangeSlider]'s track
1719/// shape that paints a stadium-like track.
1720/// * [RangeSliderTickMarkShape], which can be used to create custom shapes for
1721/// the [RangeSlider]'s tick marks.
1722/// * [RangeSliderThumbShape], which can be used to create custom shapes for
1723/// the [RangeSlider]'s thumb.
1724/// * [RangeSliderValueIndicatorShape], which can be used to create custom
1725/// shapes for the [RangeSlider]'s value indicator.
1726/// * [SliderComponentShape], which can be used to create custom shapes for
1727/// the [Slider]'s thumb, overlay, and value indicator and the
1728/// [RangeSlider]'s overlay.
1729abstract class RangeSliderTrackShape {
1730 /// This abstract const constructor enables subclasses to provide
1731 /// const constructors so that they can be used in const expressions.
1732 const RangeSliderTrackShape();
1733
1734 /// Returns the preferred bounds of the shape.
1735 ///
1736 /// It is used to provide horizontal boundaries for the position of the
1737 /// thumbs, and to help position the slider thumbs and tick marks relative to
1738 /// the track.
1739 ///
1740 /// The `parentBox` argument can be used to help determine the preferredRect
1741 /// relative to attributes of the render box of the slider itself, such as
1742 /// size.
1743 ///
1744 /// The `offset` argument is relative to the caller's bounding box. It can be
1745 /// used to convert gesture coordinates from global to slider-relative
1746 /// coordinates.
1747 ///
1748 /// {@macro flutter.material.RangeSliderThumbShape.paint.sliderTheme}
1749 ///
1750 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
1751 ///
1752 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
1753 Rect getPreferredRect({
1754 required RenderBox parentBox,
1755 Offset offset = Offset.zero,
1756 required SliderThemeData sliderTheme,
1757 bool isEnabled,
1758 bool isDiscrete,
1759 });
1760
1761 /// Paints the track shape based on the state passed to it.
1762 ///
1763 /// {@macro flutter.material.SliderComponentShape.paint.context}
1764 ///
1765 /// The `offset` argument is the offset of the origin of the `parentBox` to
1766 /// the origin of its `context` canvas. This shape must be painted relative
1767 /// to this offset. See [PaintingContextCallback].
1768 ///
1769 /// {@macro flutter.material.RangeSliderValueIndicatorShape.paint.parentBox}
1770 ///
1771 /// {@macro flutter.material.RangeSliderThumbShape.paint.sliderTheme}
1772 ///
1773 /// {@macro flutter.material.RangeSliderThumbShape.paint.enableAnimation}
1774 ///
1775 /// The `startThumbCenter` argument is the offset of the center of the start
1776 /// thumb relative to the origin of the [PaintingContext.canvas]. It can be
1777 /// used as one point that divides the track between inactive and active.
1778 ///
1779 /// The `endThumbCenter` argument is the offset of the center of the end
1780 /// thumb relative to the origin of the [PaintingContext.canvas]. It can be
1781 /// used as one point that divides the track between inactive and active.
1782 ///
1783 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
1784 ///
1785 /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
1786 ///
1787 /// The `textDirection` argument can be used to determine how the track
1788 /// segments are painted depending on whether they are on an active track
1789 /// segment or not.
1790 ///
1791 /// {@macro flutter.material.RangeSliderTickMarkShape.paint.trackSegment}
1792 void paint(
1793 PaintingContext context,
1794 Offset offset, {
1795 required RenderBox parentBox,
1796 required SliderThemeData sliderTheme,
1797 required Animation<double> enableAnimation,
1798 required Offset startThumbCenter,
1799 required Offset endThumbCenter,
1800 bool isEnabled = false,
1801 bool isDiscrete = false,
1802 required TextDirection textDirection,
1803 });
1804
1805 /// Whether the track shape is rounded. This is used to determine the correct
1806 /// position of the thumbs in relation to the track. Defaults to false.
1807 bool get isRounded => false;
1808}
1809
1810/// Base track shape that provides an implementation of [getPreferredRect] for
1811/// default sizing.
1812///
1813/// The height is set from [SliderThemeData.trackHeight] and the width of the
1814/// parent box less the larger of the widths of [SliderThemeData.thumbShape] and
1815/// [SliderThemeData.overlayShape].
1816///
1817/// See also:
1818///
1819/// * [RectangularSliderTrackShape], which is a track shape with sharp
1820/// rectangular edges
1821/// * [RoundedRectSliderTrackShape], which is a track shape with round
1822/// stadium-like edges.
1823mixin BaseSliderTrackShape {
1824 /// Returns a rect that represents the track bounds that fits within the
1825 /// [Slider].
1826 ///
1827 /// The width is the width of the [Slider] or [RangeSlider], but padded by
1828 /// the max of the overlay and thumb radius. The height is defined by the
1829 /// [SliderThemeData.trackHeight].
1830 ///
1831 /// The [Rect] is centered both horizontally and vertically within the slider
1832 /// bounds.
1833 Rect getPreferredRect({
1834 required RenderBox parentBox,
1835 Offset offset = Offset.zero,
1836 required SliderThemeData sliderTheme,
1837 bool isEnabled = false,
1838 bool isDiscrete = false,
1839 }) {
1840 final double thumbWidth = sliderTheme.thumbShape!.getPreferredSize(isEnabled, isDiscrete).width;
1841 final double overlayWidth =
1842 sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width;
1843 double trackHeight = sliderTheme.trackHeight!;
1844 assert(overlayWidth >= 0);
1845 assert(trackHeight >= 0);
1846
1847 // If the track colors are transparent, then override only the track height
1848 // to maintain overall Slider width.
1849 if (sliderTheme.activeTrackColor == Colors.transparent &&
1850 sliderTheme.inactiveTrackColor == Colors.transparent) {
1851 trackHeight = 0;
1852 }
1853
1854 final double trackLeft =
1855 offset.dx + (sliderTheme.padding == null ? math.max(overlayWidth / 2, thumbWidth / 2) : 0);
1856 final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
1857 final double trackRight =
1858 trackLeft +
1859 parentBox.size.width -
1860 (sliderTheme.padding == null ? math.max(thumbWidth, overlayWidth) : 0);
1861 final double trackBottom = trackTop + trackHeight;
1862 // If the parentBox's size less than slider's size the trackRight will be less than trackLeft, so switch them.
1863 return Rect.fromLTRB(
1864 math.min(trackLeft, trackRight),
1865 trackTop,
1866 math.max(trackLeft, trackRight),
1867 trackBottom,
1868 );
1869 }
1870
1871 /// Whether the track shape is rounded. This is used to determine the correct
1872 /// position of the thumb in relation to the track. Defaults to false.
1873 bool get isRounded => false;
1874}
1875
1876/// A [Slider] track that's a simple rectangle.
1877///
1878/// It paints a solid colored rectangle, vertically centered in the
1879/// `parentBox`. The track rectangle extends to the bounds of the `parentBox`,
1880/// but is padded by the [RoundSliderOverlayShape] radius. The height is defined
1881/// by the [SliderThemeData.trackHeight]. The color is determined by the
1882/// [Slider]'s enabled state and the track segment's active state which are
1883/// defined by:
1884/// [SliderThemeData.activeTrackColor],
1885/// [SliderThemeData.inactiveTrackColor],
1886/// [SliderThemeData.disabledActiveTrackColor],
1887/// [SliderThemeData.disabledInactiveTrackColor].
1888///
1889/// {@macro flutter.material.SliderTrackShape.paint.trackSegment}
1890///
1891/// ![A slider widget, consisting of 5 divisions and showing the rectangular slider track shape.](https://flutter.github.io/assets-for-api-docs/assets/material/rectangular_slider_track_shape.png)
1892///
1893/// See also:
1894///
1895/// * [Slider], for the component that is meant to display this shape.
1896/// * [SliderThemeData], where an instance of this class is set to inform the
1897/// slider of the visual details of the its track.
1898/// * [SliderTrackShape], which can be used to create custom shapes for the
1899/// [Slider]'s track.
1900/// * [RoundedRectSliderTrackShape], for a similar track with rounded edges.
1901class RectangularSliderTrackShape extends SliderTrackShape with BaseSliderTrackShape {
1902 /// Creates a slider track that draws 2 rectangles.
1903 const RectangularSliderTrackShape();
1904
1905 @override
1906 void paint(
1907 PaintingContext context,
1908 Offset offset, {
1909 required RenderBox parentBox,
1910 required SliderThemeData sliderTheme,
1911 required Animation<double> enableAnimation,
1912 required TextDirection textDirection,
1913 required Offset thumbCenter,
1914 Offset? secondaryOffset,
1915 bool isDiscrete = false,
1916 bool isEnabled = false,
1917 }) {
1918 assert(sliderTheme.disabledActiveTrackColor != null);
1919 assert(sliderTheme.disabledInactiveTrackColor != null);
1920 assert(sliderTheme.activeTrackColor != null);
1921 assert(sliderTheme.inactiveTrackColor != null);
1922 assert(sliderTheme.thumbShape != null);
1923 // If the slider [SliderThemeData.trackHeight] is less than or equal to 0,
1924 // then it makes no difference whether the track is painted or not,
1925 // therefore the painting can be a no-op.
1926 if (sliderTheme.trackHeight! <= 0) {
1927 return;
1928 }
1929
1930 // Assign the track segment paints, which are left: active, right: inactive,
1931 // but reversed for right to left text.
1932 final ColorTween activeTrackColorTween = ColorTween(
1933 begin: sliderTheme.disabledActiveTrackColor,
1934 end: sliderTheme.activeTrackColor,
1935 );
1936 final ColorTween inactiveTrackColorTween = ColorTween(
1937 begin: sliderTheme.disabledInactiveTrackColor,
1938 end: sliderTheme.inactiveTrackColor,
1939 );
1940 final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation)!;
1941 final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
1942 final (Paint leftTrackPaint, Paint rightTrackPaint) = switch (textDirection) {
1943 TextDirection.ltr => (activePaint, inactivePaint),
1944 TextDirection.rtl => (inactivePaint, activePaint),
1945 };
1946
1947 final Rect trackRect = getPreferredRect(
1948 parentBox: parentBox,
1949 offset: offset,
1950 sliderTheme: sliderTheme,
1951 isEnabled: isEnabled,
1952 isDiscrete: isDiscrete,
1953 );
1954
1955 final Rect leftTrackSegment = Rect.fromLTRB(
1956 trackRect.left,
1957 trackRect.top,
1958 thumbCenter.dx,
1959 trackRect.bottom,
1960 );
1961 if (!leftTrackSegment.isEmpty) {
1962 context.canvas.drawRect(leftTrackSegment, leftTrackPaint);
1963 }
1964 final Rect rightTrackSegment = Rect.fromLTRB(
1965 thumbCenter.dx,
1966 trackRect.top,
1967 trackRect.right,
1968 trackRect.bottom,
1969 );
1970 if (!rightTrackSegment.isEmpty) {
1971 context.canvas.drawRect(rightTrackSegment, rightTrackPaint);
1972 }
1973
1974 final bool showSecondaryTrack =
1975 secondaryOffset != null &&
1976 switch (textDirection) {
1977 TextDirection.rtl => secondaryOffset.dx < thumbCenter.dx,
1978 TextDirection.ltr => secondaryOffset.dx > thumbCenter.dx,
1979 };
1980
1981 if (showSecondaryTrack) {
1982 final ColorTween secondaryTrackColorTween = ColorTween(
1983 begin: sliderTheme.disabledSecondaryActiveTrackColor,
1984 end: sliderTheme.secondaryActiveTrackColor,
1985 );
1986 final Paint secondaryTrackPaint =
1987 Paint()..color = secondaryTrackColorTween.evaluate(enableAnimation)!;
1988 final Rect secondaryTrackSegment = switch (textDirection) {
1989 TextDirection.rtl => Rect.fromLTRB(
1990 secondaryOffset.dx,
1991 trackRect.top,
1992 thumbCenter.dx,
1993 trackRect.bottom,
1994 ),
1995 TextDirection.ltr => Rect.fromLTRB(
1996 thumbCenter.dx,
1997 trackRect.top,
1998 secondaryOffset.dx,
1999 trackRect.bottom,
2000 ),
2001 };
2002 if (!secondaryTrackSegment.isEmpty) {
2003 context.canvas.drawRect(secondaryTrackSegment, secondaryTrackPaint);
2004 }
2005 }
2006 }
2007}
2008
2009/// The default shape of a [Slider]'s track.
2010///
2011/// It paints a solid colored rectangle with rounded edges, vertically centered
2012/// in the `parentBox`. The track rectangle extends to the bounds of the
2013/// `parentBox`, but is padded by the larger of [RoundSliderOverlayShape]'s
2014/// radius and [RoundSliderThumbShape]'s radius. The height is defined by the
2015/// [SliderThemeData.trackHeight]. The color is determined by the [Slider]'s
2016/// enabled state and the track segment's active state which are defined by:
2017/// [SliderThemeData.activeTrackColor],
2018/// [SliderThemeData.inactiveTrackColor],
2019/// [SliderThemeData.disabledActiveTrackColor],
2020/// [SliderThemeData.disabledInactiveTrackColor].
2021///
2022/// {@macro flutter.material.SliderTrackShape.paint.trackSegment}
2023///
2024/// ![A slider widget, consisting of 5 divisions and showing the rounded rect slider track shape.](https://flutter.github.io/assets-for-api-docs/assets/material/rounded_rect_slider_track_shape.png)
2025///
2026/// See also:
2027///
2028/// * [Slider], for the component that is meant to display this shape.
2029/// * [SliderThemeData], where an instance of this class is set to inform the
2030/// slider of the visual details of the its track.
2031/// * [SliderTrackShape], which can be used to create custom shapes for the
2032/// [Slider]'s track.
2033/// * [RectangularSliderTrackShape], for a similar track with sharp edges.
2034class RoundedRectSliderTrackShape extends SliderTrackShape with BaseSliderTrackShape {
2035 /// Create a slider track that draws two rectangles with rounded outer edges.
2036 const RoundedRectSliderTrackShape();
2037
2038 @override
2039 void paint(
2040 PaintingContext context,
2041 Offset offset, {
2042 required RenderBox parentBox,
2043 required SliderThemeData sliderTheme,
2044 required Animation<double> enableAnimation,
2045 required TextDirection textDirection,
2046 required Offset thumbCenter,
2047 Offset? secondaryOffset,
2048 bool isDiscrete = false,
2049 bool isEnabled = false,
2050 double additionalActiveTrackHeight = 2,
2051 }) {
2052 assert(sliderTheme.disabledActiveTrackColor != null);
2053 assert(sliderTheme.disabledInactiveTrackColor != null);
2054 assert(sliderTheme.activeTrackColor != null);
2055 assert(sliderTheme.inactiveTrackColor != null);
2056 assert(sliderTheme.thumbShape != null);
2057 // If the slider [SliderThemeData.trackHeight] is less than or equal to 0,
2058 // then it makes no difference whether the track is painted or not,
2059 // therefore the painting can be a no-op.
2060 if (sliderTheme.trackHeight == null || sliderTheme.trackHeight! <= 0) {
2061 return;
2062 }
2063
2064 // Assign the track segment paints, which are leading: active and
2065 // trailing: inactive.
2066 final ColorTween activeTrackColorTween = ColorTween(
2067 begin: sliderTheme.disabledActiveTrackColor,
2068 end: sliderTheme.activeTrackColor,
2069 );
2070 final ColorTween inactiveTrackColorTween = ColorTween(
2071 begin: sliderTheme.disabledInactiveTrackColor,
2072 end: sliderTheme.inactiveTrackColor,
2073 );
2074 final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation)!;
2075 final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
2076 final (Paint leftTrackPaint, Paint rightTrackPaint) = switch (textDirection) {
2077 TextDirection.ltr => (activePaint, inactivePaint),
2078 TextDirection.rtl => (inactivePaint, activePaint),
2079 };
2080
2081 final Rect trackRect = getPreferredRect(
2082 parentBox: parentBox,
2083 offset: offset,
2084 sliderTheme: sliderTheme,
2085 isEnabled: isEnabled,
2086 isDiscrete: isDiscrete,
2087 );
2088 final Radius trackRadius = Radius.circular(trackRect.height / 2);
2089 final Radius activeTrackRadius = Radius.circular(
2090 (trackRect.height + additionalActiveTrackHeight) / 2,
2091 );
2092 final bool isLTR = textDirection == TextDirection.ltr;
2093 final bool isRTL = textDirection == TextDirection.rtl;
2094
2095 final bool drawInactiveTrack =
2096 thumbCenter.dx < (trackRect.right - (sliderTheme.trackHeight! / 2));
2097 if (drawInactiveTrack) {
2098 // Draw the inactive track segment.
2099 context.canvas.drawRRect(
2100 RRect.fromLTRBR(
2101 thumbCenter.dx - (sliderTheme.trackHeight! / 2),
2102 isRTL ? trackRect.top - (additionalActiveTrackHeight / 2) : trackRect.top,
2103 trackRect.right,
2104 isRTL ? trackRect.bottom + (additionalActiveTrackHeight / 2) : trackRect.bottom,
2105 isLTR ? trackRadius : activeTrackRadius,
2106 ),
2107 rightTrackPaint,
2108 );
2109 }
2110 final bool drawActiveTrack = thumbCenter.dx > (trackRect.left + (sliderTheme.trackHeight! / 2));
2111 if (drawActiveTrack) {
2112 // Draw the active track segment.
2113 context.canvas.drawRRect(
2114 RRect.fromLTRBR(
2115 trackRect.left,
2116 isLTR ? trackRect.top - (additionalActiveTrackHeight / 2) : trackRect.top,
2117 thumbCenter.dx + (sliderTheme.trackHeight! / 2),
2118 isLTR ? trackRect.bottom + (additionalActiveTrackHeight / 2) : trackRect.bottom,
2119 isLTR ? activeTrackRadius : trackRadius,
2120 ),
2121 leftTrackPaint,
2122 );
2123 }
2124
2125 final bool showSecondaryTrack =
2126 (secondaryOffset != null) &&
2127 (isLTR ? (secondaryOffset.dx > thumbCenter.dx) : (secondaryOffset.dx < thumbCenter.dx));
2128
2129 if (showSecondaryTrack) {
2130 final ColorTween secondaryTrackColorTween = ColorTween(
2131 begin: sliderTheme.disabledSecondaryActiveTrackColor,
2132 end: sliderTheme.secondaryActiveTrackColor,
2133 );
2134 final Paint secondaryTrackPaint =
2135 Paint()..color = secondaryTrackColorTween.evaluate(enableAnimation)!;
2136 if (isLTR) {
2137 context.canvas.drawRRect(
2138 RRect.fromLTRBAndCorners(
2139 thumbCenter.dx,
2140 trackRect.top,
2141 secondaryOffset.dx,
2142 trackRect.bottom,
2143 topRight: trackRadius,
2144 bottomRight: trackRadius,
2145 ),
2146 secondaryTrackPaint,
2147 );
2148 } else {
2149 context.canvas.drawRRect(
2150 RRect.fromLTRBAndCorners(
2151 secondaryOffset.dx,
2152 trackRect.top,
2153 thumbCenter.dx,
2154 trackRect.bottom,
2155 topLeft: trackRadius,
2156 bottomLeft: trackRadius,
2157 ),
2158 secondaryTrackPaint,
2159 );
2160 }
2161 }
2162 }
2163
2164 @override
2165 bool get isRounded => true;
2166}
2167
2168/// Base range slider track shape that provides an implementation of [getPreferredRect] for
2169/// default sizing.
2170///
2171/// The height is set from [SliderThemeData.trackHeight] and the width of the
2172/// parent box less the larger of the widths of [SliderThemeData.rangeThumbShape] and
2173/// [SliderThemeData.overlayShape].
2174///
2175/// See also:
2176///
2177/// * [RectangularRangeSliderTrackShape], which is a track shape with sharp
2178/// rectangular edges
2179mixin BaseRangeSliderTrackShape {
2180 /// Returns a rect that represents the track bounds that fits within the
2181 /// [Slider].
2182 ///
2183 /// The width is the width of the [RangeSlider], but padded by the max
2184 /// of the overlay and thumb radius. The height is defined by the [SliderThemeData.trackHeight].
2185 ///
2186 /// The [Rect] is centered both horizontally and vertically within the slider
2187 /// bounds.
2188 Rect getPreferredRect({
2189 required RenderBox parentBox,
2190 Offset offset = Offset.zero,
2191 required SliderThemeData sliderTheme,
2192 bool isEnabled = false,
2193 bool isDiscrete = false,
2194 }) {
2195 assert(sliderTheme.rangeThumbShape != null);
2196 assert(sliderTheme.overlayShape != null);
2197 assert(sliderTheme.trackHeight != null);
2198 final double thumbWidth =
2199 sliderTheme.rangeThumbShape!.getPreferredSize(isEnabled, isDiscrete).width;
2200 final double overlayWidth =
2201 sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width;
2202 double trackHeight = sliderTheme.trackHeight!;
2203 assert(overlayWidth >= 0);
2204 assert(trackHeight >= 0);
2205
2206 // If the track colors are transparent, then override only the track height
2207 // to maintain overall Slider width.
2208 if (sliderTheme.activeTrackColor == Colors.transparent &&
2209 sliderTheme.inactiveTrackColor == Colors.transparent) {
2210 trackHeight = 0;
2211 }
2212
2213 final double trackLeft = offset.dx + math.max(overlayWidth / 2, thumbWidth / 2);
2214 final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
2215 final double trackRight = trackLeft + parentBox.size.width - math.max(thumbWidth, overlayWidth);
2216 final double trackBottom = trackTop + trackHeight;
2217 // If the parentBox's size less than slider's size the trackRight will be less than trackLeft, so switch them.
2218 return Rect.fromLTRB(
2219 math.min(trackLeft, trackRight),
2220 trackTop,
2221 math.max(trackLeft, trackRight),
2222 trackBottom,
2223 );
2224 }
2225}
2226
2227/// A [RangeSlider] track that's a simple rectangle.
2228///
2229/// It paints a solid colored rectangle, vertically centered in the
2230/// `parentBox`. The track rectangle extends to the bounds of the `parentBox`,
2231/// but is padded by the [RoundSliderOverlayShape] radius. The height is
2232/// defined by the [SliderThemeData.trackHeight]. The color is determined by the
2233/// [Slider]'s enabled state and the track segment's active state which are
2234/// defined by:
2235/// [SliderThemeData.activeTrackColor],
2236/// [SliderThemeData.inactiveTrackColor],
2237/// [SliderThemeData.disabledActiveTrackColor],
2238/// [SliderThemeData.disabledInactiveTrackColor].
2239///
2240/// {@macro flutter.material.RangeSliderTickMarkShape.paint.trackSegment}
2241///
2242/// ![A range slider widget, consisting of 5 divisions and showing the rectangular range slider track shape.](https://flutter.github.io/assets-for-api-docs/assets/material/rectangular_range_slider_track_shape.png)
2243///
2244/// See also:
2245///
2246/// * [RangeSlider], for the component that is meant to display this shape.
2247/// * [SliderThemeData], where an instance of this class is set to inform the
2248/// slider of the visual details of the its track.
2249/// * [RangeSliderTrackShape], which can be used to create custom shapes for
2250/// the [RangeSlider]'s track.
2251/// * [RoundedRectRangeSliderTrackShape], for a similar track with rounded
2252/// edges.
2253class RectangularRangeSliderTrackShape extends RangeSliderTrackShape
2254 with BaseRangeSliderTrackShape {
2255 /// Create a slider track with rectangular outer edges.
2256 ///
2257 /// The middle track segment is the selected range and is active, and the two
2258 /// outer track segments are inactive.
2259 const RectangularRangeSliderTrackShape();
2260
2261 @override
2262 void paint(
2263 PaintingContext context,
2264 Offset offset, {
2265 required RenderBox parentBox,
2266 required SliderThemeData sliderTheme,
2267 required Animation<double>? enableAnimation,
2268 required Offset startThumbCenter,
2269 required Offset endThumbCenter,
2270 bool isEnabled = false,
2271 bool isDiscrete = false,
2272 required TextDirection textDirection,
2273 }) {
2274 assert(sliderTheme.disabledActiveTrackColor != null);
2275 assert(sliderTheme.disabledInactiveTrackColor != null);
2276 assert(sliderTheme.activeTrackColor != null);
2277 assert(sliderTheme.inactiveTrackColor != null);
2278 assert(sliderTheme.rangeThumbShape != null);
2279 assert(enableAnimation != null);
2280 // Assign the track segment paints, which are left: active, right: inactive,
2281 // but reversed for right to left text.
2282 final ColorTween activeTrackColorTween = ColorTween(
2283 begin: sliderTheme.disabledActiveTrackColor,
2284 end: sliderTheme.activeTrackColor,
2285 );
2286 final ColorTween inactiveTrackColorTween = ColorTween(
2287 begin: sliderTheme.disabledInactiveTrackColor,
2288 end: sliderTheme.inactiveTrackColor,
2289 );
2290 final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation!)!;
2291 final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
2292
2293 final (Offset leftThumbOffset, Offset rightThumbOffset) = switch (textDirection) {
2294 TextDirection.ltr => (startThumbCenter, endThumbCenter),
2295 TextDirection.rtl => (endThumbCenter, startThumbCenter),
2296 };
2297
2298 final Rect trackRect = getPreferredRect(
2299 parentBox: parentBox,
2300 offset: offset,
2301 sliderTheme: sliderTheme,
2302 isEnabled: isEnabled,
2303 isDiscrete: isDiscrete,
2304 );
2305 final Rect leftTrackSegment = Rect.fromLTRB(
2306 trackRect.left,
2307 trackRect.top,
2308 leftThumbOffset.dx,
2309 trackRect.bottom,
2310 );
2311 if (!leftTrackSegment.isEmpty) {
2312 context.canvas.drawRect(leftTrackSegment, inactivePaint);
2313 }
2314 final Rect middleTrackSegment = Rect.fromLTRB(
2315 leftThumbOffset.dx,
2316 trackRect.top,
2317 rightThumbOffset.dx,
2318 trackRect.bottom,
2319 );
2320 if (!middleTrackSegment.isEmpty) {
2321 context.canvas.drawRect(middleTrackSegment, activePaint);
2322 }
2323 final Rect rightTrackSegment = Rect.fromLTRB(
2324 rightThumbOffset.dx,
2325 trackRect.top,
2326 trackRect.right,
2327 trackRect.bottom,
2328 );
2329 if (!rightTrackSegment.isEmpty) {
2330 context.canvas.drawRect(rightTrackSegment, inactivePaint);
2331 }
2332 }
2333}
2334
2335/// The default shape of a [RangeSlider]'s track.
2336///
2337/// It paints a solid colored rectangle with rounded edges, vertically centered
2338/// in the `parentBox`. The track rectangle extends to the bounds of the
2339/// `parentBox`, but is padded by the larger of [RoundSliderOverlayShape]'s
2340/// radius and [RoundRangeSliderThumbShape]'s radius. The height is defined by
2341/// the [SliderThemeData.trackHeight]. The color is determined by the
2342/// [RangeSlider]'s enabled state and the track segment's active state which are
2343/// defined by:
2344/// [SliderThemeData.activeTrackColor],
2345/// [SliderThemeData.inactiveTrackColor],
2346/// [SliderThemeData.disabledActiveTrackColor],
2347/// [SliderThemeData.disabledInactiveTrackColor].
2348///
2349/// {@macro flutter.material.RangeSliderTickMarkShape.paint.trackSegment}
2350///
2351/// ![A range slider widget, consisting of 5 divisions and showing the rounded rect range slider track shape.](https://flutter.github.io/assets-for-api-docs/assets/material/rounded_rect_range_slider_track_shape.png)
2352///
2353/// See also:
2354///
2355/// * [RangeSlider], for the component that is meant to display this shape.
2356/// * [SliderThemeData], where an instance of this class is set to inform the
2357/// slider of the visual details of the its track.
2358/// * [RangeSliderTrackShape], which can be used to create custom shapes for
2359/// the [RangeSlider]'s track.
2360/// * [RectangularRangeSliderTrackShape], for a similar track with sharp edges.
2361class RoundedRectRangeSliderTrackShape extends RangeSliderTrackShape
2362 with BaseRangeSliderTrackShape {
2363 /// Create a slider track with rounded outer edges.
2364 ///
2365 /// The middle track segment is the selected range and is active, and the two
2366 /// outer track segments are inactive.
2367 const RoundedRectRangeSliderTrackShape();
2368
2369 @override
2370 void paint(
2371 PaintingContext context,
2372 Offset offset, {
2373 required RenderBox parentBox,
2374 required SliderThemeData sliderTheme,
2375 required Animation<double> enableAnimation,
2376 required Offset startThumbCenter,
2377 required Offset endThumbCenter,
2378 bool isEnabled = false,
2379 bool isDiscrete = false,
2380 required TextDirection textDirection,
2381 double additionalActiveTrackHeight = 2,
2382 }) {
2383 assert(sliderTheme.disabledActiveTrackColor != null);
2384 assert(sliderTheme.disabledInactiveTrackColor != null);
2385 assert(sliderTheme.activeTrackColor != null);
2386 assert(sliderTheme.inactiveTrackColor != null);
2387 assert(sliderTheme.rangeThumbShape != null);
2388
2389 if (sliderTheme.trackHeight == null || sliderTheme.trackHeight! <= 0) {
2390 return;
2391 }
2392
2393 // Assign the track segment paints, which are left: active, right: inactive,
2394 // but reversed for right to left text.
2395 final ColorTween activeTrackColorTween = ColorTween(
2396 begin: sliderTheme.disabledActiveTrackColor,
2397 end: sliderTheme.activeTrackColor,
2398 );
2399 final ColorTween inactiveTrackColorTween = ColorTween(
2400 begin: sliderTheme.disabledInactiveTrackColor,
2401 end: sliderTheme.inactiveTrackColor,
2402 );
2403 final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation)!;
2404 final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
2405
2406 final (Offset leftThumbOffset, Offset rightThumbOffset) = switch (textDirection) {
2407 TextDirection.ltr => (startThumbCenter, endThumbCenter),
2408 TextDirection.rtl => (endThumbCenter, startThumbCenter),
2409 };
2410 final Size thumbSize = sliderTheme.rangeThumbShape!.getPreferredSize(isEnabled, isDiscrete);
2411 final double thumbRadius = thumbSize.width / 2;
2412 assert(thumbRadius > 0);
2413
2414 final Rect trackRect = getPreferredRect(
2415 parentBox: parentBox,
2416 offset: offset,
2417 sliderTheme: sliderTheme,
2418 isEnabled: isEnabled,
2419 isDiscrete: isDiscrete,
2420 );
2421
2422 final Radius trackRadius = Radius.circular(trackRect.height / 2);
2423
2424 context.canvas.drawRRect(
2425 RRect.fromLTRBAndCorners(
2426 trackRect.left,
2427 trackRect.top,
2428 leftThumbOffset.dx,
2429 trackRect.bottom,
2430 topLeft: trackRadius,
2431 bottomLeft: trackRadius,
2432 ),
2433 inactivePaint,
2434 );
2435 context.canvas.drawRRect(
2436 RRect.fromLTRBAndCorners(
2437 rightThumbOffset.dx,
2438 trackRect.top,
2439 trackRect.right,
2440 trackRect.bottom,
2441 topRight: trackRadius,
2442 bottomRight: trackRadius,
2443 ),
2444 inactivePaint,
2445 );
2446 context.canvas.drawRRect(
2447 RRect.fromLTRBR(
2448 leftThumbOffset.dx - (sliderTheme.trackHeight! / 2),
2449 trackRect.top - (additionalActiveTrackHeight / 2),
2450 rightThumbOffset.dx + (sliderTheme.trackHeight! / 2),
2451 trackRect.bottom + (additionalActiveTrackHeight / 2),
2452 trackRadius,
2453 ),
2454 activePaint,
2455 );
2456 }
2457
2458 @override
2459 bool get isRounded => true;
2460}
2461
2462/// The default shape of each [Slider] tick mark.
2463///
2464/// Tick marks are only displayed if the slider is discrete, which can be done
2465/// by setting the [Slider.divisions] to an integer value.
2466///
2467/// It paints a solid circle, centered in the on the track.
2468/// The color is determined by the [Slider]'s enabled state and track's active
2469/// states. These colors are defined in:
2470/// [SliderThemeData.activeTrackColor],
2471/// [SliderThemeData.inactiveTrackColor],
2472/// [SliderThemeData.disabledActiveTrackColor],
2473/// [SliderThemeData.disabledInactiveTrackColor].
2474///
2475/// ![A slider widget, consisting of 5 divisions and showing the round slider tick mark shape.](https://flutter.github.io/assets-for-api-docs/assets/material/rounded_slider_tick_mark_shape.png)
2476///
2477/// See also:
2478///
2479/// * [Slider], which includes tick marks defined by this shape.
2480/// * [SliderTheme], which can be used to configure the tick mark shape of all
2481/// sliders in a widget subtree.
2482class RoundSliderTickMarkShape extends SliderTickMarkShape {
2483 /// Create a slider tick mark that draws a circle.
2484 const RoundSliderTickMarkShape({this.tickMarkRadius});
2485
2486 /// The preferred radius of the round tick mark.
2487 ///
2488 /// If it is not provided, then 1/4 of the [SliderThemeData.trackHeight] is used.
2489 final double? tickMarkRadius;
2490
2491 @override
2492 Size getPreferredSize({required SliderThemeData sliderTheme, required bool isEnabled}) {
2493 assert(sliderTheme.trackHeight != null);
2494 // The tick marks are tiny circles. If no radius is provided, then the
2495 // radius is defaulted to be a fraction of the
2496 // [SliderThemeData.trackHeight]. The fraction is 1/4.
2497 return Size.fromRadius(tickMarkRadius ?? sliderTheme.trackHeight! / 4);
2498 }
2499
2500 @override
2501 void paint(
2502 PaintingContext context,
2503 Offset center, {
2504 required RenderBox parentBox,
2505 required SliderThemeData sliderTheme,
2506 required Animation<double> enableAnimation,
2507 required TextDirection textDirection,
2508 required Offset thumbCenter,
2509 required bool isEnabled,
2510 }) {
2511 assert(sliderTheme.disabledActiveTickMarkColor != null);
2512 assert(sliderTheme.disabledInactiveTickMarkColor != null);
2513 assert(sliderTheme.activeTickMarkColor != null);
2514 assert(sliderTheme.inactiveTickMarkColor != null);
2515 // The paint color of the tick mark depends on its position relative
2516 // to the thumb and the text direction.
2517 final double xOffset = center.dx - thumbCenter.dx;
2518 final (Color? begin, Color? end) = switch (textDirection) {
2519 TextDirection.ltr when xOffset > 0 => (
2520 sliderTheme.disabledInactiveTickMarkColor,
2521 sliderTheme.inactiveTickMarkColor,
2522 ),
2523 TextDirection.rtl when xOffset < 0 => (
2524 sliderTheme.disabledInactiveTickMarkColor,
2525 sliderTheme.inactiveTickMarkColor,
2526 ),
2527 TextDirection.ltr || TextDirection.rtl => (
2528 sliderTheme.disabledActiveTickMarkColor,
2529 sliderTheme.activeTickMarkColor,
2530 ),
2531 };
2532 final Paint paint =
2533 Paint()..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!;
2534
2535 // The tick marks are tiny circles that are the same height as the track.
2536 final double tickMarkRadius =
2537 getPreferredSize(isEnabled: isEnabled, sliderTheme: sliderTheme).width / 2;
2538 if (tickMarkRadius > 0) {
2539 context.canvas.drawCircle(center, tickMarkRadius, paint);
2540 }
2541 }
2542}
2543
2544/// The default shape of each [RangeSlider] tick mark.
2545///
2546/// Tick marks are only displayed if the slider is discrete, which can be done
2547/// by setting the [RangeSlider.divisions] to an integer value.
2548///
2549/// It paints a solid circle, centered on the track.
2550/// The color is determined by the [Slider]'s enabled state and track's active
2551/// states. These colors are defined in:
2552/// [SliderThemeData.activeTrackColor],
2553/// [SliderThemeData.inactiveTrackColor],
2554/// [SliderThemeData.disabledActiveTrackColor],
2555/// [SliderThemeData.disabledInactiveTrackColor].
2556///
2557/// ![A slider widget, consisting of 5 divisions and showing the round range slider tick mark shape.](https://flutter.github.io/assets-for-api-docs/assets/material/round_range_slider_tick_mark_shape.png)
2558///
2559/// See also:
2560///
2561/// * [RangeSlider], which includes tick marks defined by this shape.
2562/// * [SliderTheme], which can be used to configure the tick mark shape of all
2563/// sliders in a widget subtree.
2564class RoundRangeSliderTickMarkShape extends RangeSliderTickMarkShape {
2565 /// Create a range slider tick mark that draws a circle.
2566 const RoundRangeSliderTickMarkShape({this.tickMarkRadius});
2567
2568 /// The preferred radius of the round tick mark.
2569 ///
2570 /// If it is not provided, then 1/4 of the [SliderThemeData.trackHeight] is used.
2571 final double? tickMarkRadius;
2572
2573 @override
2574 Size getPreferredSize({required SliderThemeData sliderTheme, bool isEnabled = false}) {
2575 assert(sliderTheme.trackHeight != null);
2576 return Size.fromRadius(tickMarkRadius ?? sliderTheme.trackHeight! / 4);
2577 }
2578
2579 @override
2580 void paint(
2581 PaintingContext context,
2582 Offset center, {
2583 required RenderBox parentBox,
2584 required SliderThemeData sliderTheme,
2585 required Animation<double> enableAnimation,
2586 required Offset startThumbCenter,
2587 required Offset endThumbCenter,
2588 bool isEnabled = false,
2589 required TextDirection textDirection,
2590 }) {
2591 assert(sliderTheme.disabledActiveTickMarkColor != null);
2592 assert(sliderTheme.disabledInactiveTickMarkColor != null);
2593 assert(sliderTheme.activeTickMarkColor != null);
2594 assert(sliderTheme.inactiveTickMarkColor != null);
2595
2596 final bool isBetweenThumbs = switch (textDirection) {
2597 TextDirection.ltr => startThumbCenter.dx < center.dx && center.dx < endThumbCenter.dx,
2598 TextDirection.rtl => endThumbCenter.dx < center.dx && center.dx < startThumbCenter.dx,
2599 };
2600 final Color? begin =
2601 isBetweenThumbs
2602 ? sliderTheme.disabledActiveTickMarkColor
2603 : sliderTheme.disabledInactiveTickMarkColor;
2604 final Color? end =
2605 isBetweenThumbs ? sliderTheme.activeTickMarkColor : sliderTheme.inactiveTickMarkColor;
2606 final Paint paint =
2607 Paint()..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!;
2608
2609 // The tick marks are tiny circles that are the same height as the track.
2610 final double tickMarkRadius =
2611 getPreferredSize(isEnabled: isEnabled, sliderTheme: sliderTheme).width / 2;
2612 if (tickMarkRadius > 0) {
2613 context.canvas.drawCircle(center, tickMarkRadius, paint);
2614 }
2615 }
2616}
2617
2618/// A special version of [SliderTickMarkShape] that has a zero size and paints
2619/// nothing.
2620///
2621/// This class is used to create a special instance of a [SliderTickMarkShape]
2622/// that will not paint any tick mark shape. A static reference is stored in
2623/// [SliderTickMarkShape.noTickMark]. When this value is specified for
2624/// [SliderThemeData.tickMarkShape], the tick mark painting is skipped.
2625class _EmptySliderTickMarkShape extends SliderTickMarkShape {
2626 @override
2627 Size getPreferredSize({required SliderThemeData sliderTheme, required bool isEnabled}) {
2628 return Size.zero;
2629 }
2630
2631 @override
2632 void paint(
2633 PaintingContext context,
2634 Offset center, {
2635 required RenderBox parentBox,
2636 required SliderThemeData sliderTheme,
2637 required Animation<double> enableAnimation,
2638 required Offset thumbCenter,
2639 required bool isEnabled,
2640 required TextDirection textDirection,
2641 }) {
2642 // no-op.
2643 }
2644}
2645
2646/// A special version of [SliderComponentShape] that has a zero size and paints
2647/// nothing.
2648///
2649/// This class is used to create a special instance of a [SliderComponentShape]
2650/// that will not paint any component shape. A static reference is stored in
2651/// [SliderComponentShape.noThumb] and [SliderComponentShape.noOverlay]. When this value
2652/// is specified for [SliderThemeData.thumbShape], the thumb painting is
2653/// skipped. When this value is specified for [SliderThemeData.overlayShape],
2654/// the overlay painting is skipped.
2655class _EmptySliderComponentShape extends SliderComponentShape {
2656 @override
2657 Size getPreferredSize(bool isEnabled, bool isDiscrete) => Size.zero;
2658
2659 @override
2660 void paint(
2661 PaintingContext context,
2662 Offset center, {
2663 required Animation<double> activationAnimation,
2664 required Animation<double> enableAnimation,
2665 required bool isDiscrete,
2666 required TextPainter labelPainter,
2667 required RenderBox parentBox,
2668 required SliderThemeData sliderTheme,
2669 required TextDirection textDirection,
2670 required double value,
2671 required double textScaleFactor,
2672 required Size sizeWithOverflow,
2673 }) {
2674 // no-op.
2675 }
2676}
2677
2678/// The default shape of a [Slider]'s thumb.
2679///
2680/// There is a shadow for the resting, pressed, hovered, and focused state.
2681///
2682/// ![A slider widget, consisting of 5 divisions and showing the round slider thumb shape.](https://flutter.github.io/assets-for-api-docs/assets/material/round_slider_thumb_shape.png)
2683///
2684/// See also:
2685///
2686/// * [Slider], which includes a thumb defined by this shape.
2687/// * [SliderTheme], which can be used to configure the thumb shape of all
2688/// sliders in a widget subtree.
2689class RoundSliderThumbShape extends SliderComponentShape {
2690 /// Create a slider thumb that draws a circle.
2691 const RoundSliderThumbShape({
2692 this.enabledThumbRadius = 10.0,
2693 this.disabledThumbRadius,
2694 this.elevation = 1.0,
2695 this.pressedElevation = 6.0,
2696 });
2697
2698 /// The preferred radius of the round thumb shape when the slider is enabled.
2699 ///
2700 /// If it is not provided, then the Material Design default of 10 is used.
2701 final double enabledThumbRadius;
2702
2703 /// The preferred radius of the round thumb shape when the slider is disabled.
2704 ///
2705 /// If no disabledRadius is provided, then it is equal to the
2706 /// [enabledThumbRadius]
2707 final double? disabledThumbRadius;
2708 double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
2709
2710 /// The resting elevation adds shadow to the unpressed thumb.
2711 ///
2712 /// The default is 1.
2713 ///
2714 /// Use 0 for no shadow. The higher the value, the larger the shadow. For
2715 /// example, a value of 12 will create a very large shadow.
2716 ///
2717 final double elevation;
2718
2719 /// The pressed elevation adds shadow to the pressed thumb.
2720 ///
2721 /// The default is 6.
2722 ///
2723 /// Use 0 for no shadow. The higher the value, the larger the shadow. For
2724 /// example, a value of 12 will create a very large shadow.
2725 final double pressedElevation;
2726
2727 @override
2728 Size getPreferredSize(bool isEnabled, bool isDiscrete) {
2729 return Size.fromRadius(isEnabled ? enabledThumbRadius : _disabledThumbRadius);
2730 }
2731
2732 @override
2733 void paint(
2734 PaintingContext context,
2735 Offset center, {
2736 required Animation<double> activationAnimation,
2737 required Animation<double> enableAnimation,
2738 required bool isDiscrete,
2739 required TextPainter labelPainter,
2740 required RenderBox parentBox,
2741 required SliderThemeData sliderTheme,
2742 required TextDirection textDirection,
2743 required double value,
2744 required double textScaleFactor,
2745 required Size sizeWithOverflow,
2746 }) {
2747 assert(sliderTheme.disabledThumbColor != null);
2748 assert(sliderTheme.thumbColor != null);
2749
2750 final Canvas canvas = context.canvas;
2751 final Tween<double> radiusTween = Tween<double>(
2752 begin: _disabledThumbRadius,
2753 end: enabledThumbRadius,
2754 );
2755 final ColorTween colorTween = ColorTween(
2756 begin: sliderTheme.disabledThumbColor,
2757 end: sliderTheme.thumbColor,
2758 );
2759
2760 final Color color = colorTween.evaluate(enableAnimation)!;
2761 final double radius = radiusTween.evaluate(enableAnimation);
2762
2763 final Tween<double> elevationTween = Tween<double>(begin: elevation, end: pressedElevation);
2764
2765 final double evaluatedElevation = elevationTween.evaluate(activationAnimation);
2766 final Path path =
2767 Path()..addArc(
2768 Rect.fromCenter(center: center, width: 2 * radius, height: 2 * radius),
2769 0,
2770 math.pi * 2,
2771 );
2772
2773 bool paintShadows = true;
2774 assert(() {
2775 if (debugDisableShadows) {
2776 _debugDrawShadow(canvas, path, evaluatedElevation);
2777 paintShadows = false;
2778 }
2779 return true;
2780 }());
2781
2782 if (paintShadows) {
2783 canvas.drawShadow(path, Colors.black, evaluatedElevation, true);
2784 }
2785
2786 canvas.drawCircle(center, radius, Paint()..color = color);
2787 }
2788}
2789
2790/// The default shape of a [RangeSlider]'s thumbs.
2791///
2792/// There is a shadow for the resting and pressed state.
2793///
2794/// ![A slider widget, consisting of 5 divisions and showing the round range slider thumb shape.](https://flutter.github.io/assets-for-api-docs/assets/material/round_range_slider_thumb_shape.png)
2795///
2796/// See also:
2797///
2798/// * [RangeSlider], which includes thumbs defined by this shape.
2799/// * [SliderTheme], which can be used to configure the thumb shapes of all
2800/// range sliders in a widget subtree.
2801class RoundRangeSliderThumbShape extends RangeSliderThumbShape {
2802 /// Create a slider thumb that draws a circle.
2803 const RoundRangeSliderThumbShape({
2804 this.enabledThumbRadius = 10.0,
2805 this.disabledThumbRadius,
2806 this.elevation = 1.0,
2807 this.pressedElevation = 6.0,
2808 });
2809
2810 /// The preferred radius of the round thumb shape when the slider is enabled.
2811 ///
2812 /// If it is not provided, then the Material Design default of 10 is used.
2813 final double enabledThumbRadius;
2814
2815 /// The preferred radius of the round thumb shape when the slider is disabled.
2816 ///
2817 /// If no disabledRadius is provided, then it is equal to the
2818 /// [enabledThumbRadius].
2819 final double? disabledThumbRadius;
2820 double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
2821
2822 /// The resting elevation adds shadow to the unpressed thumb.
2823 ///
2824 /// The default is 1.
2825 final double elevation;
2826
2827 /// The pressed elevation adds shadow to the pressed thumb.
2828 ///
2829 /// The default is 6.
2830 final double pressedElevation;
2831
2832 @override
2833 Size getPreferredSize(bool isEnabled, bool isDiscrete) {
2834 return Size.fromRadius(isEnabled ? enabledThumbRadius : _disabledThumbRadius);
2835 }
2836
2837 @override
2838 void paint(
2839 PaintingContext context,
2840 Offset center, {
2841 required Animation<double> activationAnimation,
2842 required Animation<double> enableAnimation,
2843 bool isDiscrete = false,
2844 bool isEnabled = false,
2845 bool? isOnTop,
2846 required SliderThemeData sliderTheme,
2847 TextDirection? textDirection,
2848 Thumb? thumb,
2849 bool? isPressed,
2850 }) {
2851 assert(sliderTheme.showValueIndicator != null);
2852 assert(sliderTheme.overlappingShapeStrokeColor != null);
2853 final Canvas canvas = context.canvas;
2854 final Tween<double> radiusTween = Tween<double>(
2855 begin: _disabledThumbRadius,
2856 end: enabledThumbRadius,
2857 );
2858 final ColorTween colorTween = ColorTween(
2859 begin: sliderTheme.disabledThumbColor,
2860 end: sliderTheme.thumbColor,
2861 );
2862 final double radius = radiusTween.evaluate(enableAnimation);
2863 final Tween<double> elevationTween = Tween<double>(begin: elevation, end: pressedElevation);
2864
2865 // Add a stroke of 1dp around the circle if this thumb would overlap
2866 // the other thumb.
2867 if (isOnTop ?? false) {
2868 final Paint strokePaint =
2869 Paint()
2870 ..color = sliderTheme.overlappingShapeStrokeColor!
2871 ..strokeWidth = 1.0
2872 ..style = PaintingStyle.stroke;
2873 canvas.drawCircle(center, radius, strokePaint);
2874 }
2875
2876 final Color color = colorTween.evaluate(enableAnimation)!;
2877
2878 final double evaluatedElevation =
2879 isPressed! ? elevationTween.evaluate(activationAnimation) : elevation;
2880 final Path shadowPath =
2881 Path()..addArc(
2882 Rect.fromCenter(center: center, width: 2 * radius, height: 2 * radius),
2883 0,
2884 math.pi * 2,
2885 );
2886
2887 bool paintShadows = true;
2888 assert(() {
2889 if (debugDisableShadows) {
2890 _debugDrawShadow(canvas, shadowPath, evaluatedElevation);
2891 paintShadows = false;
2892 }
2893 return true;
2894 }());
2895
2896 if (paintShadows) {
2897 canvas.drawShadow(shadowPath, Colors.black, evaluatedElevation, true);
2898 }
2899
2900 canvas.drawCircle(center, radius, Paint()..color = color);
2901 }
2902}
2903
2904/// The default shape of a [Slider]'s thumb overlay.
2905///
2906/// The shape of the overlay is a circle with the same center as the thumb, but
2907/// with a larger radius. It animates to full size when the thumb is pressed,
2908/// and animates back down to size 0 when it is released. It is painted behind
2909/// the thumb, and is expected to extend beyond the bounds of the thumb so that
2910/// it is visible.
2911///
2912/// The overlay color is defined by [SliderThemeData.overlayColor].
2913///
2914/// See also:
2915///
2916/// * [Slider], which includes an overlay defined by this shape.
2917/// * [SliderTheme], which can be used to configure the overlay shape of all
2918/// sliders in a widget subtree.
2919class RoundSliderOverlayShape extends SliderComponentShape {
2920 /// Create a slider thumb overlay that draws a circle.
2921 const RoundSliderOverlayShape({this.overlayRadius = 24.0});
2922
2923 /// The preferred radius of the round thumb shape when enabled.
2924 ///
2925 /// If it is not provided, then half of the [SliderThemeData.trackHeight] is
2926 /// used.
2927 final double overlayRadius;
2928
2929 @override
2930 Size getPreferredSize(bool isEnabled, bool isDiscrete) {
2931 return Size.fromRadius(overlayRadius);
2932 }
2933
2934 @override
2935 void paint(
2936 PaintingContext context,
2937 Offset center, {
2938 required Animation<double> activationAnimation,
2939 required Animation<double> enableAnimation,
2940 required bool isDiscrete,
2941 required TextPainter labelPainter,
2942 required RenderBox parentBox,
2943 required SliderThemeData sliderTheme,
2944 required TextDirection textDirection,
2945 required double value,
2946 required double textScaleFactor,
2947 required Size sizeWithOverflow,
2948 }) {
2949 final Canvas canvas = context.canvas;
2950 final Tween<double> radiusTween = Tween<double>(begin: 0.0, end: overlayRadius);
2951
2952 canvas.drawCircle(
2953 center,
2954 radiusTween.evaluate(activationAnimation),
2955 Paint()..color = sliderTheme.overlayColor!,
2956 );
2957 }
2958}
2959
2960/// A variant shape of a [Slider]'s value indicator . The value indicator is in
2961/// the shape of an upside-down pear.
2962///
2963/// ![A slider widget, consisting of 5 divisions and showing the paddle slider value indicator shape.](https://flutter.github.io/assets-for-api-docs/assets/material/paddle_slider_value_indicator_shape.png)
2964///
2965/// See also:
2966///
2967/// * [Slider], which includes a value indicator defined by this shape.
2968/// * [SliderTheme], which can be used to configure the slider value indicator
2969/// of all sliders in a widget subtree.
2970class PaddleSliderValueIndicatorShape extends SliderComponentShape {
2971 /// Create a slider value indicator in the shape of an upside-down pear.
2972 const PaddleSliderValueIndicatorShape();
2973
2974 static const _PaddleSliderValueIndicatorPathPainter _pathPainter =
2975 _PaddleSliderValueIndicatorPathPainter();
2976
2977 @override
2978 Size getPreferredSize(
2979 bool isEnabled,
2980 bool isDiscrete, {
2981 TextPainter? labelPainter,
2982 double? textScaleFactor,
2983 }) {
2984 assert(labelPainter != null);
2985 assert(textScaleFactor != null && textScaleFactor >= 0);
2986 return _pathPainter.getPreferredSize(labelPainter!, textScaleFactor!);
2987 }
2988
2989 @override
2990 void paint(
2991 PaintingContext context,
2992 Offset center, {
2993 required Animation<double> activationAnimation,
2994 required Animation<double> enableAnimation,
2995 required bool isDiscrete,
2996 required TextPainter labelPainter,
2997 required RenderBox parentBox,
2998 required SliderThemeData sliderTheme,
2999 required TextDirection textDirection,
3000 required double value,
3001 required double textScaleFactor,
3002 required Size sizeWithOverflow,
3003 }) {
3004 assert(!sizeWithOverflow.isEmpty);
3005 final ColorTween enableColor = ColorTween(
3006 begin: sliderTheme.disabledThumbColor,
3007 end: sliderTheme.valueIndicatorColor,
3008 );
3009 _pathPainter.paint(
3010 context.canvas,
3011 center,
3012 Paint()..color = enableColor.evaluate(enableAnimation)!,
3013 activationAnimation.value,
3014 labelPainter,
3015 textScaleFactor,
3016 sizeWithOverflow,
3017 sliderTheme.valueIndicatorStrokeColor,
3018 );
3019 }
3020}
3021
3022/// A variant shape of a [RangeSlider]'s value indicators. The value indicator
3023/// is in the shape of an upside-down pear.
3024///
3025/// ![A slider widget, consisting of 5 divisions and showing the paddle range slider value indicator shape.](https://flutter.github.io/assets-for-api-docs/assets/material/paddle_range_slider_value_indicator_shape.png)
3026///
3027/// See also:
3028///
3029/// * [RangeSlider], which includes value indicators defined by this shape.
3030/// * [SliderTheme], which can be used to configure the range slider value
3031/// indicator of all sliders in a widget subtree.
3032class PaddleRangeSliderValueIndicatorShape extends RangeSliderValueIndicatorShape {
3033 /// Create a slider value indicator in the shape of an upside-down pear.
3034 const PaddleRangeSliderValueIndicatorShape();
3035
3036 static const _PaddleSliderValueIndicatorPathPainter _pathPainter =
3037 _PaddleSliderValueIndicatorPathPainter();
3038
3039 @override
3040 Size getPreferredSize(
3041 bool isEnabled,
3042 bool isDiscrete, {
3043 required TextPainter labelPainter,
3044 required double textScaleFactor,
3045 }) {
3046 assert(textScaleFactor >= 0);
3047 return _pathPainter.getPreferredSize(labelPainter, textScaleFactor);
3048 }
3049
3050 @override
3051 double getHorizontalShift({
3052 RenderBox? parentBox,
3053 Offset? center,
3054 TextPainter? labelPainter,
3055 Animation<double>? activationAnimation,
3056 double? textScaleFactor,
3057 Size? sizeWithOverflow,
3058 }) {
3059 return _pathPainter.getHorizontalShift(
3060 center: center!,
3061 labelPainter: labelPainter!,
3062 scale: activationAnimation!.value,
3063 textScaleFactor: textScaleFactor!,
3064 sizeWithOverflow: sizeWithOverflow!,
3065 );
3066 }
3067
3068 @override
3069 void paint(
3070 PaintingContext context,
3071 Offset center, {
3072 required Animation<double> activationAnimation,
3073 required Animation<double> enableAnimation,
3074 bool? isDiscrete,
3075 bool isOnTop = false,
3076 required TextPainter labelPainter,
3077 required RenderBox parentBox,
3078 required SliderThemeData sliderTheme,
3079 TextDirection? textDirection,
3080 Thumb? thumb,
3081 double? value,
3082 double? textScaleFactor,
3083 Size? sizeWithOverflow,
3084 }) {
3085 assert(!sizeWithOverflow!.isEmpty);
3086 final ColorTween enableColor = ColorTween(
3087 begin: sliderTheme.disabledThumbColor,
3088 end: sliderTheme.valueIndicatorColor,
3089 );
3090 // Add a stroke of 1dp around the top paddle.
3091 _pathPainter.paint(
3092 context.canvas,
3093 center,
3094 Paint()..color = enableColor.evaluate(enableAnimation)!,
3095 activationAnimation.value,
3096 labelPainter,
3097 textScaleFactor!,
3098 sizeWithOverflow!,
3099 isOnTop ? sliderTheme.overlappingShapeStrokeColor : sliderTheme.valueIndicatorStrokeColor,
3100 );
3101 }
3102}
3103
3104class _PaddleSliderValueIndicatorPathPainter {
3105 const _PaddleSliderValueIndicatorPathPainter();
3106
3107 // These constants define the shape of the default value indicator.
3108 // The value indicator changes shape based on the size of
3109 // the label: The top lobe spreads horizontally, and the
3110 // top arc on the neck moves down to keep it merging smoothly
3111 // with the top lobe as it expands.
3112
3113 // Radius of the top lobe of the value indicator.
3114 static const double _topLobeRadius = 16.0;
3115 static const double _minLabelWidth = 16.0;
3116 // Radius of the bottom lobe of the value indicator.
3117 static const double _bottomLobeRadius = 10.0;
3118 static const double _labelPadding = 8.0;
3119 static const double _distanceBetweenTopBottomCenters = 40.0;
3120 static const double _middleNeckWidth = 3.0;
3121 static const double _bottomNeckRadius = 4.5;
3122 // The base of the triangle between the top lobe center and the centers of
3123 // the two top neck arcs.
3124 static const double _neckTriangleBase = _topNeckRadius + _middleNeckWidth / 2;
3125 static const double _rightBottomNeckCenterX = _middleNeckWidth / 2 + _bottomNeckRadius;
3126 static const double _rightBottomNeckAngleStart = math.pi;
3127 static const Offset _topLobeCenter = Offset(0.0, -_distanceBetweenTopBottomCenters);
3128 static const double _topNeckRadius = 13.0;
3129 // The length of the hypotenuse of the triangle formed by the center
3130 // of the left top lobe arc and the center of the top left neck arc.
3131 // Used to calculate the position of the center of the arc.
3132 static const double _neckTriangleHypotenuse = _topLobeRadius + _topNeckRadius;
3133 // Some convenience values to help readability.
3134 static const double _twoSeventyDegrees = 3.0 * math.pi / 2.0;
3135 static const double _ninetyDegrees = math.pi / 2.0;
3136 static const double _thirtyDegrees = math.pi / 6.0;
3137 static const double _preferredHeight =
3138 _distanceBetweenTopBottomCenters + _topLobeRadius + _bottomLobeRadius;
3139 // Set to true if you want a rectangle to be drawn around the label bubble.
3140 // This helps with building tests that check that the label draws in the right
3141 // place (because it prints the rect in the failed test output). It should not
3142 // be checked in while set to "true".
3143 static const bool _debuggingLabelLocation = false;
3144
3145 Size getPreferredSize(TextPainter labelPainter, double textScaleFactor) {
3146 assert(textScaleFactor >= 0);
3147 final double width =
3148 math.max(_minLabelWidth * textScaleFactor, labelPainter.width) +
3149 _labelPadding * 2 * textScaleFactor;
3150 return Size(width, _preferredHeight * textScaleFactor);
3151 }
3152
3153 // Adds an arc to the path that has the attributes passed in. This is
3154 // a convenience to make adding arcs have less boilerplate.
3155 static void _addArc(Path path, Offset center, double radius, double startAngle, double endAngle) {
3156 assert(center.isFinite);
3157 final Rect arcRect = Rect.fromCircle(center: center, radius: radius);
3158 path.arcTo(arcRect, startAngle, endAngle - startAngle, false);
3159 }
3160
3161 double getHorizontalShift({
3162 required Offset center,
3163 required TextPainter labelPainter,
3164 required double scale,
3165 required double textScaleFactor,
3166 required Size sizeWithOverflow,
3167 }) {
3168 assert(!sizeWithOverflow.isEmpty);
3169 final double inverseTextScale = textScaleFactor != 0 ? 1.0 / textScaleFactor : 0.0;
3170 final double labelHalfWidth = labelPainter.width / 2.0;
3171 final double halfWidthNeeded = math.max(
3172 0.0,
3173 inverseTextScale * labelHalfWidth - (_topLobeRadius - _labelPadding),
3174 );
3175 final double shift = _getIdealOffset(
3176 halfWidthNeeded,
3177 textScaleFactor * scale,
3178 center,
3179 sizeWithOverflow.width,
3180 );
3181 return shift * textScaleFactor;
3182 }
3183
3184 // Determines the "best" offset to keep the bubble within the slider. The
3185 // calling code will bound that with the available movement in the paddle shape.
3186 double _getIdealOffset(
3187 double halfWidthNeeded,
3188 double scale,
3189 Offset center,
3190 double widthWithOverflow,
3191 ) {
3192 const double edgeMargin = 8.0;
3193 final Rect topLobeRect = Rect.fromLTWH(
3194 -_topLobeRadius - halfWidthNeeded,
3195 -_topLobeRadius - _distanceBetweenTopBottomCenters,
3196 2.0 * (_topLobeRadius + halfWidthNeeded),
3197 2.0 * _topLobeRadius,
3198 );
3199 // We can just multiply by scale instead of a transform, since we're scaling
3200 // around (0, 0).
3201 final Offset topLeft = (topLobeRect.topLeft * scale) + center;
3202 final Offset bottomRight = (topLobeRect.bottomRight * scale) + center;
3203 double shift = 0.0;
3204
3205 if (topLeft.dx < edgeMargin) {
3206 shift = edgeMargin - topLeft.dx;
3207 }
3208
3209 final double endGlobal = widthWithOverflow;
3210 if (bottomRight.dx > endGlobal - edgeMargin) {
3211 shift = endGlobal - edgeMargin - bottomRight.dx;
3212 }
3213
3214 shift = scale == 0.0 ? 0.0 : shift / scale;
3215 if (shift < 0.0) {
3216 // Shifting to the left.
3217 shift = math.max(shift, -halfWidthNeeded);
3218 } else {
3219 // Shifting to the right.
3220 shift = math.min(shift, halfWidthNeeded);
3221 }
3222 return shift;
3223 }
3224
3225 void paint(
3226 Canvas canvas,
3227 Offset center,
3228 Paint paint,
3229 double scale,
3230 TextPainter labelPainter,
3231 double textScaleFactor,
3232 Size sizeWithOverflow,
3233 Color? strokePaintColor,
3234 ) {
3235 if (scale == 0.0) {
3236 // Zero scale essentially means "do not draw anything", so it's safe to just return. Otherwise,
3237 // our math below will attempt to divide by zero and send needless NaNs to the engine.
3238 return;
3239 }
3240 assert(!sizeWithOverflow.isEmpty);
3241
3242 // The entire value indicator should scale with the size of the label,
3243 // to keep it large enough to encompass the label text.
3244 final double overallScale = scale * textScaleFactor;
3245 final double inverseTextScale = textScaleFactor != 0 ? 1.0 / textScaleFactor : 0.0;
3246 final double labelHalfWidth = labelPainter.width / 2.0;
3247
3248 canvas.save();
3249 canvas.translate(center.dx, center.dy);
3250 canvas.scale(overallScale, overallScale);
3251
3252 final double bottomNeckTriangleHypotenuse =
3253 _bottomNeckRadius + _bottomLobeRadius / overallScale;
3254 final double rightBottomNeckCenterY =
3255 -math.sqrt(
3256 math.pow(bottomNeckTriangleHypotenuse, 2) - math.pow(_rightBottomNeckCenterX, 2),
3257 );
3258 final double rightBottomNeckAngleEnd =
3259 math.pi + math.atan(rightBottomNeckCenterY / _rightBottomNeckCenterX);
3260 final Path path = Path()..moveTo(_middleNeckWidth / 2, rightBottomNeckCenterY);
3261 _addArc(
3262 path,
3263 Offset(_rightBottomNeckCenterX, rightBottomNeckCenterY),
3264 _bottomNeckRadius,
3265 _rightBottomNeckAngleStart,
3266 rightBottomNeckAngleEnd,
3267 );
3268 _addArc(
3269 path,
3270 Offset.zero,
3271 _bottomLobeRadius / overallScale,
3272 rightBottomNeckAngleEnd - math.pi,
3273 2 * math.pi - rightBottomNeckAngleEnd,
3274 );
3275 _addArc(
3276 path,
3277 Offset(-_rightBottomNeckCenterX, rightBottomNeckCenterY),
3278 _bottomNeckRadius,
3279 math.pi - rightBottomNeckAngleEnd,
3280 0,
3281 );
3282
3283 // This is the needed extra width for the label. It is only positive when
3284 // the label exceeds the minimum size contained by the round top lobe.
3285 final double halfWidthNeeded = math.max(
3286 0.0,
3287 inverseTextScale * labelHalfWidth - (_topLobeRadius - _labelPadding),
3288 );
3289
3290 final double shift = _getIdealOffset(
3291 halfWidthNeeded,
3292 overallScale,
3293 center,
3294 sizeWithOverflow.width,
3295 );
3296 final double leftWidthNeeded = halfWidthNeeded - shift;
3297 final double rightWidthNeeded = halfWidthNeeded + shift;
3298
3299 // The parameter that describes how far along the transition from round to
3300 // stretched we are.
3301 final double leftAmount = math.max(0.0, math.min(1.0, leftWidthNeeded / _neckTriangleBase));
3302 final double rightAmount = math.max(0.0, math.min(1.0, rightWidthNeeded / _neckTriangleBase));
3303 // The angle between the top neck arc's center and the top lobe's center
3304 // and vertical. The base amount is chosen so that the neck is smooth,
3305 // even when the lobe is shifted due to its size.
3306 final double leftTheta = (1.0 - leftAmount) * _thirtyDegrees;
3307 final double rightTheta = (1.0 - rightAmount) * _thirtyDegrees;
3308 // The center of the top left neck arc.
3309 final Offset leftTopNeckCenter = Offset(
3310 -_neckTriangleBase,
3311 _topLobeCenter.dy + math.cos(leftTheta) * _neckTriangleHypotenuse,
3312 );
3313 final Offset neckRightCenter = Offset(
3314 _neckTriangleBase,
3315 _topLobeCenter.dy + math.cos(rightTheta) * _neckTriangleHypotenuse,
3316 );
3317 final double leftNeckArcAngle = _ninetyDegrees - leftTheta;
3318 final double rightNeckArcAngle = math.pi + _ninetyDegrees - rightTheta;
3319 // The distance between the end of the bottom neck arc and the beginning of
3320 // the top neck arc. We use this to shrink/expand it based on the scale
3321 // factor of the value indicator.
3322 final double neckStretchBaseline = math.max(
3323 0.0,
3324 rightBottomNeckCenterY - math.max(leftTopNeckCenter.dy, neckRightCenter.dy),
3325 );
3326 final double t = math.pow(inverseTextScale, 3.0) as double;
3327 final double stretch = clampDouble(neckStretchBaseline * t, 0.0, 10.0 * neckStretchBaseline);
3328 final Offset neckStretch = Offset(0.0, neckStretchBaseline - stretch);
3329
3330 assert(
3331 !_debuggingLabelLocation ||
3332 () {
3333 final Offset leftCenter = _topLobeCenter - Offset(leftWidthNeeded, 0.0) + neckStretch;
3334 final Offset rightCenter = _topLobeCenter + Offset(rightWidthNeeded, 0.0) + neckStretch;
3335 final Rect valueRect = Rect.fromLTRB(
3336 leftCenter.dx - _topLobeRadius,
3337 leftCenter.dy - _topLobeRadius,
3338 rightCenter.dx + _topLobeRadius,
3339 rightCenter.dy + _topLobeRadius,
3340 );
3341 final Paint outlinePaint =
3342 Paint()
3343 ..color = const Color(0xffff0000)
3344 ..style = PaintingStyle.stroke
3345 ..strokeWidth = 1.0;
3346 canvas.drawRect(valueRect, outlinePaint);
3347 return true;
3348 }(),
3349 );
3350
3351 _addArc(path, leftTopNeckCenter + neckStretch, _topNeckRadius, 0.0, -leftNeckArcAngle);
3352 _addArc(
3353 path,
3354 _topLobeCenter - Offset(leftWidthNeeded, 0.0) + neckStretch,
3355 _topLobeRadius,
3356 _ninetyDegrees + leftTheta,
3357 _twoSeventyDegrees,
3358 );
3359 _addArc(
3360 path,
3361 _topLobeCenter + Offset(rightWidthNeeded, 0.0) + neckStretch,
3362 _topLobeRadius,
3363 _twoSeventyDegrees,
3364 _twoSeventyDegrees + math.pi - rightTheta,
3365 );
3366 _addArc(path, neckRightCenter + neckStretch, _topNeckRadius, rightNeckArcAngle, math.pi);
3367
3368 if (strokePaintColor != null) {
3369 final Paint strokePaint =
3370 Paint()
3371 ..color = strokePaintColor
3372 ..strokeWidth = 1.0
3373 ..style = PaintingStyle.stroke;
3374 canvas.drawPath(path, strokePaint);
3375 }
3376
3377 canvas.drawPath(path, paint);
3378
3379 // Draw the label.
3380 canvas.save();
3381 canvas.translate(shift, -_distanceBetweenTopBottomCenters + neckStretch.dy);
3382 canvas.scale(inverseTextScale, inverseTextScale);
3383 labelPainter.paint(canvas, Offset.zero - Offset(labelHalfWidth, labelPainter.height / 2.0));
3384 canvas.restore();
3385 canvas.restore();
3386 }
3387}
3388
3389/// A callback that formats a numeric value from a [Slider] or [RangeSlider] widget.
3390///
3391/// See also:
3392///
3393/// * [Slider.semanticFormatterCallback], which shows an example use case.
3394/// * [RangeSlider.semanticFormatterCallback], which shows an example use case.
3395typedef SemanticFormatterCallback = String Function(double value);
3396
3397/// Decides which thumbs (if any) should be selected.
3398///
3399/// The default finds the closest thumb, but if the thumbs are close to each
3400/// other, it waits for movement defined by [dx] to determine the selected
3401/// thumb.
3402///
3403/// Override [SliderThemeData.thumbSelector] for custom thumb selection.
3404typedef RangeThumbSelector =
3405 Thumb? Function(
3406 TextDirection textDirection,
3407 RangeValues values,
3408 double tapValue,
3409 Size thumbSize,
3410 Size trackSize,
3411 double dx,
3412 );
3413
3414/// Object for representing range slider thumb values.
3415///
3416/// This object is passed into [RangeSlider.values] to set its values, and it
3417/// is emitted in [RangeSlider.onChanged], [RangeSlider.onChangeStart], and
3418/// [RangeSlider.onChangeEnd] when the values change.
3419@immutable
3420class RangeValues {
3421 /// Creates pair of start and end values.
3422 const RangeValues(this.start, this.end);
3423
3424 /// The value of the start thumb.
3425 ///
3426 /// For LTR text direction, the start is the left thumb, and for RTL text
3427 /// direction, the start is the right thumb.
3428 final double start;
3429
3430 /// The value of the end thumb.
3431 ///
3432 /// For LTR text direction, the end is the right thumb, and for RTL text
3433 /// direction, the end is the left thumb.
3434 final double end;
3435
3436 @override
3437 bool operator ==(Object other) {
3438 if (other.runtimeType != runtimeType) {
3439 return false;
3440 }
3441 return other is RangeValues && other.start == start && other.end == end;
3442 }
3443
3444 @override
3445 int get hashCode => Object.hash(start, end);
3446
3447 @override
3448 String toString() {
3449 return '${objectRuntimeType(this, 'RangeValues')}($start, $end)';
3450 }
3451}
3452
3453/// Object for setting range slider label values that appear in the value
3454/// indicator for each thumb.
3455///
3456/// Used in combination with [SliderThemeData.showValueIndicator] to display
3457/// labels above the thumbs.
3458@immutable
3459class RangeLabels {
3460 /// Creates pair of start and end labels.
3461 const RangeLabels(this.start, this.end);
3462
3463 /// The label of the start thumb.
3464 ///
3465 /// For LTR text direction, the start is the left thumb, and for RTL text
3466 /// direction, the start is the right thumb.
3467 final String start;
3468
3469 /// The label of the end thumb.
3470 ///
3471 /// For LTR text direction, the end is the right thumb, and for RTL text
3472 /// direction, the end is the left thumb.
3473 final String end;
3474
3475 @override
3476 bool operator ==(Object other) {
3477 if (other.runtimeType != runtimeType) {
3478 return false;
3479 }
3480 return other is RangeLabels && other.start == start && other.end == end;
3481 }
3482
3483 @override
3484 int get hashCode => Object.hash(start, end);
3485
3486 @override
3487 String toString() {
3488 return '${objectRuntimeType(this, 'RangeLabels')}($start, $end)';
3489 }
3490}
3491
3492void _debugDrawShadow(Canvas canvas, Path path, double elevation) {
3493 if (elevation > 0.0) {
3494 canvas.drawPath(
3495 path,
3496 Paint()
3497 ..color = Colors.black
3498 ..style = PaintingStyle.stroke
3499 ..strokeWidth = elevation * 2.0,
3500 );
3501 }
3502}
3503
3504/// The default shape of a Material 3 [Slider]'s value indicator.
3505///
3506/// See also:
3507///
3508/// * [Slider], which includes a value indicator defined by this shape.
3509/// * [SliderTheme], which can be used to configure the slider value indicator
3510/// of all sliders in a widget subtree.
3511class DropSliderValueIndicatorShape extends SliderComponentShape {
3512 /// Create a slider value indicator that resembles a drop shape.
3513 const DropSliderValueIndicatorShape();
3514
3515 static const _DropSliderValueIndicatorPathPainter _pathPainter =
3516 _DropSliderValueIndicatorPathPainter();
3517
3518 @override
3519 Size getPreferredSize(
3520 bool isEnabled,
3521 bool isDiscrete, {
3522 TextPainter? labelPainter,
3523 double? textScaleFactor,
3524 }) {
3525 assert(labelPainter != null);
3526 assert(textScaleFactor != null && textScaleFactor >= 0);
3527 return _pathPainter.getPreferredSize(labelPainter!, textScaleFactor!);
3528 }
3529
3530 @override
3531 void paint(
3532 PaintingContext context,
3533 Offset center, {
3534 required Animation<double> activationAnimation,
3535 required Animation<double> enableAnimation,
3536 required bool isDiscrete,
3537 required TextPainter labelPainter,
3538 required RenderBox parentBox,
3539 required SliderThemeData sliderTheme,
3540 required TextDirection textDirection,
3541 required double value,
3542 required double textScaleFactor,
3543 required Size sizeWithOverflow,
3544 }) {
3545 final Canvas canvas = context.canvas;
3546 final double scale = activationAnimation.value;
3547 _pathPainter.paint(
3548 parentBox: parentBox,
3549 canvas: canvas,
3550 center: center,
3551 scale: scale,
3552 labelPainter: labelPainter,
3553 textScaleFactor: textScaleFactor,
3554 sizeWithOverflow: sizeWithOverflow,
3555 backgroundPaintColor: sliderTheme.valueIndicatorColor!,
3556 strokePaintColor: sliderTheme.valueIndicatorStrokeColor,
3557 );
3558 }
3559}
3560
3561class _DropSliderValueIndicatorPathPainter {
3562 const _DropSliderValueIndicatorPathPainter();
3563
3564 static const double _triangleHeight = 10.0;
3565 static const double _labelPadding = 8.0;
3566 static const double _preferredHeight = 32.0;
3567 static const double _minLabelWidth = 20.0;
3568 static const double _minRectHeight = 28.0;
3569 static const double _rectYOffset = 6.0;
3570 static const double _bottomTipYOffset = 16.0;
3571 static const double _preferredHalfHeight = _preferredHeight / 2;
3572 static const double _upperRectRadius = 4;
3573
3574 Size getPreferredSize(TextPainter labelPainter, double textScaleFactor) {
3575 final double width =
3576 math.max(_minLabelWidth, labelPainter.width) + _labelPadding * 2 * textScaleFactor;
3577 return Size(width, _preferredHeight * textScaleFactor);
3578 }
3579
3580 double getHorizontalShift({
3581 required RenderBox parentBox,
3582 required Offset center,
3583 required TextPainter labelPainter,
3584 required double textScaleFactor,
3585 required Size sizeWithOverflow,
3586 required double scale,
3587 }) {
3588 assert(!sizeWithOverflow.isEmpty);
3589
3590 const double edgePadding = 8.0;
3591 final double rectangleWidth = _upperRectangleWidth(labelPainter, scale);
3592
3593 /// Value indicator draws on the Overlay and by using the global Offset
3594 /// we are making sure we use the bounds of the Overlay instead of the Slider.
3595 final Offset globalCenter = parentBox.localToGlobal(center);
3596
3597 // The rectangle must be shifted towards the center so that it minimizes the
3598 // chance of it rendering outside the bounds of the render box. If the shift
3599 // is negative, then the lobe is shifted from right to left, and if it is
3600 // positive, then the lobe is shifted from left to right.
3601 final double overflowLeft = math.max(0, rectangleWidth / 2 - globalCenter.dx + edgePadding);
3602 final double overflowRight = math.max(
3603 0,
3604 rectangleWidth / 2 - (sizeWithOverflow.width - globalCenter.dx - edgePadding),
3605 );
3606
3607 if (rectangleWidth < sizeWithOverflow.width) {
3608 return overflowLeft - overflowRight;
3609 } else if (overflowLeft - overflowRight > 0) {
3610 return overflowLeft - (edgePadding * textScaleFactor);
3611 } else {
3612 return -overflowRight + (edgePadding * textScaleFactor);
3613 }
3614 }
3615
3616 double _upperRectangleWidth(TextPainter labelPainter, double scale) {
3617 final double unscaledWidth = math.max(_minLabelWidth, labelPainter.width) + _labelPadding;
3618 return unscaledWidth * scale;
3619 }
3620
3621 BorderRadius _adjustBorderRadius(Rect rect) {
3622 const double rectness = 0.0;
3623 return BorderRadius.lerp(
3624 BorderRadius.circular(_upperRectRadius),
3625 BorderRadius.all(Radius.circular(rect.shortestSide / 2.0)),
3626 1.0 - rectness,
3627 )!;
3628 }
3629
3630 void paint({
3631 required RenderBox parentBox,
3632 required Canvas canvas,
3633 required Offset center,
3634 required double scale,
3635 required TextPainter labelPainter,
3636 required double textScaleFactor,
3637 required Size sizeWithOverflow,
3638 required Color backgroundPaintColor,
3639 Color? strokePaintColor,
3640 }) {
3641 if (scale == 0.0) {
3642 // Zero scale essentially means "do not draw anything", so it's safe to just return.
3643 return;
3644 }
3645 assert(!sizeWithOverflow.isEmpty);
3646 final double rectangleWidth = _upperRectangleWidth(labelPainter, scale);
3647 final double horizontalShift = getHorizontalShift(
3648 parentBox: parentBox,
3649 center: center,
3650 labelPainter: labelPainter,
3651 textScaleFactor: textScaleFactor,
3652 sizeWithOverflow: sizeWithOverflow,
3653 scale: scale,
3654 );
3655 final Rect upperRect = Rect.fromLTWH(
3656 -rectangleWidth / 2 + horizontalShift,
3657 -_rectYOffset - _minRectHeight,
3658 rectangleWidth,
3659 _minRectHeight,
3660 );
3661
3662 final Paint fillPaint = Paint()..color = backgroundPaintColor;
3663
3664 canvas.save();
3665 canvas.translate(center.dx, center.dy - _bottomTipYOffset);
3666 canvas.scale(scale, scale);
3667
3668 final BorderRadius adjustedBorderRadius = _adjustBorderRadius(upperRect);
3669 final RRect borderRect = adjustedBorderRadius
3670 .resolve(labelPainter.textDirection)
3671 .toRRect(upperRect);
3672 final Path trianglePath =
3673 Path()
3674 ..lineTo(-_triangleHeight, -_triangleHeight)
3675 ..lineTo(_triangleHeight, -_triangleHeight)
3676 ..close();
3677 trianglePath.addRRect(borderRect);
3678
3679 if (strokePaintColor != null) {
3680 final Paint strokePaint =
3681 Paint()
3682 ..color = strokePaintColor
3683 ..strokeWidth = 1.0
3684 ..style = PaintingStyle.stroke;
3685 canvas.drawPath(trianglePath, strokePaint);
3686 }
3687
3688 canvas.drawPath(trianglePath, fillPaint);
3689
3690 // The label text is centered within the value indicator.
3691 final double bottomTipToUpperRectTranslateY = -_preferredHalfHeight / 2 - upperRect.height;
3692 canvas.translate(0, bottomTipToUpperRectTranslateY);
3693 final Offset boxCenter = Offset(horizontalShift, upperRect.height / 1.75);
3694 final Offset halfLabelPainterOffset = Offset(labelPainter.width / 2, labelPainter.height / 2);
3695 final Offset labelOffset = boxCenter - halfLabelPainterOffset;
3696 labelPainter.paint(canvas, labelOffset);
3697 canvas.restore();
3698 }
3699}
3700
3701/// The bar shape of a [Slider]'s thumb.
3702///
3703/// When the slider is enabled, the [ColorScheme.primary] color is used for the
3704/// thumb. When the slider is disabled, the [ColorScheme.onSurface] color with an
3705/// opacity of 0.38 is used for the thumb.
3706///
3707/// The thumb bar shape width is reduced when the thumb is pressed.
3708///
3709/// If [SliderThemeData.thumbSize] is null, then the thumb size is 4 pixels for the width
3710/// and 44 pixels for the height.
3711///
3712/// This is the default thumb shape for [Slider]. If [ThemeData.useMaterial3] is false,
3713/// then the default thumb shape is [RoundSliderThumbShape].
3714///
3715/// See also:
3716///
3717/// * [Slider], which includes an overlay defined by this shape.
3718/// * [SliderTheme], which can be used to configure the overlay shape of all
3719/// sliders in a widget subtree.
3720class HandleThumbShape extends SliderComponentShape {
3721 /// Create a slider thumb that draws a bar.
3722 const HandleThumbShape();
3723
3724 @override
3725 Size getPreferredSize(bool isEnabled, bool isDiscrete) {
3726 return const Size(4.0, 44.0);
3727 }
3728
3729 @override
3730 void paint(
3731 PaintingContext context,
3732 Offset center, {
3733 required Animation<double> activationAnimation,
3734 required Animation<double> enableAnimation,
3735 required bool isDiscrete,
3736 required TextPainter labelPainter,
3737 required RenderBox parentBox,
3738 required SliderThemeData sliderTheme,
3739 required TextDirection textDirection,
3740 required double value,
3741 required double textScaleFactor,
3742 required Size sizeWithOverflow,
3743 }) {
3744 assert(sliderTheme.disabledThumbColor != null);
3745 assert(sliderTheme.thumbColor != null);
3746 assert(sliderTheme.thumbSize != null);
3747
3748 final ColorTween colorTween = ColorTween(
3749 begin: sliderTheme.disabledThumbColor,
3750 end: sliderTheme.thumbColor,
3751 );
3752 final Color color = colorTween.evaluate(enableAnimation)!;
3753
3754 final Canvas canvas = context.canvas;
3755 final Size thumbSize =
3756 sliderTheme.thumbSize!.resolve(<MaterialState>{})!; // This is resolved in the paint method.
3757 final RRect rrect = RRect.fromRectAndRadius(
3758 Rect.fromCenter(center: center, width: thumbSize.width, height: thumbSize.height),
3759 Radius.circular(thumbSize.shortestSide / 2),
3760 );
3761 canvas.drawRRect(rrect, Paint()..color = color);
3762 }
3763}
3764
3765/// The gapped shape of a [Slider]'s track.
3766///
3767/// The [GappedSliderTrackShape] consists of active and inactive
3768/// tracks. The active track uses the [SliderThemeData.activeTrackColor] and the
3769/// inactive track uses the [SliderThemeData.inactiveTrackColor].
3770///
3771/// The track shape uses circular corner radius for the edge corners and a corner radius
3772/// of 2 pixels for the inside corners.
3773///
3774/// Between the active and inactive tracks there is a gap of size [SliderThemeData.trackGap].
3775/// If the [SliderThemeData.thumbShape] is [HandleThumbShape] and the thumb is pressed, the thumb's
3776/// width is reduced; as a result, the track gap size in [GappedSliderTrackShape]
3777/// is also reduced.
3778///
3779/// If [SliderThemeData.trackGap] is null, then the track gap size defaults to 6 pixels.
3780///
3781/// This is the default track shape for [Slider]. If [ThemeData.useMaterial3] is false,
3782/// then the default track shape is [RoundedRectSliderTrackShape].
3783///
3784/// See also:
3785///
3786/// * [Slider], which includes an overlay defined by this shape.
3787/// * [SliderTheme], which can be used to configure the overlay shape of all
3788/// sliders in a widget subtree.
3789class GappedSliderTrackShape extends SliderTrackShape with BaseSliderTrackShape {
3790 /// Create a slider track that draws two rectangles with rounded outer edges.
3791 const GappedSliderTrackShape();
3792
3793 @override
3794 void paint(
3795 PaintingContext context,
3796 Offset offset, {
3797 required RenderBox parentBox,
3798 required SliderThemeData sliderTheme,
3799 required Animation<double> enableAnimation,
3800 required TextDirection textDirection,
3801 required Offset thumbCenter,
3802 Offset? secondaryOffset,
3803 bool isDiscrete = false,
3804 bool isEnabled = false,
3805 double additionalActiveTrackHeight = 2,
3806 }) {
3807 assert(sliderTheme.disabledActiveTrackColor != null);
3808 assert(sliderTheme.disabledInactiveTrackColor != null);
3809 assert(sliderTheme.activeTrackColor != null);
3810 assert(sliderTheme.inactiveTrackColor != null);
3811 assert(sliderTheme.thumbShape != null);
3812 assert(sliderTheme.trackGap != null);
3813 assert(!sliderTheme.trackGap!.isNegative);
3814 // If the slider [SliderThemeData.trackHeight] is less than or equal to 0,
3815 // then it makes no difference whether the track is painted or not,
3816 // therefore the painting can be a no-op.
3817 if (sliderTheme.trackHeight == null || sliderTheme.trackHeight! <= 0) {
3818 return;
3819 }
3820
3821 // Assign the track segment paints, which are left: active, right: inactive,
3822 // but reversed for right to left text.
3823 final ColorTween activeTrackColorTween = ColorTween(
3824 begin: sliderTheme.disabledActiveTrackColor,
3825 end: sliderTheme.activeTrackColor,
3826 );
3827 final ColorTween inactiveTrackColorTween = ColorTween(
3828 begin: sliderTheme.disabledInactiveTrackColor,
3829 end: sliderTheme.inactiveTrackColor,
3830 );
3831 final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation)!;
3832 final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
3833 final Paint leftTrackPaint;
3834 final Paint rightTrackPaint;
3835 switch (textDirection) {
3836 case TextDirection.ltr:
3837 leftTrackPaint = activePaint;
3838 rightTrackPaint = inactivePaint;
3839 case TextDirection.rtl:
3840 leftTrackPaint = inactivePaint;
3841 rightTrackPaint = activePaint;
3842 }
3843
3844 // Gap, starting from the middle of the thumb.
3845 final double trackGap = sliderTheme.trackGap!;
3846
3847 final Rect trackRect = getPreferredRect(
3848 parentBox: parentBox,
3849 offset: offset,
3850 sliderTheme: sliderTheme,
3851 isEnabled: isEnabled,
3852 isDiscrete: isDiscrete,
3853 );
3854
3855 final Radius trackCornerRadius = Radius.circular(trackRect.shortestSide / 2);
3856 const Radius trackInsideCornerRadius = Radius.circular(2.0);
3857
3858 final RRect trackRRect = RRect.fromRectAndCorners(
3859 trackRect,
3860 topLeft: trackCornerRadius,
3861 bottomLeft: trackCornerRadius,
3862 topRight: trackCornerRadius,
3863 bottomRight: trackCornerRadius,
3864 );
3865
3866 final RRect leftRRect = RRect.fromLTRBAndCorners(
3867 trackRect.left,
3868 trackRect.top,
3869 math.max(trackRect.left, thumbCenter.dx - trackGap),
3870 trackRect.bottom,
3871 topLeft: trackCornerRadius,
3872 bottomLeft: trackCornerRadius,
3873 topRight: trackInsideCornerRadius,
3874 bottomRight: trackInsideCornerRadius,
3875 );
3876
3877 final RRect rightRRect = RRect.fromLTRBAndCorners(
3878 thumbCenter.dx + trackGap,
3879 trackRect.top,
3880 trackRect.right,
3881 trackRect.bottom,
3882 topRight: trackCornerRadius,
3883 bottomRight: trackCornerRadius,
3884 topLeft: trackInsideCornerRadius,
3885 bottomLeft: trackInsideCornerRadius,
3886 );
3887
3888 context.canvas
3889 ..save()
3890 ..clipRRect(trackRRect);
3891 final bool drawLeftTrack = thumbCenter.dx > (leftRRect.left + (sliderTheme.trackHeight! / 2));
3892 final bool drawRightTrack =
3893 thumbCenter.dx < (rightRRect.right - (sliderTheme.trackHeight! / 2));
3894 if (drawLeftTrack) {
3895 context.canvas.drawRRect(leftRRect, leftTrackPaint);
3896 }
3897 if (drawRightTrack) {
3898 context.canvas.drawRRect(rightRRect, rightTrackPaint);
3899 }
3900
3901 final bool isLTR = textDirection == TextDirection.ltr;
3902 final bool showSecondaryTrack =
3903 (secondaryOffset != null) &&
3904 switch (isLTR) {
3905 true => secondaryOffset.dx > thumbCenter.dx + trackGap,
3906 false => secondaryOffset.dx < thumbCenter.dx - trackGap,
3907 };
3908
3909 if (showSecondaryTrack) {
3910 final ColorTween secondaryTrackColorTween = ColorTween(
3911 begin: sliderTheme.disabledSecondaryActiveTrackColor,
3912 end: sliderTheme.secondaryActiveTrackColor,
3913 );
3914 final Paint secondaryTrackPaint =
3915 Paint()..color = secondaryTrackColorTween.evaluate(enableAnimation)!;
3916 if (isLTR) {
3917 context.canvas.drawRRect(
3918 RRect.fromLTRBAndCorners(
3919 thumbCenter.dx + trackGap,
3920 trackRect.top,
3921 secondaryOffset.dx,
3922 trackRect.bottom,
3923 topLeft: trackInsideCornerRadius,
3924 bottomLeft: trackInsideCornerRadius,
3925 topRight: trackCornerRadius,
3926 bottomRight: trackCornerRadius,
3927 ),
3928 secondaryTrackPaint,
3929 );
3930 } else {
3931 context.canvas.drawRRect(
3932 RRect.fromLTRBAndCorners(
3933 secondaryOffset.dx - trackGap,
3934 trackRect.top,
3935 thumbCenter.dx,
3936 trackRect.bottom,
3937 topLeft: trackInsideCornerRadius,
3938 bottomLeft: trackInsideCornerRadius,
3939 topRight: trackCornerRadius,
3940 bottomRight: trackCornerRadius,
3941 ),
3942 secondaryTrackPaint,
3943 );
3944 }
3945 }
3946 context.canvas.restore();
3947
3948 const double stopIndicatorRadius = 2.0;
3949 final double stopIndicatorTrailingSpace = sliderTheme.trackHeight! / 2;
3950 final Offset stopIndicatorOffset = Offset(
3951 (textDirection == TextDirection.ltr)
3952 ? trackRect.centerRight.dx - stopIndicatorTrailingSpace
3953 : trackRect.centerLeft.dx + stopIndicatorTrailingSpace,
3954 trackRect.center.dy,
3955 );
3956
3957 final bool showStopIndicator =
3958 (textDirection == TextDirection.ltr)
3959 ? thumbCenter.dx < stopIndicatorOffset.dx
3960 : thumbCenter.dx > stopIndicatorOffset.dx;
3961 if (showStopIndicator && !isDiscrete) {
3962 final Rect stopIndicatorRect = Rect.fromCircle(
3963 center: stopIndicatorOffset,
3964 radius: stopIndicatorRadius,
3965 );
3966 context.canvas.drawCircle(stopIndicatorRect.center, stopIndicatorRadius, activePaint);
3967 }
3968 }
3969
3970 @override
3971 bool get isRounded => true;
3972}
3973
3974/// The rounded rectangle shape of a [Slider]'s value indicator.
3975///
3976/// If the [SliderThemeData.valueIndicatorColor] is null, then the shape uses the [ColorScheme.inverseSurface]
3977/// color to draw the value indicator.
3978///
3979/// If the [SliderThemeData.valueIndicatorTextStyle] is null, then the indicator label text style
3980/// defaults to [TextTheme.labelMedium] with the color set to [ColorScheme.onInverseSurface]. If the
3981/// [ThemeData.useMaterial3] is set to false, then the indicator label text style defaults to
3982/// [TextTheme.bodyLarge] with the color set to [ColorScheme.onInverseSurface].
3983///
3984/// If the [SliderThemeData.valueIndicatorStrokeColor] is provided, then the value indicator is drawn with a
3985/// stroke border with the color provided.
3986///
3987/// This is the default value indicator shape for [Slider]. If [ThemeData.useMaterial3] is false,
3988/// then the default value indicator shape is [RectangularSliderValueIndicatorShape].
3989///
3990/// See also:
3991///
3992/// * [Slider], which includes a value indicator defined by this shape.
3993/// * [SliderTheme], which can be used to configure the slider value indicator
3994/// of all sliders in a widget subtree.
3995class RoundedRectSliderValueIndicatorShape extends SliderComponentShape {
3996 /// Create a slider value indicator that resembles a rounded rectangle.
3997 const RoundedRectSliderValueIndicatorShape();
3998
3999 static const _RoundedRectSliderValueIndicatorPathPainter _pathPainter =
4000 _RoundedRectSliderValueIndicatorPathPainter();
4001
4002 @override
4003 Size getPreferredSize(
4004 bool isEnabled,
4005 bool isDiscrete, {
4006 TextPainter? labelPainter,
4007 double? textScaleFactor,
4008 }) {
4009 assert(labelPainter != null);
4010 assert(textScaleFactor != null && textScaleFactor >= 0);
4011 return _pathPainter.getPreferredSize(labelPainter!, textScaleFactor!);
4012 }
4013
4014 @override
4015 void paint(
4016 PaintingContext context,
4017 Offset center, {
4018 required Animation<double> activationAnimation,
4019 required Animation<double> enableAnimation,
4020 required bool isDiscrete,
4021 required TextPainter labelPainter,
4022 required RenderBox parentBox,
4023 required SliderThemeData sliderTheme,
4024 required TextDirection textDirection,
4025 required double value,
4026 required double textScaleFactor,
4027 required Size sizeWithOverflow,
4028 }) {
4029 final Canvas canvas = context.canvas;
4030 final double scale = activationAnimation.value;
4031 _pathPainter.paint(
4032 parentBox: parentBox,
4033 canvas: canvas,
4034 center: center,
4035 scale: scale,
4036 labelPainter: labelPainter,
4037 textScaleFactor: textScaleFactor,
4038 sizeWithOverflow: sizeWithOverflow,
4039 backgroundPaintColor: sliderTheme.valueIndicatorColor!,
4040 strokePaintColor: sliderTheme.valueIndicatorStrokeColor,
4041 );
4042 }
4043}
4044
4045class _RoundedRectSliderValueIndicatorPathPainter {
4046 const _RoundedRectSliderValueIndicatorPathPainter();
4047
4048 static const double _labelPadding = 10.0;
4049 static const double _preferredHeight = 32.0;
4050 static const double _minLabelWidth = 16.0;
4051 static const double _rectYOffset = 10.0;
4052 static const double _bottomTipYOffset = 16.0;
4053 static const double _preferredHalfHeight = _preferredHeight / 2;
4054
4055 Size getPreferredSize(TextPainter labelPainter, double textScaleFactor) {
4056 final double width =
4057 math.max(_minLabelWidth, labelPainter.width) + (_labelPadding * 2) * textScaleFactor;
4058 return Size(width, _preferredHeight * textScaleFactor);
4059 }
4060
4061 double getHorizontalShift({
4062 required RenderBox parentBox,
4063 required Offset center,
4064 required TextPainter labelPainter,
4065 required double textScaleFactor,
4066 required Size sizeWithOverflow,
4067 required double scale,
4068 }) {
4069 assert(!sizeWithOverflow.isEmpty);
4070
4071 const double edgePadding = 8.0;
4072 final double rectangleWidth = _upperRectangleWidth(labelPainter, scale);
4073
4074 /// Value indicator draws on the Overlay and by using the global Offset
4075 /// we are making sure we use the bounds of the Overlay instead of the Slider.
4076 final Offset globalCenter = parentBox.localToGlobal(center);
4077
4078 // The rectangle must be shifted towards the center so that it minimizes the
4079 // chance of it rendering outside the bounds of the render box. If the shift
4080 // is negative, then the lobe is shifted from right to left, and if it is
4081 // positive, then the lobe is shifted from left to right.
4082 final double overflowLeft = math.max(0, rectangleWidth / 2 - globalCenter.dx + edgePadding);
4083 final double overflowRight = math.max(
4084 0,
4085 rectangleWidth / 2 - (sizeWithOverflow.width - globalCenter.dx - edgePadding),
4086 );
4087
4088 if (rectangleWidth < sizeWithOverflow.width) {
4089 return overflowLeft - overflowRight;
4090 } else if (overflowLeft - overflowRight > 0) {
4091 return overflowLeft - (edgePadding * textScaleFactor);
4092 } else {
4093 return -overflowRight + (edgePadding * textScaleFactor);
4094 }
4095 }
4096
4097 double _upperRectangleWidth(TextPainter labelPainter, double scale) {
4098 final double unscaledWidth = math.max(_minLabelWidth, labelPainter.width) + (_labelPadding * 2);
4099 return unscaledWidth * scale;
4100 }
4101
4102 void paint({
4103 required RenderBox parentBox,
4104 required Canvas canvas,
4105 required Offset center,
4106 required double scale,
4107 required TextPainter labelPainter,
4108 required double textScaleFactor,
4109 required Size sizeWithOverflow,
4110 required Color backgroundPaintColor,
4111 Color? strokePaintColor,
4112 }) {
4113 if (scale == 0.0) {
4114 // Zero scale essentially means "do not draw anything", so it's safe to just return.
4115 return;
4116 }
4117 assert(!sizeWithOverflow.isEmpty);
4118
4119 final double rectangleWidth = _upperRectangleWidth(labelPainter, scale);
4120 final double horizontalShift = getHorizontalShift(
4121 parentBox: parentBox,
4122 center: center,
4123 labelPainter: labelPainter,
4124 textScaleFactor: textScaleFactor,
4125 sizeWithOverflow: sizeWithOverflow,
4126 scale: scale,
4127 );
4128
4129 final Rect upperRect = Rect.fromLTWH(
4130 -rectangleWidth / 2 + horizontalShift,
4131 -_rectYOffset - _preferredHeight,
4132 rectangleWidth,
4133 _preferredHeight,
4134 );
4135
4136 final Paint fillPaint = Paint()..color = backgroundPaintColor;
4137
4138 canvas.save();
4139 // Prepare the canvas for the base of the tooltip, which is relative to the
4140 // center of the thumb.
4141 canvas.translate(center.dx, center.dy - _bottomTipYOffset);
4142 canvas.scale(scale, scale);
4143
4144 final RRect rrect = RRect.fromRectAndRadius(upperRect, Radius.circular(upperRect.height / 2));
4145 if (strokePaintColor != null) {
4146 final Paint strokePaint =
4147 Paint()
4148 ..color = strokePaintColor
4149 ..strokeWidth = 1.0
4150 ..style = PaintingStyle.stroke;
4151 canvas.drawRRect(rrect, strokePaint);
4152 }
4153
4154 canvas.drawRRect(rrect, fillPaint);
4155
4156 // The label text is centered within the value indicator.
4157 final double bottomTipToUpperRectTranslateY = -_preferredHalfHeight / 2 - upperRect.height;
4158 canvas.translate(0, bottomTipToUpperRectTranslateY);
4159 final Offset boxCenter = Offset(horizontalShift, upperRect.height / 2.3);
4160 final Offset halfLabelPainterOffset = Offset(labelPainter.width / 2, labelPainter.height / 2);
4161 final Offset labelOffset = boxCenter - halfLabelPainterOffset;
4162 labelPainter.paint(canvas, labelOffset);
4163 canvas.restore();
4164 }
4165}
4166

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com