Commit 63992e4f authored by lisa-liao's avatar lisa-liao Committed by rami-a

Add PopupMenuTheme to enable theming color, shape, elevation, text style of Menu (#36088)

* [Menu] Create Menu theme

* [Menu] Create Menu theme

* [Menu] Formatting changes for Menu theme

* [Menu] Fix spacing difference in theme_data.dart.

* [Menu] Fix spacing difference in theme_data.dart.

* Specifying types

* Formatting changes

* Address PR feedback

* Formatting changes

* Address PR feedback

* Add inherited widget

* Add inherited widget

* Address PR feedback and add inherited widget.

* Formatting changes.

* Address PR feedback

* Address PR feedback

* Address PR feedback

* Address PR feedback
parent a3a350df
......@@ -83,6 +83,7 @@ export 'src/material/page.dart';
export 'src/material/page_transitions_theme.dart';
export 'src/material/paginated_data_table.dart';
export 'src/material/popup_menu.dart';
export 'src/material/popup_menu_theme.dart';
export 'src/material/progress_indicator.dart';
export 'src/material/radio.dart';
export 'src/material/radio_list_tile.dart';
......
......@@ -16,6 +16,7 @@ import 'ink_well.dart';
import 'list_tile.dart';
import 'material.dart';
import 'material_localizations.dart';
import 'popup_menu_theme.dart';
import 'theme.dart';
// Examples can assume:
......@@ -171,6 +172,7 @@ class PopupMenuItem<T> extends PopupMenuEntry<T> {
this.value,
this.enabled = true,
this.height = _kMenuItemHeight,
this.textStyle,
@required this.child,
}) : assert(enabled != null),
assert(height != null),
......@@ -191,6 +193,12 @@ class PopupMenuItem<T> extends PopupMenuEntry<T> {
@override
final double height;
/// The text style of the popup menu entry.
///
/// If this property is null, then [PopupMenuThemeData.textStyle] is used.
/// If [PopupMenuThemeData.textStyle] is also null, then [ThemeData.textTheme.subhead] is used.
final TextStyle textStyle;
/// The widget below this widget in the tree.
///
/// Typically a single-line [ListTile] (for menus with icons) or a [Text]. An
......@@ -245,7 +253,9 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
TextStyle style = theme.textTheme.subhead;
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
TextStyle style = widget.textStyle ?? popupMenuTheme.textStyle ?? theme.textTheme.subhead;
if (!widget.enabled)
style = style.copyWith(color: theme.disabledColor);
......@@ -433,6 +443,7 @@ 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 PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
for (int i = 0; i < route.items.length; i += 1) {
final double start = (i + 1) * unit;
......@@ -486,8 +497,10 @@ class _PopupMenu<T> extends StatelessWidget {
return Opacity(
opacity: opacity.evaluate(route.animation),
child: Material(
shape: route.shape ?? popupMenuTheme.shape,
color: route.color ?? popupMenuTheme.color,
type: MaterialType.card,
elevation: route.elevation,
elevation: route.elevation ?? popupMenuTheme.elevation ?? 8.0,
child: Align(
alignment: AlignmentDirectional.topEnd,
widthFactor: width.evaluate(route.animation),
......@@ -589,8 +602,11 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
this.initialValue,
this.elevation,
this.theme,
this.popupMenuTheme,
this.barrierLabel,
this.semanticLabel,
this.shape,
this.color,
});
final RelativeRect position;
......@@ -599,6 +615,9 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
final double elevation;
final ThemeData theme;
final String semanticLabel;
final ShapeBorder shape;
final Color color;
final PopupMenuThemeData popupMenuTheme;
@override
Animation<double> createAnimation() {
......@@ -636,6 +655,8 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
}
Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
if (popupMenuTheme != null)
menu = PopupMenuTheme(textStyle: popupMenuTheme.textStyle, child: menu);
if (theme != null)
menu = Theme(data: theme, child: menu);
......@@ -717,8 +738,10 @@ Future<T> showMenu<T>({
@required RelativeRect position,
@required List<PopupMenuEntry<T>> items,
T initialValue,
double elevation = 8.0,
double elevation,
String semanticLabel,
ShapeBorder shape,
Color color,
}) {
assert(context != null);
assert(position != null);
......@@ -741,7 +764,10 @@ Future<T> showMenu<T>({
elevation: elevation,
semanticLabel: label,
theme: Theme.of(context, shadowThemeOnly: true),
popupMenuTheme: PopupMenuTheme.of(context),
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
shape: shape,
color: color,
));
}
......@@ -826,12 +852,14 @@ class PopupMenuButton<T> extends StatefulWidget {
this.onSelected,
this.onCanceled,
this.tooltip,
this.elevation = 8.0,
this.elevation,
this.padding = const EdgeInsets.all(8.0),
this.child,
this.icon,
this.offset = Offset.zero,
this.enabled = true,
this.shape,
this.color,
}) : assert(itemBuilder != null),
assert(offset != null),
assert(enabled != null),
......@@ -898,12 +926,28 @@ class PopupMenuButton<T> extends StatefulWidget {
/// but doesn't currently have anything to show in the menu.
final bool enabled;
/// If provided, the shape used for the menu.
///
/// If this property is null, then [PopupMenuThemeData.shape] is used.
/// If [PopupMenuThemeData.shape] is also null, then the default shape for
/// [MaterialType.card] is used. This default shape is a rectangle with
/// rounded edges of BorderRadius.circular(2.0).
final ShapeBorder shape;
/// If provided, the background color used for the menu.
///
/// If this property is null, then [PopupMenuThemeData.color] is used.
/// If [PopupMenuThemeData.color] is also null, then
/// Theme.of(context).cardColor is used.
final Color color;
@override
_PopupMenuButtonState<T> createState() => _PopupMenuButtonState<T>();
}
class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
void showButtonMenu() {
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
final RenderBox button = context.findRenderObject();
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
final RelativeRect position = RelativeRect.fromRect(
......@@ -918,10 +962,12 @@ class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
if (items.isNotEmpty) {
showMenu<T>(
context: context,
elevation: widget.elevation,
elevation: widget.elevation ?? popupMenuTheme.elevation,
items: items,
initialValue: widget.initialValue,
position: position,
shape: widget.shape ?? popupMenuTheme.shape,
color: widget.color ?? popupMenuTheme.color,
)
.then<void>((T newValue) {
if (!mounted)
......
// Copyright 2019 The Chromium 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 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'theme.dart';
/// Defines the visual properties of the routes used to display popup menus
/// as well as [PopupMenuItem] and [PopupMenuDivider] widgets.
///
/// Descendant widgets obtain the current [PopupMenuThemeData] object
/// using `PopupMenuTheme.of(context)`. Instances of
/// [PopupMenuThemeData] can be customized with
/// [PopupMenuThemeData.copyWith].
///
/// Typically, a [PopupMenuThemeData] is specified as part of the
/// overall [Theme] with [ThemeData.popupMenuTheme]. Otherwise,
/// [PopupMenuTheme] can be used to configure its own widget subtree.
///
/// All [PopupMenuThemeData] properties are `null` by default.
/// If any of these properties are null, the popup menu will provide its
/// own defaults.
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
class PopupMenuThemeData extends Diagnosticable {
/// Creates the set of properties used to configure [PopupMenuTheme].
const PopupMenuThemeData({
this.color,
this.shape,
this.elevation,
this.textStyle,
});
/// The background color of the popup menu.
final Color color;
/// The shape of the popup menu.
final ShapeBorder shape;
/// The elevation of the popup menu.
final double elevation;
/// The text style of items in the popup menu.
final TextStyle textStyle;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
PopupMenuThemeData copyWith({
Color color,
ShapeBorder shape,
double elevation,
TextStyle textStyle,
}) {
return PopupMenuThemeData(
color: color ?? this.color,
shape: shape ?? this.shape,
elevation: elevation ?? this.elevation,
textStyle: textStyle ?? this.textStyle,
);
}
/// Linearly interpolate between two popup menu themes.
///
/// If both arguments are null, then null is returned.
///
/// {@macro dart.ui.shadow.lerp}
static PopupMenuThemeData lerp(PopupMenuThemeData a, PopupMenuThemeData b, double t) {
assert(t != null);
if (a == null && b == null)
return null;
return PopupMenuThemeData(
color: Color.lerp(a?.color, b?.color, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
elevation: lerpDouble(a?.elevation, b?.elevation, t),
textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
);
}
@override
int get hashCode {
return hashValues(
color,
shape,
elevation,
textStyle,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
final PopupMenuThemeData typedOther = other;
return typedOther.elevation == elevation
&& typedOther.color == color
&& typedOther.shape == shape
&& typedOther.textStyle == textStyle;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('text style', textStyle, defaultValue: null));
}
}
/// An inherited widget that defines the configuration for
/// popup menus in this widget's subtree.
///
/// Values specified here are used for popup menu properties that are not
/// given an explicit non-null value.
class PopupMenuTheme extends InheritedWidget {
/// Creates a popup menu theme that controls the configurations for
/// popup menus in its widget subtree.
PopupMenuTheme({
Key key,
Color color,
ShapeBorder shape,
double elevation,
TextStyle textStyle,
Widget child,
}) : data = PopupMenuThemeData(
color: color,
shape: shape,
elevation: elevation,
textStyle: textStyle,
),
super(key: key, child: child);
/// The properties for descendant popup menu widgets.
final PopupMenuThemeData data;
/// The closest instance of this class's [data] value that encloses the given
/// context. If there is no ancestor, it returns [ThemeData.popupMenuTheme].
/// Applications can assume that the returned value will not be null.
///
/// Typical usage is as follows:
///
/// ```dart
/// PopupMenuThemeData theme = PopupMenuTheme.of(context);
/// ```
static PopupMenuThemeData of(BuildContext context) {
final PopupMenuTheme popupMenuTheme = context.inheritFromWidgetOfExactType(PopupMenuTheme);
return popupMenuTheme?.data ?? Theme.of(context).popupMenuTheme;
}
@override
bool updateShouldNotify(PopupMenuTheme oldWidget) => data != oldWidget.data;
}
......@@ -23,6 +23,7 @@ import 'ink_splash.dart';
import 'ink_well.dart' show InteractiveInkFeatureFactory;
import 'input_decorator.dart';
import 'page_transitions_theme.dart';
import 'popup_menu_theme.dart';
import 'slider_theme.dart';
import 'snack_bar_theme.dart';
import 'tab_bar_theme.dart';
......@@ -174,6 +175,7 @@ class ThemeData extends Diagnosticable {
CupertinoThemeData cupertinoOverrideTheme,
SnackBarThemeData snackBarTheme,
BottomSheetThemeData bottomSheetTheme,
PopupMenuThemeData popupMenuTheme,
}) {
brightness ??= Brightness.light;
final bool isDark = brightness == Brightness.dark;
......@@ -276,6 +278,7 @@ class ThemeData extends Diagnosticable {
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
snackBarTheme ??= const SnackBarThemeData();
bottomSheetTheme ??= const BottomSheetThemeData();
popupMenuTheme ??= const PopupMenuThemeData();
return ThemeData.raw(
brightness: brightness,
......@@ -336,6 +339,7 @@ class ThemeData extends Diagnosticable {
cupertinoOverrideTheme: cupertinoOverrideTheme,
snackBarTheme: snackBarTheme,
bottomSheetTheme: bottomSheetTheme,
popupMenuTheme: popupMenuTheme,
);
}
......@@ -408,6 +412,7 @@ class ThemeData extends Diagnosticable {
@required this.cupertinoOverrideTheme,
@required this.snackBarTheme,
@required this.bottomSheetTheme,
@required this.popupMenuTheme,
}) : assert(brightness != null),
assert(primaryColor != null),
assert(primaryColorBrightness != null),
......@@ -462,7 +467,8 @@ class ThemeData extends Diagnosticable {
assert(floatingActionButtonTheme != null),
assert(typography != null),
assert(snackBarTheme != null),
assert(bottomSheetTheme != null);
assert(bottomSheetTheme != null),
assert(popupMenuTheme != null);
// Warning: make sure these properties are in the exact same order as in
// hashValues() and in the raw constructor and in the order of fields in
......@@ -788,6 +794,10 @@ class ThemeData extends Diagnosticable {
/// A theme for customizing the color, elevation, and shape of a bottom sheet.
final BottomSheetThemeData bottomSheetTheme;
/// A theme for customizing the color, shape, elevation, and text style of
/// popup menus.
final PopupMenuThemeData popupMenuTheme;
/// Creates a copy of this theme but with the given fields replaced with the new values.
ThemeData copyWith({
Brightness brightness,
......@@ -848,6 +858,7 @@ class ThemeData extends Diagnosticable {
CupertinoThemeData cupertinoOverrideTheme,
SnackBarThemeData snackBarTheme,
BottomSheetThemeData bottomSheetTheme,
PopupMenuThemeData popupMenuTheme,
}) {
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
return ThemeData.raw(
......@@ -909,6 +920,7 @@ class ThemeData extends Diagnosticable {
cupertinoOverrideTheme: cupertinoOverrideTheme ?? this.cupertinoOverrideTheme,
snackBarTheme: snackBarTheme ?? this.snackBarTheme,
bottomSheetTheme: bottomSheetTheme ?? this.bottomSheetTheme,
popupMenuTheme: popupMenuTheme ?? this.popupMenuTheme,
);
}
......@@ -1048,6 +1060,7 @@ class ThemeData extends Diagnosticable {
cupertinoOverrideTheme: t < 0.5 ? a.cupertinoOverrideTheme : b.cupertinoOverrideTheme,
snackBarTheme: SnackBarThemeData.lerp(a.snackBarTheme, b.snackBarTheme, t),
bottomSheetTheme: BottomSheetThemeData.lerp(a.bottomSheetTheme, b.bottomSheetTheme, t),
popupMenuTheme: PopupMenuThemeData.lerp(a.popupMenuTheme, b.popupMenuTheme, t),
);
}
......@@ -1114,7 +1127,8 @@ class ThemeData extends Diagnosticable {
(otherData.typography == typography) &&
(otherData.cupertinoOverrideTheme == cupertinoOverrideTheme) &&
(otherData.snackBarTheme == snackBarTheme) &&
(otherData.bottomSheetTheme == bottomSheetTheme);
(otherData.bottomSheetTheme == bottomSheetTheme) &&
(otherData.popupMenuTheme == popupMenuTheme);
}
@override
......@@ -1181,6 +1195,7 @@ class ThemeData extends Diagnosticable {
cupertinoOverrideTheme,
snackBarTheme,
bottomSheetTheme,
popupMenuTheme,
];
return hashList(values);
}
......@@ -1244,6 +1259,7 @@ class ThemeData extends Diagnosticable {
properties.add(DiagnosticsProperty<CupertinoThemeData>('cupertinoOverrideTheme', cupertinoOverrideTheme, defaultValue: defaultData.cupertinoOverrideTheme));
properties.add(DiagnosticsProperty<SnackBarThemeData>('snackBarTheme', snackBarTheme, defaultValue: defaultData.snackBarTheme));
properties.add(DiagnosticsProperty<BottomSheetThemeData>('bottomSheetTheme', bottomSheetTheme, defaultValue: defaultData.bottomSheetTheme));
properties.add(DiagnosticsProperty<PopupMenuThemeData>('popupMenuTheme', popupMenuTheme, defaultValue: defaultData.popupMenuTheme));
}
}
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment