blob: 028c39509aa05bed23065a5cd3404d411f94abdb [file] [log] [blame]
// 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 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'input_decorator.dart';
import 'menu_style.dart';
import 'theme.dart';
// Examples can assume:
// late BuildContext context;
/// Overrides the default values of visual properties for descendant [DropdownMenu] widgets.
///
/// Descendant widgets obtain the current [DropdownMenuThemeData] object with
/// [DropdownMenuTheme.of]. Instances of [DropdownMenuTheme] can
/// be customized with [DropdownMenuThemeData.copyWith].
///
/// Typically a [DropdownMenuTheme] is specified as part of the overall [Theme] with
/// [ThemeData.dropdownMenuTheme].
///
/// All [DropdownMenuThemeData] properties are null by default. When null, the [DropdownMenu]
/// computes its own default values, typically based on the overall
/// theme's [ThemeData.colorScheme], [ThemeData.textTheme], and [ThemeData.iconTheme].
@immutable
class DropdownMenuThemeData with Diagnosticable {
/// Creates a [DropdownMenuThemeData] that can be used to override default properties
/// in a [DropdownMenuTheme] widget.
const DropdownMenuThemeData({
this.textStyle,
this.inputDecorationTheme,
this.menuStyle,
});
/// Overrides the default value for [DropdownMenu.textStyle].
final TextStyle? textStyle;
/// The input decoration theme for the [TextField]s in a [DropdownMenu].
///
/// If this is null, the [DropdownMenu] provides its own defaults.
final InputDecorationTheme? inputDecorationTheme;
/// Overrides the menu's default style in a [DropdownMenu].
///
/// Any values not set in the [MenuStyle] will use the menu default for that
/// property.
final MenuStyle? menuStyle;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
DropdownMenuThemeData copyWith({
TextStyle? textStyle,
InputDecorationTheme? inputDecorationTheme,
MenuStyle? menuStyle,
}) {
return DropdownMenuThemeData(
textStyle: textStyle ?? this.textStyle,
inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
menuStyle: menuStyle ?? this.menuStyle,
);
}
/// Linearly interpolates between two dropdown menu themes.
static DropdownMenuThemeData lerp(DropdownMenuThemeData? a, DropdownMenuThemeData? b, double t) {
return DropdownMenuThemeData(
textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
inputDecorationTheme: t < 0.5 ? a?.inputDecorationTheme : b?.inputDecorationTheme,
menuStyle: MenuStyle.lerp(a?.menuStyle, b?.menuStyle, t),
);
}
@override
int get hashCode => Object.hash(
textStyle,
inputDecorationTheme,
menuStyle,
);
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is DropdownMenuThemeData
&& other.textStyle == textStyle
&& other.inputDecorationTheme == inputDecorationTheme
&& other.menuStyle == menuStyle;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle, defaultValue: null));
properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme, defaultValue: null));
properties.add(DiagnosticsProperty<MenuStyle>('menuStyle', menuStyle, defaultValue: null));
}
}
/// An inherited widget that defines the visual properties for [DropdownMenu]s in this widget's subtree.
///
/// Values specified here are used for [DropdownMenu] properties that are not
/// given an explicit non-null value.
class DropdownMenuTheme extends InheritedTheme {
/// Creates a [DropdownMenuTheme] that controls visual parameters for
/// descendant [DropdownMenu]s.
const DropdownMenuTheme({
super.key,
required this.data,
required super.child,
}) : assert(data != null);
/// Specifies the visual properties used by descendant [DropdownMenu]
/// widgets.
final DropdownMenuThemeData data;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [DropdownMenuTheme] widget, then
/// [ThemeData.dropdownMenuTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// DropdownMenuThemeData theme = DropdownMenuTheme.of(context);
/// ```
///
/// See also:
///
/// * [maybeOf], which returns null if it doesn't find a
/// [DropdownMenuTheme] ancestor.
static DropdownMenuThemeData of(BuildContext context) {
return maybeOf(context) ?? Theme.of(context).dropdownMenuTheme;
}
/// The data from the closest instance of this class that encloses the given
/// context, if any.
///
/// Use this function if you want to allow situations where no
/// [DropdownMenuTheme] is in scope. Prefer using [DropdownMenuTheme.of]
/// in situations where a [DropdownMenuThemeData] is expected to be
/// non-null.
///
/// If there is no [DropdownMenuTheme] in scope, then this function will
/// return null.
///
/// Typical usage is as follows:
///
/// ```dart
/// DropdownMenuThemeData? theme = DropdownMenuTheme.maybeOf(context);
/// if (theme == null) {
/// // Do something else instead.
/// }
/// ```
///
/// See also:
///
/// * [of], which will return [ThemeData.dropdownMenuTheme] if it doesn't
/// find a [DropdownMenuTheme] ancestor, instead of returning null.
static DropdownMenuThemeData? maybeOf(BuildContext context) {
assert(context != null);
return context.dependOnInheritedWidgetOfExactType<DropdownMenuTheme>()?.data;
}
@override
Widget wrap(BuildContext context, Widget child) {
return DropdownMenuTheme(data: data, child: child);
}
@override
bool updateShouldNotify(DropdownMenuTheme oldWidget) => data != oldWidget.data;
}