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
5/// @docImport 'package:flutter/material.dart';
6///
7/// @docImport 'app.dart';
8/// @docImport 'image_icon.dart';
9library;
10
11import 'package:flutter/foundation.dart';
12import 'package:flutter/rendering.dart';
13
14import 'basic.dart';
15import 'debug.dart';
16import 'framework.dart';
17import 'icon_data.dart';
18import 'icon_theme.dart';
19import 'icon_theme_data.dart';
20import 'media_query.dart';
21
22/// A graphical icon widget drawn with a glyph from a font described in
23/// an [IconData] such as material's predefined [IconData]s in [Icons].
24///
25/// Icons are not interactive. For an interactive icon, consider material's
26/// [IconButton].
27///
28/// There must be an ambient [Directionality] widget when using [Icon].
29/// Typically this is introduced automatically by the [WidgetsApp] or
30/// [MaterialApp].
31///
32/// This widget assumes that the rendered icon is squared. Non-squared icons may
33/// render incorrectly.
34///
35/// {@tool snippet}
36///
37/// This example shows how to create a [Row] of [Icon]s in different colors and
38/// sizes. The first [Icon] uses a [semanticLabel] to announce in accessibility
39/// modes like TalkBack and VoiceOver.
40///
41/// ![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)
42///
43/// ```dart
44/// const Row(
45/// mainAxisAlignment: MainAxisAlignment.spaceAround,
46/// children: <Widget>[
47/// Icon(
48/// Icons.favorite,
49/// color: Colors.pink,
50/// size: 24.0,
51/// semanticLabel: 'Text to announce in accessibility modes',
52/// ),
53/// Icon(
54/// Icons.audiotrack,
55/// color: Colors.green,
56/// size: 30.0,
57/// ),
58/// Icon(
59/// Icons.beach_access,
60/// color: Colors.blue,
61/// size: 36.0,
62/// ),
63/// ],
64/// )
65/// ```
66/// {@end-tool}
67///
68/// See also:
69///
70/// * [IconButton], for interactive icons.
71/// * [Icons], for the list of available Material Icons for use with this class.
72/// * [IconTheme], which provides ambient configuration for icons.
73/// * [ImageIcon], for showing icons from [AssetImage]s or other [ImageProvider]s.
74class Icon extends StatelessWidget {
75 /// Creates an icon.
76 const Icon(
77 this.icon, {
78 super.key,
79 this.size,
80 this.fill,
81 this.weight,
82 this.grade,
83 this.opticalSize,
84 this.color,
85 this.shadows,
86 this.semanticLabel,
87 this.textDirection,
88 this.applyTextScaling,
89 this.blendMode,
90 }) : assert(fill == null || (0.0 <= fill && fill <= 1.0)),
91 assert(weight == null || (0.0 < weight)),
92 assert(opticalSize == null || (0.0 < opticalSize));
93
94 /// The icon to display. The available icons are described in [Icons].
95 ///
96 /// The icon can be null, in which case the widget will render as an empty
97 /// space of the specified [size].
98 final IconData? icon;
99
100 /// The size of the icon in logical pixels.
101 ///
102 /// Icons occupy a square with width and height equal to size.
103 ///
104 /// Defaults to the nearest [IconTheme]'s [IconThemeData.size].
105 ///
106 /// If this [Icon] is being placed inside an [IconButton], then use
107 /// [IconButton.iconSize] instead, so that the [IconButton] can make the splash
108 /// area the appropriate size as well. The [IconButton] uses an [IconTheme] to
109 /// pass down the size to the [Icon].
110 final double? size;
111
112 /// The fill for drawing the icon.
113 ///
114 /// Requires the underlying icon font to support the `FILL` [FontVariation]
115 /// axis, otherwise has no effect. Variable font filenames often indicate
116 /// the supported axes. Must be between 0.0 (unfilled) and 1.0 (filled),
117 /// inclusive.
118 ///
119 /// Can be used to convey a state transition for animation or interaction.
120 ///
121 /// Defaults to nearest [IconTheme]'s [IconThemeData.fill].
122 ///
123 /// See also:
124 /// * [weight], for controlling stroke weight.
125 /// * [grade], for controlling stroke weight in a more granular way.
126 /// * [opticalSize], for controlling optical size.
127 final double? fill;
128
129 /// The stroke weight for drawing the icon.
130 ///
131 /// Requires the underlying icon font to support the `wght` [FontVariation]
132 /// axis, otherwise has no effect. Variable font filenames often indicate
133 /// the supported axes. Must be greater than 0.
134 ///
135 /// Defaults to nearest [IconTheme]'s [IconThemeData.weight].
136 ///
137 /// See also:
138 /// * [fill], for controlling fill.
139 /// * [grade], for controlling stroke weight in a more granular way.
140 /// * [opticalSize], for controlling optical size.
141 /// * https://fonts.google.com/knowledge/glossary/weight_axis
142 final double? weight;
143
144 /// The grade (granular stroke weight) for drawing the icon.
145 ///
146 /// Requires the underlying icon font to support the `GRAD` [FontVariation]
147 /// axis, otherwise has no effect. Variable font filenames often indicate
148 /// the supported axes. Can be negative.
149 ///
150 /// Grade and [weight] both affect a symbol's stroke weight (thickness), but
151 /// grade has a smaller impact on the size of the symbol.
152 ///
153 /// Grade is also available in some text fonts. One can match grade levels
154 /// between text and symbols for a harmonious visual effect. For example, if
155 /// the text font has a -25 grade value, the symbols can match it with a
156 /// suitable value, say -25.
157 ///
158 /// Defaults to nearest [IconTheme]'s [IconThemeData.grade].
159 ///
160 /// See also:
161 /// * [fill], for controlling fill.
162 /// * [weight], for controlling stroke weight in a less granular way.
163 /// * [opticalSize], for controlling optical size.
164 /// * https://fonts.google.com/knowledge/glossary/grade_axis
165 final double? grade;
166
167 /// The optical size for drawing the icon.
168 ///
169 /// Requires the underlying icon font to support the `opsz` [FontVariation]
170 /// axis, otherwise has no effect. Variable font filenames often indicate
171 /// the supported axes. Must be greater than 0.
172 ///
173 /// For an icon to look the same at different sizes, the stroke weight
174 /// (thickness) must change as the icon size scales. Optical size offers a way
175 /// to automatically adjust the stroke weight as icon size changes.
176 ///
177 /// Defaults to nearest [IconTheme]'s [IconThemeData.opticalSize].
178 ///
179 /// See also:
180 /// * [fill], for controlling fill.
181 /// * [weight], for controlling stroke weight.
182 /// * [grade], for controlling stroke weight in a more granular way.
183 /// * https://fonts.google.com/knowledge/glossary/optical_size_axis
184 final double? opticalSize;
185
186 /// The color to use when drawing the icon.
187 ///
188 /// Defaults to the nearest [IconTheme]'s [IconThemeData.color].
189 ///
190 /// The color (whether specified explicitly here or obtained from the
191 /// [IconTheme]) will be further adjusted by the nearest [IconTheme]'s
192 /// [IconThemeData.opacity].
193 ///
194 /// {@tool snippet}
195 /// Typically, a Material Design color will be used, as follows:
196 ///
197 /// ```dart
198 /// Icon(
199 /// Icons.widgets,
200 /// color: Colors.blue.shade400,
201 /// )
202 /// ```
203 /// {@end-tool}
204 final Color? color;
205
206 /// A list of [Shadow]s that will be painted underneath the icon.
207 ///
208 /// Multiple shadows are supported to replicate lighting from multiple light
209 /// sources.
210 ///
211 /// Shadows must be in the same order for [Icon] to be considered as
212 /// equivalent as order produces differing transparency.
213 ///
214 /// Defaults to the nearest [IconTheme]'s [IconThemeData.shadows].
215 final List<Shadow>? shadows;
216
217 /// Semantic label for the icon.
218 ///
219 /// Announced by assistive technologies (e.g TalkBack/VoiceOver).
220 /// This label does not show in the UI.
221 ///
222 /// * [SemanticsProperties.label], which is set to [semanticLabel] in the
223 /// underlying [Semantics] widget.
224 final String? semanticLabel;
225
226 /// The text direction to use for rendering the icon.
227 ///
228 /// If this is null, the ambient [Directionality] is used instead.
229 ///
230 /// Some icons follow the reading direction. For example, "back" buttons point
231 /// left in left-to-right environments and right in right-to-left
232 /// environments. Such icons have their [IconData.matchTextDirection] field
233 /// set to true, and the [Icon] widget uses the [textDirection] to determine
234 /// the orientation in which to draw the icon.
235 ///
236 /// This property has no effect if the [icon]'s [IconData.matchTextDirection]
237 /// field is false, but for consistency a text direction value must always be
238 /// specified, either directly using this property or using [Directionality].
239 final TextDirection? textDirection;
240
241 /// Whether to scale the size of this widget using the ambient [MediaQuery]'s [TextScaler].
242 ///
243 /// This is specially useful when you have an icon associated with a text, as
244 /// scaling the text without scaling the icon would result in a confusing
245 /// interface.
246 ///
247 /// Defaults to the nearest [IconTheme]'s
248 /// [IconThemeData.applyTextScaling].
249 final bool? applyTextScaling;
250
251 /// The [BlendMode] to apply to the foreground of the icon.
252 ///
253 /// Defaults to [BlendMode.srcOver]
254 final BlendMode? blendMode;
255
256 @override
257 Widget build(BuildContext context) {
258 assert(this.textDirection != null || debugCheckHasDirectionality(context));
259 final TextDirection textDirection = this.textDirection ?? Directionality.of(context);
260
261 final IconThemeData iconTheme = IconTheme.of(context);
262
263 final bool applyTextScaling = this.applyTextScaling ?? iconTheme.applyTextScaling ?? false;
264
265 final double tentativeIconSize = size ?? iconTheme.size ?? kDefaultFontSize;
266
267 final double iconSize = applyTextScaling ? MediaQuery.textScalerOf(context).scale(tentativeIconSize) : tentativeIconSize;
268
269 final double? iconFill = fill ?? iconTheme.fill;
270
271 final double? iconWeight = weight ?? iconTheme.weight;
272
273 final double? iconGrade = grade ?? iconTheme.grade;
274
275 final double? iconOpticalSize = opticalSize ?? iconTheme.opticalSize;
276
277 final List<Shadow>? iconShadows = shadows ?? iconTheme.shadows;
278
279 final IconData? icon = this.icon;
280 if (icon == null) {
281 return Semantics(
282 label: semanticLabel,
283 child: SizedBox(width: iconSize, height: iconSize),
284 );
285 }
286
287 final double iconOpacity = iconTheme.opacity ?? 1.0;
288 Color? iconColor = color ?? iconTheme.color!;
289 Paint? foreground;
290 if (iconOpacity != 1.0) {
291 iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity);
292 }
293 if (blendMode != null) {
294 foreground = Paint()
295 ..blendMode = blendMode!
296 ..color = iconColor;
297 // Cannot provide both a color and a foreground.
298 iconColor = null;
299 }
300
301 final TextStyle fontStyle = TextStyle(
302 fontVariations: <FontVariation>[
303 if (iconFill != null) FontVariation('FILL', iconFill),
304 if (iconWeight != null) FontVariation('wght', iconWeight),
305 if (iconGrade != null) FontVariation('GRAD', iconGrade),
306 if (iconOpticalSize != null) FontVariation('opsz', iconOpticalSize),
307 ],
308 inherit: false,
309 color: iconColor,
310 fontSize: iconSize,
311 fontFamily: icon.fontFamily,
312 package: icon.fontPackage,
313 fontFamilyFallback: icon.fontFamilyFallback,
314 shadows: iconShadows,
315 height: 1.0, // Makes sure the font's body is vertically centered within the iconSize x iconSize square.
316 leadingDistribution: TextLeadingDistribution.even,
317 foreground: foreground,
318 );
319
320 Widget iconWidget = RichText(
321 overflow: TextOverflow.visible, // Never clip.
322 textDirection: textDirection, // Since we already fetched it for the assert...
323 text: TextSpan(
324 text: String.fromCharCode(icon.codePoint),
325 style: fontStyle,
326 ),
327 );
328
329 if (icon.matchTextDirection) {
330 switch (textDirection) {
331 case TextDirection.rtl:
332 iconWidget = Transform(
333 transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0),
334 alignment: Alignment.center,
335 transformHitTests: false,
336 child: iconWidget,
337 );
338 case TextDirection.ltr:
339 break;
340 }
341 }
342
343 return Semantics(
344 label: semanticLabel,
345 child: ExcludeSemantics(
346 child: SizedBox(
347 width: iconSize,
348 height: iconSize,
349 child: Center(
350 child: iconWidget,
351 ),
352 ),
353 ),
354 );
355 }
356
357 @override
358 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
359 super.debugFillProperties(properties);
360 properties.add(IconDataProperty('icon', icon, ifNull: '<empty>', showName: false));
361 properties.add(DoubleProperty('size', size, defaultValue: null));
362 properties.add(DoubleProperty('fill', fill, defaultValue: null));
363 properties.add(DoubleProperty('weight', weight, defaultValue: null));
364 properties.add(DoubleProperty('grade', grade, defaultValue: null));
365 properties.add(DoubleProperty('opticalSize', opticalSize, defaultValue: null));
366 properties.add(ColorProperty('color', color, defaultValue: null));
367 properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null));
368 properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
369 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
370 properties.add(DiagnosticsProperty<bool>('applyTextScaling', applyTextScaling, defaultValue: null));
371 }
372}
373