From 3efc517ab5bb0263c010fc6710d2b1c32b048b2e Mon Sep 17 00:00:00 2001 From: MH Johnson Date: Fri, 12 Jun 2020 12:20:04 -0400 Subject: [PATCH] [Widgets] Add DefaultTextHeightBehavior inherited widget. (#59196) --- packages/flutter/lib/src/widgets/text.dart | 62 +++++++- .../default_text_height_behavior_test.dart | 132 ++++++++++++++++++ 2 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 packages/flutter/test/widgets/default_text_height_behavior_test.dart diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart index eec599208c..aad1ced8c2 100644 --- a/packages/flutter/lib/src/widgets/text.dart +++ b/packages/flutter/lib/src/widgets/text.dart @@ -203,6 +203,66 @@ class DefaultTextStyle extends InheritedTheme { } } +/// The [TextHeightBehavior] that will apply to descendant [Text] widgets which +/// have not explicitly set [Text.textHeightBehavior]. +/// +/// If there is a [DefaultTextStyle] with a non-null [DefaultTextStyle.textHeightBehavior] +/// below this widget, the [DefaultTextStyle.textHeightBehavior] will be used +/// over this widget's [TextHeightBehavior]. +/// +/// See also: +/// +/// * [DefaultTextStyle], which defines a [TextStyle] to apply to descendant +/// [Text] widgets. +class DefaultTextHeightBehavior extends InheritedTheme { + /// Creates a default text height behavior for the given subtree. + /// + /// The [textHeightBehavior] and [child] arguments are required and must not be null. + const DefaultTextHeightBehavior({ + Key key, + @required this.textHeightBehavior, + @required Widget child, + }) : assert(textHeightBehavior != null), + assert(child != null), + super(key: key, child: child); + + /// {@macro flutter.dart:ui.textHeightBehavior} + final TextHeightBehavior textHeightBehavior; + + /// The closest instance of this class that encloses the given context. + /// + /// If no such instance exists, this method will return `null`. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// DefaultTextHeightBehavior defaultTextHeightBehavior = DefaultTextHeightBehavior.of(context); + /// ``` + static TextHeightBehavior of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType()?.textHeightBehavior; + } + + @override + bool updateShouldNotify(DefaultTextHeightBehavior oldWidget) { + return textHeightBehavior != oldWidget.textHeightBehavior; + } + + @override + Widget wrap(BuildContext context, Widget child) { + final DefaultTextHeightBehavior defaultTextHeightBehavior = context.findAncestorWidgetOfExactType(); + return identical(this, defaultTextHeightBehavior) ? child : DefaultTextHeightBehavior( + textHeightBehavior: textHeightBehavior, + child: child, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('textHeightBehavior', textHeightBehavior, defaultValue: null)); + } +} + /// A run of text with a single style. /// /// The [Text] widget displays a string of text with single style. The string @@ -451,7 +511,7 @@ class Text extends StatelessWidget { maxLines: maxLines ?? defaultTextStyle.maxLines, strutStyle: strutStyle, textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis, - textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior, + textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context), text: TextSpan( style: effectiveTextStyle, text: data, diff --git a/packages/flutter/test/widgets/default_text_height_behavior_test.dart b/packages/flutter/test/widgets/default_text_height_behavior_test.dart new file mode 100644 index 0000000000..99cbf08b86 --- /dev/null +++ b/packages/flutter/test/widgets/default_text_height_behavior_test.dart @@ -0,0 +1,132 @@ +// 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 'dart:ui' show TextHeightBehavior; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter/painting.dart'; + +void main() { + testWidgets('Text widget parameter takes precedence over DefaultTextHeightBehavior', (WidgetTester tester) async { + const TextHeightBehavior behavior1 = TextHeightBehavior( + applyHeightToLastDescent: false, + applyHeightToFirstAscent: false, + ); + const TextHeightBehavior behavior2 = TextHeightBehavior( + applyHeightToLastDescent: true, + applyHeightToFirstAscent: false, + ); + + await tester.pumpWidget( + const DefaultTextHeightBehavior( + textHeightBehavior: behavior2, + child: Text( + 'Hello', + textDirection: TextDirection.ltr, + textHeightBehavior: behavior1, + ), + ), + ); + + final RichText text = tester.firstWidget(find.byType(RichText)); + expect(text, isNotNull); + expect(text.textHeightBehavior, behavior1); + }); + + testWidgets('DefaultTextStyle.textHeightBehavior takes precedence over DefaultTextHeightBehavior ', (WidgetTester tester) async { + const TextHeightBehavior behavior1 = TextHeightBehavior( + applyHeightToLastDescent: false, + applyHeightToFirstAscent: false, + ); + const TextHeightBehavior behavior2 = TextHeightBehavior( + applyHeightToLastDescent: true, + applyHeightToFirstAscent: false, + ); + + await tester.pumpWidget( + const DefaultTextStyle( + style: TextStyle(), + textHeightBehavior: behavior1, + child: DefaultTextHeightBehavior( + textHeightBehavior: behavior2, + child: Text( + 'Hello', + textDirection: TextDirection.ltr, + ), + ), + ), + ); + + RichText text = tester.firstWidget(find.byType(RichText)); + expect(text, isNotNull); + expect(text.textHeightBehavior, behavior1); + + await tester.pumpWidget( + const DefaultTextHeightBehavior( + textHeightBehavior: behavior2, + child: DefaultTextStyle( + style: TextStyle(), + textHeightBehavior: behavior1, + child: Text( + 'Hello', + textDirection: TextDirection.ltr, + ), + ), + ), + ); + + text = tester.firstWidget(find.byType(RichText)); + expect(text, isNotNull); + expect(text.textHeightBehavior, behavior1); + }); + + testWidgets('DefaultTextHeightBehavior changes propagate to Text', (WidgetTester tester) async { + const Text textWidget = Text('Hello', textDirection: TextDirection.ltr); + const TextHeightBehavior behavior1 = TextHeightBehavior( + applyHeightToLastDescent: false, + applyHeightToFirstAscent: false, + ); + const TextHeightBehavior behavior2 = TextHeightBehavior( + applyHeightToLastDescent: false, + applyHeightToFirstAscent: false, + ); + + await tester.pumpWidget(const DefaultTextHeightBehavior( + textHeightBehavior: behavior1, + child: textWidget, + )); + + RichText text = tester.firstWidget(find.byType(RichText)); + expect(text, isNotNull); + expect(text.textHeightBehavior, behavior1); + + await tester.pumpWidget(const DefaultTextHeightBehavior( + textHeightBehavior: behavior2, + child: textWidget, + )); + + text = tester.firstWidget(find.byType(RichText)); + expect(text, isNotNull); + expect(text.textHeightBehavior, behavior2); + }); + + testWidgets('DefaultTextHeightBehavior.of(context) returns null if no ' + 'DefaultTextHeightBehavior widget in tree', (WidgetTester tester) async { + const Text textWidget = Text('Hello', textDirection: TextDirection.ltr); + TextHeightBehavior textHeightBehavior; + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + textHeightBehavior = DefaultTextHeightBehavior.of(context); + return textWidget; + }, + )); + + expect(textHeightBehavior, isNull); + final RichText text = tester.firstWidget(find.byType(RichText)); + expect(text, isNotNull); + expect(text.textHeightBehavior, isNull); + }); +}