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'; |
8 | library; |
9 | |
10 | import 'dart:math' as math; |
11 | import 'dart:ui' show Path, lerpDouble; |
12 | |
13 | import 'package:flutter/foundation.dart'; |
14 | import 'package:flutter/rendering.dart'; |
15 | import 'package:flutter/widgets.dart'; |
16 | |
17 | import 'colors.dart'; |
18 | import 'material_state.dart'; |
19 | import 'slider.dart'; |
20 | import '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. |
55 | class 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. |
121 | enum 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. |
144 | enum 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 |
228 | class 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. |
1156 | abstract 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. |
1276 | abstract 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. |
1352 | abstract 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. |
1452 | abstract 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. |
1547 | abstract 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. |
1657 | abstract 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. |
1729 | abstract 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. |
1823 | mixin 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 | ///  |
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. |
1901 | class 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 | ///  |
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. |
2034 | class 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 |
2179 | mixin 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 | ///  |
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. |
2253 | class 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 | ///  |
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. |
2361 | class 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 | ///  |
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. |
2482 | class 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 | ///  |
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. |
2564 | class 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. |
2625 | class _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. |
2655 | class _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 | ///  |
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. |
2689 | class 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 | ///  |
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. |
2801 | class 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. |
2919 | class 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 | ///  |
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. |
2970 | class 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 | ///  |
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. |
3032 | class 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 | |
3104 | class _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. |
3395 | typedef 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. |
3404 | typedef 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 |
3420 | class 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 |
3459 | class 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 | |
3492 | void _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. |
3511 | class 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 | |
3561 | class _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. |
3720 | class 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. |
3789 | class 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. |
3995 | class 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 | |
4045 | class _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 |
Definitions
- SliderTheme
- SliderTheme
- of
- wrap
- updateShouldNotify
- ShowValueIndicator
- Thumb
- SliderThemeData
- SliderThemeData
- fromPrimaryColors
- copyWith
- lerp
- hashCode
- ==
- debugFillProperties
- SliderComponentShape
- SliderComponentShape
- getPreferredSize
- paint
- SliderTickMarkShape
- SliderTickMarkShape
- getPreferredSize
- paint
- SliderTrackShape
- SliderTrackShape
- getPreferredRect
- paint
- isRounded
- RangeSliderThumbShape
- RangeSliderThumbShape
- getPreferredSize
- paint
- RangeSliderValueIndicatorShape
- RangeSliderValueIndicatorShape
- getPreferredSize
- getHorizontalShift
- paint
- RangeSliderTickMarkShape
- RangeSliderTickMarkShape
- getPreferredSize
- paint
- RangeSliderTrackShape
- RangeSliderTrackShape
- getPreferredRect
- paint
- isRounded
- BaseSliderTrackShape
- getPreferredRect
- isRounded
- RectangularSliderTrackShape
- RectangularSliderTrackShape
- paint
- RoundedRectSliderTrackShape
- RoundedRectSliderTrackShape
- paint
- isRounded
- BaseRangeSliderTrackShape
- getPreferredRect
- RectangularRangeSliderTrackShape
- RectangularRangeSliderTrackShape
- paint
- RoundedRectRangeSliderTrackShape
- RoundedRectRangeSliderTrackShape
- paint
- isRounded
- RoundSliderTickMarkShape
- RoundSliderTickMarkShape
- getPreferredSize
- paint
- RoundRangeSliderTickMarkShape
- RoundRangeSliderTickMarkShape
- getPreferredSize
- paint
- _EmptySliderTickMarkShape
- getPreferredSize
- paint
- _EmptySliderComponentShape
- getPreferredSize
- paint
- RoundSliderThumbShape
- RoundSliderThumbShape
- _disabledThumbRadius
- getPreferredSize
- paint
- RoundRangeSliderThumbShape
- RoundRangeSliderThumbShape
- _disabledThumbRadius
- getPreferredSize
- paint
- RoundSliderOverlayShape
- RoundSliderOverlayShape
- getPreferredSize
- paint
- PaddleSliderValueIndicatorShape
- PaddleSliderValueIndicatorShape
- getPreferredSize
- paint
- PaddleRangeSliderValueIndicatorShape
- PaddleRangeSliderValueIndicatorShape
- getPreferredSize
- getHorizontalShift
- paint
- _PaddleSliderValueIndicatorPathPainter
- _PaddleSliderValueIndicatorPathPainter
- getPreferredSize
- _addArc
- getHorizontalShift
- _getIdealOffset
- paint
- RangeValues
- RangeValues
- ==
- hashCode
- toString
- RangeLabels
- RangeLabels
- ==
- hashCode
- toString
- _debugDrawShadow
- DropSliderValueIndicatorShape
- DropSliderValueIndicatorShape
- getPreferredSize
- paint
- _DropSliderValueIndicatorPathPainter
- _DropSliderValueIndicatorPathPainter
- getPreferredSize
- getHorizontalShift
- _upperRectangleWidth
- _adjustBorderRadius
- paint
- HandleThumbShape
- HandleThumbShape
- getPreferredSize
- paint
- GappedSliderTrackShape
- GappedSliderTrackShape
- paint
- isRounded
- RoundedRectSliderValueIndicatorShape
- RoundedRectSliderValueIndicatorShape
- getPreferredSize
- paint
- _RoundedRectSliderValueIndicatorPathPainter
- _RoundedRectSliderValueIndicatorPathPainter
- getPreferredSize
- getHorizontalShift
- _upperRectangleWidth
Learn more about Flutter for embedded and desktop on industrialflutter.com