mirror of
https://github.com/flutter/flutter
synced 2024-09-17 23:31:55 +00:00
Update Popup Menu to support Material 3 (#103606)
This commit is contained in:
parent
700b449d0a
commit
b4058b95f5
|
@ -33,6 +33,7 @@ import 'package:gen_defaults/input_chip_template.dart';
|
|||
import 'package:gen_defaults/input_decorator_template.dart';
|
||||
import 'package:gen_defaults/navigation_bar_template.dart';
|
||||
import 'package:gen_defaults/navigation_rail_template.dart';
|
||||
import 'package:gen_defaults/popup_menu_template.dart';
|
||||
import 'package:gen_defaults/progress_indicator_template.dart';
|
||||
import 'package:gen_defaults/radio_template.dart';
|
||||
import 'package:gen_defaults/surface_tint.dart';
|
||||
|
@ -136,6 +137,7 @@ Future<void> main(List<String> args) async {
|
|||
InputDecoratorTemplate('InputDecorator', '$materialLib/input_decorator.dart', tokens).updateFile();
|
||||
NavigationBarTemplate('NavigationBar', '$materialLib/navigation_bar.dart', tokens).updateFile();
|
||||
NavigationRailTemplate('NavigationRail', '$materialLib/navigation_rail.dart', tokens).updateFile();
|
||||
PopupMenuTemplate('PopupMenu', '$materialLib/popup_menu.dart', tokens).updateFile();
|
||||
ProgressIndicatorTemplate('ProgressIndicator', '$materialLib/progress_indicator.dart', tokens).updateFile();
|
||||
RadioTemplate('Radio<T>', '$materialLib/radio.dart', tokens).updateFile();
|
||||
SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile();
|
||||
|
|
46
dev/tools/gen_defaults/lib/popup_menu_template.dart
Normal file
46
dev/tools/gen_defaults/lib/popup_menu_template.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'template.dart';
|
||||
|
||||
class PopupMenuTemplate extends TokenTemplate {
|
||||
const PopupMenuTemplate(super.blockName, super.fileName, super.tokens, {
|
||||
super.colorSchemePrefix = '_colors.',
|
||||
super.textThemePrefix = '_textTheme.',
|
||||
});
|
||||
|
||||
@override
|
||||
String generate() => '''
|
||||
class _${blockName}DefaultsM3 extends PopupMenuThemeData {
|
||||
_${blockName}DefaultsM3(this.context)
|
||||
: super(elevation: ${elevation('md.comp.menu.container')});
|
||||
|
||||
final BuildContext context;
|
||||
late final ThemeData _theme = Theme.of(context);
|
||||
late final ColorScheme _colors = _theme.colorScheme;
|
||||
late final TextTheme _textTheme = _theme.textTheme;
|
||||
|
||||
@override MaterialStateProperty<TextStyle?>? get labelTextStyle {
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
final TextStyle style = _textTheme.labelLarge!;
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return style.apply(color: ${componentColor('md.comp.menu.list-item.disabled.label-text')});
|
||||
}
|
||||
return style.apply(color: ${componentColor('md.comp.menu.list-item.label-text')});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Color? get color => ${componentColor('md.comp.menu.container')};
|
||||
|
||||
@override
|
||||
Color? get shadowColor => ${color("md.comp.menu.container.shadow-color")};
|
||||
|
||||
@override
|
||||
Color? get surfaceTintColor => ${color("md.comp.menu.container.surface-tint-layer.color")};
|
||||
|
||||
@override
|
||||
ShapeBorder? get shape => ${shape("md.comp.menu.container")};
|
||||
}''';
|
||||
}
|
|
@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'color_scheme.dart';
|
||||
import 'constants.dart';
|
||||
import 'debug.dart';
|
||||
import 'divider.dart';
|
||||
|
@ -17,6 +18,7 @@ import 'material.dart';
|
|||
import 'material_localizations.dart';
|
||||
import 'material_state.dart';
|
||||
import 'popup_menu_theme.dart';
|
||||
import 'text_theme.dart';
|
||||
import 'theme.dart';
|
||||
import 'tooltip.dart';
|
||||
|
||||
|
@ -224,6 +226,7 @@ class PopupMenuItem<T> extends PopupMenuEntry<T> {
|
|||
this.height = kMinInteractiveDimension,
|
||||
this.padding,
|
||||
this.textStyle,
|
||||
this.labelTextStyle,
|
||||
this.mouseCursor,
|
||||
required this.child,
|
||||
}) : assert(enabled != null),
|
||||
|
@ -263,6 +266,16 @@ class PopupMenuItem<T> extends PopupMenuEntry<T> {
|
|||
/// of [ThemeData.textTheme] is used.
|
||||
final TextStyle? textStyle;
|
||||
|
||||
/// The label style of the popup menu item.
|
||||
///
|
||||
/// When [ThemeData.useMaterial3] is true, this styles the text of the popup menu item.
|
||||
///
|
||||
/// If this property is null, then [PopupMenuThemeData.labelTextStyle] is used.
|
||||
/// If [PopupMenuThemeData.labelTextStyle] is also null, then [TextTheme.labelLarge]
|
||||
/// is used with the [ColorScheme.onSurface] color when popup menu item is enabled and
|
||||
/// the [ColorScheme.onSurface] color with 0.38 opacity when the popup menu item is disabled.
|
||||
final MaterialStateProperty<TextStyle?>? labelTextStyle;
|
||||
|
||||
/// {@template flutter.material.popupmenu.mouseCursor}
|
||||
/// The cursor for a mouse pointer when it enters or is hovering over the
|
||||
/// widget.
|
||||
|
@ -336,9 +349,20 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
|
|||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
TextStyle style = widget.textStyle ?? popupMenuTheme.textStyle ?? theme.textTheme.titleMedium!;
|
||||
final PopupMenuThemeData defaults = theme.useMaterial3 ? _PopupMenuDefaultsM3(context) : _PopupMenuDefaultsM2(context);
|
||||
final Set<MaterialState> states = <MaterialState>{
|
||||
if (!widget.enabled) MaterialState.disabled,
|
||||
};
|
||||
|
||||
if (!widget.enabled) {
|
||||
TextStyle style = theme.useMaterial3
|
||||
? (widget.labelTextStyle?.resolve(states)
|
||||
?? popupMenuTheme.labelTextStyle?.resolve(states)!
|
||||
?? defaults.labelTextStyle!.resolve(states)!)
|
||||
: (widget.textStyle
|
||||
?? popupMenuTheme.textStyle
|
||||
?? defaults.textStyle!);
|
||||
|
||||
if (!widget.enabled && !theme.useMaterial3) {
|
||||
style = style.copyWith(color: theme.disabledColor);
|
||||
}
|
||||
|
||||
|
@ -537,7 +561,9 @@ class _PopupMenu<T> extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final double unit = 1.0 / (route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
|
||||
final List<Widget> children = <Widget>[];
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
final PopupMenuThemeData defaults = theme.useMaterial3 ? _PopupMenuDefaultsM3(context) : _PopupMenuDefaultsM2(context);
|
||||
|
||||
for (int i = 0; i < route.items.length; i += 1) {
|
||||
final double start = (i + 1) * unit;
|
||||
|
@ -598,11 +624,13 @@ class _PopupMenu<T> extends StatelessWidget {
|
|||
return FadeTransition(
|
||||
opacity: opacity.animate(route.animation!),
|
||||
child: Material(
|
||||
shape: route.shape ?? popupMenuTheme.shape,
|
||||
color: route.color ?? popupMenuTheme.color,
|
||||
shape: route.shape ?? popupMenuTheme.shape ?? defaults.shape,
|
||||
color: route.color ?? popupMenuTheme.color ?? defaults.color,
|
||||
clipBehavior: clipBehavior,
|
||||
type: MaterialType.card,
|
||||
elevation: route.elevation ?? popupMenuTheme.elevation ?? 8.0,
|
||||
elevation: route.elevation ?? popupMenuTheme.elevation ?? defaults.elevation!,
|
||||
shadowColor: route.shadowColor ?? popupMenuTheme.shadowColor ?? defaults.shadowColor,
|
||||
surfaceTintColor: route.surfaceTintColor ?? popupMenuTheme.surfaceTintColor ?? defaults.surfaceTintColor,
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
widthFactor: width.evaluate(route.animation!),
|
||||
|
@ -757,6 +785,8 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||
required this.items,
|
||||
this.initialValue,
|
||||
this.elevation,
|
||||
this.surfaceTintColor,
|
||||
this.shadowColor,
|
||||
required this.barrierLabel,
|
||||
this.semanticLabel,
|
||||
this.shape,
|
||||
|
@ -771,6 +801,8 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||
final List<Size?> itemSizes;
|
||||
final T? initialValue;
|
||||
final double? elevation;
|
||||
final Color? surfaceTintColor;
|
||||
final Color? shadowColor;
|
||||
final String? semanticLabel;
|
||||
final ShapeBorder? shape;
|
||||
final Color? color;
|
||||
|
@ -911,6 +943,8 @@ Future<T?> showMenu<T>({
|
|||
required List<PopupMenuEntry<T>> items,
|
||||
T? initialValue,
|
||||
double? elevation,
|
||||
Color? shadowColor,
|
||||
Color? surfaceTintColor,
|
||||
String? semanticLabel,
|
||||
ShapeBorder? shape,
|
||||
Color? color,
|
||||
|
@ -941,6 +975,8 @@ Future<T?> showMenu<T>({
|
|||
items: items,
|
||||
initialValue: initialValue,
|
||||
elevation: elevation,
|
||||
shadowColor: shadowColor,
|
||||
surfaceTintColor: surfaceTintColor,
|
||||
semanticLabel: semanticLabel,
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
shape: shape,
|
||||
|
@ -1006,6 +1042,8 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||
this.onCanceled,
|
||||
this.tooltip,
|
||||
this.elevation,
|
||||
this.shadowColor,
|
||||
this.surfaceTintColor,
|
||||
this.padding = const EdgeInsets.all(8.0),
|
||||
this.child,
|
||||
this.splashRadius,
|
||||
|
@ -1058,6 +1096,22 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||
/// Defaults to 8, the appropriate elevation for popup menus.
|
||||
final double? elevation;
|
||||
|
||||
/// The color used to paint the shadow below the menu.
|
||||
///
|
||||
/// If null then the ambient [PopupMenuThemeData.shadowColor] is used.
|
||||
/// If that is null too, then the overall theme's [ThemeData.shadowColor]
|
||||
/// (default black) is used.
|
||||
final Color? shadowColor;
|
||||
|
||||
/// The color used as an overlay on [color] to indicate elevation.
|
||||
///
|
||||
/// If null, [PopupMenuThemeData.surfaceTintColor] is used. If that
|
||||
/// is also null, the default value is [ColorScheme.surfaceTint].
|
||||
///
|
||||
/// See [Material.surfaceTintColor] for more details on how this
|
||||
/// overlay is applied.
|
||||
final Color? surfaceTintColor;
|
||||
|
||||
/// Matches IconButton's 8 dps padding by default. In some cases, notably where
|
||||
/// this button appears as the trailing element of a list item, it's useful to be able
|
||||
/// to set the padding to zero.
|
||||
|
@ -1207,6 +1261,8 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
|||
showMenu<T?>(
|
||||
context: context,
|
||||
elevation: widget.elevation ?? popupMenuTheme.elevation,
|
||||
shadowColor: widget.shadowColor ?? popupMenuTheme.shadowColor,
|
||||
surfaceTintColor: widget.surfaceTintColor ?? popupMenuTheme.surfaceTintColor,
|
||||
items: items,
|
||||
initialValue: widget.initialValue,
|
||||
position: position,
|
||||
|
@ -1240,6 +1296,7 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final IconThemeData iconTheme = IconTheme.of(context);
|
||||
final bool enableFeedback = widget.enableFeedback
|
||||
?? PopupMenuTheme.of(context).enableFeedback
|
||||
?? true;
|
||||
|
@ -1263,7 +1320,8 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
|||
icon: widget.icon ?? Icon(Icons.adaptive.more),
|
||||
padding: widget.padding,
|
||||
splashRadius: widget.splashRadius,
|
||||
iconSize: widget.iconSize,
|
||||
iconSize: widget.iconSize ?? iconTheme.size,
|
||||
color: widget.color ?? iconTheme.color,
|
||||
tooltip: widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
|
||||
onPressed: widget.enabled ? showButtonMenu : null,
|
||||
enableFeedback: enableFeedback,
|
||||
|
@ -1290,3 +1348,57 @@ class _EffectiveMouseCursor extends MaterialStateMouseCursor {
|
|||
@override
|
||||
String get debugDescription => 'MaterialStateMouseCursor(PopupMenuItemState)';
|
||||
}
|
||||
|
||||
class _PopupMenuDefaultsM2 extends PopupMenuThemeData {
|
||||
_PopupMenuDefaultsM2(this.context)
|
||||
: super(elevation: 8.0);
|
||||
|
||||
final BuildContext context;
|
||||
late final ThemeData _theme = Theme.of(context);
|
||||
late final TextTheme _textTheme = _theme.textTheme;
|
||||
|
||||
@override
|
||||
TextStyle? get textStyle => _textTheme.subtitle1;
|
||||
}
|
||||
|
||||
// BEGIN GENERATED TOKEN PROPERTIES - PopupMenu
|
||||
|
||||
// Do not edit by hand. The code between the "BEGIN GENERATED" and
|
||||
// "END GENERATED" comments are generated from data in the Material
|
||||
// Design token database by the script:
|
||||
// dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||||
|
||||
// Token database version: v0_132
|
||||
|
||||
class _PopupMenuDefaultsM3 extends PopupMenuThemeData {
|
||||
_PopupMenuDefaultsM3(this.context)
|
||||
: super(elevation: 3.0);
|
||||
|
||||
final BuildContext context;
|
||||
late final ThemeData _theme = Theme.of(context);
|
||||
late final ColorScheme _colors = _theme.colorScheme;
|
||||
late final TextTheme _textTheme = _theme.textTheme;
|
||||
|
||||
@override MaterialStateProperty<TextStyle?>? get labelTextStyle {
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
final TextStyle style = _textTheme.labelLarge!;
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return style.apply(color: _colors.onSurface.withOpacity(0.38));
|
||||
}
|
||||
return style.apply(color: _colors.onSurface);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Color? get color => _colors.surface;
|
||||
|
||||
@override
|
||||
Color? get shadowColor => _colors.shadow;
|
||||
|
||||
@override
|
||||
Color? get surfaceTintColor => _colors.surfaceTint;
|
||||
|
||||
@override
|
||||
ShapeBorder? get shape => const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
|
||||
}
|
||||
// END GENERATED TOKEN PROPERTIES - PopupMenu
|
||||
|
|
|
@ -48,7 +48,10 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
this.color,
|
||||
this.shape,
|
||||
this.elevation,
|
||||
this.shadowColor,
|
||||
this.surfaceTintColor,
|
||||
this.textStyle,
|
||||
this.labelTextStyle,
|
||||
this.enableFeedback,
|
||||
this.mouseCursor,
|
||||
this.position,
|
||||
|
@ -63,9 +66,19 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
/// The elevation of the popup menu.
|
||||
final double? elevation;
|
||||
|
||||
/// The color used to paint shadow below the popup menu.
|
||||
final Color? shadowColor;
|
||||
|
||||
/// The color used as an overlay on [color] of the popup menu.
|
||||
final Color? surfaceTintColor;
|
||||
|
||||
/// The text style of items in the popup menu.
|
||||
final TextStyle? textStyle;
|
||||
|
||||
/// You can use this to specify a different style of the label
|
||||
/// when the popup menu item is enabled and disabled.
|
||||
final MaterialStateProperty<TextStyle?>? labelTextStyle;
|
||||
|
||||
/// If specified, defines the feedback property for [PopupMenuButton].
|
||||
///
|
||||
/// If [PopupMenuButton.enableFeedback] is provided, [enableFeedback] is ignored.
|
||||
|
@ -88,7 +101,10 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
Color? color,
|
||||
ShapeBorder? shape,
|
||||
double? elevation,
|
||||
Color? shadowColor,
|
||||
Color? surfaceTintColor,
|
||||
TextStyle? textStyle,
|
||||
MaterialStateProperty<TextStyle?>? labelTextStyle,
|
||||
bool? enableFeedback,
|
||||
MaterialStateProperty<MouseCursor?>? mouseCursor,
|
||||
PopupMenuPosition? position,
|
||||
|
@ -97,7 +113,10 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
color: color ?? this.color,
|
||||
shape: shape ?? this.shape,
|
||||
elevation: elevation ?? this.elevation,
|
||||
shadowColor: shadowColor ?? this.shadowColor,
|
||||
surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
|
||||
textStyle: textStyle ?? this.textStyle,
|
||||
labelTextStyle: labelTextStyle ?? this.labelTextStyle,
|
||||
enableFeedback: enableFeedback ?? this.enableFeedback,
|
||||
mouseCursor: mouseCursor ?? this.mouseCursor,
|
||||
position: position ?? this.position,
|
||||
|
@ -118,7 +137,10 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
color: Color.lerp(a?.color, b?.color, t),
|
||||
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
|
||||
elevation: lerpDouble(a?.elevation, b?.elevation, t),
|
||||
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
|
||||
surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
|
||||
textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
|
||||
labelTextStyle: MaterialStateProperty.lerp<TextStyle?>(a?.labelTextStyle, b?.labelTextStyle, t, TextStyle.lerp),
|
||||
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
|
||||
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
|
||||
position: t < 0.5 ? a?.position : b?.position,
|
||||
|
@ -130,7 +152,10 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
color,
|
||||
shape,
|
||||
elevation,
|
||||
shadowColor,
|
||||
surfaceTintColor,
|
||||
textStyle,
|
||||
labelTextStyle,
|
||||
enableFeedback,
|
||||
mouseCursor,
|
||||
position,
|
||||
|
@ -145,10 +170,13 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
return false;
|
||||
}
|
||||
return other is PopupMenuThemeData
|
||||
&& other.elevation == elevation
|
||||
&& other.color == color
|
||||
&& other.shape == shape
|
||||
&& other.elevation == elevation
|
||||
&& other.shadowColor == shadowColor
|
||||
&& other.surfaceTintColor == surfaceTintColor
|
||||
&& other.textStyle == textStyle
|
||||
&& other.labelTextStyle == labelTextStyle
|
||||
&& other.enableFeedback == enableFeedback
|
||||
&& other.mouseCursor == mouseCursor
|
||||
&& other.position == position;
|
||||
|
@ -160,7 +188,10 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
properties.add(ColorProperty('color', color, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
|
||||
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
|
||||
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
|
||||
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('text style', textStyle, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<TextStyle?>>('labelTextStyle', labelTextStyle, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
|
||||
properties.add(EnumProperty<PopupMenuPosition?>('position', position, defaultValue: null));
|
||||
|
|
|
@ -7,13 +7,40 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
PopupMenuThemeData _popupMenuTheme() {
|
||||
return const PopupMenuThemeData(
|
||||
PopupMenuThemeData _popupMenuThemeM2() {
|
||||
return PopupMenuThemeData(
|
||||
color: Colors.orange,
|
||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
shape: const BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
elevation: 12.0,
|
||||
textStyle: TextStyle(color: Color(0xffffffff), textBaseline: TextBaseline.alphabetic),
|
||||
position: PopupMenuPosition.under,
|
||||
textStyle: const TextStyle(color: Color(0xffffffff), textBaseline: TextBaseline.alphabetic),
|
||||
mouseCursor: MaterialStateProperty.resolveWith<MouseCursor?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return SystemMouseCursors.contextMenu;
|
||||
}
|
||||
return SystemMouseCursors.alias;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
PopupMenuThemeData _popupMenuThemeM3() {
|
||||
return PopupMenuThemeData(
|
||||
color: Colors.orange,
|
||||
shape: const BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
elevation: 12.0,
|
||||
shadowColor: const Color(0xff00ff00),
|
||||
surfaceTintColor: const Color(0xff00ff00),
|
||||
labelTextStyle: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return const TextStyle(color: Color(0xfff99ff0), fontSize: 12.0);
|
||||
}
|
||||
return const TextStyle(color: Color(0xfff12099), fontSize: 17.0);
|
||||
}),
|
||||
mouseCursor: MaterialStateProperty.resolveWith<MouseCursor?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return SystemMouseCursors.contextMenu;
|
||||
}
|
||||
return SystemMouseCursors.alias;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -28,9 +55,12 @@ void main() {
|
|||
expect(popupMenuTheme.color, null);
|
||||
expect(popupMenuTheme.shape, null);
|
||||
expect(popupMenuTheme.elevation, null);
|
||||
expect(popupMenuTheme.shadowColor, null);
|
||||
expect(popupMenuTheme.surfaceTintColor, null);
|
||||
expect(popupMenuTheme.textStyle, null);
|
||||
expect(popupMenuTheme.labelTextStyle, null);
|
||||
expect(popupMenuTheme.enableFeedback, null);
|
||||
expect(popupMenuTheme.mouseCursor, null);
|
||||
expect(popupMenuTheme.position, null);
|
||||
});
|
||||
|
||||
testWidgets('Default PopupMenuThemeData debugFillProperties', (WidgetTester tester) async {
|
||||
|
@ -47,13 +77,20 @@ void main() {
|
|||
|
||||
testWidgets('PopupMenuThemeData implements debugFillProperties', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const PopupMenuThemeData(
|
||||
color: Color(0xFFFFFFFF),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))),
|
||||
PopupMenuThemeData(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))),
|
||||
elevation: 2.0,
|
||||
textStyle: TextStyle(color: Color(0xffffffff)),
|
||||
shadowColor: const Color(0xff00ff00),
|
||||
surfaceTintColor: const Color(0xff00ff00),
|
||||
textStyle: const TextStyle(color: Color(0xffffffff)),
|
||||
labelTextStyle: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return const TextStyle(color: Color(0xfff99ff0), fontSize: 12.0);
|
||||
}
|
||||
return const TextStyle(color: Color(0xfff12099), fontSize: 17.0);
|
||||
}),
|
||||
mouseCursor: MaterialStateMouseCursor.clickable,
|
||||
position: PopupMenuPosition.over,
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
|
@ -65,19 +102,23 @@ void main() {
|
|||
'color: Color(0xffffffff)',
|
||||
'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(2.0))',
|
||||
'elevation: 2.0',
|
||||
'shadowColor: Color(0xff00ff00)',
|
||||
'surfaceTintColor: Color(0xff00ff00)',
|
||||
'text style: TextStyle(inherit: true, color: Color(0xffffffff))',
|
||||
"labelTextStyle: Instance of '_MaterialStatePropertyWith<TextStyle?>'",
|
||||
'mouseCursor: MaterialStateMouseCursor(clickable)',
|
||||
'position: over'
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('Passing no PopupMenuThemeData returns defaults', (WidgetTester tester) async {
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key popupItemKey = UniqueKey();
|
||||
final Key enabledPopupItemKey = UniqueKey();
|
||||
final Key disabledPopupItemKey = UniqueKey();
|
||||
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(),
|
||||
theme: theme,
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
|
@ -91,8 +132,14 @@ void main() {
|
|||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<void>>[
|
||||
PopupMenuItem<void>(
|
||||
key: popupItemKey,
|
||||
child: const Text('Example'),
|
||||
key: enabledPopupItemKey,
|
||||
child: const Text('Enabled PopupMenuItem'),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem<void>(
|
||||
key: disabledPopupItemKey,
|
||||
enabled: false,
|
||||
child: const Text('Disabled PopupMenuItem'),
|
||||
),
|
||||
];
|
||||
},
|
||||
|
@ -116,37 +163,63 @@ void main() {
|
|||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, null);
|
||||
expect(button.shape, null);
|
||||
expect(button.elevation, 8.0);
|
||||
expect(button.color, theme.colorScheme.surface);
|
||||
expect(button.shadowColor, theme.colorScheme.shadow);
|
||||
expect(button.surfaceTintColor, theme.colorScheme.surfaceTint);
|
||||
expect(button.shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.0)));
|
||||
expect(button.elevation, 3.0);
|
||||
|
||||
/// The last DefaultTextStyle widget under popupItemKey is the
|
||||
/// [PopupMenuItem] specified above, so by finding the last descendent of
|
||||
/// popupItemKey that is of type DefaultTextStyle, this code retrieves the
|
||||
/// built [PopupMenuItem].
|
||||
final DefaultTextStyle text = tester.widget<DefaultTextStyle>(
|
||||
final DefaultTextStyle enabledText = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupItemKey),
|
||||
of: find.byKey(enabledPopupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(text.style.fontFamily, 'Roboto');
|
||||
expect(text.style.color, const Color(0xdd000000));
|
||||
expect(text.style.color, const Color(0xdd000000));
|
||||
expect(enabledText.style.fontFamily, 'Roboto');
|
||||
expect(enabledText.style.color, const Color(0xff000000));
|
||||
/// Test disabled text color
|
||||
final DefaultTextStyle disabledText = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(disabledPopupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(disabledText.style.color, theme.colorScheme.onSurface.withOpacity(0.38));
|
||||
|
||||
final Offset topLeftButton = tester.getTopLeft(find.byType(PopupMenuButton<void>));
|
||||
final Offset topLeftMenu = tester.getTopLeft(find.byWidget(button));
|
||||
expect(topLeftMenu, topLeftButton);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(tester.getCenter(find.byKey(disabledPopupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.basic,
|
||||
);
|
||||
await gesture.down(tester.getCenter(find.byKey(enabledPopupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.click,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Popup menu uses values from PopupMenuThemeData', (WidgetTester tester) async {
|
||||
final PopupMenuThemeData popupMenuTheme = _popupMenuTheme();
|
||||
final PopupMenuThemeData popupMenuTheme = _popupMenuThemeM3();
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key popupItemKey = UniqueKey();
|
||||
final Key enabledPopupItemKey = UniqueKey();
|
||||
final Key disabledPopupItemKey = UniqueKey();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(popupMenuTheme: popupMenuTheme),
|
||||
theme: ThemeData(useMaterial3: true, popupMenuTheme: popupMenuTheme),
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
|
@ -160,7 +233,117 @@ void main() {
|
|||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<Object>>[
|
||||
PopupMenuItem<Object>(
|
||||
key: disabledPopupItemKey,
|
||||
enabled: false,
|
||||
child: const Text('disabled'),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem<Object>(
|
||||
key: enabledPopupItemKey,
|
||||
onTap: () { },
|
||||
child: const Text('enabled'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
/// [PopupMenuButton].
|
||||
final Material button = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupButtonApp),
|
||||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, Colors.orange);
|
||||
expect(button.surfaceTintColor, const Color(0xff00ff00));
|
||||
expect(button.shadowColor, const Color(0xff00ff00));
|
||||
expect(button.shape, const BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))));
|
||||
expect(button.elevation, 12.0);
|
||||
|
||||
final DefaultTextStyle enabledText = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(enabledPopupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(
|
||||
enabledText.style,
|
||||
popupMenuTheme.labelTextStyle?.resolve(enabled),
|
||||
);
|
||||
/// Test disabled text color
|
||||
final DefaultTextStyle disabledText = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(disabledPopupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(
|
||||
disabledText.style,
|
||||
popupMenuTheme.labelTextStyle?.resolve(disabled),
|
||||
);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(tester.getCenter(find.byKey(disabledPopupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
popupMenuTheme.mouseCursor?.resolve(disabled),
|
||||
);
|
||||
await gesture.down(tester.getCenter(find.byKey(enabledPopupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
popupMenuTheme.mouseCursor?.resolve(enabled),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Popup menu widget properties take priority over theme', (WidgetTester tester) async {
|
||||
final PopupMenuThemeData popupMenuTheme = _popupMenuThemeM3();
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key popupItemKey = UniqueKey();
|
||||
|
||||
const Color color = Colors.purple;
|
||||
const Color surfaceTintColor = Colors.amber;
|
||||
const Color shadowColor = Colors.green;
|
||||
const ShapeBorder shape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(9.0)),
|
||||
);
|
||||
const double elevation = 7.0;
|
||||
const TextStyle textStyle = TextStyle(color: Color(0xffffffef), fontSize: 19.0);
|
||||
const MouseCursor cursor = SystemMouseCursors.forbidden;
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(useMaterial3: true, popupMenuTheme: popupMenuTheme),
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
PopupMenuButton<void>(
|
||||
key: popupButtonKey,
|
||||
elevation: elevation,
|
||||
shadowColor: shadowColor,
|
||||
surfaceTintColor: surfaceTintColor,
|
||||
color: color,
|
||||
shape: shape,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<void>>[
|
||||
PopupMenuItem<void>(
|
||||
key: popupItemKey,
|
||||
labelTextStyle: MaterialStateProperty.all<TextStyle>(textStyle),
|
||||
mouseCursor: cursor,
|
||||
child: const Text('Example'),
|
||||
),
|
||||
];
|
||||
|
@ -174,85 +357,6 @@ void main() {
|
|||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
/// [PopupMenuButton].
|
||||
final Material button = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupButtonApp),
|
||||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, popupMenuTheme.color);
|
||||
expect(button.shape, popupMenuTheme.shape);
|
||||
expect(button.elevation, popupMenuTheme.elevation);
|
||||
|
||||
/// The last DefaultTextStyle widget under popupItemKey is the
|
||||
/// [PopupMenuItem] specified above, so by finding the last descendent of
|
||||
/// popupItemKey that is of type DefaultTextStyle, this code retrieves the
|
||||
/// built [PopupMenuItem].
|
||||
final DefaultTextStyle text = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(text.style, popupMenuTheme.textStyle);
|
||||
|
||||
final Offset bottomLeftButton = tester.getBottomLeft(find.byType(PopupMenuButton<void>));
|
||||
final Offset topLeftMenu = tester.getTopLeft(find.byWidget(button));
|
||||
expect(topLeftMenu, bottomLeftButton);
|
||||
});
|
||||
|
||||
testWidgets('Popup menu widget properties take priority over theme', (WidgetTester tester) async {
|
||||
final PopupMenuThemeData popupMenuTheme = _popupMenuTheme();
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key popupItemKey = UniqueKey();
|
||||
|
||||
const Color color = Colors.purple;
|
||||
const ShapeBorder shape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(9.0)),
|
||||
);
|
||||
const double elevation = 7.0;
|
||||
const TextStyle textStyle = TextStyle(color: Color(0x00000000), textBaseline: TextBaseline.alphabetic);
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(popupMenuTheme: popupMenuTheme),
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
// The padding makes sure the menu has enough space around it to
|
||||
// get properly aligned when displayed (`_kMenuScreenPadding`).
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: PopupMenuButton<void>(
|
||||
key: popupButtonKey,
|
||||
elevation: elevation,
|
||||
color: color,
|
||||
shape: shape,
|
||||
position: PopupMenuPosition.over,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<void>>[
|
||||
PopupMenuItem<void>(
|
||||
key: popupItemKey,
|
||||
textStyle: textStyle,
|
||||
child: const Text('Example'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
|
@ -266,6 +370,8 @@ void main() {
|
|||
expect(button.color, color);
|
||||
expect(button.shape, shape);
|
||||
expect(button.elevation, elevation);
|
||||
expect(button.shadowColor, shadowColor);
|
||||
expect(button.surfaceTintColor, surfaceTintColor);
|
||||
|
||||
/// The last DefaultTextStyle widget under popupItemKey is the
|
||||
/// [PopupMenuItem] specified above, so by finding the last descendent of
|
||||
|
@ -279,45 +385,145 @@ void main() {
|
|||
);
|
||||
expect(text.style, textStyle);
|
||||
|
||||
final Offset topLeftButton = tester.getTopLeft(find.byType(PopupMenuButton<void>));
|
||||
final Offset topLeftMenu = tester.getTopLeft(find.byWidget(button));
|
||||
expect(topLeftMenu, topLeftButton);
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(tester.getCenter(find.byKey(popupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), cursor);
|
||||
});
|
||||
|
||||
testWidgets('ThemeData.popupMenuTheme properties are utilized', (WidgetTester tester) async {
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key enabledPopupItemKey = UniqueKey();
|
||||
final Key disabledPopupItemKey = UniqueKey();
|
||||
group('Material 2', () {
|
||||
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
|
||||
// is turned on by default, these tests can be removed.
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
PopupMenuTheme(
|
||||
data: PopupMenuThemeData(
|
||||
color: Colors.pink,
|
||||
shape: const BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
elevation: 6.0,
|
||||
textStyle: const TextStyle(color: Color(0xfffff000), textBaseline: TextBaseline.alphabetic),
|
||||
mouseCursor: MaterialStateProperty.resolveWith<MouseCursor?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return SystemMouseCursors.contextMenu;
|
||||
}
|
||||
return SystemMouseCursors.alias;
|
||||
}),
|
||||
testWidgets('Passing no PopupMenuThemeData returns defaults', (WidgetTester tester) async {
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key enabledPopupItemKey = UniqueKey();
|
||||
final Key disabledPopupItemKey = UniqueKey();
|
||||
final ThemeData theme = ThemeData();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
// The padding makes sure the menu has enough space around it to
|
||||
// get properly aligned when displayed (`_kMenuScreenPadding`).
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: PopupMenuButton<void>(
|
||||
key: popupButtonKey,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<void>>[
|
||||
PopupMenuItem<void>(
|
||||
key: enabledPopupItemKey,
|
||||
child: const Text('Enabled PopupMenuItem'),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem<void>(
|
||||
key: disabledPopupItemKey,
|
||||
enabled: false,
|
||||
child: const Text('Disabled PopupMenuItem'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
child: PopupMenuButton<void>(
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
/// [PopupMenuButton].
|
||||
final Material button = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupButtonApp),
|
||||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, null);
|
||||
expect(button.shape, null);
|
||||
expect(button.elevation, 8.0);
|
||||
|
||||
/// The last DefaultTextStyle widget under popupItemKey is the
|
||||
/// [PopupMenuItem] specified above, so by finding the last descendent of
|
||||
/// popupItemKey that is of type DefaultTextStyle, this code retrieves the
|
||||
/// built [PopupMenuItem].
|
||||
final DefaultTextStyle enabledText = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(enabledPopupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(enabledText.style.fontFamily, 'Roboto');
|
||||
expect(enabledText.style.color, const Color(0xdd000000));
|
||||
/// Test disabled text color
|
||||
final DefaultTextStyle disabledText = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(disabledPopupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(disabledText.style.color, theme.disabledColor);
|
||||
|
||||
final Offset topLeftButton = tester.getTopLeft(find.byType(PopupMenuButton<void>));
|
||||
final Offset topLeftMenu = tester.getTopLeft(find.byWidget(button));
|
||||
expect(topLeftMenu, topLeftButton);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(tester.getCenter(find.byKey(disabledPopupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.basic,
|
||||
);
|
||||
await gesture.down(tester.getCenter(find.byKey(enabledPopupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.click,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Popup menu uses values from PopupMenuThemeData', (WidgetTester tester) async {
|
||||
final PopupMenuThemeData popupMenuTheme = _popupMenuThemeM2();
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key enabledPopupItemKey = UniqueKey();
|
||||
final Key disabledPopupItemKey = UniqueKey();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(popupMenuTheme: popupMenuTheme),
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
PopupMenuButton<void>(
|
||||
// The padding is used in the positioning of the menu when the
|
||||
// position is `PopupMenuPosition.under`. Setting it to zero makes
|
||||
// it easier to test.
|
||||
padding: EdgeInsets.zero,
|
||||
key: popupButtonKey,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<void>>[
|
||||
PopupMenuItem<void>(
|
||||
return <PopupMenuEntry<Object>>[
|
||||
PopupMenuItem<Object>(
|
||||
key: disabledPopupItemKey,
|
||||
enabled: false,
|
||||
child: const Text('disabled'),
|
||||
),
|
||||
PopupMenuItem<void>(
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem<Object>(
|
||||
key: enabledPopupItemKey,
|
||||
onTap: () { },
|
||||
child: const Text('enabled'),
|
||||
|
@ -325,44 +531,142 @@ void main() {
|
|||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
));
|
||||
|
||||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
/// [PopupMenuButton].
|
||||
final Material button = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupButtonApp),
|
||||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, Colors.pink);
|
||||
expect(button.shape, const BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))));
|
||||
expect(button.elevation, 6.0);
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
/// [PopupMenuButton].
|
||||
final Material button = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupButtonApp),
|
||||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, popupMenuTheme.color);
|
||||
expect(button.shape, popupMenuTheme.shape);
|
||||
expect(button.elevation, popupMenuTheme.elevation);
|
||||
|
||||
final DefaultTextStyle text = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(enabledPopupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
),
|
||||
);
|
||||
expect(text.style.color, const Color(0xfffff000));
|
||||
/// The last DefaultTextStyle widget under popupItemKey is the
|
||||
/// [PopupMenuItem] specified above, so by finding the last descendent of
|
||||
/// popupItemKey that is of type DefaultTextStyle, this code retrieves the
|
||||
/// built [PopupMenuItem].
|
||||
final DefaultTextStyle text = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(enabledPopupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(text.style, popupMenuTheme.textStyle);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(tester.getCenter(find.byKey(disabledPopupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.contextMenu);
|
||||
await gesture.down(tester.getCenter(find.byKey(enabledPopupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.alias);
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(tester.getCenter(find.byKey(disabledPopupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
popupMenuTheme.mouseCursor?.resolve(disabled),
|
||||
);
|
||||
await gesture.down(tester.getCenter(find.byKey(enabledPopupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
popupMenuTheme.mouseCursor?.resolve(enabled),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Popup menu widget properties take priority over theme', (WidgetTester tester) async {
|
||||
final PopupMenuThemeData popupMenuTheme = _popupMenuThemeM2();
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key popupItemKey = UniqueKey();
|
||||
|
||||
const Color color = Colors.purple;
|
||||
const Color surfaceTintColor = Colors.amber;
|
||||
const Color shadowColor = Colors.green;
|
||||
const ShapeBorder shape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(9.0)),
|
||||
);
|
||||
const double elevation = 7.0;
|
||||
const TextStyle textStyle = TextStyle(color: Color(0xffffffef), fontSize: 19.0);
|
||||
const MouseCursor cursor = SystemMouseCursors.forbidden;
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(useMaterial3: true, popupMenuTheme: popupMenuTheme),
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
PopupMenuButton<void>(
|
||||
key: popupButtonKey,
|
||||
elevation: elevation,
|
||||
shadowColor: shadowColor,
|
||||
surfaceTintColor: surfaceTintColor,
|
||||
color: color,
|
||||
shape: shape,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<void>>[
|
||||
PopupMenuItem<void>(
|
||||
key: popupItemKey,
|
||||
labelTextStyle: MaterialStateProperty.all<TextStyle>(textStyle),
|
||||
mouseCursor: cursor,
|
||||
child: const Text('Example'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
/// [PopupMenuButton].
|
||||
final Material button = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupButtonApp),
|
||||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, color);
|
||||
expect(button.shape, shape);
|
||||
expect(button.elevation, elevation);
|
||||
expect(button.shadowColor, shadowColor);
|
||||
expect(button.surfaceTintColor, surfaceTintColor);
|
||||
|
||||
/// The last DefaultTextStyle widget under popupItemKey is the
|
||||
/// [PopupMenuItem] specified above, so by finding the last descendent of
|
||||
/// popupItemKey that is of type DefaultTextStyle, this code retrieves the
|
||||
/// built [PopupMenuItem].
|
||||
final DefaultTextStyle text = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(text.style, textStyle);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(tester.getCenter(find.byKey(popupItemKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), cursor);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Set<MaterialState> enabled = <MaterialState>{};
|
||||
Set<MaterialState> disabled = <MaterialState>{MaterialState.disabled};
|
||||
|
|
Loading…
Reference in a new issue