Skip to content

Commit be3802c

Browse files
authored
Add support for fill, weight, grade, and optical size to Icon (#106896)
* wip * update documentation * x * remove trailing spaces * x * remove useless CupertinoIconThemeData copyWith override * add tests * remove trailing spaces * fix isConcrete * x * x * x * remove trailing spaces * tweak docs * mention that font filenames often indicate the supported axes * add back cupertino IconThemeData copyWith * update copyWith
1 parent bfcceeb commit be3802c

File tree

7 files changed

+365
-88
lines changed

7 files changed

+365
-88
lines changed

packages/flutter/lib/src/cupertino/icon_theme_data.dart

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import 'colors.dart';
1010
/// using [IconTheme.of].
1111
class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
1212
/// Creates a [CupertinoIconThemeData].
13-
///
14-
/// The opacity applies to both explicit and default icon colors. The value
15-
/// is clamped between 0.0 and 1.0.
1613
const CupertinoIconThemeData({
14+
super.size,
15+
super.fill,
16+
super.weight,
17+
super.grade,
18+
super.opticalSize,
1719
super.color,
1820
super.opacity,
19-
super.size,
2021
super.shadows,
2122
});
2223

@@ -30,11 +31,24 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
3031
/// Creates a copy of this icon theme but with the given fields replaced with
3132
/// the new values.
3233
@override
33-
CupertinoIconThemeData copyWith({ Color? color, double? opacity, double? size, List<Shadow>? shadows }) {
34+
CupertinoIconThemeData copyWith({
35+
double? size,
36+
double? fill,
37+
double? weight,
38+
double? grade,
39+
double? opticalSize,
40+
Color? color,
41+
double? opacity,
42+
List<Shadow>? shadows,
43+
}) {
3444
return CupertinoIconThemeData(
45+
size: size ?? this.size,
46+
fill: fill ?? this.fill,
47+
weight: weight ?? this.weight,
48+
grade: grade ?? this.grade,
49+
opticalSize: opticalSize ?? this.opticalSize,
3550
color: color ?? this.color,
3651
opacity: opacity ?? this.opacity,
37-
size: size ?? this.size,
3852
shadows: shadows ?? this.shadows,
3953
);
4054
}

packages/flutter/lib/src/widgets/icon.dart

Lines changed: 111 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:ui';
6+
57
import 'package:flutter/foundation.dart';
68
import 'package:flutter/rendering.dart';
79

@@ -61,22 +63,26 @@ import 'icon_theme_data.dart';
6163
/// See also:
6264
///
6365
/// * [IconButton], for interactive icons.
64-
/// * [Icons], the library of Material Icons available for use with this class.
66+
/// * [Icons], for the list of available Material Icons for use with this class.
6567
/// * [IconTheme], which provides ambient configuration for icons.
6668
/// * [ImageIcon], for showing icons from [AssetImage]s or other [ImageProvider]s.
6769
class Icon extends StatelessWidget {
6870
/// Creates an icon.
69-
///
70-
/// The [size] and [color] default to the value given by the current [IconTheme].
7171
const Icon(
7272
this.icon, {
7373
super.key,
7474
this.size,
75+
this.fill,
76+
this.weight,
77+
this.grade,
78+
this.opticalSize,
7579
this.color,
80+
this.shadows,
7681
this.semanticLabel,
7782
this.textDirection,
78-
this.shadows,
79-
});
83+
}) : assert(fill == null || (0.0 <= fill && fill <= 1.0)),
84+
assert(weight == null || (0.0 < weight)),
85+
assert(opticalSize == null || (0.0 < opticalSize));
8086

8187
/// The icon to display. The available icons are described in [Icons].
8288
///
@@ -88,33 +94,95 @@ class Icon extends StatelessWidget {
8894
///
8995
/// Icons occupy a square with width and height equal to size.
9096
///
91-
/// Defaults to the current [IconTheme] size, if any. If there is no
92-
/// [IconTheme], or it does not specify an explicit size, then it defaults to
93-
/// 24.0.
97+
/// Defaults to the nearest [IconTheme]'s [IconThemeData.size].
9498
///
9599
/// If this [Icon] is being placed inside an [IconButton], then use
96100
/// [IconButton.iconSize] instead, so that the [IconButton] can make the splash
97101
/// area the appropriate size as well. The [IconButton] uses an [IconTheme] to
98102
/// pass down the size to the [Icon].
99103
final double? size;
100104

101-
/// The color to use when drawing the icon.
105+
/// The fill for drawing the icon.
102106
///
103-
/// Defaults to the current [IconTheme] color, if any.
107+
/// Requires the underlying icon font to support the `FILL` [FontVariation]
108+
/// axis, otherwise has no effect. Variable font filenames often indicate
109+
/// the supported axes. Must be between 0.0 (unfilled) and 1.0 (filled),
110+
/// inclusive.
104111
///
105-
/// The color (whether specified explicitly here or obtained from the
106-
/// [IconTheme]) will be further adjusted by the opacity of the current
107-
/// [IconTheme], if any.
112+
/// Can be used to convey a state transition for animation or interaction.
113+
///
114+
/// Defaults to nearest [IconTheme]'s [IconThemeData.fill].
115+
///
116+
/// See also:
117+
/// * [weight], for controlling stroke weight.
118+
/// * [grade], for controlling stroke weight in a more granular way.
119+
/// * [opticalSize], for controlling optical size.
120+
final double? fill;
121+
122+
/// The stroke weight for drawing the icon.
123+
///
124+
/// Requires the underlying icon font to support the `wght` [FontVariation]
125+
/// axis, otherwise has no effect. Variable font filenames often indicate
126+
/// the supported axes. Must be greater than 0.
127+
///
128+
/// Defaults to nearest [IconTheme]'s [IconThemeData.weight].
129+
///
130+
/// See also:
131+
/// * [fill], for controlling fill.
132+
/// * [grade], for controlling stroke weight in a more granular way.
133+
/// * [opticalSize], for controlling optical size.
134+
/// * https://fonts.google.com/knowledge/glossary/weight_axis
135+
final double? weight;
136+
137+
/// The grade (granular stroke weight) for drawing the icon.
138+
///
139+
/// Requires the underlying icon font to support the `GRAD` [FontVariation]
140+
/// axis, otherwise has no effect. Variable font filenames often indicate
141+
/// the supported axes. Can be negative.
142+
///
143+
/// Grade and [weight] both affect a symbol's stroke weight (thickness), but
144+
/// grade has a smaller impact on the size of the symbol.
145+
///
146+
/// Grade is also available in some text fonts. One can match grade levels
147+
/// between text and symbols for a harmonious visual effect. For example, if
148+
/// the text font has a -25 grade value, the symbols can match it with a
149+
/// suitable value, say -25.
150+
///
151+
/// Defaults to nearest [IconTheme]'s [IconThemeData.grade].
152+
///
153+
/// See also:
154+
/// * [fill], for controlling fill.
155+
/// * [weight], for controlling stroke weight in a less granular way.
156+
/// * [opticalSize], for controlling optical size.
157+
/// * https://fonts.google.com/knowledge/glossary/grade_axis
158+
final double? grade;
159+
160+
/// The optical size for drawing the icon.
161+
///
162+
/// Requires the underlying icon font to support the `opsz` [FontVariation]
163+
/// axis, otherwise has no effect. Variable font filenames often indicate
164+
/// the supported axes. Must be greater than 0.
165+
///
166+
/// For an icon to look the same at different sizes, the stroke weight
167+
/// (thickness) must change as the icon size scales. Optical size offers a way
168+
/// to automatically adjust the stroke weight as icon size changes.
169+
///
170+
/// Defaults to nearest [IconTheme]'s [IconThemeData.opticalSize].
108171
///
109-
/// In material apps, if there is a [Theme] without any [IconTheme]s
110-
/// specified, icon colors default to white if the theme is dark
111-
/// and black if the theme is light.
172+
/// See also:
173+
/// * [fill], for controlling fill.
174+
/// * [weight], for controlling stroke weight.
175+
/// * [grade], for controlling stroke weight in a more granular way.
176+
/// * https://fonts.google.com/knowledge/glossary/optical_size_axis
177+
final double? opticalSize;
178+
179+
/// The color to use when drawing the icon.
112180
///
113-
/// If no [IconTheme] and no [Theme] is specified, icons will default to
114-
/// black.
181+
/// Defaults to the nearest [IconTheme]'s [IconThemeData.color].
115182
///
116-
/// See [Theme] to set the current theme and [ThemeData.brightness]
117-
/// for setting the current theme's brightness.
183+
/// The color (whether specified explicitly here or obtained from the
184+
/// [IconTheme]) will be further adjusted by the nearest [IconTheme]'s
185+
/// [IconThemeData.opacity].
118186
///
119187
/// {@tool snippet}
120188
/// Typically, a Material Design color will be used, as follows:
@@ -128,6 +196,17 @@ class Icon extends StatelessWidget {
128196
/// {@end-tool}
129197
final Color? color;
130198

199+
/// A list of [Shadow]s that will be painted underneath the icon.
200+
///
201+
/// Multiple shadows are supported to replicate lighting from multiple light
202+
/// sources.
203+
///
204+
/// Shadows must be in the same order for [Icon] to be considered as
205+
/// equivalent as order produces differing transparency.
206+
///
207+
/// Defaults to the nearest [IconTheme]'s [IconThemeData.shadows].
208+
final List<Shadow>? shadows;
209+
131210
/// Semantic label for the icon.
132211
///
133212
/// Announced in accessibility modes (e.g TalkBack/VoiceOver).
@@ -152,15 +231,6 @@ class Icon extends StatelessWidget {
152231
/// specified, either directly using this property or using [Directionality].
153232
final TextDirection? textDirection;
154233

155-
/// A list of [Shadow]s that will be painted underneath the icon.
156-
///
157-
/// Multiple shadows are supported to replicate lighting from multiple light
158-
/// sources.
159-
///
160-
/// Shadows must be in the same order for [Icon] to be considered as
161-
/// equivalent as order produces differing transparency.
162-
final List<Shadow>? shadows;
163-
164234
@override
165235
Widget build(BuildContext context) {
166236
assert(this.textDirection != null || debugCheckHasDirectionality(context));
@@ -191,6 +261,12 @@ class Icon extends StatelessWidget {
191261
text: TextSpan(
192262
text: String.fromCharCode(icon!.codePoint),
193263
style: TextStyle(
264+
fontVariations: <FontVariation>[
265+
if (fill != null) FontVariation('FILL', fill!),
266+
if (weight != null) FontVariation('wght', weight!),
267+
if (grade != null) FontVariation('GRAD', grade!),
268+
if (opticalSize != null) FontVariation('opsz', opticalSize!),
269+
],
194270
inherit: false,
195271
color: iconColor,
196272
fontSize: iconSize,
@@ -235,7 +311,13 @@ class Icon extends StatelessWidget {
235311
super.debugFillProperties(properties);
236312
properties.add(IconDataProperty('icon', icon, ifNull: '<empty>', showName: false));
237313
properties.add(DoubleProperty('size', size, defaultValue: null));
314+
properties.add(DoubleProperty('fill', fill, defaultValue: null));
315+
properties.add(DoubleProperty('weight', weight, defaultValue: null));
316+
properties.add(DoubleProperty('grade', grade, defaultValue: null));
317+
properties.add(DoubleProperty('opticalSize', opticalSize, defaultValue: null));
238318
properties.add(ColorProperty('color', color, defaultValue: null));
239319
properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null));
320+
properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
321+
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
240322
}
241323
}

packages/flutter/lib/src/widgets/icon_theme.dart

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ import 'framework.dart';
99
import 'icon_theme_data.dart';
1010
import 'inherited_theme.dart';
1111

12-
/// Controls the default color, opacity, and size of icons in a widget subtree.
12+
/// Controls the default properties of icons in a widget subtree.
1313
///
1414
/// The icon theme is honored by [Icon] and [ImageIcon] widgets.
1515
class IconTheme extends InheritedTheme {
16-
/// Creates an icon theme that controls the color, opacity, and size of
17-
/// descendant widgets.
16+
/// Creates an icon theme that controls properties of descendant widgets.
1817
///
1918
/// Both [data] and [child] arguments must not be null.
2019
const IconTheme({
@@ -24,7 +23,7 @@ class IconTheme extends InheritedTheme {
2423
}) : assert(data != null),
2524
assert(child != null);
2625

27-
/// Creates an icon theme that controls the color, opacity, and size of
26+
/// Creates an icon theme that controls the properties of
2827
/// descendant widgets, and merges in the current icon theme, if any.
2928
///
3029
/// The [data] and [child] arguments must not be null.
@@ -44,7 +43,7 @@ class IconTheme extends InheritedTheme {
4443
);
4544
}
4645

47-
/// The color, opacity, and size to use for icons in this subtree.
46+
/// The set of properties to use for icons in this subtree.
4847
final IconThemeData data;
4948

5049
/// The data from the closest instance of this class that encloses the given
@@ -72,6 +71,10 @@ class IconTheme extends InheritedTheme {
7271
? iconThemeData
7372
: iconThemeData.copyWith(
7473
size: iconThemeData.size ?? const IconThemeData.fallback().size,
74+
fill: iconThemeData.fill ?? const IconThemeData.fallback().fill,
75+
weight: iconThemeData.weight ?? const IconThemeData.fallback().weight,
76+
grade: iconThemeData.grade ?? const IconThemeData.fallback().grade,
77+
opticalSize: iconThemeData.opticalSize ?? const IconThemeData.fallback().opticalSize,
7578
color: iconThemeData.color ?? const IconThemeData.fallback().color,
7679
opacity: iconThemeData.opacity ?? const IconThemeData.fallback().opacity,
7780
shadows: iconThemeData.shadows ?? const IconThemeData.fallback().shadows,

0 commit comments

Comments
 (0)