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 'package:flutter/material.dart';
6library;
7
8import 'framework.dart';
9
10// Examples can assume:
11// TooltipThemeData data = const TooltipThemeData();
12
13/// An [InheritedWidget] that defines visual properties like colors
14/// and text styles, which the [child]'s subtree depends on.
15///
16/// The [wrap] method is used by [captureAll] and [CapturedThemes.wrap] to
17/// construct a widget that will wrap a child in all of the inherited themes
18/// which are present in a specified part of the widget tree.
19///
20/// A widget that's shown in a different context from the one it's built in,
21/// like the contents of a new route or an overlay, will be able to see the
22/// ancestor inherited themes of the context it was built in.
23///
24/// {@tool dartpad}
25/// This example demonstrates how `InheritedTheme.capture()` can be used
26/// to wrap the contents of a new route with the inherited themes that
27/// are present when the route was built - but are not present when route
28/// is actually shown.
29///
30/// If the same code is run without `InheritedTheme.capture(), the
31/// new route's Text widget will inherit the "something must be wrong"
32/// fallback text style, rather than the default text style defined in MyApp.
33///
34/// ** See code in examples/api/lib/widgets/inherited_theme/inherited_theme.0.dart **
35/// {@end-tool}
36abstract class InheritedTheme extends InheritedWidget {
37 /// Abstract const constructor. This constructor enables subclasses to provide
38 /// const constructors so that they can be used in const expressions.
39
40 const InheritedTheme({super.key, required super.child});
41
42 /// Return a copy of this inherited theme with the specified [child].
43 ///
44 /// This implementation for [TooltipTheme] is typical:
45 ///
46 /// ```dart
47 /// Widget wrap(BuildContext context, Widget child) {
48 /// return TooltipTheme(data: data, child: child);
49 /// }
50 /// ```
51 Widget wrap(BuildContext context, Widget child);
52
53 /// Returns a widget that will [wrap] `child` in all of the inherited themes
54 /// which are present between `context` and the specified `to`
55 /// [BuildContext].
56 ///
57 /// The `to` context must be an ancestor of `context`. If `to` is not
58 /// specified, all inherited themes up to the root of the widget tree are
59 /// captured.
60 ///
61 /// After calling this method, the themes present between `context` and `to`
62 /// are frozen for the provided `child`. If the themes (or their theme data)
63 /// change in the original subtree, those changes will not be visible to
64 /// the wrapped `child` - unless this method is called again to re-wrap the
65 /// child.
66 static Widget captureAll(BuildContext context, Widget child, {BuildContext? to}) {
67 return capture(from: context, to: to).wrap(child);
68 }
69
70 /// Returns a [CapturedThemes] object that includes all the [InheritedTheme]s
71 /// between the given `from` and `to` [BuildContext]s.
72 ///
73 /// The `to` context must be an ancestor of the `from` context. If `to` is
74 /// null, all ancestor inherited themes of `from` up to the root of the
75 /// widget tree are captured.
76 ///
77 /// After calling this method, the themes present between `from` and `to` are
78 /// frozen in the returned [CapturedThemes] object. If the themes (or their
79 /// theme data) change in the original subtree, those changes will not be
80 /// applied to the themes captured in the [CapturedThemes] object - unless
81 /// this method is called again to re-capture the updated themes.
82 ///
83 /// To wrap a [Widget] in the captured themes, call [CapturedThemes.wrap].
84 ///
85 /// This method can be expensive if there are many widgets between `from` and
86 /// `to` (it walks the element tree between those nodes).
87 static CapturedThemes capture({required BuildContext from, required BuildContext? to}) {
88 if (from == to) {
89 // Nothing to capture.
90 return CapturedThemes._(const <InheritedTheme>[]);
91 }
92
93 final List<InheritedTheme> themes = <InheritedTheme>[];
94 final Set<Type> themeTypes = <Type>{};
95 late bool debugDidFindAncestor;
96 assert(() {
97 debugDidFindAncestor = to == null;
98 return true;
99 }());
100 from.visitAncestorElements((Element ancestor) {
101 if (ancestor == to) {
102 assert(() {
103 debugDidFindAncestor = true;
104 return true;
105 }());
106 return false;
107 }
108 if (ancestor case InheritedElement(widget: final InheritedTheme theme)) {
109 final Type themeType = theme.runtimeType;
110 // Only remember the first theme of any type. This assumes
111 // that inherited themes completely shadow ancestors of the
112 // same type.
113 if (!themeTypes.contains(themeType)) {
114 themeTypes.add(themeType);
115 themes.add(theme);
116 }
117 }
118 return true;
119 });
120
121 assert(
122 debugDidFindAncestor,
123 'The provided `to` context must be an ancestor of the `from` context.',
124 );
125 return CapturedThemes._(themes);
126 }
127}
128
129/// Stores a list of captured [InheritedTheme]s that can be wrapped around a
130/// child [Widget].
131///
132/// Used as return type by [InheritedTheme.capture].
133class CapturedThemes {
134 CapturedThemes._(this._themes);
135
136 final List<InheritedTheme> _themes;
137
138 /// Wraps a `child` [Widget] in the [InheritedTheme]s captured in this object.
139 Widget wrap(Widget child) {
140 return _CaptureAll(themes: _themes, child: child);
141 }
142}
143
144class _CaptureAll extends StatelessWidget {
145 const _CaptureAll({required this.themes, required this.child});
146
147 final List<InheritedTheme> themes;
148 final Widget child;
149
150 @override
151 Widget build(BuildContext context) {
152 Widget wrappedChild = child;
153 for (final InheritedTheme theme in themes) {
154 wrappedChild = theme.wrap(context, wrappedChild);
155 }
156 return wrappedChild;
157 }
158}
159

Provided by KDAB

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