blob: 0704f3d360edebcc095b6976d12069b0798b08d2 [file] [log] [blame]
// 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 'package:flutter/foundation.dart';
import 'framework.dart';
/// An [InheritedWidget] that defines visual properties like colors
/// and text styles, which the [child]'s subtree depends on.
///
/// The [wrap] method is used by [captureAll] to construct a widget
/// that will wrap a child in all of the inherited themes which
/// are present in a build context but are not present in the
/// context that the returned widget is eventually built in.
///
/// A widget that's shown in a different context from the one it's
/// built in, like the contents of a new route or an overlay, will
/// be able to depend on inherited widget ancestors of the context
/// it's built in.
///
/// {@tool snippet --template=freeform}
/// This example demonstrates how `InheritedTheme.captureAll()` can be used
/// to wrap the contents of a new route with the inherited themes that
/// are present when the route is built - but are not present when route
/// is actually shown.
///
/// If the same code is run without `InheritedTheme.captureAll(), the
/// new route's Text widget will inherit the "something must be wrong"
/// fallback text style, rather than the default text style defined in MyApp.
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
///
/// ```dart main
/// void main() {
/// runApp(MyApp());
/// }
/// ```
///
/// ```dart
/// class MyAppBody extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return GestureDetector(
/// onTap: () {
/// Navigator.of(context).push(
/// MaterialPageRoute(
/// builder: (BuildContext _) {
/// // InheritedTheme.captureAll() saves references to themes that
/// // are found above the context provided to this widget's build
/// // method, notably the DefaultTextStyle defined in MyApp. The
/// // context passed to the MaterialPageRoute's builder is not used,
/// // because its ancestors are above MyApp's home.
/// return InheritedTheme.captureAll(context, Container(
/// alignment: Alignment.center,
/// color: Theme.of(context).colorScheme.surface,
/// child: Text('Hello World'),
/// ));
/// },
/// ),
/// );
/// },
/// child: Center(child: Text('Tap Here')),
/// );
/// }
/// }
///
/// class MyApp extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// home: Scaffold(
/// // Override the DefaultTextStyle defined by the Scaffold.
/// // Descendant widgets will inherit this big blue text style.
/// body: DefaultTextStyle(
/// style: TextStyle(fontSize: 48, color: Colors.blue),
/// child: MyAppBody(),
/// ),
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
abstract class InheritedTheme extends InheritedWidget {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const InheritedTheme({
Key key,
@required Widget child,
}) : super(key: key, child: child);
/// Return a copy of this inherited theme with the specified [child].
///
/// If the identical inherited theme is already visible from [context] then
/// just return the [child].
///
/// This implementation for [TooltipTheme] is typical:
/// ```dart
/// Widget wrap(BuildContext context, Widget child) {
/// final TooltipTheme ancestorTheme = context.ancestorWidgetOfExactType(TooltipTheme);
/// return identical(this, ancestorTheme) ? child : TooltipTheme(data: data, child: child);
/// }
/// ```
Widget wrap(BuildContext context, Widget child);
/// Returns a widget that will [wrap] child in all of the inherited themes
/// which are visible from [context].
static Widget captureAll(BuildContext context, Widget child) {
assert(child != null);
assert(context != null);
final List<InheritedTheme> themes = <InheritedTheme>[];
final Set<Type> themeTypes = <Type>{};
context.visitAncestorElements((Element ancestor) {
if (ancestor is InheritedElement && ancestor.widget is InheritedTheme) {
final InheritedTheme theme = ancestor.widget;
final Type themeType = theme.runtimeType;
// Only remember the first theme of any type. This assumes
// that inherited themes completely shadow ancestors of the
// the same type.
if (!themeTypes.contains(themeType)) {
themeTypes.add(themeType);
themes.add(theme);
}
}
return true;
});
return _CaptureAll(themes: themes, child: child);
}
}
class _CaptureAll extends StatelessWidget {
const _CaptureAll({
Key key,
@required this.themes,
@required this.child
}) : assert(themes != null), assert(child != null), super(key: key);
final List<InheritedTheme> themes;
final Widget child;
@override
Widget build(BuildContext context) {
Widget wrappedChild = child;
for (InheritedTheme theme in themes)
wrappedChild = theme.wrap(context, wrappedChild);
return wrappedChild;
}
}