1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6import 'package:flutter/rendering.dart';
7
8import 'basic.dart';
9import 'debug.dart';
10import 'framework.dart';
11import 'icon_data.dart';
12import 'icon_theme.dart';
13import 'icon_theme_data.dart';
14import 'media_query.dart';
15
16/// A graphical icon widget drawn with a glyph from a font described in
17/// an [IconData] such as material's predefined [IconData]s in [Icons].
18///
19/// Icons are not interactive. For an interactive icon, consider material's
20/// [IconButton].
21///
22/// There must be an ambient [Directionality] widget when using [Icon].
23/// Typically this is introduced automatically by the [WidgetsApp] or
24/// [MaterialApp].
25///
26/// This widget assumes that the rendered icon is squared. Non-squared icons may
27/// render incorrectly.
28///
29/// {@tool snippet}
30///
31/// This example shows how to create a [Row] of [Icon]s in different colors and
32/// sizes. The first [Icon] uses a [semanticLabel] to announce in accessibility
33/// modes like TalkBack and VoiceOver.
34///
35/// ![The following code snippet would generate a row of icons consisting of a pink heart, a green musical note, and a blue umbrella, each progressively bigger than the last.](https://flutter.github.io/assets-for-api-docs/assets/widgets/icon.png)
36///
37/// ```dart
38/// const Row(
39/// mainAxisAlignment: MainAxisAlignment.spaceAround,
40/// children: <Widget>[
41/// Icon(
42/// Icons.favorite,
43/// color: Colors.pink,
44/// size: 24.0,
45/// semanticLabel: 'Text to announce in accessibility modes',
46/// ),
47/// Icon(
48/// Icons.audiotrack,
49/// color: Colors.green,
50/// size: 30.0,
51/// ),
52/// Icon(
53/// Icons.beach_access,
54/// color: Colors.blue,
55/// size: 36.0,
56/// ),
57/// ],
58/// )
59/// ```
60/// {@end-tool}
61///
62/// See also:
63///
64/// * [IconButton], for interactive icons.
65/// * [Icons], for the list of available Material Icons for use with this class.
66/// * [IconTheme], which provides ambient configuration for icons.
67/// * [ImageIcon], for showing icons from [AssetImage]s or other [ImageProvider]s.
68class Icon extends StatelessWidget {
69 /// Creates an icon.
70 const Icon(
71 this.icon, {
72 super.key,
73 this.size,
74 this.fill,
75 this.weight,
76 this.grade,
77 this.opticalSize,
78 this.color,
79 this.shadows,
80 this.semanticLabel,
81 this.textDirection,
82 this.applyTextScaling,
83 }) : assert(fill == null || (0.0 <= fill && fill <= 1.0)),
84 assert(weight == null || (0.0 < weight)),
85 assert(opticalSize == null || (0.0 < opticalSize));
86
87 /// The icon to display. The available icons are described in [Icons].
88 ///
89 /// The icon can be null, in which case the widget will render as an empty
90 /// space of the specified [size].
91 final IconData? icon;
92
93 /// The size of the icon in logical pixels.
94 ///
95 /// Icons occupy a square with width and height equal to size.
96 ///
97 /// Defaults to the nearest [IconTheme]'s [IconThemeData.size].
98 ///
99 /// If this [Icon] is being placed inside an [IconButton], then use
100 /// [IconButton.iconSize] instead, so that the [IconButton] can make the splash
101 /// area the appropriate size as well. The [IconButton] uses an [IconTheme] to
102 /// pass down the size to the [Icon].
103 final double? size;
104
105 /// The fill for drawing the icon.
106 ///
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.
111 ///
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].
171 ///
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.
180 ///
181 /// Defaults to the nearest [IconTheme]'s [IconThemeData.color].
182 ///
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].
186 ///
187 /// {@tool snippet}
188 /// Typically, a Material Design color will be used, as follows:
189 ///
190 /// ```dart
191 /// Icon(
192 /// Icons.widgets,
193 /// color: Colors.blue.shade400,
194 /// )
195 /// ```
196 /// {@end-tool}
197 final Color? color;
198
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
210 /// Semantic label for the icon.
211 ///
212 /// Announced in accessibility modes (e.g TalkBack/VoiceOver).
213 /// This label does not show in the UI.
214 ///
215 /// * [SemanticsProperties.label], which is set to [semanticLabel] in the
216 /// underlying [Semantics] widget.
217 final String? semanticLabel;
218
219 /// The text direction to use for rendering the icon.
220 ///
221 /// If this is null, the ambient [Directionality] is used instead.
222 ///
223 /// Some icons follow the reading direction. For example, "back" buttons point
224 /// left in left-to-right environments and right in right-to-left
225 /// environments. Such icons have their [IconData.matchTextDirection] field
226 /// set to true, and the [Icon] widget uses the [textDirection] to determine
227 /// the orientation in which to draw the icon.
228 ///
229 /// This property has no effect if the [icon]'s [IconData.matchTextDirection]
230 /// field is false, but for consistency a text direction value must always be
231 /// specified, either directly using this property or using [Directionality].
232 final TextDirection? textDirection;
233
234 /// Whether to scale the size of this widget using the ambient [MediaQuery]'s [TextScaler].
235 ///
236 /// This is specially useful when you have an icon associated with a text, as
237 /// scaling the text without scaling the icon would result in a confusing
238 /// interface.
239 ///
240 /// Defaults to the nearest [IconTheme]'s
241 /// [IconThemeData.applyTextScaling].
242 final bool? applyTextScaling;
243
244 @override
245 Widget build(BuildContext context) {
246 assert(this.textDirection != null || debugCheckHasDirectionality(context));
247 final TextDirection textDirection = this.textDirection ?? Directionality.of(context);
248
249 final IconThemeData iconTheme = IconTheme.of(context);
250
251 final bool applyTextScaling = this.applyTextScaling ?? iconTheme.applyTextScaling ?? false;
252
253 final double tentativeIconSize = size ?? iconTheme.size ?? kDefaultFontSize;
254
255 final double iconSize = applyTextScaling ? MediaQuery.textScalerOf(context).scale(tentativeIconSize) : tentativeIconSize;
256
257 final double? iconFill = fill ?? iconTheme.fill;
258
259 final double? iconWeight = weight ?? iconTheme.weight;
260
261 final double? iconGrade = grade ?? iconTheme.grade;
262
263 final double? iconOpticalSize = opticalSize ?? iconTheme.opticalSize;
264
265 final List<Shadow>? iconShadows = shadows ?? iconTheme.shadows;
266
267 final IconData? icon = this.icon;
268 if (icon == null) {
269 return Semantics(
270 label: semanticLabel,
271 child: SizedBox(width: iconSize, height: iconSize),
272 );
273 }
274
275 final double iconOpacity = iconTheme.opacity ?? 1.0;
276 Color iconColor = color ?? iconTheme.color!;
277 if (iconOpacity != 1.0) {
278 iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity);
279 }
280
281 final TextStyle fontStyle = TextStyle(
282 fontVariations: <FontVariation>[
283 if (iconFill != null) FontVariation('FILL', iconFill),
284 if (iconWeight != null) FontVariation('wght', iconWeight),
285 if (iconGrade != null) FontVariation('GRAD', iconGrade),
286 if (iconOpticalSize != null) FontVariation('opsz', iconOpticalSize),
287 ],
288 inherit: false,
289 color: iconColor,
290 fontSize: iconSize,
291 fontFamily: icon.fontFamily,
292 package: icon.fontPackage,
293 fontFamilyFallback: icon.fontFamilyFallback,
294 shadows: iconShadows,
295 height: 1.0, // Makes sure the font's body is vertically centered within the iconSize x iconSize square.
296 leadingDistribution: TextLeadingDistribution.even,
297 );
298
299 Widget iconWidget = RichText(
300 overflow: TextOverflow.visible, // Never clip.
301 textDirection: textDirection, // Since we already fetched it for the assert...
302 text: TextSpan(
303 text: String.fromCharCode(icon.codePoint),
304 style: fontStyle,
305 ),
306 );
307
308 if (icon.matchTextDirection) {
309 switch (textDirection) {
310 case TextDirection.rtl:
311 iconWidget = Transform(
312 transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0),
313 alignment: Alignment.center,
314 transformHitTests: false,
315 child: iconWidget,
316 );
317 case TextDirection.ltr:
318 break;
319 }
320 }
321
322 return Semantics(
323 label: semanticLabel,
324 child: ExcludeSemantics(
325 child: SizedBox(
326 width: iconSize,
327 height: iconSize,
328 child: Center(
329 child: iconWidget,
330 ),
331 ),
332 ),
333 );
334 }
335
336 @override
337 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
338 super.debugFillProperties(properties);
339 properties.add(IconDataProperty('icon', icon, ifNull: '<empty>', showName: false));
340 properties.add(DoubleProperty('size', size, defaultValue: null));
341 properties.add(DoubleProperty('fill', fill, defaultValue: null));
342 properties.add(DoubleProperty('weight', weight, defaultValue: null));
343 properties.add(DoubleProperty('grade', grade, defaultValue: null));
344 properties.add(DoubleProperty('opticalSize', opticalSize, defaultValue: null));
345 properties.add(ColorProperty('color', color, defaultValue: null));
346 properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null));
347 properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
348 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
349 properties.add(DiagnosticsProperty<bool>('applyTextScaling', applyTextScaling, defaultValue: null));
350 }
351}
352