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 'package:flutter/material.dart'; |
6 | /// @docImport 'package:flutter/scheduler.dart'; |
7 | library; |
8 | |
9 | import 'package:collection/collection.dart' ; |
10 | import 'package:flutter/foundation.dart'; |
11 | import 'package:flutter/rendering.dart'; |
12 | import 'package:flutter/services.dart'; |
13 | |
14 | // Examples can assume: |
15 | // late BuildContext context; |
16 | // late Set states; |
17 | |
18 | /// This class allows [WidgetState] enum values to be combined |
19 | /// using [WidgetStateOperators]. |
20 | /// |
21 | /// A [Map] with [WidgetStatesConstraint] objects as keys can be used |
22 | /// in the [WidgetStateProperty.fromMap] constructor to resolve to |
23 | /// one of its values, based on the first key that [isSatisfiedBy] |
24 | /// the current set of states. |
25 | /// |
26 | /// {@macro flutter.widgets.WidgetStateMap} |
27 | abstract interface class WidgetStatesConstraint { |
28 | /// Whether the provided [states] satisfy this object's criteria. |
29 | /// |
30 | /// If the constraint is a single [WidgetState] object, |
31 | /// it's satisfied by the set if the set contains the object. |
32 | /// |
33 | /// The constraint can also be created using one or more operators, for example: |
34 | /// |
35 | /// {@template flutter.widgets.WidgetStatesConstraint.isSatisfiedBy} |
36 | /// ```dart |
37 | /// final WidgetStatesConstraint constraint = WidgetState.focused | WidgetState.hovered; |
38 | /// ``` |
39 | /// |
40 | /// In the above case, `constraint.isSatisfiedBy(states)` is equivalent to: |
41 | /// |
42 | /// ```dart |
43 | /// states.contains(WidgetState.focused) || states.contains(WidgetState.hovered); |
44 | /// ``` |
45 | /// {@endtemplate} |
46 | bool isSatisfiedBy(Set<WidgetState> states); |
47 | } |
48 | |
49 | @immutable |
50 | sealed class _WidgetStateCombo implements WidgetStatesConstraint { |
51 | const _WidgetStateCombo(this.first, this.second); |
52 | |
53 | final WidgetStatesConstraint first; |
54 | final WidgetStatesConstraint second; |
55 | |
56 | @override |
57 | // ignore: hash_and_equals, since == is defined in subclasses |
58 | int get hashCode => Object.hash(first, second); |
59 | } |
60 | |
61 | class _WidgetStateAnd extends _WidgetStateCombo { |
62 | const _WidgetStateAnd(super.first, super.second); |
63 | |
64 | @override |
65 | bool isSatisfiedBy(Set<WidgetState> states) { |
66 | return first.isSatisfiedBy(states) && second.isSatisfiedBy(states); |
67 | } |
68 | |
69 | @override |
70 | // ignore: hash_and_equals, hashCode is defined in the sealed super-class |
71 | bool operator ==(Object other) { |
72 | return other is _WidgetStateAnd |
73 | && other.first == first |
74 | && other.second == second; |
75 | } |
76 | |
77 | @override |
78 | String toString() => '( $first & $second)' ; |
79 | } |
80 | |
81 | class _WidgetStateOr extends _WidgetStateCombo { |
82 | const _WidgetStateOr(super.first, super.second); |
83 | |
84 | @override |
85 | bool isSatisfiedBy(Set<WidgetState> states) { |
86 | return first.isSatisfiedBy(states) || second.isSatisfiedBy(states); |
87 | } |
88 | |
89 | @override |
90 | // ignore: hash_and_equals, hashCode is defined in the sealed super-class |
91 | bool operator ==(Object other) { |
92 | return other is _WidgetStateOr |
93 | && other.first == first |
94 | && other.second == second; |
95 | } |
96 | |
97 | @override |
98 | String toString() => '( $first | $second)' ; |
99 | } |
100 | |
101 | @immutable |
102 | class _WidgetStateNot implements WidgetStatesConstraint { |
103 | const _WidgetStateNot(this.value); |
104 | |
105 | final WidgetStatesConstraint value; |
106 | |
107 | @override |
108 | bool isSatisfiedBy(Set<WidgetState> states) => !value.isSatisfiedBy(states); |
109 | |
110 | @override |
111 | bool operator ==(Object other) { |
112 | return other is _WidgetStateNot && other.value == value; |
113 | } |
114 | |
115 | @override |
116 | int get hashCode => value.hashCode; |
117 | |
118 | @override |
119 | String toString() => '~ $value' ; |
120 | } |
121 | |
122 | /// These operators can be used inside a [WidgetStateMap] to combine states |
123 | /// and find a match. |
124 | /// |
125 | /// Example: |
126 | /// |
127 | /// {@macro flutter.widgets.WidgetStatesConstraint.isSatisfiedBy} |
128 | /// |
129 | /// Since enums can't extend other classes, [WidgetState] instead `implements` |
130 | /// the [WidgetStatesConstraint] interface. This `extension` ensures that |
131 | /// the operators can be used without being directly inherited. |
132 | extension WidgetStateOperators on WidgetStatesConstraint { |
133 | /// Combines two [WidgetStatesConstraint] values using logical "and". |
134 | WidgetStatesConstraint operator &(WidgetStatesConstraint other) => _WidgetStateAnd(this, other); |
135 | |
136 | /// Combines two [WidgetStatesConstraint] values using logical "or". |
137 | WidgetStatesConstraint operator |(WidgetStatesConstraint other) => _WidgetStateOr(this, other); |
138 | |
139 | /// Takes a [WidgetStatesConstraint] and applies the logical "not". |
140 | WidgetStatesConstraint operator ~() => _WidgetStateNot(this); |
141 | } |
142 | |
143 | // A private class, used to create [WidgetState.any]. |
144 | class _AnyWidgetStates implements WidgetStatesConstraint { |
145 | const _AnyWidgetStates(); |
146 | |
147 | @override |
148 | bool isSatisfiedBy(Set<WidgetState> states) => true; |
149 | |
150 | @override |
151 | String toString() => 'WidgetState.any' ; |
152 | } |
153 | |
154 | /// Interactive states that some of the widgets can take on when receiving input |
155 | /// from the user. |
156 | /// |
157 | /// States are defined by https://material.io/design/interaction/states.html#usage, |
158 | /// but are not limited to the Material design system or library. |
159 | /// |
160 | /// Some widgets track their current state in a `Set<WidgetState>`. |
161 | /// |
162 | /// See also: |
163 | /// |
164 | /// * [MaterialState], the Material specific version of `WidgetState`. |
165 | /// * [WidgetStateProperty], an interface for objects that "resolve" to |
166 | /// different values depending on a widget's state. |
167 | /// {@template flutter.widgets.WidgetStateProperty.implementations} |
168 | /// * [WidgetStateColor], a [Color] that implements `WidgetStateProperty` |
169 | /// which is used in APIs that need to accept either a [Color] or a |
170 | /// `WidgetStateProperty<Color>`. |
171 | /// * [WidgetStateMouseCursor], a [MouseCursor] that implements |
172 | /// `WidgetStateProperty` which is used in APIs that need to accept either |
173 | /// a [MouseCursor] or a [WidgetStateProperty<MouseCursor>]. |
174 | /// * [WidgetStateOutlinedBorder], an [OutlinedBorder] that implements |
175 | /// `WidgetStateProperty` which is used in APIs that need to accept either |
176 | /// an [OutlinedBorder] or a [WidgetStateProperty<OutlinedBorder>]. |
177 | /// * [WidgetStateBorderSide], a [BorderSide] that implements |
178 | /// `WidgetStateProperty` which is used in APIs that need to accept either |
179 | /// a [BorderSide] or a [WidgetStateProperty<BorderSide>]. |
180 | /// * [WidgetStateTextStyle], a [TextStyle] that implements |
181 | /// `WidgetStateProperty` which is used in APIs that need to accept either |
182 | /// a [TextStyle] or a [WidgetStateProperty<TextStyle>]. |
183 | /// {@endtemplate} |
184 | enum WidgetState implements WidgetStatesConstraint { |
185 | /// The state when the user drags their mouse cursor over the given widget. |
186 | /// |
187 | /// See: https://material.io/design/interaction/states.html#hover. |
188 | hovered, |
189 | |
190 | /// The state when the user navigates with the keyboard to a given widget. |
191 | /// |
192 | /// This can also sometimes be triggered when a widget is tapped. For example, |
193 | /// when a [TextField] is tapped, it becomes [focused]. |
194 | /// |
195 | /// See: https://material.io/design/interaction/states.html#focus. |
196 | focused, |
197 | |
198 | /// The state when the user is actively pressing down on the given widget. |
199 | /// |
200 | /// See: https://material.io/design/interaction/states.html#pressed. |
201 | pressed, |
202 | |
203 | /// The state when this widget is being dragged from one place to another by |
204 | /// the user. |
205 | /// |
206 | /// https://material.io/design/interaction/states.html#dragged. |
207 | dragged, |
208 | |
209 | /// The state when this item has been selected. |
210 | /// |
211 | /// This applies to things that can be toggled (such as chips and checkboxes) |
212 | /// and things that are selected from a set of options (such as tabs and radio buttons). |
213 | /// |
214 | /// See: https://material.io/design/interaction/states.html#selected. |
215 | selected, |
216 | |
217 | /// The state when this widget overlaps the content of a scrollable below. |
218 | /// |
219 | /// Used by [AppBar] to indicate that the primary scrollable's |
220 | /// content has scrolled up and behind the app bar. |
221 | scrolledUnder, |
222 | |
223 | /// The state when this widget is disabled and cannot be interacted with. |
224 | /// |
225 | /// Disabled widgets should not respond to hover, focus, press, or drag |
226 | /// interactions. |
227 | /// |
228 | /// See: https://material.io/design/interaction/states.html#disabled. |
229 | disabled, |
230 | |
231 | /// The state when the widget has entered some form of invalid state. |
232 | /// |
233 | /// See https://material.io/design/interaction/states.html#usage. |
234 | error; |
235 | |
236 | /// {@template flutter.widgets.WidgetState.any} |
237 | /// To prevent a situation where each [WidgetStatesConstraint] |
238 | /// isn't satisfied by the given set of states, consier adding |
239 | /// [WidgetState.any] as the final [WidgetStateMap] key. |
240 | /// {@endtemplate} |
241 | static const WidgetStatesConstraint any = _AnyWidgetStates(); |
242 | |
243 | @override |
244 | bool isSatisfiedBy(Set<WidgetState> states) => states.contains(this); |
245 | } |
246 | |
247 | /// Signature for the function that returns a value of type `T` based on a given |
248 | /// set of states. |
249 | typedef WidgetPropertyResolver<T> = T Function(Set<WidgetState> states); |
250 | |
251 | /// Defines a [Color] that is also a [WidgetStateProperty]. |
252 | /// |
253 | /// This class exists to enable widgets with [Color] valued properties |
254 | /// to also accept [WidgetStateProperty<Color>] values. A widget |
255 | /// state color property represents a color which depends on |
256 | /// a widget's "interactive state". This state is represented as a |
257 | /// [Set] of [WidgetState]s, like [WidgetState.pressed], |
258 | /// [WidgetState.focused] and [WidgetState.hovered]. |
259 | /// |
260 | /// [WidgetStateColor] should only be used with widgets that document |
261 | /// their support, like [TimePickerThemeData.dayPeriodColor]. |
262 | /// |
263 | /// To use a [WidgetStateColor], you can either: |
264 | /// 1. Create a subclass of [WidgetStateColor] and implement the abstract `resolve` method. |
265 | /// 2. Use [WidgetStateColor.resolveWith] and pass in a callback that |
266 | /// will be used to resolve the color in the given states. |
267 | /// 3. Use [WidgetStateColor.fromMap] to assign a value using a [WidgetStateMap]. |
268 | /// |
269 | /// If a [WidgetStateColor] is used for a property or a parameter that doesn't |
270 | /// support resolving [WidgetStateProperty<Color>]s, then its default color |
271 | /// value will be used for all states. |
272 | /// |
273 | /// To define a `const` [WidgetStateColor], you'll need to extend |
274 | /// [WidgetStateColor] and override its [resolve] method. You'll also need |
275 | /// to provide a `defaultValue` to the super constructor, so that we can know |
276 | /// at compile-time what its default color is. |
277 | /// |
278 | /// {@tool snippet} |
279 | /// |
280 | /// This example defines a [WidgetStateColor] with a const constructor. |
281 | /// |
282 | /// ```dart |
283 | /// class MyColor extends WidgetStateColor { |
284 | /// const MyColor() : super(_defaultColor); |
285 | /// |
286 | /// static const int _defaultColor = 0xcafefeed; |
287 | /// static const int _pressedColor = 0xdeadbeef; |
288 | /// |
289 | /// @override |
290 | /// Color resolve(Set<WidgetState> states) { |
291 | /// if (states.contains(WidgetState.pressed)) { |
292 | /// return const Color(_pressedColor); |
293 | /// } |
294 | /// return const Color(_defaultColor); |
295 | /// } |
296 | /// } |
297 | /// ``` |
298 | /// {@end-tool} |
299 | /// |
300 | /// See also: |
301 | /// |
302 | /// * [MaterialStateColor], the Material specific version of `WidgetStateColor`. |
303 | abstract class WidgetStateColor extends Color implements WidgetStateProperty<Color> { |
304 | /// Abstract const constructor. This constructor enables subclasses to provide |
305 | /// const constructors so that they can be used in const expressions. |
306 | const WidgetStateColor(super.defaultValue); |
307 | |
308 | /// Creates a [WidgetStateColor] from a [WidgetPropertyResolver<Color>] |
309 | /// callback function. |
310 | /// |
311 | /// If used as a regular color, the color resolved in the default state (the |
312 | /// empty set of states) will be used. |
313 | /// |
314 | /// The given callback parameter must return a non-null color in the default |
315 | /// state. |
316 | factory WidgetStateColor.resolveWith(WidgetPropertyResolver<Color> callback) = _WidgetStateColor; |
317 | |
318 | /// Creates a [WidgetStateColor] from a [WidgetStateMap<Color>]. |
319 | /// |
320 | /// {@macro flutter.widgets.WidgetStateProperty.fromMap} |
321 | /// |
322 | /// If used as a regular color, the first key that matches an empty |
323 | /// [Set] of [WidgetState]s will be selected. |
324 | /// |
325 | /// {@macro flutter.widgets.WidgetState.any} |
326 | const factory WidgetStateColor.fromMap(WidgetStateMap<Color> map) = _WidgetStateColorMapper; |
327 | |
328 | /// Returns a [Color] that's to be used when a component is in the specified |
329 | /// state. |
330 | @override |
331 | Color resolve(Set<WidgetState> states); |
332 | |
333 | /// A constant whose value is transparent for all states. |
334 | static const WidgetStateColor transparent = _WidgetStateColorTransparent(); |
335 | } |
336 | |
337 | class _WidgetStateColor extends WidgetStateColor { |
338 | _WidgetStateColor(this._resolve) : super(_resolve(_defaultStates).value); |
339 | |
340 | final WidgetPropertyResolver<Color> _resolve; |
341 | |
342 | static const Set<WidgetState> _defaultStates = <WidgetState>{}; |
343 | |
344 | @override |
345 | Color resolve(Set<WidgetState> states) => _resolve(states); |
346 | } |
347 | |
348 | class _WidgetStateColorTransparent extends WidgetStateColor { |
349 | const _WidgetStateColorTransparent() : super(0x00000000); |
350 | |
351 | @override |
352 | Color resolve(Set<WidgetState> states) => const Color(0x00000000); |
353 | } |
354 | |
355 | @immutable |
356 | class _WidgetStateColorMapper extends _WidgetStateColorTransparent { |
357 | const _WidgetStateColorMapper(this.map); |
358 | |
359 | final WidgetStateMap<Color> map; |
360 | |
361 | _WidgetStateMapper<Color> get _mapper => _WidgetStateMapper<Color>(map); |
362 | |
363 | @override |
364 | Color resolve(Set<WidgetState> states) => _mapper.resolve(states); |
365 | |
366 | @override |
367 | bool operator ==(Object other) { |
368 | return other is _WidgetStateColorMapper && other.map == map; |
369 | } |
370 | |
371 | @override |
372 | int get hashCode => map.hashCode; |
373 | } |
374 | |
375 | /// Defines a [MouseCursor] whose value depends on a set of [WidgetState]s which |
376 | /// represent the interactive state of a component. |
377 | /// |
378 | /// This kind of [MouseCursor] is useful when the set of interactive |
379 | /// actions a widget supports varies with its state. For example, a |
380 | /// mouse pointer hovering over a disabled [ListTile] should not |
381 | /// display [SystemMouseCursors.click], since a disabled list tile |
382 | /// doesn't respond to mouse clicks. [ListTile]'s default mouse cursor |
383 | /// is a [WidgetStateMouseCursor.clickable], which resolves to |
384 | /// [SystemMouseCursors.basic] when the button is disabled. |
385 | /// |
386 | /// To use a [WidgetStateMouseCursor], you should create a subclass of |
387 | /// [WidgetStateMouseCursor] and implement the abstract `resolve` method. |
388 | /// |
389 | /// {@tool dartpad} |
390 | /// This example defines a mouse cursor that resolves to |
391 | /// [SystemMouseCursors.forbidden] when its widget is disabled. |
392 | /// |
393 | /// ** See code in examples/api/lib/widgets/widget_state/widget_state_mouse_cursor.0.dart ** |
394 | /// {@end-tool} |
395 | /// |
396 | /// This class should only be used for parameters which are documented to take |
397 | /// [WidgetStateMouseCursor], otherwise only the default state will be used. |
398 | /// |
399 | /// See also: |
400 | /// |
401 | /// * [MaterialStateMouseCursor], the Material specific version of |
402 | /// `WidgetStateMouseCursor`. |
403 | /// * [MouseCursor] for introduction on the mouse cursor system. |
404 | /// * [SystemMouseCursors], which defines cursors that are supported by |
405 | /// native platforms. |
406 | abstract class WidgetStateMouseCursor extends MouseCursor implements WidgetStateProperty<MouseCursor> { |
407 | /// Abstract const constructor. This constructor enables subclasses to provide |
408 | /// const constructors so that they can be used in const expressions. |
409 | const WidgetStateMouseCursor(); |
410 | |
411 | @protected |
412 | @override |
413 | MouseCursorSession createSession(int device) { |
414 | return resolve(<WidgetState>{}).createSession(device); |
415 | } |
416 | |
417 | /// Returns a [MouseCursor] that's to be used when a component is in the |
418 | /// specified state. |
419 | @override |
420 | MouseCursor resolve(Set<WidgetState> states); |
421 | |
422 | /// A mouse cursor for clickable widgets, which resolves differently when the |
423 | /// widget is disabled. |
424 | /// |
425 | /// By default this cursor resolves to [SystemMouseCursors.click]. If the widget is |
426 | /// disabled, the cursor resolves to [SystemMouseCursors.basic]. |
427 | /// |
428 | /// This cursor is the default for many widgets. |
429 | static const WidgetStateMouseCursor clickable = _EnabledAndDisabledMouseCursor( |
430 | enabledCursor: SystemMouseCursors.click, |
431 | disabledCursor: SystemMouseCursors.basic, |
432 | name: 'clickable' , |
433 | ); |
434 | |
435 | /// A mouse cursor for widgets related to text, which resolves differently |
436 | /// when the widget is disabled. |
437 | /// |
438 | /// By default this cursor resolves to [SystemMouseCursors.text]. If the widget is |
439 | /// disabled, the cursor resolves to [SystemMouseCursors.basic]. |
440 | /// |
441 | /// This cursor is the default for many widgets. |
442 | static const WidgetStateMouseCursor textable = _EnabledAndDisabledMouseCursor( |
443 | enabledCursor: SystemMouseCursors.text, |
444 | disabledCursor: SystemMouseCursors.basic, |
445 | name: 'textable' , |
446 | ); |
447 | } |
448 | |
449 | class _EnabledAndDisabledMouseCursor extends WidgetStateMouseCursor { |
450 | const _EnabledAndDisabledMouseCursor({ |
451 | required this.enabledCursor, |
452 | required this.disabledCursor, |
453 | required this.name, |
454 | }); |
455 | |
456 | final MouseCursor enabledCursor; |
457 | final MouseCursor disabledCursor; |
458 | final String name; |
459 | |
460 | @override |
461 | MouseCursor resolve(Set<WidgetState> states) { |
462 | if (states.contains(WidgetState.disabled)) { |
463 | return disabledCursor; |
464 | } |
465 | return enabledCursor; |
466 | } |
467 | |
468 | @override |
469 | String get debugDescription => 'WidgetStateMouseCursor( $name)' ; |
470 | } |
471 | |
472 | /// Defines a [BorderSide] whose value depends on a set of [WidgetState]s |
473 | /// which represent the interactive state of a component. |
474 | /// |
475 | /// To use a [WidgetStateBorderSide], you should create a subclass of a |
476 | /// [WidgetStateBorderSide] and override the abstract `resolve` method. |
477 | /// |
478 | /// This class enables existing widget implementations with [BorderSide] |
479 | /// properties to be extended to also effectively support `WidgetStateProperty<BorderSide>` |
480 | /// property values. [WidgetStateBorderSide] should only be used with widgets that document |
481 | /// their support, like [ActionChip.side]. |
482 | /// |
483 | /// {@tool dartpad} |
484 | /// This example defines a [WidgetStateBorderSide] which resolves to different |
485 | /// border colors depending on how the user interacts with it. |
486 | /// |
487 | /// ** See code in examples/api/lib/widgets/widget_state/widget_state_border_side.0.dart ** |
488 | /// {@end-tool} |
489 | /// |
490 | /// This class should only be used for parameters which are documented to take |
491 | /// [WidgetStateBorderSide], otherwise only the default state will be used. |
492 | /// |
493 | /// See also: |
494 | /// |
495 | /// * [MaterialStateBorderSide], the Material specific version of |
496 | /// `WidgetStateBorderSide`. |
497 | abstract class WidgetStateBorderSide extends BorderSide implements WidgetStateProperty<BorderSide?> { |
498 | /// Abstract const constructor. This constructor enables subclasses to provide |
499 | /// const constructors so that they can be used in const expressions. |
500 | const WidgetStateBorderSide(); |
501 | |
502 | /// Creates a [WidgetStateBorderSide] from a |
503 | /// [WidgetPropertyResolver<BorderSide?>] callback function. |
504 | /// |
505 | /// If used as a regular [BorderSide], the border resolved in the default state |
506 | /// (the empty set of states) will be used. |
507 | /// |
508 | /// Usage: |
509 | /// |
510 | /// ```dart |
511 | /// ChipTheme( |
512 | /// data: Theme.of(context).chipTheme.copyWith( |
513 | /// side: WidgetStateBorderSide.resolveWith((Set<WidgetState> states) { |
514 | /// if (states.contains(WidgetState.selected)) { |
515 | /// return const BorderSide(color: Colors.red); |
516 | /// } |
517 | /// return null; // Defer to default value on the theme or widget. |
518 | /// }), |
519 | /// ), |
520 | /// child: const Chip( |
521 | /// label: Text('Transceiver'), |
522 | /// ), |
523 | /// ), |
524 | /// ``` |
525 | /// |
526 | /// Alternatively: |
527 | /// |
528 | /// ```dart |
529 | /// Chip( |
530 | /// label: const Text('Transceiver'), |
531 | /// side: WidgetStateBorderSide.resolveWith((Set<WidgetState> states) { |
532 | /// if (states.contains(WidgetState.selected)) { |
533 | /// return const BorderSide(color: Colors.red); |
534 | /// } |
535 | /// return null; // Defer to default value on the theme or widget. |
536 | /// }), |
537 | /// ), |
538 | /// ``` |
539 | const factory WidgetStateBorderSide.resolveWith(WidgetPropertyResolver<BorderSide?> callback) = _WidgetStateBorderSide; |
540 | |
541 | /// Creates a [WidgetStateBorderSide] from a [WidgetStateMap]. |
542 | /// |
543 | /// {@macro flutter.widgets.WidgetStateProperty.fromMap} |
544 | /// |
545 | /// If used as a regular [BorderSide], the first key that matches an empty |
546 | /// [Set] of [WidgetState]s will be selected. |
547 | /// |
548 | /// Example: |
549 | /// |
550 | /// ```dart |
551 | /// const Chip( |
552 | /// label: Text('Transceiver'), |
553 | /// side: WidgetStateBorderSide.fromMap(<WidgetStatesConstraint, BorderSide?>{ |
554 | /// WidgetState.selected: BorderSide(color: Colors.red), |
555 | /// // returns null if not selected, deferring to default theme/widget value. |
556 | /// }), |
557 | /// ), |
558 | /// ``` |
559 | /// |
560 | /// {@macro flutter.widgets.WidgetState.any} |
561 | const factory WidgetStateBorderSide.fromMap(WidgetStateMap<BorderSide?> map) = _WidgetBorderSideMapper; |
562 | |
563 | /// Returns a [BorderSide] that's to be used when a Widget is in the |
564 | /// specified state. Return null to defer to the default value of the |
565 | /// widget or theme. |
566 | @override |
567 | BorderSide? resolve(Set<WidgetState> states); |
568 | |
569 | /// Linearly interpolate between two [WidgetStateProperty]s of [BorderSide]. |
570 | static WidgetStateProperty<BorderSide?>? lerp( |
571 | WidgetStateProperty<BorderSide?>? a, |
572 | WidgetStateProperty<BorderSide?>? b, |
573 | double t, |
574 | ) { |
575 | // Avoid creating a _LerpSides object for a common case. |
576 | if (a == null && b == null) { |
577 | return null; |
578 | } |
579 | return _LerpSides(a, b, t); |
580 | } |
581 | } |
582 | |
583 | class _LerpSides implements WidgetStateProperty<BorderSide?> { |
584 | const _LerpSides(this.a, this.b, this.t); |
585 | |
586 | final WidgetStateProperty<BorderSide?>? a; |
587 | final WidgetStateProperty<BorderSide?>? b; |
588 | final double t; |
589 | |
590 | @override |
591 | BorderSide? resolve(Set<WidgetState> states) { |
592 | final BorderSide? resolvedA = a?.resolve(states); |
593 | final BorderSide? resolvedB = b?.resolve(states); |
594 | if (resolvedA == null && resolvedB == null) { |
595 | return null; |
596 | } |
597 | if (resolvedA == null) { |
598 | return BorderSide.lerp(BorderSide(width: 0, color: resolvedB!.color.withAlpha(0)), resolvedB, t); |
599 | } |
600 | if (resolvedB == null) { |
601 | return BorderSide.lerp(resolvedA, BorderSide(width: 0, color: resolvedA.color.withAlpha(0)), t); |
602 | } |
603 | return BorderSide.lerp(resolvedA, resolvedB, t); |
604 | } |
605 | } |
606 | |
607 | class _WidgetStateBorderSide extends WidgetStateBorderSide { |
608 | const _WidgetStateBorderSide(this._resolve); |
609 | |
610 | final WidgetPropertyResolver<BorderSide?> _resolve; |
611 | |
612 | @override |
613 | BorderSide? resolve(Set<WidgetState> states) => _resolve(states); |
614 | } |
615 | |
616 | class _WidgetBorderSideMapper extends WidgetStateBorderSide { |
617 | const _WidgetBorderSideMapper(this.map); |
618 | |
619 | final WidgetStateMap<BorderSide?> map; |
620 | |
621 | _WidgetStateMapper<BorderSide?> get _mapper => _WidgetStateMapper<BorderSide?>(map); |
622 | |
623 | @override |
624 | BorderSide? resolve(Set<WidgetState> states) => _mapper.resolve(states); |
625 | |
626 | @override |
627 | bool operator ==(Object other) { |
628 | return other is _WidgetBorderSideMapper && other.map == map; |
629 | } |
630 | |
631 | @override |
632 | int get hashCode => map.hashCode; |
633 | } |
634 | |
635 | /// Defines an [OutlinedBorder] whose value depends on a set of [WidgetState]s |
636 | /// which represent the interactive state of a component. |
637 | /// |
638 | /// To use a [WidgetStateOutlinedBorder], you should create a subclass of an |
639 | /// [OutlinedBorder] and implement [WidgetStateOutlinedBorder]'s abstract |
640 | /// `resolve` method. |
641 | /// |
642 | /// {@tool dartpad} |
643 | /// This example defines a subclass of [RoundedRectangleBorder] and an |
644 | /// implementation of [WidgetStateOutlinedBorder], that resolves to |
645 | /// [RoundedRectangleBorder] when its widget is selected. |
646 | /// |
647 | /// ** See code in examples/api/lib/material/material_state/material_state_outlined_border.0.dart ** |
648 | /// {@end-tool} |
649 | /// |
650 | /// This class should only be used for parameters which are documented to take |
651 | /// [WidgetStateOutlinedBorder], otherwise only the default state will be used. |
652 | /// |
653 | /// See also: |
654 | /// |
655 | /// * [ShapeBorder] the base class for shape outlines. |
656 | /// * [MaterialStateOutlinedBorder], the Material specific version of |
657 | /// `WidgetStateOutlinedBorder`. |
658 | abstract class WidgetStateOutlinedBorder extends OutlinedBorder implements WidgetStateProperty<OutlinedBorder?> { |
659 | /// Abstract const constructor. This constructor enables subclasses to provide |
660 | /// const constructors so that they can be used in const expressions. |
661 | const WidgetStateOutlinedBorder(); |
662 | |
663 | /// Returns an [OutlinedBorder] that's to be used when a component is in the |
664 | /// specified state. Return null to defer to the default value of the widget |
665 | /// or theme. |
666 | @override |
667 | OutlinedBorder? resolve(Set<WidgetState> states); |
668 | } |
669 | |
670 | /// Defines a [TextStyle] that is also a [WidgetStateProperty]. |
671 | /// |
672 | /// This class exists to enable widgets with [TextStyle] valued properties |
673 | /// to also accept [WidgetStateProperty<TextStyle>] values. A widget |
674 | /// state text style property represents a text style which depends on |
675 | /// a widget's "interactive state". This state is represented as a |
676 | /// [Set] of [WidgetState]s, like [WidgetState.pressed], |
677 | /// [WidgetState.focused] and [WidgetState.hovered]. |
678 | /// |
679 | /// [WidgetStateTextStyle] should only be used with widgets that document |
680 | /// their support, like [InputDecoration.labelStyle]. |
681 | /// |
682 | /// To use a [WidgetStateTextStyle], you can either: |
683 | /// 1. Create a subclass of [WidgetStateTextStyle] and implement the abstract `resolve` method. |
684 | /// 2. Use [WidgetStateTextStyle.resolveWith] and pass in a callback that |
685 | /// will be used to resolve the text style in the given states. |
686 | /// 3. Use [WidgetStateTextStyle.fromMap] to assign a style using a [WidgetStateMap]. |
687 | /// |
688 | /// If a [WidgetStateTextStyle] is used for a property or a parameter that doesn't |
689 | /// support resolving [WidgetStateProperty<TextStyle>]s, then its default text style |
690 | /// value will be used for all states. |
691 | /// |
692 | /// To define a `const` [WidgetStateTextStyle], you'll need to extend |
693 | /// [WidgetStateTextStyle] and override its [resolve] method. |
694 | /// See also: |
695 | /// |
696 | /// * [MaterialStateTextStyle], the Material specific version of |
697 | /// `WidgetStateTextStyle`. |
698 | abstract class WidgetStateTextStyle extends TextStyle implements WidgetStateProperty<TextStyle> { |
699 | /// Abstract const constructor. This constructor enables subclasses to provide |
700 | /// const constructors so that they can be used in const expressions. |
701 | const WidgetStateTextStyle(); |
702 | |
703 | /// Creates a [WidgetStateTextStyle] from a [WidgetPropertyResolver<TextStyle>] |
704 | /// callback function. |
705 | /// |
706 | /// If used as a regular text style, the style resolved in the default state (the |
707 | /// empty set of states) will be used. |
708 | /// |
709 | /// The given callback parameter must return a non-null text style in the default |
710 | /// state. |
711 | const factory WidgetStateTextStyle.resolveWith(WidgetPropertyResolver<TextStyle> callback) = _WidgetStateTextStyle; |
712 | |
713 | /// Creates a [WidgetStateTextStyle] from a [WidgetStateMap]. |
714 | /// |
715 | /// {@macro flutter.widgets.WidgetStateProperty.fromMap} |
716 | /// |
717 | /// If used as a regular text style, the first key that matches an empty |
718 | /// [Set] of [WidgetState]s will be selected. |
719 | /// |
720 | /// {@macro flutter.widgets.WidgetState.any} |
721 | const factory WidgetStateTextStyle.fromMap(WidgetStateMap<TextStyle> map) = _WidgetTextStyleMapper; |
722 | |
723 | /// Returns a [TextStyle] that's to be used when a component is in the |
724 | /// specified state. |
725 | @override |
726 | TextStyle resolve(Set<WidgetState> states); |
727 | } |
728 | |
729 | class _WidgetStateTextStyle extends WidgetStateTextStyle { |
730 | const _WidgetStateTextStyle(this._resolve); |
731 | |
732 | final WidgetPropertyResolver<TextStyle> _resolve; |
733 | |
734 | @override |
735 | TextStyle resolve(Set<WidgetState> states) => _resolve(states); |
736 | } |
737 | |
738 | class _WidgetTextStyleMapper extends WidgetStateTextStyle { |
739 | const _WidgetTextStyleMapper(this.map); |
740 | |
741 | final WidgetStateMap<TextStyle> map; |
742 | |
743 | _WidgetStateMapper<TextStyle> get _mapper => _WidgetStateMapper<TextStyle>(map); |
744 | |
745 | @override |
746 | TextStyle resolve(Set<WidgetState> states) => _mapper.resolve(states); |
747 | |
748 | @override |
749 | bool operator ==(Object other) { |
750 | return other is _WidgetTextStyleMapper && other.map == map; |
751 | } |
752 | |
753 | @override |
754 | int get hashCode => map.hashCode; |
755 | } |
756 | |
757 | /// Interface for classes that [resolve] to a value of type `T` based |
758 | /// on a widget's interactive "state", which is defined as a set |
759 | /// of [WidgetState]s. |
760 | /// |
761 | /// Widget state properties represent values that depend on a widget's "state". |
762 | /// The state is encoded as a set of [WidgetState] values, like |
763 | /// [WidgetState.focused], [WidgetState.hovered], [WidgetState.pressed]. For |
764 | /// example the [InkWell.overlayColor] defines the color that fills the ink well |
765 | /// when it's pressed (the "splash color"), focused, or hovered. The [InkWell] |
766 | /// uses the overlay color's [resolve] method to compute the color for the |
767 | /// ink well's current state. |
768 | /// |
769 | /// [ButtonStyle], which is used to configure the appearance of |
770 | /// buttons like [TextButton], [ElevatedButton], and [OutlinedButton], |
771 | /// has many material state properties. The button widgets keep track |
772 | /// of their current material state and [resolve] the button style's |
773 | /// material state properties when their value is needed. |
774 | /// |
775 | /// {@tool dartpad} |
776 | /// This example shows how the default text and icon color |
777 | /// (the "foreground color") of a [TextButton] can be overridden with a |
778 | /// [WidgetStateProperty]. In this example, the button's text color will be |
779 | /// `Colors.blue` when the button is being pressed, hovered, or focused. |
780 | /// Otherwise, the text color will be `Colors.red`. |
781 | /// |
782 | /// ** See code in examples/api/lib/widgets/widget_state/widget_state_property.0.dart ** |
783 | /// {@end-tool} |
784 | /// |
785 | /// See also: |
786 | /// |
787 | /// * [MaterialStateProperty], the Material specific version of |
788 | /// `WidgetStateProperty`. |
789 | /// {@macro flutter.widgets.WidgetStateProperty.implementations} |
790 | abstract class WidgetStateProperty<T> { |
791 | /// This abstract constructor allows extending the class. |
792 | /// |
793 | /// [WidgetStateProperty] is designed as an interface, so this constructor |
794 | /// is only needed for backward compatibility. |
795 | WidgetStateProperty(); |
796 | |
797 | /// Creates a property that resolves using a [WidgetStateMap]. |
798 | /// |
799 | /// {@template flutter.widgets.WidgetStateProperty.fromMap} |
800 | /// This constructor's [resolve] method finds the first [MapEntry] whose |
801 | /// key is satisfied by the set of states, and returns its associated value. |
802 | /// {@endtemplate} |
803 | /// |
804 | /// Returns `null` if no keys match, or if [T] is non-nullable, |
805 | /// the method throws an [ArgumentError]. |
806 | /// |
807 | /// {@macro flutter.widgets.WidgetState.any} |
808 | /// |
809 | /// {@macro flutter.widgets.WidgetStateMap} |
810 | const factory WidgetStateProperty.fromMap(WidgetStateMap<T> map) = _WidgetStateMapper<T>; |
811 | |
812 | /// Resolves the value for the given set of states if `value` is a |
813 | /// [WidgetStateProperty], otherwise returns the value itself. |
814 | /// |
815 | /// This is useful for widgets that have parameters which can optionally be a |
816 | /// [WidgetStateProperty]. For example, [InkWell.mouseCursor] can be a |
817 | /// [MouseCursor] or a [WidgetStateProperty<MouseCursor>]. |
818 | static T resolveAs<T>(T value, Set<WidgetState> states) { |
819 | if (value is WidgetStateProperty<T>) { |
820 | final WidgetStateProperty<T> property = value; |
821 | return property.resolve(states); |
822 | } |
823 | return value; |
824 | } |
825 | |
826 | /// Convenience method for creating a [WidgetStateProperty] from a |
827 | /// [WidgetPropertyResolver] function alone. |
828 | static WidgetStateProperty<T> resolveWith<T>(WidgetPropertyResolver<T> callback) => _WidgetStatePropertyWith<T>(callback); |
829 | |
830 | /// Convenience method for creating a [WidgetStateProperty] that resolves |
831 | /// to a single value for all states. |
832 | /// |
833 | /// If you need a const value, use [WidgetStatePropertyAll] directly. |
834 | /// |
835 | // TODO(darrenaustin): Deprecate this when we have the ability to create |
836 | // a dart fix that will replace this with WidgetStatePropertyAll: |
837 | // https://github.com/dart-lang/sdk/issues/49056. |
838 | static WidgetStateProperty<T> all<T>(T value) => WidgetStatePropertyAll<T>(value); |
839 | |
840 | /// Linearly interpolate between two [WidgetStateProperty]s. |
841 | static WidgetStateProperty<T?>? lerp<T>( |
842 | WidgetStateProperty<T>? a, |
843 | WidgetStateProperty<T>? b, |
844 | double t, |
845 | T? Function(T?, T?, double) lerpFunction, |
846 | ) { |
847 | // Avoid creating a _LerpProperties object for a common case. |
848 | if (a == null && b == null) { |
849 | return null; |
850 | } |
851 | return _LerpProperties<T>(a, b, t, lerpFunction); |
852 | } |
853 | |
854 | /// Returns a value of type `T` that depends on [states]. |
855 | /// |
856 | /// Widgets like [TextButton] and [ElevatedButton] apply this method to their |
857 | /// current [WidgetState]s to compute colors and other visual parameters |
858 | /// at build time. |
859 | T resolve(Set<WidgetState> states); |
860 | } |
861 | |
862 | class _LerpProperties<T> implements WidgetStateProperty<T?> { |
863 | const _LerpProperties(this.a, this.b, this.t, this.lerpFunction); |
864 | |
865 | final WidgetStateProperty<T>? a; |
866 | final WidgetStateProperty<T>? b; |
867 | final double t; |
868 | final T? Function(T?, T?, double) lerpFunction; |
869 | |
870 | @override |
871 | T? resolve(Set<WidgetState> states) { |
872 | final T? resolvedA = a?.resolve(states); |
873 | final T? resolvedB = b?.resolve(states); |
874 | return lerpFunction(resolvedA, resolvedB, t); |
875 | } |
876 | } |
877 | |
878 | class _WidgetStatePropertyWith<T> implements WidgetStateProperty<T> { |
879 | _WidgetStatePropertyWith(this._resolve); |
880 | |
881 | final WidgetPropertyResolver<T> _resolve; |
882 | |
883 | @override |
884 | T resolve(Set<WidgetState> states) => _resolve(states); |
885 | } |
886 | |
887 | /// A [Map] used to resolve to a single value of type `T` based on |
888 | /// the current set of Widget states. |
889 | /// |
890 | /// {@template flutter.widgets.WidgetStateMap} |
891 | /// Example: |
892 | /// |
893 | /// ```dart |
894 | /// // This WidgetStateMap resolves to null if no keys match. |
895 | /// WidgetStateProperty<Color?>.fromMap(<WidgetStatesConstraint, Color?>{ |
896 | /// WidgetState.error: Colors.red, |
897 | /// WidgetState.hovered & WidgetState.focused: Colors.blueAccent, |
898 | /// WidgetState.focused: Colors.blue, |
899 | /// ~WidgetState.disabled: Colors.black, |
900 | /// }); |
901 | /// |
902 | /// // The same can be accomplished with a WidgetPropertyResolver, |
903 | /// // but it's more verbose: |
904 | /// WidgetStateProperty.resolveWith<Color?>((Set<WidgetState> states) { |
905 | /// if (states.contains(WidgetState.error)) { |
906 | /// return Colors.red; |
907 | /// } else if (states.contains(WidgetState.hovered) && states.contains(WidgetState.focused)) { |
908 | /// return Colors.blueAccent; |
909 | /// } else if (states.contains(WidgetState.focused)) { |
910 | /// return Colors.blue; |
911 | /// } else if (!states.contains(WidgetState.disabled)) { |
912 | /// return Colors.black; |
913 | /// } |
914 | /// return null; |
915 | /// }); |
916 | /// ``` |
917 | /// |
918 | /// A widget state combination can be stored in a variable, |
919 | /// and [WidgetState.any] can be used for non-nullable types to ensure |
920 | /// that there's a match: |
921 | /// |
922 | /// ```dart |
923 | /// final WidgetStatesConstraint selectedError = WidgetState.selected & WidgetState.error; |
924 | /// |
925 | /// final WidgetStateProperty<Color> color = WidgetStateProperty<Color>.fromMap( |
926 | /// <WidgetStatesConstraint, Color>{ |
927 | /// selectedError & WidgetState.hovered: Colors.redAccent, |
928 | /// selectedError: Colors.red, |
929 | /// WidgetState.any: Colors.black, |
930 | /// }, |
931 | /// ); |
932 | /// |
933 | /// // The (more verbose) WidgetPropertyResolver implementation: |
934 | /// final WidgetStateProperty<Color> colorResolveWith = WidgetStateProperty.resolveWith<Color>( |
935 | /// (Set<WidgetState> states) { |
936 | /// if (states.containsAll(<WidgetState>{WidgetState.selected, WidgetState.error})) { |
937 | /// if (states.contains(WidgetState.hovered)) { |
938 | /// return Colors.redAccent; |
939 | /// } |
940 | /// return Colors.red; |
941 | /// } |
942 | /// return Colors.black; |
943 | /// }, |
944 | /// ); |
945 | /// ``` |
946 | /// {@endtemplate} |
947 | typedef WidgetStateMap<T> = Map<WidgetStatesConstraint, T>; |
948 | |
949 | // A private class, used to create the [WidgetStateProperty.fromMap] constructor. |
950 | @immutable |
951 | class _WidgetStateMapper<T> implements WidgetStateProperty<T> { |
952 | const _WidgetStateMapper(this.map); |
953 | |
954 | final WidgetStateMap<T> map; |
955 | |
956 | @override |
957 | T resolve(Set<WidgetState> states) { |
958 | for (final MapEntry<WidgetStatesConstraint, T> entry in map.entries) { |
959 | if (entry.key.isSatisfiedBy(states)) { |
960 | return entry.value; |
961 | } |
962 | } |
963 | |
964 | try { |
965 | return null as T; |
966 | } on TypeError { |
967 | throw ArgumentError( |
968 | 'The current set of material states is $states.\n' |
969 | 'None of the provided map keys matched this set, ' |
970 | 'and the type " $T" is non-nullable.\n' |
971 | 'Consider using "WidgetStateProperty< $T?>.fromMap()", ' |
972 | 'or adding the "WidgetState.any" key to this map.' , |
973 | ); |
974 | } |
975 | } |
976 | |
977 | @override |
978 | bool operator ==(Object other) { |
979 | return other is _WidgetStateMapper && mapEquals(map, other.map); |
980 | } |
981 | |
982 | @override |
983 | int get hashCode => MapEquality<WidgetStatesConstraint, T>().hash(map); |
984 | |
985 | @override |
986 | String toString() => ' $map' ; |
987 | } |
988 | |
989 | /// Convenience class for creating a [WidgetStateProperty] that |
990 | /// resolves to the given value for all states. |
991 | /// |
992 | /// See also: |
993 | /// |
994 | /// * [MaterialStatePropertyAll], the Material specific version of |
995 | /// `WidgetStatePropertyAll`. |
996 | @immutable |
997 | class WidgetStatePropertyAll<T> implements WidgetStateProperty<T> { |
998 | |
999 | /// Constructs a [WidgetStateProperty] that always resolves to the given |
1000 | /// value. |
1001 | const WidgetStatePropertyAll(this.value); |
1002 | |
1003 | /// The value of the property that will be used for all states. |
1004 | final T value; |
1005 | |
1006 | @override |
1007 | T resolve(Set<WidgetState> states) => value; |
1008 | |
1009 | @override |
1010 | String toString() { |
1011 | if (value is double) { |
1012 | return 'WidgetStatePropertyAll( ${debugFormatDouble(value as double)})' ; |
1013 | } else { |
1014 | return 'WidgetStatePropertyAll( $value)' ; |
1015 | } |
1016 | } |
1017 | |
1018 | @override |
1019 | bool operator ==(Object other) { |
1020 | return other is WidgetStatePropertyAll<T> |
1021 | && other.runtimeType == runtimeType |
1022 | && other.value == value; |
1023 | } |
1024 | |
1025 | @override |
1026 | int get hashCode => value.hashCode; |
1027 | } |
1028 | |
1029 | /// Manages a set of [WidgetState]s and notifies listeners of changes. |
1030 | /// |
1031 | /// Used by widgets that expose their internal state for the sake of |
1032 | /// extensions that add support for additional states. See |
1033 | /// [TextButton] for an example. |
1034 | /// |
1035 | /// The controller's [value] is its current set of states. Listeners |
1036 | /// are notified whenever the [value] changes. The [value] should only be |
1037 | /// changed with [update]; it should not be modified directly. |
1038 | /// |
1039 | /// The controller's [value] represents the set of states that a |
1040 | /// widget's visual properties, typically [WidgetStateProperty] |
1041 | /// values, are resolved against. It is _not_ the intrinsic state of |
1042 | /// the widget. The widget is responsible for ensuring that the |
1043 | /// controller's [value] tracks its intrinsic state. For example one |
1044 | /// cannot request the keyboard focus for a widget by adding |
1045 | /// [WidgetState.focused] to its controller. When the widget gains the |
1046 | /// or loses the focus it will [update] its controller's [value] and |
1047 | /// notify listeners of the change. |
1048 | /// |
1049 | /// When calling `setState` in a [WidgetStatesController] listener, use the |
1050 | /// [SchedulerBinding.addPostFrameCallback] to delay the call to `setState` after |
1051 | /// the frame has been rendered. It's generally prudent to use the |
1052 | /// [SchedulerBinding.addPostFrameCallback] because some of the widgets that |
1053 | /// depend on [WidgetStatesController] may call [update] in their build method. |
1054 | /// In such cases, listener's that call `setState` - during the build phase - will cause |
1055 | /// an error. |
1056 | /// |
1057 | /// See also: |
1058 | /// |
1059 | /// * [MaterialStatesController], the Material specific version of |
1060 | /// `WidgetStatesController`. |
1061 | class WidgetStatesController extends ValueNotifier<Set<WidgetState>> { |
1062 | /// Creates a WidgetStatesController. |
1063 | WidgetStatesController([Set<WidgetState>? value]) : super(<WidgetState>{...?value}); |
1064 | |
1065 | /// Adds [state] to [value] if [add] is true, and removes it otherwise, |
1066 | /// and notifies listeners if [value] has changed. |
1067 | void update(WidgetState state, bool add) { |
1068 | final bool valueChanged = add ? value.add(state) : value.remove(state); |
1069 | if (valueChanged) { |
1070 | notifyListeners(); |
1071 | } |
1072 | } |
1073 | } |
1074 | |