// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'basic.dart'; import 'debug.dart'; import 'framework.dart'; import 'icon_data.dart'; import 'icon_theme.dart'; import 'icon_theme_data.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]. /// /// Icons are not interactive. For an interactive icon, consider material's /// [IconButton]. /// /// There must be an ambient [Directionality] widget when using [Icon]. /// Typically this is introduced automatically by the [WidgetsApp] or /// [MaterialApp]. /// /// This widget assumes that the rendered icon is squared. Non-squared icons may /// render incorrectly. /// /// {@tool snippet} /// /// This example shows how to create a [Row] of [Icon]s in different colors and /// sizes. The first [Icon] uses a [semanticLabel] to announce in accessibility /// modes like TalkBack and VoiceOver. /// /// ![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) /// /// ```dart /// Row( /// mainAxisAlignment: MainAxisAlignment.spaceAround, /// children: const <Widget>[ /// Icon( /// Icons.favorite, /// color: Colors.pink, /// size: 24.0, /// semanticLabel: 'Text to announce in accessibility modes', /// ), /// Icon( /// Icons.audiotrack, /// color: Colors.green, /// size: 30.0, /// ), /// Icon( /// Icons.beach_access, /// color: Colors.blue, /// size: 36.0, /// ), /// ], /// ) /// ``` /// {@end-tool} /// /// See also: /// /// * [IconButton], for interactive icons. /// * [Icons], for the list of available Material Icons for use with this class. /// * [IconTheme], which provides ambient configuration for icons. /// * [ImageIcon], for showing icons from [AssetImage]s or other [ImageProvider]s. class Icon extends StatelessWidget { /// Creates an icon. const Icon( this.icon, { super.key, this.size, this.fill, this.weight, this.grade, this.opticalSize, this.color, this.shadows, this.semanticLabel, this.textDirection, }) : assert(fill == null || (0.0 <= fill && fill <= 1.0)), assert(weight == null || (0.0 < weight)), assert(opticalSize == null || (0.0 < opticalSize)); /// The icon to display. The available icons are described in [Icons]. /// /// The icon can be null, in which case the widget will render as an empty /// space of the specified [size]. final IconData? icon; /// The size of the icon in logical pixels. /// /// Icons occupy a square with width and height equal to size. /// /// Defaults to the nearest [IconTheme]'s [IconThemeData.size]. /// /// If this [Icon] is being placed inside an [IconButton], then use /// [IconButton.iconSize] instead, so that the [IconButton] can make the splash /// area the appropriate size as well. The [IconButton] uses an [IconTheme] to /// pass down the size to the [Icon]. final double? size; /// The fill for drawing the icon. /// /// Requires the underlying icon font to support the `FILL` [FontVariation] /// axis, otherwise has no effect. Variable font filenames often indicate /// the supported axes. Must be between 0.0 (unfilled) and 1.0 (filled), /// inclusive. /// /// Can be used to convey a state transition for animation or interaction. /// /// Defaults to nearest [IconTheme]'s [IconThemeData.fill]. /// /// See also: /// * [weight], for controlling stroke weight. /// * [grade], for controlling stroke weight in a more granular way. /// * [opticalSize], for controlling optical size. final double? fill; /// The stroke weight for drawing the icon. /// /// Requires the underlying icon font to support the `wght` [FontVariation] /// axis, otherwise has no effect. Variable font filenames often indicate /// the supported axes. Must be greater than 0. /// /// Defaults to nearest [IconTheme]'s [IconThemeData.weight]. /// /// See also: /// * [fill], for controlling fill. /// * [grade], for controlling stroke weight in a more granular way. /// * [opticalSize], for controlling optical size. /// * https://fonts.google.com/knowledge/glossary/weight_axis final double? weight; /// The grade (granular stroke weight) for drawing the icon. /// /// Requires the underlying icon font to support the `GRAD` [FontVariation] /// axis, otherwise has no effect. Variable font filenames often indicate /// the supported axes. Can be negative. /// /// Grade and [weight] both affect a symbol's stroke weight (thickness), but /// grade has a smaller impact on the size of the symbol. /// /// Grade is also available in some text fonts. One can match grade levels /// between text and symbols for a harmonious visual effect. For example, if /// the text font has a -25 grade value, the symbols can match it with a /// suitable value, say -25. /// /// Defaults to nearest [IconTheme]'s [IconThemeData.grade]. /// /// See also: /// * [fill], for controlling fill. /// * [weight], for controlling stroke weight in a less granular way. /// * [opticalSize], for controlling optical size. /// * https://fonts.google.com/knowledge/glossary/grade_axis final double? grade; /// The optical size for drawing the icon. /// /// Requires the underlying icon font to support the `opsz` [FontVariation] /// axis, otherwise has no effect. Variable font filenames often indicate /// the supported axes. Must be greater than 0. /// /// For an icon to look the same at different sizes, the stroke weight /// (thickness) must change as the icon size scales. Optical size offers a way /// to automatically adjust the stroke weight as icon size changes. /// /// Defaults to nearest [IconTheme]'s [IconThemeData.opticalSize]. /// /// See also: /// * [fill], for controlling fill. /// * [weight], for controlling stroke weight. /// * [grade], for controlling stroke weight in a more granular way. /// * https://fonts.google.com/knowledge/glossary/optical_size_axis final double? opticalSize; /// The color to use when drawing the icon. /// /// Defaults to the nearest [IconTheme]'s [IconThemeData.color]. /// /// The color (whether specified explicitly here or obtained from the /// [IconTheme]) will be further adjusted by the nearest [IconTheme]'s /// [IconThemeData.opacity]. /// /// {@tool snippet} /// Typically, a Material Design color will be used, as follows: /// /// ```dart /// Icon( /// Icons.widgets, /// color: Colors.blue.shade400, /// ) /// ``` /// {@end-tool} final Color? color; /// A list of [Shadow]s that will be painted underneath the icon. /// /// Multiple shadows are supported to replicate lighting from multiple light /// sources. /// /// Shadows must be in the same order for [Icon] to be considered as /// equivalent as order produces differing transparency. /// /// Defaults to the nearest [IconTheme]'s [IconThemeData.shadows]. final List<Shadow>? shadows; /// Semantic label for the icon. /// /// Announced in accessibility modes (e.g TalkBack/VoiceOver). /// This label does not show in the UI. /// /// * [SemanticsProperties.label], which is set to [semanticLabel] in the /// underlying [Semantics] widget. final String? semanticLabel; /// The text direction to use for rendering the icon. /// /// If this is null, the ambient [Directionality] is used instead. /// /// Some icons follow the reading direction. For example, "back" buttons point /// left in left-to-right environments and right in right-to-left /// environments. Such icons have their [IconData.matchTextDirection] field /// set to true, and the [Icon] widget uses the [textDirection] to determine /// the orientation in which to draw the icon. /// /// This property has no effect if the [icon]'s [IconData.matchTextDirection] /// field is false, but for consistency a text direction value must always be /// specified, either directly using this property or using [Directionality]. final TextDirection? textDirection; @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 double? iconFill = fill ?? iconTheme.fill; final double? iconWeight = weight ?? iconTheme.weight; final double? iconGrade = grade ?? iconTheme.grade; final double? iconOpticalSize = opticalSize ?? iconTheme.opticalSize; final List<Shadow>? iconShadows = shadows ?? iconTheme.shadows; if (icon == null) { return Semantics( label: semanticLabel, child: SizedBox(width: iconSize, height: iconSize), ); } final double iconOpacity = iconTheme.opacity ?? 1.0; Color iconColor = color ?? iconTheme.color!; if (iconOpacity != 1.0) { iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity); } Widget iconWidget = RichText( overflow: TextOverflow.visible, // Never clip. textDirection: textDirection, // Since we already fetched it for the assert... text: TextSpan( text: String.fromCharCode(icon!.codePoint), style: TextStyle( fontVariations: <FontVariation>[ if (iconFill != null) FontVariation('FILL', iconFill), if (iconWeight != null) FontVariation('wght', iconWeight), if (iconGrade != null) FontVariation('GRAD', iconGrade), if (iconOpticalSize != null) FontVariation('opsz', iconOpticalSize), ], inherit: false, color: iconColor, fontSize: iconSize, fontFamily: icon!.fontFamily, package: icon!.fontPackage, shadows: iconShadows, ), ), ); if (icon!.matchTextDirection) { switch (textDirection) { case TextDirection.rtl: iconWidget = Transform( transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0), alignment: Alignment.center, transformHitTests: false, child: iconWidget, ); break; case TextDirection.ltr: break; } } return Semantics( label: semanticLabel, child: ExcludeSemantics( child: SizedBox( width: iconSize, height: iconSize, child: Center( child: iconWidget, ), ), ), ); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(IconDataProperty('icon', icon, ifNull: '<empty>', showName: false)); properties.add(DoubleProperty('size', size, defaultValue: null)); properties.add(DoubleProperty('fill', fill, defaultValue: null)); properties.add(DoubleProperty('weight', weight, defaultValue: null)); properties.add(DoubleProperty('grade', grade, defaultValue: null)); properties.add(DoubleProperty('opticalSize', opticalSize, defaultValue: null)); properties.add(ColorProperty('color', color, defaultValue: null)); properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null)); properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null)); properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); } }