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 'app.dart';
6/// @docImport 'color_scheme.dart';
7/// @docImport 'text_theme.dart';
8library;
9
10import 'package:flutter/cupertino.dart';
11import 'package:flutter/foundation.dart';
12
13import 'material_localizations.dart';
14import 'theme_data.dart';
15import 'typography.dart';
16
17export 'theme_data.dart' show Brightness, MaterialTapTargetSize, ThemeData;
18
19/// The duration over which theme changes animate by default.
20const Duration kThemeAnimationDuration = Duration(milliseconds: 200);
21
22/// Applies a theme to descendant widgets.
23///
24/// A theme describes the colors and typographic choices of an application.
25///
26/// {@youtube 560 315 https://www.youtube.com/watch?v=oTvQDJOBXmM}
27///
28/// Descendant widgets obtain the current theme's [ThemeData] object using
29/// [Theme.of]. When a widget uses [Theme.of], it is automatically rebuilt if
30/// the theme later changes, so that the changes can be applied.
31///
32/// The [Theme] widget implies an [IconTheme] widget, set to the value of the
33/// [ThemeData.iconTheme] of the [data] for the [Theme].
34///
35/// To interact seamlessly with descendant Cupertino widgets, the [Theme] widget
36/// provides a [CupertinoTheme] for its descendants with a [CupertinoThemeData] inherited
37/// from the nearest ancestor [CupertinoTheme] or if none exists, derived from the
38/// Material [data] for the [Theme]. The values in the Material derived [CupertinoThemeData]
39/// are overridable through [ThemeData.cupertinoOverrideTheme]. The values from an
40/// inherited [CupertinoThemeData] can be overridden by wrapping the desired subtree
41/// with a [CupertinoTheme].
42///
43/// See also:
44///
45/// * [ThemeData], which describes the actual configuration of a theme.
46/// * [AnimatedTheme], which animates the [ThemeData] when it changes rather
47/// than changing the theme all at once.
48/// * [MaterialApp], which includes an [AnimatedTheme] widget configured via
49/// the [MaterialApp.theme] argument.
50class Theme extends StatelessWidget {
51 /// Applies the given theme [data] to [child].
52 const Theme({super.key, required this.data, required this.child});
53
54 /// Specifies the color and typography values for descendant widgets.
55 final ThemeData data;
56
57 /// The widget below this widget in the tree.
58 ///
59 /// {@macro flutter.widgets.ProxyWidget.child}
60 final Widget child;
61
62 static final ThemeData _kFallbackTheme = ThemeData.fallback();
63
64 /// The data from the closest [Theme] instance that encloses the given
65 /// context.
66 ///
67 /// If the given context is enclosed in a [Localizations] widget providing
68 /// [MaterialLocalizations], the returned data is localized according to the
69 /// nearest available [MaterialLocalizations].
70 ///
71 /// Defaults to [ThemeData.fallback] if there is no [Theme] in the given
72 /// build context.
73 ///
74 /// Typical usage is as follows:
75 ///
76 /// ```dart
77 /// @override
78 /// Widget build(BuildContext context) {
79 /// return Text(
80 /// 'Example',
81 /// style: Theme.of(context).textTheme.titleLarge,
82 /// );
83 /// }
84 /// ```
85 ///
86 /// When the [Theme] is actually created in the same `build` function
87 /// (possibly indirectly, e.g. as part of a [MaterialApp]), the `context`
88 /// argument to the `build` function can't be used to find the [Theme] (since
89 /// it's "above" the widget being returned). In such cases, the following
90 /// technique with a [Builder] can be used to provide a new scope with a
91 /// [BuildContext] that is "under" the [Theme]:
92 ///
93 /// ```dart
94 /// @override
95 /// Widget build(BuildContext context) {
96 /// return MaterialApp(
97 /// theme: ThemeData.light(),
98 /// home: Builder(
99 /// // Create an inner BuildContext so that we can refer to
100 /// // the Theme with Theme.of().
101 /// builder: (BuildContext context) {
102 /// return Center(
103 /// child: Text(
104 /// 'Example',
105 /// style: Theme.of(context).textTheme.titleLarge,
106 /// ),
107 /// );
108 /// },
109 /// ),
110 /// );
111 /// }
112 /// ```
113 ///
114 /// See also:
115 ///
116 /// * [ColorScheme.of], a convenience method that returns [ThemeData.colorScheme]
117 /// from the closest [Theme] ancestor. (equivalent to `Theme.of(context).colorScheme`).
118 /// * [TextTheme.of], a convenience method that returns [ThemeData.textTheme]
119 /// from the closest [Theme] ancestor. (equivalent to `Theme.of(context).textTheme`).
120 /// * [IconTheme.of], that returns [ThemeData.iconTheme] from the closest [Theme] or
121 /// [IconThemeData.fallback] if there is no [IconTheme] ancestor.
122 static ThemeData of(BuildContext context) {
123 final _InheritedTheme? inheritedTheme = context
124 .dependOnInheritedWidgetOfExactType<_InheritedTheme>();
125 final MaterialLocalizations? localizations = Localizations.of<MaterialLocalizations>(
126 context,
127 MaterialLocalizations,
128 );
129 final ScriptCategory category = localizations?.scriptCategory ?? ScriptCategory.englishLike;
130 final InheritedCupertinoTheme? inheritedCupertinoTheme = context
131 .dependOnInheritedWidgetOfExactType<InheritedCupertinoTheme>();
132 final ThemeData theme =
133 inheritedTheme?.theme.data ??
134 (inheritedCupertinoTheme != null
135 ? CupertinoBasedMaterialThemeData(
136 themeData: inheritedCupertinoTheme.theme.data,
137 ).materialTheme
138 : _kFallbackTheme);
139 return ThemeData.localize(theme, theme.typography.geometryThemeFor(category));
140 }
141
142 // The inherited themes in widgets library can not infer their values from
143 // Theme in material library. Wraps the child with these inherited themes to
144 // overrides their values directly.
145 Widget _wrapsWidgetThemes(BuildContext context, Widget child) {
146 final DefaultSelectionStyle selectionStyle = DefaultSelectionStyle.of(context);
147 return IconTheme(
148 data: data.iconTheme,
149 child: DefaultSelectionStyle(
150 selectionColor: data.textSelectionTheme.selectionColor ?? selectionStyle.selectionColor,
151 cursorColor: data.textSelectionTheme.cursorColor ?? selectionStyle.cursorColor,
152 child: child,
153 ),
154 );
155 }
156
157 CupertinoThemeData _inheritedCupertinoThemeData(BuildContext context) {
158 final InheritedCupertinoTheme? inheritedTheme = context
159 .dependOnInheritedWidgetOfExactType<InheritedCupertinoTheme>();
160 return (inheritedTheme?.theme.data ?? MaterialBasedCupertinoThemeData(materialTheme: data))
161 .resolveFrom(context);
162 }
163
164 /// Retrieves the [Brightness] to use for descendant Material widgets, based
165 /// on the value of [ThemeData.brightness] in the given [context].
166 ///
167 /// If no [InheritedTheme] can be found in the given [context], or its `brightness`
168 /// is null, it will fall back to [MediaQueryData.platformBrightness].
169 ///
170 /// See also:
171 ///
172 /// * [maybeBrightnessOf], which returns null if no valid [InheritedTheme] or
173 /// [MediaQuery] exists.
174 /// * [ThemeData.brightness], the property that takes precedence over
175 /// [MediaQueryData.platformBrightness] for descendant Material widgets.
176 static Brightness brightnessOf(BuildContext context) {
177 final _InheritedTheme? inheritedTheme = context
178 .dependOnInheritedWidgetOfExactType<_InheritedTheme>();
179 return inheritedTheme?.theme.data.brightness ?? MediaQuery.platformBrightnessOf(context);
180 }
181
182 /// Retrieves the [Brightness] to use for descendant Material widgets, based
183 /// on the value of [ThemeData.brightness] in the given [context].
184 ///
185 /// If no [InheritedTheme] or [MediaQuery] can be found in the given [context], it will
186 /// return null.
187 ///
188 /// See also:
189 ///
190 /// * [ThemeData.brightness], the property that takes precedence over
191 /// [MediaQueryData.platformBrightness] for descendant Material widgets.
192 /// * [brightnessOf], which return a default value if no valid [InheritedTheme] or
193 /// [MediaQuery] exists, instead of returning null.
194 static Brightness? maybeBrightnessOf(BuildContext context) {
195 final _InheritedTheme? inheritedTheme = context
196 .dependOnInheritedWidgetOfExactType<_InheritedTheme>();
197 return inheritedTheme?.theme.data.brightness ?? MediaQuery.maybePlatformBrightnessOf(context);
198 }
199
200 @override
201 Widget build(BuildContext context) {
202 return _InheritedTheme(
203 theme: this,
204 child: CupertinoTheme(
205 // If a CupertinoThemeData doesn't exist, we're using a
206 // MaterialBasedCupertinoThemeData here instead of a CupertinoThemeData
207 // because it defers some properties to the Material ThemeData.
208 data: _inheritedCupertinoThemeData(context),
209 child: _wrapsWidgetThemes(context, child),
210 ),
211 );
212 }
213
214 @override
215 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
216 super.debugFillProperties(properties);
217 properties.add(DiagnosticsProperty<ThemeData>('data', data, showName: false));
218 }
219}
220
221class _InheritedTheme extends InheritedTheme {
222 const _InheritedTheme({required this.theme, required super.child});
223
224 final Theme theme;
225
226 @override
227 Widget wrap(BuildContext context, Widget child) {
228 return Theme(data: theme.data, child: child);
229 }
230
231 @override
232 bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
233}
234
235/// An interpolation between two [ThemeData]s.
236///
237/// This class specializes the interpolation of [Tween<ThemeData>] to call the
238/// [ThemeData.lerp] method.
239///
240/// See [Tween] for a discussion on how to use interpolation objects.
241class ThemeDataTween extends Tween<ThemeData> {
242 /// Creates a [ThemeData] tween.
243 ///
244 /// The [begin] and [end] properties must be non-null before the tween is
245 /// first used, but the arguments can be null if the values are going to be
246 /// filled in later.
247 ThemeDataTween({super.begin, super.end});
248
249 @override
250 ThemeData lerp(double t) => ThemeData.lerp(begin!, end!, t);
251}
252
253/// Animated version of [Theme] which automatically transitions the colors,
254/// etc, over a given duration whenever the given theme changes.
255///
256/// Here's an illustration of what using this widget looks like, using a [curve]
257/// of [Curves.elasticInOut].
258/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_theme.mp4}
259///
260/// See also:
261///
262/// * [Theme], which [AnimatedTheme] uses to actually apply the interpolated
263/// theme.
264/// * [ThemeData], which describes the actual configuration of a theme.
265/// * [MaterialApp], which includes an [AnimatedTheme] widget configured via
266/// the [MaterialApp.theme] argument.
267class AnimatedTheme extends ImplicitlyAnimatedWidget {
268 /// Creates an animated theme.
269 ///
270 /// By default, the theme transition uses a linear curve.
271 const AnimatedTheme({
272 super.key,
273 required this.data,
274 super.curve,
275 super.duration = kThemeAnimationDuration,
276 super.onEnd,
277 required this.child,
278 });
279
280 /// Specifies the color and typography values for descendant widgets.
281 final ThemeData data;
282
283 /// The widget below this widget in the tree.
284 ///
285 /// {@macro flutter.widgets.ProxyWidget.child}
286 final Widget child;
287
288 @override
289 AnimatedWidgetBaseState<AnimatedTheme> createState() => _AnimatedThemeState();
290}
291
292class _AnimatedThemeState extends AnimatedWidgetBaseState<AnimatedTheme> {
293 ThemeDataTween? _data;
294
295 @override
296 void forEachTween(TweenVisitor<dynamic> visitor) {
297 _data =
298 visitor(_data, widget.data, (dynamic value) => ThemeDataTween(begin: value as ThemeData))!
299 as ThemeDataTween;
300 }
301
302 @override
303 Widget build(BuildContext context) {
304 return Theme(data: _data!.evaluate(animation), child: widget.child);
305 }
306
307 @override
308 void debugFillProperties(DiagnosticPropertiesBuilder description) {
309 super.debugFillProperties(description);
310 description.add(
311 DiagnosticsProperty<ThemeDataTween>('data', _data, showName: false, defaultValue: null),
312 );
313 }
314}
315