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';
7library;
8
9import 'package:collection/collection.dart';
10import 'package:flutter/foundation.dart';
11import 'package:flutter/rendering.dart';
12import '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}
27abstract 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
50sealed 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
61class _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
81class _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
102class _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.
132extension 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].
144class _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}
184enum 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.
249typedef 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`.
303abstract 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
337class _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
348class _WidgetStateColorTransparent extends WidgetStateColor {
349 const _WidgetStateColorTransparent() : super(0x00000000);
350
351 @override
352 Color resolve(Set<WidgetState> states) => const Color(0x00000000);
353}
354
355@immutable
356class _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.
406abstract 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
449class _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`.
497abstract 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
583class _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
607class _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
616class _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`.
658abstract 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`.
698abstract 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
729class _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
738class _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}
790abstract 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
862class _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
878class _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}
947typedef WidgetStateMap<T> = Map<WidgetStatesConstraint, T>;
948
949// A private class, used to create the [WidgetStateProperty.fromMap] constructor.
950@immutable
951class _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
997class 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`.
1061class 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