Skip to content

Commit 51ed348

Browse files
authored
Fix handling of iconSize and iconColor defaults for ButtonStyleButton subclasses. (flutter#143501)
## Description Adds defaults that use tokens to define default `iconSize` and `iconColor` values. Previously, the Material 3 token values for button icon sizes and colors were not being used as defaults when the `ButtonStyleButton.defaultStyleOf` function returned the default values. Adds tests to make sure appropriate `ButtonStyle` fields are populated when defaultStyle is called on buttons. Updated documentation for `defaultStyleOf` to indicated that not _all_ fields need to be non-null, since some fields make sense to be null (e.g. `fixedSize`) because they would otherwise override the behavior of other fields in the same `ButtonStyle`. ## Tests - Added tests to make sure that the appropriate fields are non-null in the default button styles for each type of button.
1 parent 97996b0 commit 51ed348

15 files changed

+688
-39
lines changed

dev/tools/gen_defaults/generated/used_tokens.csv

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,13 @@ md.comp.elevated-button.label-text.text-style,
145145
md.comp.elevated-button.pressed.container.elevation,
146146
md.comp.elevated-button.pressed.state-layer.color,
147147
md.comp.elevated-button.pressed.state-layer.opacity,
148+
md.comp.elevated-button.with-icon.disabled.icon.color,
149+
md.comp.elevated-button.with-icon.disabled.icon.opacity,
150+
md.comp.elevated-button.with-icon.focus.icon.color,
151+
md.comp.elevated-button.with-icon.hover.icon.color,
152+
md.comp.elevated-button.with-icon.icon.color,
153+
md.comp.elevated-button.with-icon.icon.size,
154+
md.comp.elevated-button.with-icon.pressed.icon.color,
148155
md.comp.elevated-card.container.color,
149156
md.comp.elevated-card.container.elevation,
150157
md.comp.elevated-card.container.shadow-color,
@@ -198,6 +205,13 @@ md.comp.filled-button.label-text.text-style,
198205
md.comp.filled-button.pressed.container.elevation,
199206
md.comp.filled-button.pressed.state-layer.color,
200207
md.comp.filled-button.pressed.state-layer.opacity,
208+
md.comp.filled-button.with-icon.disabled.icon.color,
209+
md.comp.filled-button.with-icon.disabled.icon.opacity,
210+
md.comp.filled-button.with-icon.focus.icon.color,
211+
md.comp.filled-button.with-icon.hover.icon.color,
212+
md.comp.filled-button.with-icon.icon.color,
213+
md.comp.filled-button.with-icon.icon.size,
214+
md.comp.filled-button.with-icon.pressed.icon.color,
201215
md.comp.filled-card.container.color,
202216
md.comp.filled-card.container.elevation,
203217
md.comp.filled-card.container.shadow-color,
@@ -296,6 +310,13 @@ md.comp.filled-tonal-button.label-text.text-style,
296310
md.comp.filled-tonal-button.pressed.container.elevation,
297311
md.comp.filled-tonal-button.pressed.state-layer.color,
298312
md.comp.filled-tonal-button.pressed.state-layer.opacity,
313+
md.comp.filled-tonal-button.with-icon.disabled.icon.color,
314+
md.comp.filled-tonal-button.with-icon.disabled.icon.opacity,
315+
md.comp.filled-tonal-button.with-icon.focus.icon.color,
316+
md.comp.filled-tonal-button.with-icon.hover.icon.color,
317+
md.comp.filled-tonal-button.with-icon.icon.color,
318+
md.comp.filled-tonal-button.with-icon.icon.size,
319+
md.comp.filled-tonal-button.with-icon.pressed.icon.color,
299320
md.comp.filled-tonal-icon-button.container.color,
300321
md.comp.filled-tonal-icon-button.container.height,
301322
md.comp.filled-tonal-icon-button.container.shape,
@@ -405,6 +426,7 @@ md.comp.list.list-item.hover.state-layer.opacity,
405426
md.comp.list.list-item.label-text.color,
406427
md.comp.list.list-item.label-text.text-style,
407428
md.comp.list.list-item.leading-icon.color,
429+
md.comp.list.list-item.leading-icon.size,
408430
md.comp.list.list-item.pressed.label-text.color,
409431
md.comp.list.list-item.pressed.leading-icon.icon.color,
410432
md.comp.list.list-item.pressed.state-layer.color,
@@ -470,6 +492,13 @@ md.comp.outlined-button.outline.color,
470492
md.comp.outlined-button.outline.width,
471493
md.comp.outlined-button.pressed.state-layer.color,
472494
md.comp.outlined-button.pressed.state-layer.opacity,
495+
md.comp.outlined-button.with-icon.disabled.icon.color,
496+
md.comp.outlined-button.with-icon.disabled.icon.opacity,
497+
md.comp.outlined-button.with-icon.focus.icon.color,
498+
md.comp.outlined-button.with-icon.hover.icon.color,
499+
md.comp.outlined-button.with-icon.icon.color,
500+
md.comp.outlined-button.with-icon.icon.size,
501+
md.comp.outlined-button.with-icon.pressed.icon.color,
473502
md.comp.outlined-card.container.color,
474503
md.comp.outlined-card.container.elevation,
475504
md.comp.outlined-card.container.shadow-color,
@@ -703,6 +732,13 @@ md.comp.text-button.label-text.color,
703732
md.comp.text-button.label-text.text-style,
704733
md.comp.text-button.pressed.state-layer.color,
705734
md.comp.text-button.pressed.state-layer.opacity,
735+
md.comp.text-button.with-icon.disabled.icon.color,
736+
md.comp.text-button.with-icon.disabled.icon.opacity,
737+
md.comp.text-button.with-icon.focus.icon.color,
738+
md.comp.text-button.with-icon.hover.icon.color,
739+
md.comp.text-button.with-icon.icon.color,
740+
md.comp.text-button.with-icon.icon.size,
741+
md.comp.text-button.with-icon.pressed.icon.color,
706742
md.comp.time-picker.clock-dial.color,
707743
md.comp.time-picker.clock-dial.container.size,
708744
md.comp.time-picker.clock-dial.label-text.text-style,

dev/tools/gen_defaults/lib/button_template.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,29 @@ class _${blockName}DefaultsM3 extends ButtonStyle {
125125
126126
// No default fixedSize
127127
128+
@override
129+
MaterialStateProperty<double>? get iconSize =>
130+
const MaterialStatePropertyAll<double>(${getToken("$tokenGroup.with-icon.icon.size")});
131+
132+
@override
133+
MaterialStateProperty<Color>? get iconColor {
134+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
135+
if (states.contains(MaterialState.disabled)) {
136+
return ${color('$tokenGroup.with-icon.disabled.icon.color')}.withOpacity(${opacity("$tokenGroup.with-icon.disabled.icon.opacity")});
137+
}
138+
if (states.contains(MaterialState.pressed)) {
139+
return ${color('$tokenGroup.with-icon.pressed.icon.color')};
140+
}
141+
if (states.contains(MaterialState.hovered)) {
142+
return ${color('$tokenGroup.with-icon.hover.icon.color')};
143+
}
144+
if (states.contains(MaterialState.focused)) {
145+
return ${color('$tokenGroup.with-icon.focus.icon.color')};
146+
}
147+
return ${color('$tokenGroup.with-icon.icon.color')};
148+
});
149+
}
150+
128151
@override
129152
MaterialStateProperty<Size>? get maximumSize =>
130153
const MaterialStatePropertyAll<Size>(Size.infinite);

dev/tools/gen_defaults/lib/menu_template.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ class _MenuButtonDefaultsM3 extends ButtonStyle {
121121
122122
// No default fixedSize
123123
124+
@override
125+
MaterialStateProperty<double>? get iconSize {
126+
return const MaterialStatePropertyAll<double>(${getToken("md.comp.list.list-item.leading-icon.size")});
127+
}
128+
124129
@override
125130
MaterialStateProperty<Size>? get maximumSize {
126131
return ButtonStyleButton.allOrNull<Size>(Size.infinite);

packages/flutter/lib/src/material/button_style_button.dart

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -162,22 +162,39 @@ abstract class ButtonStyleButton extends StatefulWidget {
162162
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
163163
final IconAlignment iconAlignment;
164164

165-
/// Returns a non-null [ButtonStyle] that's based primarily on the [Theme]'s
166-
/// [ThemeData.textTheme] and [ThemeData.colorScheme].
165+
/// Returns a [ButtonStyle] that's based primarily on the [Theme]'s
166+
/// [ThemeData.textTheme] and [ThemeData.colorScheme], but has most values
167+
/// filled out (non-null).
167168
///
168-
/// The returned style can be overridden by the [style] parameter and
169-
/// by the style returned by [themeStyleOf]. For example the default
170-
/// style of the [TextButton] subclass can be overridden with its
171-
/// [TextButton.style] constructor parameter, or with a
172-
/// [TextButtonTheme].
169+
/// The returned style can be overridden by the [style] parameter and by the
170+
/// style returned by [themeStyleOf] that some button-specific themes like
171+
/// [TextButtonTheme] or [ElevatedButtonTheme] override. For example the
172+
/// default style of the [TextButton] subclass can be overridden with its
173+
/// [TextButton.style] constructor parameter, or with a [TextButtonTheme].
173174
///
174-
/// Concrete button subclasses should return a ButtonStyle that
175-
/// has no null properties, and where all of the [WidgetStateProperty]
176-
/// properties resolve to non-null values.
175+
/// Concrete button subclasses should return a [ButtonStyle] with as many
176+
/// non-null properties as possible, where all of the non-null
177+
/// [WidgetStateProperty] properties resolve to non-null values.
178+
///
179+
/// ## Properties that can be null
180+
///
181+
/// Some properties, like [ButtonStyle.fixedSize] would override other values
182+
/// in the same [ButtonStyle] if set, so they are allowed to be null. Here is
183+
/// a summary of properties that are allowed to be null when returned in the
184+
/// [ButtonStyle] returned by this function, an why:
185+
///
186+
/// - [ButtonStyle.fixedSize] because it would override other values in the
187+
/// same [ButtonStyle], like [ButtonStyle.maximumSize].
188+
/// - [ButtonStyle.side] because null is a valid value for a button that has
189+
/// no side. [OutlinedButton] returns a non-null default for this, however.
190+
/// - [ButtonStyle.backgroundBuilder] and [ButtonStyle.foregroundBuilder]
191+
/// because they would override the [ButtonStyle.foregroundColor] and
192+
/// [ButtonStyle.backgroundColor] of the same [ButtonStyle].
177193
///
178194
/// See also:
179195
///
180-
/// * [themeStyleOf], Returns the ButtonStyle of this button's component theme.
196+
/// * [themeStyleOf], returns the ButtonStyle of this button's component
197+
/// theme.
181198
@protected
182199
ButtonStyle defaultStyleOf(BuildContext context);
183200

@@ -479,7 +496,10 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
479496
customBorder: resolvedShape.copyWith(side: resolvedSide),
480497
statesController: statesController,
481498
child: IconTheme.merge(
482-
data: IconThemeData(color: resolvedIconColor ?? resolvedForegroundColor, size: resolvedIconSize),
499+
data: IconThemeData(
500+
color: resolvedIconColor ?? resolvedForegroundColor,
501+
size: resolvedIconSize,
502+
),
483503
child: effectiveChild,
484504
),
485505
),

packages/flutter/lib/src/material/elevated_button.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,29 @@ class _ElevatedButtonDefaultsM3 extends ButtonStyle {
698698

699699
// No default fixedSize
700700

701+
@override
702+
MaterialStateProperty<double>? get iconSize =>
703+
const MaterialStatePropertyAll<double>(18.0);
704+
705+
@override
706+
MaterialStateProperty<Color>? get iconColor {
707+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
708+
if (states.contains(MaterialState.disabled)) {
709+
return _colors.onSurface.withOpacity(0.38);
710+
}
711+
if (states.contains(MaterialState.pressed)) {
712+
return _colors.primary;
713+
}
714+
if (states.contains(MaterialState.hovered)) {
715+
return _colors.primary;
716+
}
717+
if (states.contains(MaterialState.focused)) {
718+
return _colors.primary;
719+
}
720+
return _colors.primary;
721+
});
722+
}
723+
701724
@override
702725
MaterialStateProperty<Size>? get maximumSize =>
703726
const MaterialStatePropertyAll<Size>(Size.infinite);

packages/flutter/lib/src/material/filled_button.dart

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,29 @@ class _FilledButtonDefaultsM3 extends ButtonStyle {
730730

731731
// No default fixedSize
732732

733+
@override
734+
MaterialStateProperty<double>? get iconSize =>
735+
const MaterialStatePropertyAll<double>(18.0);
736+
737+
@override
738+
MaterialStateProperty<Color>? get iconColor {
739+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
740+
if (states.contains(MaterialState.disabled)) {
741+
return _colors.onSurface.withOpacity(0.38);
742+
}
743+
if (states.contains(MaterialState.pressed)) {
744+
return _colors.onPrimary;
745+
}
746+
if (states.contains(MaterialState.hovered)) {
747+
return _colors.onPrimary;
748+
}
749+
if (states.contains(MaterialState.focused)) {
750+
return _colors.onPrimary;
751+
}
752+
return _colors.onPrimary;
753+
});
754+
}
755+
733756
@override
734757
MaterialStateProperty<Size>? get maximumSize =>
735758
const MaterialStatePropertyAll<Size>(Size.infinite);
@@ -852,6 +875,29 @@ class _FilledTonalButtonDefaultsM3 extends ButtonStyle {
852875

853876
// No default fixedSize
854877

878+
@override
879+
MaterialStateProperty<double>? get iconSize =>
880+
const MaterialStatePropertyAll<double>(18.0);
881+
882+
@override
883+
MaterialStateProperty<Color>? get iconColor {
884+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
885+
if (states.contains(MaterialState.disabled)) {
886+
return _colors.onSurface.withOpacity(0.38);
887+
}
888+
if (states.contains(MaterialState.pressed)) {
889+
return _colors.onSecondaryContainer;
890+
}
891+
if (states.contains(MaterialState.hovered)) {
892+
return _colors.onSecondaryContainer;
893+
}
894+
if (states.contains(MaterialState.focused)) {
895+
return _colors.onSecondaryContainer;
896+
}
897+
return _colors.onSecondaryContainer;
898+
});
899+
}
900+
855901
@override
856902
MaterialStateProperty<Size>? get maximumSize =>
857903
const MaterialStatePropertyAll<Size>(Size.infinite);

packages/flutter/lib/src/material/menu_anchor.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3800,6 +3800,11 @@ class _MenuButtonDefaultsM3 extends ButtonStyle {
38003800

38013801
// No default fixedSize
38023802

3803+
@override
3804+
MaterialStateProperty<double>? get iconSize {
3805+
return const MaterialStatePropertyAll<double>(24.0);
3806+
}
3807+
38033808
@override
38043809
MaterialStateProperty<Size>? get maximumSize {
38053810
return ButtonStyleButton.allOrNull<Size>(Size.infinite);

packages/flutter/lib/src/material/outlined_button.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,29 @@ class _OutlinedButtonDefaultsM3 extends ButtonStyle {
606606

607607
// No default fixedSize
608608

609+
@override
610+
MaterialStateProperty<double>? get iconSize =>
611+
const MaterialStatePropertyAll<double>(18.0);
612+
613+
@override
614+
MaterialStateProperty<Color>? get iconColor {
615+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
616+
if (states.contains(MaterialState.disabled)) {
617+
return _colors.onSurface.withOpacity(0.38);
618+
}
619+
if (states.contains(MaterialState.pressed)) {
620+
return _colors.primary;
621+
}
622+
if (states.contains(MaterialState.hovered)) {
623+
return _colors.primary;
624+
}
625+
if (states.contains(MaterialState.focused)) {
626+
return _colors.primary;
627+
}
628+
return _colors.primary;
629+
});
630+
}
631+
609632
@override
610633
MaterialStateProperty<Size>? get maximumSize =>
611634
const MaterialStatePropertyAll<Size>(Size.infinite);

packages/flutter/lib/src/material/text_button.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,29 @@ class _TextButtonDefaultsM3 extends ButtonStyle {
639639

640640
// No default fixedSize
641641

642+
@override
643+
MaterialStateProperty<double>? get iconSize =>
644+
const MaterialStatePropertyAll<double>(18.0);
645+
646+
@override
647+
MaterialStateProperty<Color>? get iconColor {
648+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
649+
if (states.contains(MaterialState.disabled)) {
650+
return _colors.onSurface.withOpacity(0.38);
651+
}
652+
if (states.contains(MaterialState.pressed)) {
653+
return _colors.primary;
654+
}
655+
if (states.contains(MaterialState.hovered)) {
656+
return _colors.primary;
657+
}
658+
if (states.contains(MaterialState.focused)) {
659+
return _colors.primary;
660+
}
661+
return _colors.primary;
662+
});
663+
}
664+
642665
@override
643666
MaterialStateProperty<Size>? get maximumSize =>
644667
const MaterialStatePropertyAll<Size>(Size.infinite);

packages/flutter/lib/src/material/toggle_buttons.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,8 @@ class ToggleButtons extends StatelessWidget {
775775
style: ButtonStyle(
776776
backgroundColor: MaterialStatePropertyAll<Color?>(effectiveFillColor),
777777
foregroundColor: MaterialStatePropertyAll<Color?>(currentColor),
778+
iconSize: const MaterialStatePropertyAll<double>(24.0),
779+
iconColor: MaterialStatePropertyAll<Color?>(currentColor),
778780
overlayColor: _ToggleButtonDefaultOverlay(
779781
selected: onPressed != null && isSelected[index],
780782
unselected: onPressed != null && !isSelected[index],

0 commit comments

Comments
 (0)