Unverified Commit 49632fc3 authored by Mateus Felipe C. C. Pinto's avatar Mateus Felipe C. C. Pinto Committed by GitHub

Provide parameter to Icon and IconThemeData for they to consider the context's...

Provide parameter to Icon and IconThemeData for they to consider the context's text scaler (#135708)

Provide a parameter `applyTextScaling` to both `Icon` and `IconDataTheme`. When `true`, the context's `TextScaler` will apply it's `scale` method to the icon size.

Fixes #115466
parent 44981755
...@@ -19,6 +19,7 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable { ...@@ -19,6 +19,7 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
super.color, super.color,
super.opacity, super.opacity,
super.shadows, super.shadows,
super.applyTextScaling,
}); });
/// Called by [IconTheme.of] to resolve [color] against the given [BuildContext]. /// Called by [IconTheme.of] to resolve [color] against the given [BuildContext].
...@@ -40,6 +41,7 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable { ...@@ -40,6 +41,7 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
Color? color, Color? color,
double? opacity, double? opacity,
List<Shadow>? shadows, List<Shadow>? shadows,
bool? applyTextScaling,
}) { }) {
return CupertinoIconThemeData( return CupertinoIconThemeData(
size: size ?? this.size, size: size ?? this.size,
...@@ -50,6 +52,7 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable { ...@@ -50,6 +52,7 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
color: color ?? this.color, color: color ?? this.color,
opacity: opacity ?? this.opacity, opacity: opacity ?? this.opacity,
shadows: shadows ?? this.shadows, shadows: shadows ?? this.shadows,
applyTextScaling: applyTextScaling ?? this.applyTextScaling,
); );
} }
......
...@@ -27,10 +27,11 @@ import 'text_span.dart'; ...@@ -27,10 +27,11 @@ import 'text_span.dart';
export 'package:flutter/services.dart' show TextRange, TextSelection; export 'package:flutter/services.dart' show TextRange, TextSelection;
// The default font size if none is specified. This should be kept in /// The default font size if none is specified.
// sync with the default values in text_style.dart, as well as the ///
// defaults set in the engine (eg, LibTxt's text_style.h, paragraph_style.h). /// This should be kept in sync with the defaults set in the engine (e.g.,
const double _kDefaultFontSize = 14.0; /// LibTxt's text_style.h, paragraph_style.h).
const double kDefaultFontSize = 14.0;
/// How overflowing text should be handled. /// How overflowing text should be handled.
/// ///
...@@ -971,7 +972,7 @@ class TextPainter { ...@@ -971,7 +972,7 @@ class TextPainter {
// Use the default font size to multiply by as RichText does not // Use the default font size to multiply by as RichText does not
// perform inheriting [TextStyle]s and would otherwise // perform inheriting [TextStyle]s and would otherwise
// fail to apply textScaler. // fail to apply textScaler.
fontSize: textScaler.scale(_kDefaultFontSize), fontSize: textScaler.scale(kDefaultFontSize),
maxLines: maxLines, maxLines: maxLines,
textHeightBehavior: _textHeightBehavior, textHeightBehavior: _textHeightBehavior,
ellipsis: ellipsis, ellipsis: ellipsis,
......
...@@ -29,11 +29,6 @@ const String _kColorForegroundWarning = 'Cannot provide both a color and a foreg ...@@ -29,11 +29,6 @@ const String _kColorForegroundWarning = 'Cannot provide both a color and a foreg
const String _kColorBackgroundWarning = 'Cannot provide both a backgroundColor and a background\n' const String _kColorBackgroundWarning = 'Cannot provide both a backgroundColor and a background\n'
'The backgroundColor argument is just a shorthand for "background: Paint()..color = color".'; 'The backgroundColor argument is just a shorthand for "background: Paint()..color = color".';
// The default font size if none is specified. This should be kept in
// sync with the default values in text_painter.dart, as well as the
// defaults set in the engine (eg, LibTxt's text_style.h, paragraph_style.h).
const double _kDefaultFontSize = 14.0;
// Examples can assume: // Examples can assume:
// late BuildContext context; // late BuildContext context;
...@@ -1353,7 +1348,7 @@ class TextStyle with Diagnosticable { ...@@ -1353,7 +1348,7 @@ class TextStyle with Diagnosticable {
fontWeight: fontWeight ?? this.fontWeight, fontWeight: fontWeight ?? this.fontWeight,
fontStyle: fontStyle ?? this.fontStyle, fontStyle: fontStyle ?? this.fontStyle,
fontFamily: fontFamily ?? this.fontFamily, fontFamily: fontFamily ?? this.fontFamily,
fontSize: textScaler.scale(fontSize ?? this.fontSize ?? _kDefaultFontSize), fontSize: textScaler.scale(fontSize ?? this.fontSize ?? kDefaultFontSize),
height: height ?? this.height, height: height ?? this.height,
textHeightBehavior: effectiveTextHeightBehavior, textHeightBehavior: effectiveTextHeightBehavior,
strutStyle: strutStyle == null ? null : ui.StrutStyle( strutStyle: strutStyle == null ? null : ui.StrutStyle(
......
...@@ -13,6 +13,7 @@ import 'framework.dart'; ...@@ -13,6 +13,7 @@ import 'framework.dart';
import 'icon_data.dart'; import 'icon_data.dart';
import 'icon_theme.dart'; import 'icon_theme.dart';
import 'icon_theme_data.dart'; import 'icon_theme_data.dart';
import 'media_query.dart';
/// A graphical icon widget drawn with a glyph from a font described in /// A graphical icon widget drawn with a glyph from a font described in
/// an [IconData] such as material's predefined [IconData]s in [Icons]. /// an [IconData] such as material's predefined [IconData]s in [Icons].
...@@ -80,6 +81,7 @@ class Icon extends StatelessWidget { ...@@ -80,6 +81,7 @@ class Icon extends StatelessWidget {
this.shadows, this.shadows,
this.semanticLabel, this.semanticLabel,
this.textDirection, this.textDirection,
this.applyTextScaling,
}) : assert(fill == null || (0.0 <= fill && fill <= 1.0)), }) : assert(fill == null || (0.0 <= fill && fill <= 1.0)),
assert(weight == null || (0.0 < weight)), assert(weight == null || (0.0 < weight)),
assert(opticalSize == null || (0.0 < opticalSize)); assert(opticalSize == null || (0.0 < opticalSize));
...@@ -231,6 +233,16 @@ class Icon extends StatelessWidget { ...@@ -231,6 +233,16 @@ class Icon extends StatelessWidget {
/// specified, either directly using this property or using [Directionality]. /// specified, either directly using this property or using [Directionality].
final TextDirection? textDirection; final TextDirection? textDirection;
/// Whether to scale the size of this widget using the ambient [MediaQuery]'s [TextScaler].
///
/// This is specially useful when you have an icon associated with a text, as
/// scaling the text without scaling the icon would result in a confusing
/// interface.
///
/// Defaults to the nearest [IconTheme]'s
/// [IconThemeData.applyTextScaling].
final bool? applyTextScaling;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(this.textDirection != null || debugCheckHasDirectionality(context)); assert(this.textDirection != null || debugCheckHasDirectionality(context));
...@@ -238,7 +250,11 @@ class Icon extends StatelessWidget { ...@@ -238,7 +250,11 @@ class Icon extends StatelessWidget {
final IconThemeData iconTheme = IconTheme.of(context); final IconThemeData iconTheme = IconTheme.of(context);
final double? iconSize = size ?? iconTheme.size; final bool applyTextScaling = this.applyTextScaling ?? iconTheme.applyTextScaling ?? false;
final double tentativeIconSize = size ?? iconTheme.size ?? kDefaultFontSize;
final double iconSize = applyTextScaling ? MediaQuery.textScalerOf(context).scale(tentativeIconSize) : tentativeIconSize;
final double? iconFill = fill ?? iconTheme.fill; final double? iconFill = fill ?? iconTheme.fill;
...@@ -332,5 +348,6 @@ class Icon extends StatelessWidget { ...@@ -332,5 +348,6 @@ class Icon extends StatelessWidget {
properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null)); properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null));
properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null)); properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('applyTextScaling', applyTextScaling, defaultValue: null));
} }
} }
...@@ -76,6 +76,7 @@ class IconTheme extends InheritedTheme { ...@@ -76,6 +76,7 @@ class IconTheme extends InheritedTheme {
color: iconThemeData.color ?? const IconThemeData.fallback().color, color: iconThemeData.color ?? const IconThemeData.fallback().color,
opacity: iconThemeData.opacity ?? const IconThemeData.fallback().opacity, opacity: iconThemeData.opacity ?? const IconThemeData.fallback().opacity,
shadows: iconThemeData.shadows ?? const IconThemeData.fallback().shadows, shadows: iconThemeData.shadows ?? const IconThemeData.fallback().shadows,
applyTextScaling: iconThemeData.applyTextScaling ?? const IconThemeData.fallback().applyTextScaling,
); );
} }
......
...@@ -31,6 +31,7 @@ class IconThemeData with Diagnosticable { ...@@ -31,6 +31,7 @@ class IconThemeData with Diagnosticable {
this.color, this.color,
double? opacity, double? opacity,
this.shadows, this.shadows,
this.applyTextScaling,
}) : _opacity = opacity, }) : _opacity = opacity,
assert(fill == null || (0.0 <= fill && fill <= 1.0)), assert(fill == null || (0.0 <= fill && fill <= 1.0)),
assert(weight == null || (0.0 < weight)), assert(weight == null || (0.0 < weight)),
...@@ -48,7 +49,8 @@ class IconThemeData with Diagnosticable { ...@@ -48,7 +49,8 @@ class IconThemeData with Diagnosticable {
opticalSize = 48.0, opticalSize = 48.0,
color = const Color(0xFF000000), color = const Color(0xFF000000),
_opacity = 1.0, _opacity = 1.0,
shadows = null; shadows = null,
applyTextScaling = false;
/// Creates a copy of this icon theme but with the given fields replaced with /// Creates a copy of this icon theme but with the given fields replaced with
/// the new values. /// the new values.
...@@ -61,6 +63,7 @@ class IconThemeData with Diagnosticable { ...@@ -61,6 +63,7 @@ class IconThemeData with Diagnosticable {
Color? color, Color? color,
double? opacity, double? opacity,
List<Shadow>? shadows, List<Shadow>? shadows,
bool? applyTextScaling,
}) { }) {
return IconThemeData( return IconThemeData(
size: size ?? this.size, size: size ?? this.size,
...@@ -71,6 +74,7 @@ class IconThemeData with Diagnosticable { ...@@ -71,6 +74,7 @@ class IconThemeData with Diagnosticable {
color: color ?? this.color, color: color ?? this.color,
opacity: opacity ?? this.opacity, opacity: opacity ?? this.opacity,
shadows: shadows ?? this.shadows, shadows: shadows ?? this.shadows,
applyTextScaling: applyTextScaling ?? this.applyTextScaling,
); );
} }
...@@ -90,6 +94,7 @@ class IconThemeData with Diagnosticable { ...@@ -90,6 +94,7 @@ class IconThemeData with Diagnosticable {
color: other.color, color: other.color,
opacity: other.opacity, opacity: other.opacity,
shadows: other.shadows, shadows: other.shadows,
applyTextScaling: other.applyTextScaling,
); );
} }
...@@ -118,7 +123,8 @@ class IconThemeData with Diagnosticable { ...@@ -118,7 +123,8 @@ class IconThemeData with Diagnosticable {
&& grade != null && grade != null
&& opticalSize != null && opticalSize != null
&& color != null && color != null
&& opacity != null; && opacity != null
&& applyTextScaling != null;
/// The default for [Icon.size]. /// The default for [Icon.size].
/// ///
...@@ -163,6 +169,9 @@ class IconThemeData with Diagnosticable { ...@@ -163,6 +169,9 @@ class IconThemeData with Diagnosticable {
/// The default for [Icon.shadows]. /// The default for [Icon.shadows].
final List<Shadow>? shadows; final List<Shadow>? shadows;
/// The default for [Icon.applyTextScaling].
final bool? applyTextScaling;
/// Linearly interpolate between two icon theme data objects. /// Linearly interpolate between two icon theme data objects.
/// ///
/// {@macro dart.ui.shadow.lerp} /// {@macro dart.ui.shadow.lerp}
...@@ -179,6 +188,7 @@ class IconThemeData with Diagnosticable { ...@@ -179,6 +188,7 @@ class IconThemeData with Diagnosticable {
color: Color.lerp(a?.color, b?.color, t), color: Color.lerp(a?.color, b?.color, t),
opacity: ui.lerpDouble(a?.opacity, b?.opacity, t), opacity: ui.lerpDouble(a?.opacity, b?.opacity, t),
shadows: Shadow.lerpList(a?.shadows, b?.shadows, t), shadows: Shadow.lerpList(a?.shadows, b?.shadows, t),
applyTextScaling: t < 0.5 ? a?.applyTextScaling : b?.applyTextScaling,
); );
} }
...@@ -195,7 +205,8 @@ class IconThemeData with Diagnosticable { ...@@ -195,7 +205,8 @@ class IconThemeData with Diagnosticable {
&& other.opticalSize == opticalSize && other.opticalSize == opticalSize
&& other.color == color && other.color == color
&& other.opacity == opacity && other.opacity == opacity
&& listEquals(other.shadows, shadows); && listEquals(other.shadows, shadows)
&& other.applyTextScaling == applyTextScaling;
} }
@override @override
...@@ -208,6 +219,7 @@ class IconThemeData with Diagnosticable { ...@@ -208,6 +219,7 @@ class IconThemeData with Diagnosticable {
color, color,
opacity, opacity,
shadows == null ? null : Object.hashAll(shadows!), shadows == null ? null : Object.hashAll(shadows!),
applyTextScaling,
); );
@override @override
...@@ -221,5 +233,6 @@ class IconThemeData with Diagnosticable { ...@@ -221,5 +233,6 @@ class IconThemeData with Diagnosticable {
properties.add(ColorProperty('color', color, defaultValue: null)); properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(DoubleProperty('opacity', opacity, defaultValue: null)); properties.add(DoubleProperty('opacity', opacity, defaultValue: null));
properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null)); properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('applyTextScaling', applyTextScaling, defaultValue: null));
} }
} }
...@@ -10,8 +10,6 @@ import 'package:flutter/rendering.dart'; ...@@ -10,8 +10,6 @@ import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'framework.dart'; import 'framework.dart';
const double _kEngineDefaultFontSize = 14.0;
// Examples can assume: // Examples can assume:
// late WidgetSpan myWidgetSpan; // late WidgetSpan myWidgetSpan;
...@@ -100,7 +98,7 @@ class WidgetSpan extends PlaceholderSpan { ...@@ -100,7 +98,7 @@ class WidgetSpan extends PlaceholderSpan {
final List<Widget> widgets = <Widget>[]; final List<Widget> widgets = <Widget>[];
// _kEngineDefaultFontSize is the default font size to use when none of the // _kEngineDefaultFontSize is the default font size to use when none of the
// ancestor spans specifies one. // ancestor spans specifies one.
final List<double> fontSizeStack = <double>[_kEngineDefaultFontSize]; final List<double> fontSizeStack = <double>[kDefaultFontSize];
int index = 0; int index = 0;
// This assumes an InlineSpan tree's logical order is equivalent to preorder. // This assumes an InlineSpan tree's logical order is equivalent to preorder.
bool visitSubtree(InlineSpan span) { bool visitSubtree(InlineSpan span) {
......
...@@ -15,7 +15,8 @@ void main() { ...@@ -15,7 +15,8 @@ void main() {
grade: 0.0, grade: 0.0,
opticalSize: 48.0, opticalSize: 48.0,
color: Color(0xAAAAAAAA), color: Color(0xAAAAAAAA),
opacity: 0.5 opacity: 0.5,
applyTextScaling: true,
); );
late IconThemeData retrieved; late IconThemeData retrieved;
......
...@@ -113,6 +113,126 @@ void main() { ...@@ -113,6 +113,126 @@ void main() {
expect(renderObject.size, equals(const Size.square(24.0))); expect(renderObject.size, equals(const Size.square(24.0)));
}); });
testWidgetsWithLeakTracking('Icon sizing - no theme, default size, considering text scaler', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: MediaQueryData(
textScaler: _TextDoubler(),
),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Icon(
null,
applyTextScaling: true,
),
),
),
),
);
final RenderBox renderObject = tester.renderObject(find.byType(Icon));
expect(renderObject.size, equals(const Size.square(48.0)));
});
testWidgetsWithLeakTracking('Icon sizing - no theme, explicit size, considering text scaler', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: MediaQueryData(
textScaler: _TextDoubler(),
),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Icon(
null,
size: 96.0,
applyTextScaling: true,
),
),
),
),
);
final RenderBox renderObject = tester.renderObject(find.byType(Icon));
expect(renderObject.size, equals(const Size.square(192.0)));
});
testWidgetsWithLeakTracking('Icon sizing - sized theme, considering text scaler', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: MediaQueryData(
textScaler: _TextDoubler(),
),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconTheme(
data: IconThemeData(
size: 36.0,
applyTextScaling: true,
),
child: Icon(null),
),
),
),
),
);
final RenderBox renderObject = tester.renderObject(find.byType(Icon));
expect(renderObject.size, equals(const Size.square(72.0)));
});
testWidgetsWithLeakTracking('Icon sizing - sized theme, explicit size, considering text scale', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: MediaQueryData(
textScaler: _TextDoubler(),
),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconTheme(
data: IconThemeData(
size: 36.0,
applyTextScaling: true,
),
child: Icon(
null,
size: 48.0,
applyTextScaling: false,
),
),
),
),
),
);
final RenderBox renderObject = tester.renderObject(find.byType(Icon));
expect(renderObject.size, equals(const Size.square(48.0)));
});
testWidgetsWithLeakTracking('Icon sizing - sizeless theme, default size, default consideration for text scaler', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: MediaQueryData(
textScaler: _TextDoubler(),
),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconTheme(
data: IconThemeData(),
child: Icon(null),
),
),
),
),
);
final RenderBox renderObject = tester.renderObject(find.byType(Icon));
expect(renderObject.size, equals(const Size.square(24.0)));
});
testWidgetsWithLeakTracking('Icon with custom font', (WidgetTester tester) async { testWidgetsWithLeakTracking('Icon with custom font', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -335,3 +455,13 @@ void main() { ...@@ -335,3 +455,13 @@ void main() {
expect(() => Icon(Icons.abc, opticalSize: 0), throwsAssertionError); expect(() => Icon(Icons.abc, opticalSize: 0), throwsAssertionError);
}); });
} }
final class _TextDoubler extends TextScaler {
const _TextDoubler();
@override
double scale(double fontSize) => fontSize * 2.0;
@override
double get textScaleFactor => 2.0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment