Skip to content

Commit 3ce88d3

Browse files
authored
Replace menu defaults with tokens (#113963)
1 parent af34b10 commit 3ce88d3

File tree

13 files changed

+684
-114
lines changed

13 files changed

+684
-114
lines changed

dev/tools/gen_defaults/bin/gen_defaults.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import 'package:gen_defaults/filter_chip_template.dart';
3131
import 'package:gen_defaults/icon_button_template.dart';
3232
import 'package:gen_defaults/input_chip_template.dart';
3333
import 'package:gen_defaults/input_decorator_template.dart';
34+
import 'package:gen_defaults/menu_template.dart';
3435
import 'package:gen_defaults/navigation_bar_template.dart';
3536
import 'package:gen_defaults/navigation_rail_template.dart';
3637
import 'package:gen_defaults/popup_menu_template.dart';
@@ -135,6 +136,7 @@ Future<void> main(List<String> args) async {
135136
IconButtonTemplate('IconButton', '$materialLib/icon_button.dart', tokens).updateFile();
136137
InputChipTemplate('InputChip', '$materialLib/input_chip.dart', tokens).updateFile();
137138
InputDecoratorTemplate('InputDecorator', '$materialLib/input_decorator.dart', tokens).updateFile();
139+
MenuTemplate('Menu', '$materialLib/menu_anchor.dart', tokens).updateFile();
138140
NavigationBarTemplate('NavigationBar', '$materialLib/navigation_bar.dart', tokens).updateFile();
139141
NavigationRailTemplate('NavigationRail', '$materialLib/navigation_rail.dart', tokens).updateFile();
140142
PopupMenuTemplate('PopupMenu', '$materialLib/popup_menu.dart', tokens).updateFile();
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
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+
import 'template.dart';
6+
7+
class MenuTemplate extends TokenTemplate {
8+
const MenuTemplate(super.blockName, super.fileName, super.tokens, {
9+
super.colorSchemePrefix = '_colors.',
10+
});
11+
12+
@override
13+
String generate() => '''
14+
class _MenuBarDefaultsM3 extends MenuStyle {
15+
_MenuBarDefaultsM3(this.context)
16+
: super(
17+
elevation: const MaterialStatePropertyAll<double?>(${elevation('md.comp.menu.container')}),
18+
shape: const MaterialStatePropertyAll<OutlinedBorder>(_defaultMenuBorder),
19+
alignment: AlignmentDirectional.bottomStart,
20+
);
21+
static const RoundedRectangleBorder _defaultMenuBorder =
22+
${shape('md.comp.menu.container', '')};
23+
24+
final BuildContext context;
25+
26+
late final ColorScheme _colors = Theme.of(context).colorScheme;
27+
28+
@override
29+
MaterialStateProperty<Color?> get backgroundColor {
30+
return MaterialStatePropertyAll<Color?>(${componentColor('md.comp.menu.container')});
31+
}
32+
33+
@override
34+
MaterialStateProperty<Color?>? get shadowColor {
35+
return MaterialStatePropertyAll<Color?>(${color('md.comp.menu.container.shadow-color')});
36+
}
37+
38+
@override
39+
MaterialStateProperty<Color?>? get surfaceTintColor {
40+
return MaterialStatePropertyAll<Color?>(${componentColor('md.comp.menu.container.surface-tint-layer')});
41+
}
42+
43+
@override
44+
MaterialStateProperty<EdgeInsetsGeometry?>? get padding {
45+
return MaterialStatePropertyAll<EdgeInsetsGeometry>(
46+
EdgeInsetsDirectional.symmetric(
47+
horizontal: math.max(
48+
_kTopLevelMenuHorizontalMinPadding,
49+
2 + Theme.of(context).visualDensity.baseSizeAdjustment.dx,
50+
),
51+
),
52+
);
53+
}
54+
}
55+
56+
class _MenuButtonDefaultsM3 extends ButtonStyle {
57+
_MenuButtonDefaultsM3(this.context)
58+
: super(
59+
animationDuration: kThemeChangeDuration,
60+
enableFeedback: true,
61+
alignment: AlignmentDirectional.centerStart,
62+
);
63+
final BuildContext context;
64+
65+
late final ColorScheme _colors = Theme.of(context).colorScheme;
66+
67+
@override
68+
MaterialStateProperty<Color?>? get backgroundColor {
69+
return ButtonStyleButton.allOrNull<Color>(Colors.transparent);
70+
}
71+
72+
// No default shadow color
73+
74+
// No default surface tint color
75+
76+
@override
77+
MaterialStateProperty<double>? get elevation {
78+
return ButtonStyleButton.allOrNull<double>(0.0);
79+
}
80+
81+
@override
82+
MaterialStateProperty<Color?>? get foregroundColor {
83+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
84+
if (states.contains(MaterialState.disabled)) {
85+
return ${componentColor('md.comp.menu.list-item.disabled.label-text')};
86+
}
87+
if (states.contains(MaterialState.pressed)) {
88+
return ${componentColor('md.comp.menu.list-item.pressed.label-text')};
89+
}
90+
if (states.contains(MaterialState.hovered)) {
91+
return ${componentColor('md.comp.menu.list-item.hover.label-text')};
92+
}
93+
if (states.contains(MaterialState.focused)) {
94+
return ${componentColor('md.comp.menu.list-item.focus.label-text')};
95+
}
96+
return ${componentColor('md.comp.menu.list-item.label-text')};
97+
});
98+
}
99+
100+
@override
101+
MaterialStateProperty<Color?>? get iconColor {
102+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
103+
if (states.contains(MaterialState.disabled)) {
104+
return ${componentColor('md.comp.menu.list-item.with-leading-icon.disabled.leading-icon')};
105+
}
106+
if (states.contains(MaterialState.pressed)) {
107+
return ${componentColor('md.comp.menu.list-item.with-leading-icon.pressed.icon')};
108+
}
109+
if (states.contains(MaterialState.hovered)) {
110+
return ${componentColor('md.comp.menu.list-item.with-leading-icon.hover.icon')};
111+
}
112+
if (states.contains(MaterialState.focused)) {
113+
return ${componentColor('md.comp.menu.list-item.with-leading-icon.focus.icon')};
114+
}
115+
return ${componentColor('md.comp.menu.list-item.with-leading-icon.leading-icon')};
116+
});
117+
}
118+
119+
// No default fixedSize
120+
121+
@override
122+
MaterialStateProperty<Size>? get maximumSize {
123+
return ButtonStyleButton.allOrNull<Size>(Size.infinite);
124+
}
125+
126+
@override
127+
MaterialStateProperty<Size>? get minimumSize {
128+
return ButtonStyleButton.allOrNull<Size>(const Size(64.0, ${tokens['md.comp.menu.list-item.container.height']}));
129+
}
130+
131+
@override
132+
MaterialStateProperty<MouseCursor?>? get mouseCursor {
133+
return MaterialStateProperty.resolveWith(
134+
(Set<MaterialState> states) {
135+
if (states.contains(MaterialState.disabled)) {
136+
return SystemMouseCursors.basic;
137+
}
138+
return SystemMouseCursors.click;
139+
},
140+
);
141+
}
142+
143+
@override
144+
MaterialStateProperty<Color?>? get overlayColor {
145+
return MaterialStateProperty.resolveWith(
146+
(Set<MaterialState> states) {
147+
if (states.contains(MaterialState.pressed)) {
148+
return ${componentColor('md.comp.menu.list-item.pressed.state-layer')};
149+
}
150+
if (states.contains(MaterialState.hovered)) {
151+
return ${componentColor('md.comp.menu.list-item.hover.state-layer')};
152+
}
153+
if (states.contains(MaterialState.focused)) {
154+
return ${componentColor('md.comp.menu.list-item.focus.state-layer')};
155+
}
156+
return Colors.transparent;
157+
},
158+
);
159+
}
160+
161+
@override
162+
MaterialStateProperty<EdgeInsetsGeometry>? get padding {
163+
return ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(_scaledPadding(context));
164+
}
165+
166+
// No default side
167+
168+
@override
169+
MaterialStateProperty<OutlinedBorder>? get shape {
170+
return ButtonStyleButton.allOrNull<OutlinedBorder>(const RoundedRectangleBorder());
171+
}
172+
173+
@override
174+
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
175+
176+
@override
177+
MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;
178+
179+
@override
180+
MaterialStateProperty<TextStyle?> get textStyle {
181+
return MaterialStatePropertyAll<TextStyle?>(${textStyle('md.comp.menu.list-item.label-text')});
182+
}
183+
184+
@override
185+
VisualDensity? get visualDensity => Theme.of(context).visualDensity;
186+
187+
// The horizontal padding number comes from the spec.
188+
EdgeInsetsGeometry _scaledPadding(BuildContext context) {
189+
return ButtonStyleButton.scaledPadding(
190+
const EdgeInsets.symmetric(horizontal: 12),
191+
const EdgeInsets.symmetric(horizontal: 8),
192+
const EdgeInsets.symmetric(horizontal: 4),
193+
MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
194+
);
195+
}
196+
}
197+
198+
class _MenuDefaultsM3 extends MenuStyle {
199+
_MenuDefaultsM3(this.context)
200+
: super(
201+
elevation: const MaterialStatePropertyAll<double?>(${elevation('md.comp.menu.container')}),
202+
shape: const MaterialStatePropertyAll<OutlinedBorder>(_defaultMenuBorder),
203+
alignment: AlignmentDirectional.topEnd,
204+
);
205+
static const RoundedRectangleBorder _defaultMenuBorder =
206+
${shape('md.comp.menu.container', '')};
207+
208+
final BuildContext context;
209+
210+
late final ColorScheme _colors = Theme.of(context).colorScheme;
211+
212+
@override
213+
MaterialStateProperty<Color?> get backgroundColor {
214+
return MaterialStatePropertyAll<Color?>(${componentColor('md.comp.menu.container')});
215+
}
216+
217+
@override
218+
MaterialStateProperty<Color?>? get surfaceTintColor {
219+
return MaterialStatePropertyAll<Color?>(${componentColor('md.comp.menu.container.surface-tint-layer')});
220+
}
221+
222+
@override
223+
MaterialStateProperty<Color?>? get shadowColor {
224+
return MaterialStatePropertyAll<Color?>(${color('md.comp.menu.container.shadow-color')});
225+
}
226+
227+
@override
228+
MaterialStateProperty<EdgeInsetsGeometry?>? get padding {
229+
return MaterialStatePropertyAll<EdgeInsetsGeometry>(
230+
EdgeInsetsDirectional.symmetric(
231+
vertical: math.max(
232+
_kMenuVerticalMinPadding,
233+
2 + Theme.of(context).visualDensity.baseSizeAdjustment.dy,
234+
),
235+
),
236+
);
237+
}
238+
}
239+
''';
240+
}

examples/api/test/material/menu_anchor/menu_anchor.1_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ void main() {
2121
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight);
2222
await tester.tapAt(const Offset(100, 200));
2323
await tester.pump();
24-
expect(tester.getRect(findMenu()), equals(const Rect.fromLTRB(100.0, 200.0, 404.0, 352.0)));
24+
expect(tester.getRect(findMenu()), equals(const Rect.fromLTRB(100.0, 200.0, 388.0, 360.0)));
2525

2626
// Make sure tapping in a different place causes the menu to move.
2727
await tester.tapAt(const Offset(200, 100));
2828
await tester.pump();
2929
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight);
3030

31-
expect(tester.getRect(findMenu()), equals(const Rect.fromLTRB(200.0, 100.0, 504.0, 252.0)));
31+
expect(tester.getRect(findMenu()), equals(const Rect.fromLTRB(200.0, 100.0, 488.0, 260.0)));
3232

3333
expect(find.text(example.MenuEntry.about.label), findsOneWidget);
3434
expect(find.text(example.MenuEntry.showMessage.label), findsOneWidget);

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ class ButtonStyle with Diagnosticable {
151151
this.minimumSize,
152152
this.fixedSize,
153153
this.maximumSize,
154+
this.iconColor,
154155
this.iconSize,
155156
this.side,
156157
this.shape,
@@ -230,6 +231,11 @@ class ButtonStyle with Diagnosticable {
230231
/// This value must be greater than or equal to [minimumSize].
231232
final MaterialStateProperty<Size?>? maximumSize;
232233

234+
/// The icon's color inside of the button.
235+
///
236+
/// If this is null, the icon color will be [foregroundColor].
237+
final MaterialStateProperty<Color?>? iconColor;
238+
233239
/// The icon's size inside of the button.
234240
final MaterialStateProperty<double?>? iconSize;
235241

@@ -323,6 +329,7 @@ class ButtonStyle with Diagnosticable {
323329
MaterialStateProperty<Size?>? minimumSize,
324330
MaterialStateProperty<Size?>? fixedSize,
325331
MaterialStateProperty<Size?>? maximumSize,
332+
MaterialStateProperty<Color?>? iconColor,
326333
MaterialStateProperty<double?>? iconSize,
327334
MaterialStateProperty<BorderSide?>? side,
328335
MaterialStateProperty<OutlinedBorder?>? shape,
@@ -346,6 +353,7 @@ class ButtonStyle with Diagnosticable {
346353
minimumSize: minimumSize ?? this.minimumSize,
347354
fixedSize: fixedSize ?? this.fixedSize,
348355
maximumSize: maximumSize ?? this.maximumSize,
356+
iconColor: iconColor ?? this.iconColor,
349357
iconSize: iconSize ?? this.iconSize,
350358
side: side ?? this.side,
351359
shape: shape ?? this.shape,
@@ -380,6 +388,7 @@ class ButtonStyle with Diagnosticable {
380388
minimumSize: minimumSize ?? style.minimumSize,
381389
fixedSize: fixedSize ?? style.fixedSize,
382390
maximumSize: maximumSize ?? style.maximumSize,
391+
iconColor: iconColor ?? style.iconColor,
383392
iconSize: iconSize ?? style.iconSize,
384393
side: side ?? style.side,
385394
shape: shape ?? style.shape,
@@ -407,6 +416,7 @@ class ButtonStyle with Diagnosticable {
407416
minimumSize,
408417
fixedSize,
409418
maximumSize,
419+
iconColor,
410420
iconSize,
411421
side,
412422
shape,
@@ -441,6 +451,7 @@ class ButtonStyle with Diagnosticable {
441451
&& other.minimumSize == minimumSize
442452
&& other.fixedSize == fixedSize
443453
&& other.maximumSize == maximumSize
454+
&& other.iconColor == iconColor
444455
&& other.iconSize == iconSize
445456
&& other.side == side
446457
&& other.shape == shape
@@ -467,6 +478,7 @@ class ButtonStyle with Diagnosticable {
467478
properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('minimumSize', minimumSize, defaultValue: null));
468479
properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('fixedSize', fixedSize, defaultValue: null));
469480
properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('maximumSize', maximumSize, defaultValue: null));
481+
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('iconColor', iconColor, defaultValue: null));
470482
properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('iconSize', iconSize, defaultValue: null));
471483
properties.add(DiagnosticsProperty<MaterialStateProperty<BorderSide?>>('side', side, defaultValue: null));
472484
properties.add(DiagnosticsProperty<MaterialStateProperty<OutlinedBorder?>>('shape', shape, defaultValue: null));
@@ -496,6 +508,7 @@ class ButtonStyle with Diagnosticable {
496508
minimumSize: MaterialStateProperty.lerp<Size?>(a?.minimumSize, b?.minimumSize, t, Size.lerp),
497509
fixedSize: MaterialStateProperty.lerp<Size?>(a?.fixedSize, b?.fixedSize, t, Size.lerp),
498510
maximumSize: MaterialStateProperty.lerp<Size?>(a?.maximumSize, b?.maximumSize, t, Size.lerp),
511+
iconColor: MaterialStateProperty.lerp<Color?>(a?.iconColor, b?.iconColor, t, Color.lerp),
499512
iconSize: MaterialStateProperty.lerp<double?>(a?.iconSize, b?.iconSize, t, lerpDouble),
500513
side: _lerpSides(a?.side, b?.side, t),
501514
shape: MaterialStateProperty.lerp<OutlinedBorder?>(a?.shape, b?.shape, t, OutlinedBorder.lerp),

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
287287
final Size? resolvedMinimumSize = resolve<Size?>((ButtonStyle? style) => style?.minimumSize);
288288
final Size? resolvedFixedSize = resolve<Size?>((ButtonStyle? style) => style?.fixedSize);
289289
final Size? resolvedMaximumSize = resolve<Size?>((ButtonStyle? style) => style?.maximumSize);
290+
final Color? resolvedIconColor = resolve<Color?>((ButtonStyle? style) => style?.iconColor);
290291
final double? resolvedIconSize = resolve<double?>((ButtonStyle? style) => style?.iconSize);
291292
final BorderSide? resolvedSide = resolve<BorderSide?>((ButtonStyle? style) => style?.side);
292293
final OutlinedBorder? resolvedShape = resolve<OutlinedBorder?>((ButtonStyle? style) => style?.shape);
@@ -400,7 +401,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
400401
customBorder: resolvedShape.copyWith(side: resolvedSide),
401402
statesController: statesController,
402403
child: IconTheme.merge(
403-
data: IconThemeData(color: resolvedForegroundColor, size: resolvedIconSize),
404+
data: IconThemeData(color: resolvedIconColor ?? resolvedForegroundColor, size: resolvedIconSize),
404405
child: Padding(
405406
padding: padding,
406407
child: Align(

0 commit comments

Comments
 (0)