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 'ink_well.dart';
6/// @docImport 'material.dart';
7/// @docImport 'menu_button_theme.dart';
8library;
9
10import 'dart:ui' show lerpDouble;
11
12import 'package:flutter/foundation.dart';
13import 'package:flutter/widgets.dart';
14
15import 'button_style.dart';
16import 'material_state.dart';
17import 'menu_anchor.dart';
18import 'theme.dart';
19import 'theme_data.dart';
20
21// Examples can assume:
22// late Widget child;
23// late BuildContext context;
24// late MenuStyle style;
25// @immutable
26// class MyAppHome extends StatelessWidget {
27// const MyAppHome({super.key});
28// @override
29// Widget build(BuildContext context) => const SizedBox();
30// }
31
32/// The visual properties that menus have in common.
33///
34/// Menus created by [MenuBar] and [MenuAnchor] and their themes have a
35/// [MenuStyle] property which defines the visual properties whose default
36/// values are to be overridden. The default values are defined by the
37/// individual menu widgets and are typically based on overall theme's
38/// [ThemeData.colorScheme] and [ThemeData.textTheme].
39///
40/// All of the [MenuStyle] properties are null by default.
41///
42/// Many of the [MenuStyle] properties are [WidgetStateProperty] objects which
43/// resolve to different values depending on the menu's state. For example the
44/// [Color] properties are defined with `WidgetStateProperty<Color>` and can
45/// resolve to different colors depending on if the menu is pressed, hovered,
46/// focused, disabled, etc.
47///
48/// These properties can override the default value for just one state or all of
49/// them. For example to create a [SubmenuButton] whose background color is the
50/// color scheme’s primary color with 50% opacity, but only when the menu is
51/// pressed, one could write:
52///
53/// ```dart
54/// SubmenuButton(
55/// menuStyle: MenuStyle(
56/// backgroundColor: WidgetStateProperty.resolveWith<Color?>(
57/// (Set<WidgetState> states) {
58/// if (states.contains(WidgetState.focused)) {
59/// return Theme.of(context).colorScheme.primary.withOpacity(0.5);
60/// }
61/// return null; // Use the component's default.
62/// },
63/// ),
64/// ),
65/// menuChildren: const <Widget>[ /* ... */ ],
66/// child: const Text('Fly me to the moon'),
67/// ),
68/// ```
69///
70/// In this case the background color for all other menu states would fall back
71/// to the [SubmenuButton]'s default values. To unconditionally set the menu's
72/// [backgroundColor] for all states one could write:
73///
74/// ```dart
75/// const SubmenuButton(
76/// menuStyle: MenuStyle(
77/// backgroundColor: WidgetStatePropertyAll<Color>(Colors.green),
78/// ),
79/// menuChildren: <Widget>[ /* ... */ ],
80/// child: Text('Let me play among the stars'),
81/// ),
82/// ```
83///
84/// To configure all of the application's menus in the same way, specify the
85/// overall theme's `menuTheme`:
86///
87/// ```dart
88/// MaterialApp(
89/// theme: ThemeData(
90/// menuTheme: const MenuThemeData(
91/// style: MenuStyle(backgroundColor: WidgetStatePropertyAll<Color>(Colors.red)),
92/// ),
93/// ),
94/// home: const MyAppHome(),
95/// ),
96/// ```
97///
98/// See also:
99///
100/// * [MenuAnchor], a widget which hosts cascading menus.
101/// * [MenuBar], a widget which defines a menu bar of buttons hosting cascading
102/// menus.
103/// * [MenuButtonTheme], the theme for [SubmenuButton]s and [MenuItemButton]s.
104/// * [ButtonStyle], a similar configuration object for button styles.
105@immutable
106class MenuStyle with Diagnosticable {
107 /// Create a [MenuStyle].
108 const MenuStyle({
109 this.backgroundColor,
110 this.shadowColor,
111 this.surfaceTintColor,
112 this.elevation,
113 this.padding,
114 this.minimumSize,
115 this.fixedSize,
116 this.maximumSize,
117 this.side,
118 this.shape,
119 this.mouseCursor,
120 this.visualDensity,
121 this.alignment,
122 });
123
124 /// The menu's background fill color.
125 final MaterialStateProperty<Color?>? backgroundColor;
126
127 /// The shadow color of the menu's [Material].
128 ///
129 /// The material's elevation shadow can be difficult to see for dark themes,
130 /// so by default the menu classes add a semi-transparent overlay to indicate
131 /// elevation. See [ThemeData.applyElevationOverlayColor].
132 final MaterialStateProperty<Color?>? shadowColor;
133
134 /// The surface tint color of the menu's [Material].
135 ///
136 /// See [Material.surfaceTintColor] for more details.
137 final MaterialStateProperty<Color?>? surfaceTintColor;
138
139 /// The elevation of the menu's [Material].
140 final MaterialStateProperty<double?>? elevation;
141
142 /// The padding between the menu's boundary and its child.
143 final MaterialStateProperty<EdgeInsetsGeometry?>? padding;
144
145 /// The minimum size of the menu itself.
146 ///
147 /// This value must be less than or equal to [maximumSize].
148 final MaterialStateProperty<Size?>? minimumSize;
149
150 /// The menu's size.
151 ///
152 /// This size is still constrained by the style's [minimumSize] and
153 /// [maximumSize]. Fixed size dimensions whose value is [double.infinity] are
154 /// ignored.
155 ///
156 /// To specify menus with a fixed width and the default height use `fixedSize:
157 /// Size.fromWidth(320)`. Similarly, to specify a fixed height and the default
158 /// width use `fixedSize: Size.fromHeight(100)`.
159 final MaterialStateProperty<Size?>? fixedSize;
160
161 /// The maximum size of the menu itself.
162 ///
163 /// A [Size.infinite] or null value for this property means that the menu's
164 /// maximum size is not constrained.
165 ///
166 /// This value must be greater than or equal to [minimumSize].
167 final MaterialStateProperty<Size?>? maximumSize;
168
169 /// The color and weight of the menu's outline.
170 ///
171 /// This value is combined with [shape] to create a shape decorated with an
172 /// outline.
173 final MaterialStateProperty<BorderSide?>? side;
174
175 /// The shape of the menu's underlying [Material].
176 ///
177 /// This shape is combined with [side] to create a shape decorated with an
178 /// outline.
179 final MaterialStateProperty<OutlinedBorder?>? shape;
180
181 /// The cursor for a mouse pointer when it enters or is hovering over this
182 /// menu's [InkWell].
183 final MaterialStateProperty<MouseCursor?>? mouseCursor;
184
185 /// Defines how compact the menu's layout will be.
186 ///
187 /// {@macro flutter.material.themedata.visualDensity}
188 ///
189 /// See also:
190 ///
191 /// * [ThemeData.visualDensity], which specifies the [visualDensity] for all
192 /// widgets within a [Theme].
193 final VisualDensity? visualDensity;
194
195 /// Determines the desired alignment of the submenu when opened relative to
196 /// the button that opens it.
197 ///
198 /// If there isn't sufficient space to open the menu with the given alignment,
199 /// and there's space on the other side of the button, then the alignment is
200 /// swapped to it's opposite (1 becomes -1, etc.), and the menu will try to
201 /// appear on the other side of the button. If there isn't enough space there
202 /// either, then the menu will be pushed as far over as necessary to display
203 /// as much of itself as possible, possibly overlapping the parent button.
204 final AlignmentGeometry? alignment;
205
206 @override
207 int get hashCode {
208 final List<Object?> values = <Object?>[
209 backgroundColor,
210 shadowColor,
211 surfaceTintColor,
212 elevation,
213 padding,
214 minimumSize,
215 fixedSize,
216 maximumSize,
217 side,
218 shape,
219 mouseCursor,
220 visualDensity,
221 alignment,
222 ];
223 return Object.hashAll(values);
224 }
225
226 @override
227 bool operator ==(Object other) {
228 if (identical(this, other)) {
229 return true;
230 }
231 if (other.runtimeType != runtimeType) {
232 return false;
233 }
234 return other is MenuStyle
235 && other.backgroundColor == backgroundColor
236 && other.shadowColor == shadowColor
237 && other.surfaceTintColor == surfaceTintColor
238 && other.elevation == elevation
239 && other.padding == padding
240 && other.minimumSize == minimumSize
241 && other.fixedSize == fixedSize
242 && other.maximumSize == maximumSize
243 && other.side == side
244 && other.shape == shape
245 && other.mouseCursor == mouseCursor
246 && other.visualDensity == visualDensity
247 && other.alignment == alignment;
248 }
249
250 /// Returns a copy of this MenuStyle with the given fields replaced with
251 /// the new values.
252 MenuStyle copyWith({
253 MaterialStateProperty<Color?>? backgroundColor,
254 MaterialStateProperty<Color?>? shadowColor,
255 MaterialStateProperty<Color?>? surfaceTintColor,
256 MaterialStateProperty<double?>? elevation,
257 MaterialStateProperty<EdgeInsetsGeometry?>? padding,
258 MaterialStateProperty<Size?>? minimumSize,
259 MaterialStateProperty<Size?>? fixedSize,
260 MaterialStateProperty<Size?>? maximumSize,
261 MaterialStateProperty<BorderSide?>? side,
262 MaterialStateProperty<OutlinedBorder?>? shape,
263 MaterialStateProperty<MouseCursor?>? mouseCursor,
264 VisualDensity? visualDensity,
265 AlignmentGeometry? alignment,
266 }) {
267 return MenuStyle(
268 backgroundColor: backgroundColor ?? this.backgroundColor,
269 shadowColor: shadowColor ?? this.shadowColor,
270 surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
271 elevation: elevation ?? this.elevation,
272 padding: padding ?? this.padding,
273 minimumSize: minimumSize ?? this.minimumSize,
274 fixedSize: fixedSize ?? this.fixedSize,
275 maximumSize: maximumSize ?? this.maximumSize,
276 side: side ?? this.side,
277 shape: shape ?? this.shape,
278 mouseCursor: mouseCursor ?? this.mouseCursor,
279 visualDensity: visualDensity ?? this.visualDensity,
280 alignment: alignment ?? this.alignment,
281 );
282 }
283
284 /// Returns a copy of this MenuStyle where the non-null fields in [style]
285 /// have replaced the corresponding null fields in this MenuStyle.
286 ///
287 /// In other words, [style] is used to fill in unspecified (null) fields
288 /// this MenuStyle.
289 MenuStyle merge(MenuStyle? style) {
290 if (style == null) {
291 return this;
292 }
293 return copyWith(
294 backgroundColor: backgroundColor ?? style.backgroundColor,
295 shadowColor: shadowColor ?? style.shadowColor,
296 surfaceTintColor: surfaceTintColor ?? style.surfaceTintColor,
297 elevation: elevation ?? style.elevation,
298 padding: padding ?? style.padding,
299 minimumSize: minimumSize ?? style.minimumSize,
300 fixedSize: fixedSize ?? style.fixedSize,
301 maximumSize: maximumSize ?? style.maximumSize,
302 side: side ?? style.side,
303 shape: shape ?? style.shape,
304 mouseCursor: mouseCursor ?? style.mouseCursor,
305 visualDensity: visualDensity ?? style.visualDensity,
306 alignment: alignment ?? style.alignment,
307 );
308 }
309
310 /// Linearly interpolate between two [MenuStyle]s.
311 static MenuStyle? lerp(MenuStyle? a, MenuStyle? b, double t) {
312 if (identical(a, b)) {
313 return a;
314 }
315 return MenuStyle(
316 backgroundColor: MaterialStateProperty.lerp<Color?>(a?.backgroundColor, b?.backgroundColor, t, Color.lerp),
317 shadowColor: MaterialStateProperty.lerp<Color?>(a?.shadowColor, b?.shadowColor, t, Color.lerp),
318 surfaceTintColor: MaterialStateProperty.lerp<Color?>(a?.surfaceTintColor, b?.surfaceTintColor, t, Color.lerp),
319 elevation: MaterialStateProperty.lerp<double?>(a?.elevation, b?.elevation, t, lerpDouble),
320 padding: MaterialStateProperty.lerp<EdgeInsetsGeometry?>(a?.padding, b?.padding, t, EdgeInsetsGeometry.lerp),
321 minimumSize: MaterialStateProperty.lerp<Size?>(a?.minimumSize, b?.minimumSize, t, Size.lerp),
322 fixedSize: MaterialStateProperty.lerp<Size?>(a?.fixedSize, b?.fixedSize, t, Size.lerp),
323 maximumSize: MaterialStateProperty.lerp<Size?>(a?.maximumSize, b?.maximumSize, t, Size.lerp),
324 side: MaterialStateBorderSide.lerp(a?.side, b?.side, t),
325 shape: MaterialStateProperty.lerp<OutlinedBorder?>(a?.shape, b?.shape, t, OutlinedBorder.lerp),
326 mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
327 visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
328 alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
329 );
330 }
331
332 @override
333 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
334 super.debugFillProperties(properties);
335 properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('backgroundColor', backgroundColor, defaultValue: null));
336 properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('shadowColor', shadowColor, defaultValue: null));
337 properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('surfaceTintColor', surfaceTintColor, defaultValue: null));
338 properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('elevation', elevation, defaultValue: null));
339 properties.add(DiagnosticsProperty<MaterialStateProperty<EdgeInsetsGeometry?>>('padding', padding, defaultValue: null));
340 properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('minimumSize', minimumSize, defaultValue: null));
341 properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('fixedSize', fixedSize, defaultValue: null));
342 properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('maximumSize', maximumSize, defaultValue: null));
343 properties.add(DiagnosticsProperty<MaterialStateProperty<BorderSide?>>('side', side, defaultValue: null));
344 properties.add(DiagnosticsProperty<MaterialStateProperty<OutlinedBorder?>>('shape', shape, defaultValue: null));
345 properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
346 properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
347 properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
348 }
349}
350

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com