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