Skip to content

Commit

Permalink
Provide parameter to Icon and IconThemeData for they to consider the …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
mateusfccp authored Nov 29, 2023
1 parent 4498175 commit 49632fc
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 19 deletions.
3 changes: 3 additions & 0 deletions packages/flutter/lib/src/cupertino/icon_theme_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
super.color,
super.opacity,
super.shadows,
super.applyTextScaling,
});

/// Called by [IconTheme.of] to resolve [color] against the given [BuildContext].
Expand All @@ -40,6 +41,7 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
Color? color,
double? opacity,
List<Shadow>? shadows,
bool? applyTextScaling,
}) {
return CupertinoIconThemeData(
size: size ?? this.size,
Expand All @@ -50,6 +52,7 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
color: color ?? this.color,
opacity: opacity ?? this.opacity,
shadows: shadows ?? this.shadows,
applyTextScaling: applyTextScaling ?? this.applyTextScaling,
);
}

Expand Down
11 changes: 6 additions & 5 deletions packages/flutter/lib/src/painting/text_painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ import 'text_span.dart';

export 'package:flutter/services.dart' show TextRange, TextSelection;

// The default font size if none is specified. This should be kept in
// 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).
const double _kDefaultFontSize = 14.0;
/// The default font size if none is specified.
///
/// This should be kept in sync with the defaults set in the engine (e.g.,
/// LibTxt's text_style.h, paragraph_style.h).
const double kDefaultFontSize = 14.0;

/// How overflowing text should be handled.
///
Expand Down Expand Up @@ -971,7 +972,7 @@ class TextPainter {
// Use the default font size to multiply by as RichText does not
// perform inheriting [TextStyle]s and would otherwise
// fail to apply textScaler.
fontSize: textScaler.scale(_kDefaultFontSize),
fontSize: textScaler.scale(kDefaultFontSize),
maxLines: maxLines,
textHeightBehavior: _textHeightBehavior,
ellipsis: ellipsis,
Expand Down
7 changes: 1 addition & 6 deletions packages/flutter/lib/src/painting/text_style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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'
'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:
// late BuildContext context;

Expand Down Expand Up @@ -1353,7 +1348,7 @@ class TextStyle with Diagnosticable {
fontWeight: fontWeight ?? this.fontWeight,
fontStyle: fontStyle ?? this.fontStyle,
fontFamily: fontFamily ?? this.fontFamily,
fontSize: textScaler.scale(fontSize ?? this.fontSize ?? _kDefaultFontSize),
fontSize: textScaler.scale(fontSize ?? this.fontSize ?? kDefaultFontSize),
height: height ?? this.height,
textHeightBehavior: effectiveTextHeightBehavior,
strutStyle: strutStyle == null ? null : ui.StrutStyle(
Expand Down
19 changes: 18 additions & 1 deletion packages/flutter/lib/src/widgets/icon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'framework.dart';
import 'icon_data.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'media_query.dart';

/// A graphical icon widget drawn with a glyph from a font described in
/// an [IconData] such as material's predefined [IconData]s in [Icons].
Expand Down Expand Up @@ -80,6 +81,7 @@ class Icon extends StatelessWidget {
this.shadows,
this.semanticLabel,
this.textDirection,
this.applyTextScaling,
}) : assert(fill == null || (0.0 <= fill && fill <= 1.0)),
assert(weight == null || (0.0 < weight)),
assert(opticalSize == null || (0.0 < opticalSize));
Expand Down Expand Up @@ -231,14 +233,28 @@ class Icon extends StatelessWidget {
/// specified, either directly using this property or using [Directionality].
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
Widget build(BuildContext context) {
assert(this.textDirection != null || debugCheckHasDirectionality(context));
final TextDirection textDirection = this.textDirection ?? Directionality.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;

Expand Down Expand Up @@ -332,5 +348,6 @@ class Icon extends StatelessWidget {
properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null));
properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('applyTextScaling', applyTextScaling, defaultValue: null));
}
}
1 change: 1 addition & 0 deletions packages/flutter/lib/src/widgets/icon_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class IconTheme extends InheritedTheme {
color: iconThemeData.color ?? const IconThemeData.fallback().color,
opacity: iconThemeData.opacity ?? const IconThemeData.fallback().opacity,
shadows: iconThemeData.shadows ?? const IconThemeData.fallback().shadows,
applyTextScaling: iconThemeData.applyTextScaling ?? const IconThemeData.fallback().applyTextScaling,
);
}

Expand Down
19 changes: 16 additions & 3 deletions packages/flutter/lib/src/widgets/icon_theme_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class IconThemeData with Diagnosticable {
this.color,
double? opacity,
this.shadows,
this.applyTextScaling,
}) : _opacity = opacity,
assert(fill == null || (0.0 <= fill && fill <= 1.0)),
assert(weight == null || (0.0 < weight)),
Expand All @@ -48,7 +49,8 @@ class IconThemeData with Diagnosticable {
opticalSize = 48.0,
color = const Color(0xFF000000),
_opacity = 1.0,
shadows = null;
shadows = null,
applyTextScaling = false;

/// Creates a copy of this icon theme but with the given fields replaced with
/// the new values.
Expand All @@ -61,6 +63,7 @@ class IconThemeData with Diagnosticable {
Color? color,
double? opacity,
List<Shadow>? shadows,
bool? applyTextScaling,
}) {
return IconThemeData(
size: size ?? this.size,
Expand All @@ -71,6 +74,7 @@ class IconThemeData with Diagnosticable {
color: color ?? this.color,
opacity: opacity ?? this.opacity,
shadows: shadows ?? this.shadows,
applyTextScaling: applyTextScaling ?? this.applyTextScaling,
);
}

Expand All @@ -90,6 +94,7 @@ class IconThemeData with Diagnosticable {
color: other.color,
opacity: other.opacity,
shadows: other.shadows,
applyTextScaling: other.applyTextScaling,
);
}

Expand Down Expand Up @@ -118,7 +123,8 @@ class IconThemeData with Diagnosticable {
&& grade != null
&& opticalSize != null
&& color != null
&& opacity != null;
&& opacity != null
&& applyTextScaling != null;

/// The default for [Icon.size].
///
Expand Down Expand Up @@ -163,6 +169,9 @@ class IconThemeData with Diagnosticable {
/// The default for [Icon.shadows].
final List<Shadow>? shadows;

/// The default for [Icon.applyTextScaling].
final bool? applyTextScaling;

/// Linearly interpolate between two icon theme data objects.
///
/// {@macro dart.ui.shadow.lerp}
Expand All @@ -179,6 +188,7 @@ class IconThemeData with Diagnosticable {
color: Color.lerp(a?.color, b?.color, t),
opacity: ui.lerpDouble(a?.opacity, b?.opacity, t),
shadows: Shadow.lerpList(a?.shadows, b?.shadows, t),
applyTextScaling: t < 0.5 ? a?.applyTextScaling : b?.applyTextScaling,
);
}

Expand All @@ -195,7 +205,8 @@ class IconThemeData with Diagnosticable {
&& other.opticalSize == opticalSize
&& other.color == color
&& other.opacity == opacity
&& listEquals(other.shadows, shadows);
&& listEquals(other.shadows, shadows)
&& other.applyTextScaling == applyTextScaling;
}

@override
Expand All @@ -208,6 +219,7 @@ class IconThemeData with Diagnosticable {
color,
opacity,
shadows == null ? null : Object.hashAll(shadows!),
applyTextScaling,
);

@override
Expand All @@ -221,5 +233,6 @@ class IconThemeData with Diagnosticable {
properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(DoubleProperty('opacity', opacity, defaultValue: null));
properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('applyTextScaling', applyTextScaling, defaultValue: null));
}
}
4 changes: 1 addition & 3 deletions packages/flutter/lib/src/widgets/widget_span.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'framework.dart';

const double _kEngineDefaultFontSize = 14.0;

// Examples can assume:
// late WidgetSpan myWidgetSpan;

Expand Down Expand Up @@ -100,7 +98,7 @@ class WidgetSpan extends PlaceholderSpan {
final List<Widget> widgets = <Widget>[];
// _kEngineDefaultFontSize is the default font size to use when none of the
// ancestor spans specifies one.
final List<double> fontSizeStack = <double>[_kEngineDefaultFontSize];
final List<double> fontSizeStack = <double>[kDefaultFontSize];
int index = 0;
// This assumes an InlineSpan tree's logical order is equivalent to preorder.
bool visitSubtree(InlineSpan span) {
Expand Down
3 changes: 2 additions & 1 deletion packages/flutter/test/cupertino/icon_theme_data_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ void main() {
grade: 0.0,
opticalSize: 48.0,
color: Color(0xAAAAAAAA),
opacity: 0.5
opacity: 0.5,
applyTextScaling: true,
);

late IconThemeData retrieved;
Expand Down
130 changes: 130 additions & 0 deletions packages/flutter/test/widgets/icon_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,126 @@ void main() {
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 {
await tester.pumpWidget(
Expand Down Expand Up @@ -335,3 +455,13 @@ void main() {
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;
}

0 comments on commit 49632fc

Please sign in to comment.