// 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 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; /// Creates an Icon Widget that works for non-material Icons, such as the /// Font Awesome Icons. /// /// The default `Icon` Widget from the Material package assumes all Icons are /// square in size and wraps all Icons in a square SizedBox Widget. Icons from /// the FontAwesome package are often wider than they are tall, which causes /// alignment and cutoff issues. /// /// This Widget does not wrap the icons in a fixed square box, which allows the /// icons to render based on their size. class FaIcon extends StatelessWidget { /// Creates an icon. /// /// The [size] and [color] default to the value given by the current [IconTheme]. const FaIcon( this.icon, { Key? key, this.size, this.color, this.semanticLabel, this.textDirection, this.shadows, }) : super(key: key); /// The icon to display. Available icons are listed in [FontAwesomeIcons]. /// /// 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 current [IconTheme] size, if any. If there is no /// [IconTheme], or it does not specify an explicit size, then it defaults to /// 24.0. /// /// 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 [FaIcon]. final double? size; /// The color to use when drawing the icon. /// /// Defaults to the current [IconTheme] color, if any. /// /// The color (whether specified explicitly here or obtained from the /// [IconTheme]) will be further adjusted by the opacity of the current /// [IconTheme], if any. /// /// In material apps, if there is a [Theme] without any [IconTheme]s /// specified, icon colors default to white if the theme is dark /// and black if the theme is light. /// /// If no [IconTheme] and no [Theme] is specified, icons will default to /// black. /// /// See [Theme] to set the current theme and [ThemeData.brightness] /// for setting the current theme's brightness. /// /// {@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; /// 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; /// 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? shadows; @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 List? 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( 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: iconWidget, ), ); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add( IconDataProperty('icon', icon, ifNull: '', showName: false)); properties.add(DoubleProperty('size', size, defaultValue: null)); properties.add(ColorProperty('color', color, defaultValue: null)); properties .add(IterableProperty('shadows', shadows, defaultValue: null)); } }