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 'dart:ui' as ui show lerpDouble;
6
7import 'package:flutter/foundation.dart';
8import 'package:flutter/painting.dart';
9
10import 'framework.dart' show BuildContext;
11
12/// Defines the size, font variations, color, opacity, and shadows of icons.
13///
14/// Used by [IconTheme] to control those properties in a widget subtree.
15///
16/// To obtain the current icon theme, use [IconTheme.of]. To convert an icon
17/// theme to a version with all the fields filled in, use
18/// [IconThemeData.fallback].
19@immutable
20class IconThemeData with Diagnosticable {
21 /// Creates an icon theme data.
22 ///
23 /// The opacity applies to both explicit and default icon colors. The value
24 /// is clamped between 0.0 and 1.0.
25 const IconThemeData({
26 this.size,
27 this.fill,
28 this.weight,
29 this.grade,
30 this.opticalSize,
31 this.color,
32 double? opacity,
33 this.shadows,
34 this.applyTextScaling,
35 }) : _opacity = opacity,
36 assert(fill == null || (0.0 <= fill && fill <= 1.0)),
37 assert(weight == null || (0.0 < weight)),
38 assert(opticalSize == null || (0.0 < opticalSize));
39
40 /// Creates an icon theme with some reasonable default values.
41 ///
42 /// The [size] is 24.0, [fill] is 0.0, [weight] is 400.0, [grade] is 0.0,
43 /// opticalSize is 48.0, [color] is black, and [opacity] is 1.0.
44 const IconThemeData.fallback()
45 : size = 24.0,
46 fill = 0.0,
47 weight = 400.0,
48 grade = 0.0,
49 opticalSize = 48.0,
50 color = const Color(0xFF000000),
51 _opacity = 1.0,
52 shadows = null,
53 applyTextScaling = false;
54
55 /// Creates a copy of this icon theme but with the given fields replaced with
56 /// the new values.
57 IconThemeData copyWith({
58 double? size,
59 double? fill,
60 double? weight,
61 double? grade,
62 double? opticalSize,
63 Color? color,
64 double? opacity,
65 List<Shadow>? shadows,
66 bool? applyTextScaling,
67 }) {
68 return IconThemeData(
69 size: size ?? this.size,
70 fill: fill ?? this.fill,
71 weight: weight ?? this.weight,
72 grade: grade ?? this.grade,
73 opticalSize: opticalSize ?? this.opticalSize,
74 color: color ?? this.color,
75 opacity: opacity ?? this.opacity,
76 shadows: shadows ?? this.shadows,
77 applyTextScaling: applyTextScaling ?? this.applyTextScaling,
78 );
79 }
80
81 /// Returns a new icon theme that matches this icon theme but with some values
82 /// replaced by the non-null parameters of the given icon theme. If the given
83 /// icon theme is null, returns this icon theme.
84 IconThemeData merge(IconThemeData? other) {
85 if (other == null) {
86 return this;
87 }
88 return copyWith(
89 size: other.size,
90 fill: other.fill,
91 weight: other.weight,
92 grade: other.grade,
93 opticalSize: other.opticalSize,
94 color: other.color,
95 opacity: other.opacity,
96 shadows: other.shadows,
97 applyTextScaling: other.applyTextScaling,
98 );
99 }
100
101 /// Called by [IconTheme.of] to convert this instance to an [IconThemeData]
102 /// that fits the given [BuildContext].
103 ///
104 /// This method gives the ambient [IconThemeData] a chance to update itself,
105 /// after it's been retrieved by [IconTheme.of], and before being returned as
106 /// the final result. For instance, [CupertinoIconThemeData] overrides this method
107 /// to resolve [color], in case [color] is a [CupertinoDynamicColor] and needs
108 /// to be resolved against the given [BuildContext] before it can be used as a
109 /// regular [Color].
110 ///
111 /// The default implementation returns this [IconThemeData] as-is.
112 ///
113 /// See also:
114 ///
115 /// * [CupertinoIconThemeData.resolve] an implementation that resolves
116 /// the color of [CupertinoIconThemeData] before returning.
117 IconThemeData resolve(BuildContext context) => this;
118
119 /// Whether all the properties (except shadows) of this object are non-null.
120 bool get isConcrete => size != null
121 && fill != null
122 && weight != null
123 && grade != null
124 && opticalSize != null
125 && color != null
126 && opacity != null
127 && applyTextScaling != null;
128
129 /// The default for [Icon.size].
130 ///
131 /// Falls back to 24.0.
132 final double? size;
133
134 /// The default for [Icon.fill].
135 ///
136 /// Falls back to 0.0.
137 final double? fill;
138
139 /// The default for [Icon.weight].
140 ///
141 /// Falls back to 400.0.
142 final double? weight;
143
144 /// The default for [Icon.grade].
145 ///
146 /// Falls back to 0.0.
147 final double? grade;
148
149 /// The default for [Icon.opticalSize].
150 ///
151 /// Falls back to 48.0.
152 final double? opticalSize;
153
154 /// The default for [Icon.color].
155 ///
156 /// In material apps, if there is a [Theme] without any [IconTheme]s
157 /// specified, icon colors default to white if [ThemeData.brightness] is dark
158 /// and black if [ThemeData.brightness] is light.
159 ///
160 /// Otherwise, falls back to black.
161 final Color? color;
162
163 /// An opacity to apply to both explicit and default icon colors.
164 ///
165 /// Falls back to 1.0.
166 double? get opacity => _opacity == null ? null : clampDouble(_opacity, 0.0, 1.0);
167 final double? _opacity;
168
169 /// The default for [Icon.shadows].
170 final List<Shadow>? shadows;
171
172 /// The default for [Icon.applyTextScaling].
173 final bool? applyTextScaling;
174
175 /// Linearly interpolate between two icon theme data objects.
176 ///
177 /// {@macro dart.ui.shadow.lerp}
178 static IconThemeData lerp(IconThemeData? a, IconThemeData? b, double t) {
179 if (identical(a, b) && a != null) {
180 return a;
181 }
182 return IconThemeData(
183 size: ui.lerpDouble(a?.size, b?.size, t),
184 fill: ui.lerpDouble(a?.fill, b?.fill, t),
185 weight: ui.lerpDouble(a?.weight, b?.weight, t),
186 grade: ui.lerpDouble(a?.grade, b?.grade, t),
187 opticalSize: ui.lerpDouble(a?.opticalSize, b?.opticalSize, t),
188 color: Color.lerp(a?.color, b?.color, t),
189 opacity: ui.lerpDouble(a?.opacity, b?.opacity, t),
190 shadows: Shadow.lerpList(a?.shadows, b?.shadows, t),
191 applyTextScaling: t < 0.5 ? a?.applyTextScaling : b?.applyTextScaling,
192 );
193 }
194
195 @override
196 bool operator ==(Object other) {
197 if (other.runtimeType != runtimeType) {
198 return false;
199 }
200 return other is IconThemeData
201 && other.size == size
202 && other.fill == fill
203 && other.weight == weight
204 && other.grade == grade
205 && other.opticalSize == opticalSize
206 && other.color == color
207 && other.opacity == opacity
208 && listEquals(other.shadows, shadows)
209 && other.applyTextScaling == applyTextScaling;
210 }
211
212 @override
213 int get hashCode => Object.hash(
214 size,
215 fill,
216 weight,
217 grade,
218 opticalSize,
219 color,
220 opacity,
221 shadows == null ? null : Object.hashAll(shadows!),
222 applyTextScaling,
223 );
224
225 @override
226 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
227 super.debugFillProperties(properties);
228 properties.add(DoubleProperty('size', size, defaultValue: null));
229 properties.add(DoubleProperty('fill', fill, defaultValue: null));
230 properties.add(DoubleProperty('weight', weight, defaultValue: null));
231 properties.add(DoubleProperty('grade', grade, defaultValue: null));
232 properties.add(DoubleProperty('opticalSize', opticalSize, defaultValue: null));
233 properties.add(ColorProperty('color', color, defaultValue: null));
234 properties.add(DoubleProperty('opacity', opacity, defaultValue: null));
235 properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null));
236 properties.add(DiagnosticsProperty<bool>('applyTextScaling', applyTextScaling, defaultValue: null));
237 }
238}
239