Unverified Commit be3802c9 authored by Pierre-Louis's avatar Pierre-Louis Committed by GitHub

Add support for fill, weight, grade, and optical size to `Icon` (#106896)

* wip

* update documentation

* x

* remove trailing spaces

* x

* remove useless CupertinoIconThemeData copyWith override

* add tests

* remove trailing spaces

* fix isConcrete

* x

* x

* x

* remove trailing spaces

* tweak docs

* mention that font filenames often indicate the supported axes

* add back cupertino IconThemeData copyWith

* update copyWith
parent bfcceeb3
......@@ -10,13 +10,14 @@ import 'colors.dart';
/// using [IconTheme.of].
class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
/// Creates a [CupertinoIconThemeData].
///
/// The opacity applies to both explicit and default icon colors. The value
/// is clamped between 0.0 and 1.0.
const CupertinoIconThemeData({
super.size,
super.fill,
super.weight,
super.grade,
super.opticalSize,
super.color,
super.opacity,
super.size,
super.shadows,
});
......@@ -30,11 +31,24 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
/// Creates a copy of this icon theme but with the given fields replaced with
/// the new values.
@override
CupertinoIconThemeData copyWith({ Color? color, double? opacity, double? size, List<Shadow>? shadows }) {
CupertinoIconThemeData copyWith({
double? size,
double? fill,
double? weight,
double? grade,
double? opticalSize,
Color? color,
double? opacity,
List<Shadow>? shadows,
}) {
return CupertinoIconThemeData(
size: size ?? this.size,
fill: fill ?? this.fill,
weight: weight ?? this.weight,
grade: grade ?? this.grade,
opticalSize: opticalSize ?? this.opticalSize,
color: color ?? this.color,
opacity: opacity ?? this.opacity,
size: size ?? this.size,
shadows: shadows ?? this.shadows,
);
}
......
......@@ -2,6 +2,8 @@
// 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';
......@@ -61,22 +63,26 @@ import 'icon_theme_data.dart';
/// See also:
///
/// * [IconButton], for interactive icons.
/// * [Icons], the library of Material Icons available for use with this class.
/// * [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.
///
/// The [size] and [color] default to the value given by the current [IconTheme].
const Icon(
this.icon, {
super.key,
this.size,
this.fill,
this.weight,
this.grade,
this.opticalSize,
this.color,
this.shadows,
this.semanticLabel,
this.textDirection,
this.shadows,
});
}) : 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].
///
......@@ -88,9 +94,7 @@ class Icon extends StatelessWidget {
///
/// 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.
/// 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
......@@ -98,23 +102,87 @@ class Icon extends StatelessWidget {
/// pass down the size to the [Icon].
final double? size;
/// The color to use when drawing the icon.
/// The fill for drawing the icon.
///
/// Defaults to the current [IconTheme] color, if any.
/// 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.
///
/// The color (whether specified explicitly here or obtained from the
/// [IconTheme]) will be further adjusted by the opacity of the current
/// [IconTheme], if any.
/// 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].
///
/// 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.
/// 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.
///
/// If no [IconTheme] and no [Theme] is specified, icons will default to
/// black.
/// Defaults to the nearest [IconTheme]'s [IconThemeData.color].
///
/// See [Theme] to set the current theme and [ThemeData.brightness]
/// for setting the current theme's brightness.
/// 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:
......@@ -128,6 +196,17 @@ class Icon extends StatelessWidget {
/// {@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).
......@@ -152,15 +231,6 @@ class Icon extends StatelessWidget {
/// 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.
final List<Shadow>? shadows;
@override
Widget build(BuildContext context) {
assert(this.textDirection != null || debugCheckHasDirectionality(context));
......@@ -191,6 +261,12 @@ class Icon extends StatelessWidget {
text: TextSpan(
text: String.fromCharCode(icon!.codePoint),
style: TextStyle(
fontVariations: <FontVariation>[
if (fill != null) FontVariation('FILL', fill!),
if (weight != null) FontVariation('wght', weight!),
if (grade != null) FontVariation('GRAD', grade!),
if (opticalSize != null) FontVariation('opsz', opticalSize!),
],
inherit: false,
color: iconColor,
fontSize: iconSize,
......@@ -235,7 +311,13 @@ class Icon extends StatelessWidget {
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));
}
}
......@@ -9,12 +9,11 @@ import 'framework.dart';
import 'icon_theme_data.dart';
import 'inherited_theme.dart';
/// Controls the default color, opacity, and size of icons in a widget subtree.
/// Controls the default properties of icons in a widget subtree.
///
/// The icon theme is honored by [Icon] and [ImageIcon] widgets.
class IconTheme extends InheritedTheme {
/// Creates an icon theme that controls the color, opacity, and size of
/// descendant widgets.
/// Creates an icon theme that controls properties of descendant widgets.
///
/// Both [data] and [child] arguments must not be null.
const IconTheme({
......@@ -24,7 +23,7 @@ class IconTheme extends InheritedTheme {
}) : assert(data != null),
assert(child != null);
/// Creates an icon theme that controls the color, opacity, and size of
/// Creates an icon theme that controls the properties of
/// descendant widgets, and merges in the current icon theme, if any.
///
/// The [data] and [child] arguments must not be null.
......@@ -44,7 +43,7 @@ class IconTheme extends InheritedTheme {
);
}
/// The color, opacity, and size to use for icons in this subtree.
/// The set of properties to use for icons in this subtree.
final IconThemeData data;
/// The data from the closest instance of this class that encloses the given
......@@ -72,6 +71,10 @@ class IconTheme extends InheritedTheme {
? iconThemeData
: iconThemeData.copyWith(
size: iconThemeData.size ?? const IconThemeData.fallback().size,
fill: iconThemeData.fill ?? const IconThemeData.fallback().fill,
weight: iconThemeData.weight ?? const IconThemeData.fallback().weight,
grade: iconThemeData.grade ?? const IconThemeData.fallback().grade,
opticalSize: iconThemeData.opticalSize ?? const IconThemeData.fallback().opticalSize,
color: iconThemeData.color ?? const IconThemeData.fallback().color,
opacity: iconThemeData.opacity ?? const IconThemeData.fallback().opacity,
shadows: iconThemeData.shadows ?? const IconThemeData.fallback().shadows,
......
......@@ -9,38 +9,67 @@ import 'package:flutter/painting.dart';
import 'framework.dart' show BuildContext;
/// Defines the color, opacity, and size of icons.
/// Defines the size, font variations, color, opacity, and shadows of icons.
///
/// Used by [IconTheme] to control the color, opacity, and size of icons in a
/// widget subtree.
/// Used by [IconTheme] to control those properties in a widget subtree.
///
/// To obtain the current icon theme, use [IconTheme.of]. To convert an icon
/// theme to a version with all the fields filled in, use [
/// IconThemeData.fallback].
/// theme to a version with all the fields filled in, use
/// [IconThemeData.fallback].
@immutable
class IconThemeData with Diagnosticable {
/// Creates an icon theme data.
///
/// The opacity applies to both explicit and default icon colors. The value
/// is clamped between 0.0 and 1.0.
const IconThemeData({this.color, double? opacity, this.size, this.shadows}) : _opacity = opacity;
const IconThemeData({
this.size,
this.fill,
this.weight,
this.grade,
this.opticalSize,
this.color,
double? opacity,
this.shadows,
}) : _opacity = opacity,
assert(fill == null || (0.0 <= fill && fill <= 1.0)),
assert(weight == null || (0.0 < weight)),
assert(opticalSize == null || (0.0 < opticalSize));
/// Creates an icon theme with some reasonable default values.
///
/// The [color] is black, the [opacity] is 1.0, and the [size] is 24.0.
/// The [size] is 24.0, [fill] is 0.0, [weight] is 400.0, [grade] is 0.0,
/// opticalSize is 48.0, [color] is black, and [opacity] is 1.0.
const IconThemeData.fallback()
: color = const Color(0xFF000000),
: size = 24.0,
fill = 0.0,
weight = 400.0,
grade = 0.0,
opticalSize = 48.0,
color = const Color(0xFF000000),
_opacity = 1.0,
size = 24.0,
shadows = null;
/// Creates a copy of this icon theme but with the given fields replaced with
/// the new values.
IconThemeData copyWith({Color? color, double? opacity, double? size, List<Shadow>? shadows}) {
IconThemeData copyWith({
double? size,
double? fill,
double? weight,
double? grade,
double? opticalSize,
Color? color,
double? opacity,
List<Shadow>? shadows,
}) {
return IconThemeData(
size: size ?? this.size,
fill: fill ?? this.fill,
weight: weight ?? this.weight,
grade: grade ?? this.grade,
opticalSize: opticalSize ?? this.opticalSize,
color: color ?? this.color,
opacity: opacity ?? this.opacity,
size: size ?? this.size,
shadows: shadows ?? this.shadows,
);
}
......@@ -53,9 +82,13 @@ class IconThemeData with Diagnosticable {
return this;
}
return copyWith(
size: other.size,
fill: other.fill,
weight: other.weight,
grade: other.grade,
opticalSize: other.opticalSize,
color: other.color,
opacity: other.opacity,
size: other.size,
shadows: other.shadows,
);
}
......@@ -78,20 +111,56 @@ class IconThemeData with Diagnosticable {
/// the color of [CupertinoIconThemeData] before returning.
IconThemeData resolve(BuildContext context) => this;
/// Whether all the properties of this object are non-null.
bool get isConcrete => color != null && opacity != null && size != null;
/// Whether all the properties (except shadows) of this object are non-null.
bool get isConcrete => size != null
&& fill != null
&& weight != null
&& grade != null
&& opticalSize != null
&& color != null
&& opacity != null;
/// The default for [Icon.size].
///
/// Falls back to 24.0.
final double? size;
/// The default for [Icon.fill].
///
/// Falls back to 0.0.
final double? fill;
/// The default for [Icon.weight].
///
/// Falls back to 400.0.
final double? weight;
/// The default for [Icon.grade].
///
/// Falls back to 0.0.
final double? grade;
/// The default for [Icon.opticalSize].
///
/// Falls back to 48.0.
final double? opticalSize;
/// The default color for icons.
/// The default for [Icon.color].
///
/// In material apps, if there is a [Theme] without any [IconTheme]s
/// specified, icon colors default to white if [ThemeData.brightness] is dark
/// and black if [ThemeData.brightness] is light.
///
/// Otherwise, falls back to black.
final Color? color;
/// An opacity to apply to both explicit and default icon colors.
///
/// Falls back to 1.0.
double? get opacity => _opacity == null ? null : clampDouble(_opacity!, 0.0, 1.0);
final double? _opacity;
/// The default size for icons.
final double? size;
/// The default shadow for icons.
/// The default for [Icon.shadows].
final List<Shadow>? shadows;
/// Linearly interpolate between two icon theme data objects.
......@@ -100,9 +169,13 @@ class IconThemeData with Diagnosticable {
static IconThemeData lerp(IconThemeData? a, IconThemeData? b, double t) {
assert(t != null);
return IconThemeData(
size: ui.lerpDouble(a?.size, b?.size, t),
fill: ui.lerpDouble(a?.fill, b?.fill, t),
weight: ui.lerpDouble(a?.weight, b?.weight, t),
grade: ui.lerpDouble(a?.grade, b?.grade, t),
opticalSize: ui.lerpDouble(a?.opticalSize, b?.opticalSize, t),
color: Color.lerp(a?.color, b?.color, t),
opacity: ui.lerpDouble(a?.opacity, b?.opacity, t),
size: ui.lerpDouble(a?.size, b?.size, t),
shadows: Shadow.lerpList(a?.shadows, b?.shadows, t),
);
}
......@@ -113,26 +186,38 @@ class IconThemeData with Diagnosticable {
return false;
}
return other is IconThemeData
&& other.size == size
&& other.fill == fill
&& other.weight == weight
&& other.grade == grade
&& other.opticalSize == opticalSize
&& other.color == color
&& other.opacity == opacity
&& other.size == size
&& listEquals(other.shadows, shadows);
}
@override
int get hashCode => Object.hash(
size,
fill,
weight,
grade,
opticalSize,
color,
opacity,
size,
shadows == null ? null : Object.hashAll(shadows!),
);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
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(DoubleProperty('opacity', opacity, defaultValue: null));
properties.add(DoubleProperty('size', size, defaultValue: null));
properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null));
}
}
......@@ -7,7 +7,15 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('IconTheme.of works', (WidgetTester tester) async {
const IconThemeData data = IconThemeData(color: Color(0xAAAAAAAA), opacity: 0.5, size: 16.0);
const IconThemeData data = IconThemeData(
size: 16.0,
fill: 0.0,
weight: 400.0,
grade: 0.0,
opticalSize: 48.0,
color: Color(0xAAAAAAAA),
opacity: 0.5
);
late IconThemeData retrieved;
await tester.pumpWidget(
......
......@@ -2,6 +2,8 @@
// 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/material.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -207,4 +209,42 @@ void main() {
expect(const IconData(123).hashCode, isNot(const IconData(123, fontPackage: 'p').hashCode));
expect(const IconData(123).toString(), 'IconData(U+0007B)');
});
testWidgets('Fill, weight, grade, and optical size variations are passed', (WidgetTester tester) async {
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Icon(Icons.abc),
),
);
RichText text = tester.widget(find.byType(RichText));
expect(text.text.style!.fontVariations, <FontVariation>[]);
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Icon(Icons.abc, fill: 0.5, weight: 300, grade: 200, opticalSize: 48),
),
);
text = tester.widget(find.byType(RichText));
expect(text.text.style!.fontVariations, isNotNull);
expect(text.text.style!.fontVariations, <FontVariation>[
const FontVariation('FILL', 0.5),
const FontVariation('wght', 300.0),
const FontVariation('GRAD', 200.0),
const FontVariation('opsz', 48.0)
]);
});
test('Throws if given invalid values', () {
expect(() => Icon(Icons.abc, fill: -0.1), throwsAssertionError);
expect(() => Icon(Icons.abc, fill: 1.1), throwsAssertionError);
expect(() => Icon(Icons.abc, weight: -0.1), throwsAssertionError);
expect(() => Icon(Icons.abc, weight: 0.0), throwsAssertionError);
expect(() => Icon(Icons.abc, opticalSize: -0.1), throwsAssertionError);
expect(() => Icon(Icons.abc, opticalSize: 0), throwsAssertionError);
});
}
......@@ -7,44 +7,89 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
test('IconThemeData control test', () {
const IconThemeData data = IconThemeData(color: Color(0xAAAAAAAA), opacity: 0.5, size: 16.0, shadows: <Shadow>[Shadow(color: Color(0xAAAAAAAA), blurRadius: 1.0, offset: Offset(1.0, 1.0))]);
const IconThemeData data = IconThemeData(
size: 16.0,
fill: 0.5,
weight: 600,
grade: 25,
opticalSize: 45,
color: Color(0xAAAAAAAA),
opacity: 0.5,
shadows: <Shadow>[Shadow(color: Color(0xAAAAAAAA), blurRadius: 1.0, offset: Offset(1.0, 1.0))],
);
expect(data, hasOneLineDescription);
expect(data, equals(data.copyWith()));
expect(data.hashCode, equals(data.copyWith().hashCode));
final IconThemeData lerped = IconThemeData.lerp(data, const IconThemeData.fallback(), 0.25);
expect(lerped.size, 18.0);
expect(lerped.fill, 0.375);
expect(lerped.weight, 550.0);
expect(lerped.grade, 18.75);
expect(lerped.opticalSize, 45.75);
expect(lerped.color, const Color(0xBF7F7F7F));
expect(lerped.opacity, 0.625);
expect(lerped.size, 18.0);
expect(lerped.shadows, const <Shadow>[Shadow(color: Color(0xAAAAAAAA), blurRadius: 0.75, offset: Offset(0.75, 0.75))]);
});
test('IconThemeData lerp with first null', () {
const IconThemeData data = IconThemeData(color: Color(0xFFFFFFFF), opacity: 1.0, size: 16.0, shadows: <Shadow>[Shadow(color: Color(0xFFFFFFFF), blurRadius: 1.0, offset: Offset(1.0, 1.0))]);
group('IconThemeData lerp', () {
const IconThemeData data = IconThemeData(
size: 16.0,
fill: 0.5,
weight: 600,
grade: 25,
opticalSize: 45,
color: Color(0xFFFFFFFF),
opacity: 1.0,
shadows: <Shadow>[Shadow(color: Color(0xFFFFFFFF), blurRadius: 1.0, offset: Offset(1.0, 1.0))],
);
final IconThemeData lerped = IconThemeData.lerp(null, data, 0.25);
expect(lerped.color, const Color(0x40FFFFFF));
expect(lerped.opacity, 0.25);
expect(lerped.size, 4.0);
expect(lerped.shadows, const <Shadow>[Shadow(color: Color(0xFFFFFFFF), blurRadius: 0.25, offset: Offset(0.25, 0.25))]);
});
test('with first null', () {
final IconThemeData lerped = IconThemeData.lerp(null, data, 0.25);
expect(lerped.size, 4.0);
expect(lerped.fill, 0.125);
expect(lerped.weight, 150.0);
expect(lerped.grade, 6.25);
expect(lerped.opticalSize, 11.25);
expect(lerped.color, const Color(0x40FFFFFF));
expect(lerped.opacity, 0.25);
expect(lerped.shadows, const <Shadow>[Shadow(color: Color(0xFFFFFFFF), blurRadius: 0.25, offset: Offset(0.25, 0.25))]);
});
test('with second null', () {
final IconThemeData lerped = IconThemeData.lerp(data, null, 0.25);
expect(lerped.size, 12.0);
expect(lerped.fill, 0.375);
expect(lerped.weight, 450.0);
expect(lerped.grade, 18.75);
expect(lerped.opticalSize, 33.75);
expect(lerped.color, const Color(0xBFFFFFFF));
expect(lerped.opacity, 0.75);
expect(lerped.shadows, const <Shadow>[Shadow(color: Color(0xFFFFFFFF), blurRadius: 0.75, offset: Offset(0.75, 0.75))]);
});
test('IconThemeData lerp with second null', () {
const IconThemeData data = IconThemeData(color: Color(0xFFFFFFFF), opacity: 1.0, size: 16.0, shadows: <Shadow>[Shadow(color: Color(0xFFFFFFFF), blurRadius: 1.0, offset: Offset(1.0, 1.0))]);
test('with both null', () {
final IconThemeData lerped = IconThemeData.lerp(null, null, 0.25);
final IconThemeData lerped = IconThemeData.lerp(data, null, 0.25);
expect(lerped.color, const Color(0xBFFFFFFF));
expect(lerped.opacity, 0.75);
expect(lerped.size, 12.0);
expect(lerped.shadows, const <Shadow>[Shadow(color: Color(0xFFFFFFFF), blurRadius: 0.75, offset: Offset(0.75, 0.75))]);
expect(lerped.size, null);
expect(lerped.fill, null);
expect(lerped.weight, null);
expect(lerped.grade, null);
expect(lerped.opticalSize, null);
expect(lerped.color, null);
expect(lerped.opacity, null);
expect(lerped.shadows, null);
});
});
test('IconThemeData lerp with both null', () {
final IconThemeData lerped = IconThemeData.lerp(null, null, 0.25);
expect(lerped.color, null);
expect(lerped.opacity, null);
expect(lerped.size, null);
expect(lerped.shadows, null);
test('Throws if given invalid values', () {
expect(() => IconThemeData(fill: -0.1), throwsAssertionError);
expect(() => IconThemeData(fill: 1.1), throwsAssertionError);
expect(() => IconThemeData(weight: -0.1), throwsAssertionError);
expect(() => IconThemeData(weight: 0.0), throwsAssertionError);
expect(() => IconThemeData(opticalSize: -0.1), throwsAssertionError);
expect(() => IconThemeData(opticalSize: 0), throwsAssertionError);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment