Unverified Commit 87804363 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Refactor `MaterialStateProperty` lerp functions (#104507)

parent 208a418a
......@@ -465,7 +465,7 @@ class ButtonStyle with Diagnosticable {
fixedSize: _lerpProperties<Size?>(a?.fixedSize, b?.fixedSize, t, Size.lerp),
maximumSize: _lerpProperties<Size?>(a?.maximumSize, b?.maximumSize, t, Size.lerp),
side: _lerpSides(a?.side, b?.side, t),
shape: _lerpShapes(a?.shape, b?.shape, t),
shape: MaterialStateProperty.lerp<OutlinedBorder?>(a?.shape, b?.shape, t, OutlinedBorder.lerp),
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
tapTargetSize: t < 0.5 ? a?.tapTargetSize : b?.tapTargetSize,
......@@ -491,14 +491,6 @@ class ButtonStyle with Diagnosticable {
}
return _LerpSides(a, b, t);
}
// TODO(hansmuller): OutlinedBorder needs a lerp method - https://github.com/flutter/flutter/issues/60555.
static MaterialStateProperty<OutlinedBorder?>? _lerpShapes(MaterialStateProperty<OutlinedBorder?>? a, MaterialStateProperty<OutlinedBorder?>? b, double t) {
if (a == null && b == null) {
return null;
}
return _LerpShapes(a, b, t);
}
}
class _LerpProperties<T> implements MaterialStateProperty<T?> {
......@@ -540,18 +532,3 @@ class _LerpSides implements MaterialStateProperty<BorderSide?> {
return BorderSide.lerp(resolvedA, resolvedB, t);
}
}
class _LerpShapes implements MaterialStateProperty<OutlinedBorder?> {
const _LerpShapes(this.a, this.b, this.t);
final MaterialStateProperty<OutlinedBorder?>? a;
final MaterialStateProperty<OutlinedBorder?>? b;
final double t;
@override
OutlinedBorder? resolve(Set<MaterialState> states) {
final OutlinedBorder? resolvedA = a?.resolve(states);
final OutlinedBorder? resolvedB = b?.resolve(states);
return ShapeBorder.lerp(resolvedA, resolvedB, t) as OutlinedBorder?;
}
}
......@@ -129,9 +129,9 @@ class CheckboxThemeData with Diagnosticable {
static CheckboxThemeData lerp(CheckboxThemeData? a, CheckboxThemeData? b, double t) {
return CheckboxThemeData(
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
fillColor: _lerpProperties<Color?>(a?.fillColor, b?.fillColor, t, Color.lerp),
checkColor: _lerpProperties<Color?>(a?.checkColor, b?.checkColor, t, Color.lerp),
overlayColor: _lerpProperties<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
fillColor: MaterialStateProperty.lerp<Color?>(a?.fillColor, b?.fillColor, t, Color.lerp),
checkColor: MaterialStateProperty.lerp<Color?>(a?.checkColor, b?.checkColor, t, Color.lerp),
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t),
materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
......@@ -187,19 +187,6 @@ class CheckboxThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<BorderSide>('side', side, defaultValue: null));
}
static MaterialStateProperty<T>? _lerpProperties<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T Function(T?, T?, double) lerpFunction,
) {
// Avoid creating a _LerpProperties object for a common case.
if (a == null && b == null) {
return null;
}
return _LerpProperties<T>(a, b, t, lerpFunction);
}
// Special case because BorderSide.lerp() doesn't support null arguments
static BorderSide? _lerpSides(BorderSide? a, BorderSide? b, double t) {
if (a == null && b == null) {
......@@ -209,22 +196,6 @@ class CheckboxThemeData with Diagnosticable {
}
}
class _LerpProperties<T> implements MaterialStateProperty<T> {
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
final MaterialStateProperty<T>? a;
final MaterialStateProperty<T>? b;
final double t;
final T Function(T?, T?, double) lerpFunction;
@override
T resolve(Set<MaterialState> states) {
final T? resolvedA = a?.resolve(states);
final T? resolvedB = b?.resolve(states);
return lerpFunction(resolvedA, resolvedB, t);
}
}
/// Applies a checkbox theme to descendant [Checkbox] widgets.
///
/// Descendant widgets obtain the current theme's [CheckboxTheme] object using
......
......@@ -122,10 +122,10 @@ class DataTableThemeData with Diagnosticable {
assert(t != null);
return DataTableThemeData(
decoration: Decoration.lerp(a.decoration, b.decoration, t),
dataRowColor: _lerpProperties<Color?>(a.dataRowColor, b.dataRowColor, t, Color.lerp),
dataRowColor: MaterialStateProperty.lerp<Color?>(a.dataRowColor, b.dataRowColor, t, Color.lerp),
dataRowHeight: lerpDouble(a.dataRowHeight, b.dataRowHeight, t),
dataTextStyle: TextStyle.lerp(a.dataTextStyle, b.dataTextStyle, t),
headingRowColor: _lerpProperties<Color?>(a.headingRowColor, b.headingRowColor, t, Color.lerp),
headingRowColor: MaterialStateProperty.lerp<Color?>(a.headingRowColor, b.headingRowColor, t, Color.lerp),
headingRowHeight: lerpDouble(a.headingRowHeight, b.headingRowHeight, t),
headingTextStyle: TextStyle.lerp(a.headingTextStyle, b.headingTextStyle, t),
horizontalMargin: lerpDouble(a.horizontalMargin, b.horizontalMargin, t),
......@@ -187,30 +187,6 @@ class DataTableThemeData with Diagnosticable {
properties.add(DoubleProperty('dividerThickness', dividerThickness, defaultValue: null));
properties.add(DoubleProperty('checkboxHorizontalMargin', checkboxHorizontalMargin, defaultValue: null));
}
static MaterialStateProperty<T>? _lerpProperties<T>(MaterialStateProperty<T>? a, MaterialStateProperty<T>? b, double t, T Function(T?, T?, double) lerpFunction ) {
// Avoid creating a _LerpProperties object for a common case.
if (a == null && b == null) {
return null;
}
return _LerpProperties<T>(a, b, t, lerpFunction);
}
}
class _LerpProperties<T> implements MaterialStateProperty<T> {
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
final MaterialStateProperty<T>? a;
final MaterialStateProperty<T>? b;
final double t;
final T Function(T?, T?, double) lerpFunction;
@override
T resolve(Set<MaterialState> states) {
final T? resolvedA = a?.resolve(states);
final T? resolvedB = b?.resolve(states);
return lerpFunction(resolvedA, resolvedB, t);
}
}
/// Applies a data table theme to descendant [DataTable] widgets.
......
......@@ -658,6 +658,36 @@ abstract class MaterialStateProperty<T> {
// a dart fix that will replace this with MaterialStatePropertyAll:
// https://github.com/dart-lang/sdk/issues/49056.
static MaterialStateProperty<T> all<T>(T value) => MaterialStatePropertyAll<T>(value);
/// Linearly interpolate between two [MaterialStateProperty]s.
static MaterialStateProperty<T?>? lerp<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T? Function(T?, T?, double) lerpFunction,
) {
// Avoid creating a _LerpProperties object for a common case.
if (a == null && b == null) {
return null;
}
return _LerpProperties<T>(a, b, t, lerpFunction);
}
}
class _LerpProperties<T> implements MaterialStateProperty<T?> {
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
final MaterialStateProperty<T>? a;
final MaterialStateProperty<T>? b;
final double t;
final T? Function(T?, T?, double) lerpFunction;
@override
T? resolve(Set<MaterialState> states) {
final T? resolvedA = a?.resolve(states);
final T? resolvedB = b?.resolve(states);
return lerpFunction(resolvedA, resolvedB, t);
}
}
class _MaterialStatePropertyWith<T> implements MaterialStateProperty<T> {
......
......@@ -127,8 +127,8 @@ class NavigationBarThemeData with Diagnosticable {
elevation: lerpDouble(a?.elevation, b?.elevation, t),
indicatorColor: Color.lerp(a?.indicatorColor, b?.indicatorColor, t),
indicatorShape: ShapeBorder.lerp(a?.indicatorShape, b?.indicatorShape, t),
labelTextStyle: _lerpProperties<TextStyle?>(a?.labelTextStyle, b?.labelTextStyle, t, TextStyle.lerp),
iconTheme: _lerpProperties<IconThemeData?>(a?.iconTheme, b?.iconTheme, t, IconThemeData.lerp),
labelTextStyle: MaterialStateProperty.lerp<TextStyle?>(a?.labelTextStyle, b?.labelTextStyle, t, TextStyle.lerp),
iconTheme: MaterialStateProperty.lerp<IconThemeData?>(a?.iconTheme, b?.iconTheme, t, IconThemeData.lerp),
labelBehavior: t < 0.5 ? a?.labelBehavior : b?.labelBehavior,
);
}
......@@ -179,35 +179,6 @@ class NavigationBarThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<MaterialStateProperty<IconThemeData?>>('iconTheme', iconTheme, defaultValue: null));
properties.add(DiagnosticsProperty<NavigationDestinationLabelBehavior>('labelBehavior', labelBehavior, defaultValue: null));
}
static MaterialStateProperty<T>? _lerpProperties<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T Function(T?, T?, double) lerpFunction,
) {
// Avoid creating a _LerpProperties object for a common case.
if (a == null && b == null) {
return null;
}
return _LerpProperties<T>(a, b, t, lerpFunction);
}
}
class _LerpProperties<T> implements MaterialStateProperty<T> {
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
final MaterialStateProperty<T>? a;
final MaterialStateProperty<T>? b;
final double t;
final T Function(T?, T?, double) lerpFunction;
@override
T resolve(Set<MaterialState> states) {
final T? resolvedA = a?.resolve(states);
final T? resolvedB = b?.resolve(states);
return lerpFunction(resolvedA, resolvedB, t);
}
}
/// An inherited widget that defines visual properties for [NavigationBar]s and
......
......@@ -111,9 +111,9 @@ class RadioThemeData with Diagnosticable {
static RadioThemeData lerp(RadioThemeData? a, RadioThemeData? b, double t) {
return RadioThemeData(
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
fillColor: _lerpProperties<Color?>(a?.fillColor, b?.fillColor, t, Color.lerp),
fillColor: MaterialStateProperty.lerp<Color?>(a?.fillColor, b?.fillColor, t, Color.lerp),
materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
overlayColor: _lerpProperties<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t),
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
);
......@@ -156,35 +156,6 @@ class RadioThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
}
static MaterialStateProperty<T>? _lerpProperties<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T Function(T?, T?, double) lerpFunction,
) {
// Avoid creating a _LerpProperties object for a common case.
if (a == null && b == null) {
return null;
}
return _LerpProperties<T>(a, b, t, lerpFunction);
}
}
class _LerpProperties<T> implements MaterialStateProperty<T> {
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
final MaterialStateProperty<T>? a;
final MaterialStateProperty<T>? b;
final double t;
final T Function(T?, T?, double) lerpFunction;
@override
T resolve(Set<MaterialState> states) {
final T? resolvedA = a?.resolve(states);
final T? resolvedB = b?.resolve(states);
return lerpFunction(resolvedA, resolvedB, t);
}
}
/// Applies a radio theme to descendant [Radio] widgets.
......
......@@ -198,16 +198,16 @@ class ScrollbarThemeData with Diagnosticable {
static ScrollbarThemeData lerp(ScrollbarThemeData? a, ScrollbarThemeData? b, double t) {
assert(t != null);
return ScrollbarThemeData(
thumbVisibility: _lerpProperties<bool?>(a?.thumbVisibility, b?.thumbVisibility, t, _lerpBool),
thickness: _lerpProperties<double?>(a?.thickness, b?.thickness, t, lerpDouble),
trackVisibility: _lerpProperties<bool?>(a?.trackVisibility, b?.trackVisibility, t, _lerpBool),
thumbVisibility: MaterialStateProperty.lerp<bool?>(a?.thumbVisibility, b?.thumbVisibility, t, _lerpBool),
thickness: MaterialStateProperty.lerp<double?>(a?.thickness, b?.thickness, t, lerpDouble),
trackVisibility: MaterialStateProperty.lerp<bool?>(a?.trackVisibility, b?.trackVisibility, t, _lerpBool),
showTrackOnHover: _lerpBool(a?.showTrackOnHover, b?.showTrackOnHover, t),
isAlwaysShown: _lerpBool(a?.isAlwaysShown, b?.isAlwaysShown, t),
interactive: _lerpBool(a?.interactive, b?.interactive, t),
radius: Radius.lerp(a?.radius, b?.radius, t),
thumbColor: _lerpProperties<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
trackColor: _lerpProperties<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
trackBorderColor: _lerpProperties<Color?>(a?.trackBorderColor, b?.trackBorderColor, t, Color.lerp),
thumbColor: MaterialStateProperty.lerp<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
trackColor: MaterialStateProperty.lerp<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
trackBorderColor: MaterialStateProperty.lerp<Color?>(a?.trackBorderColor, b?.trackBorderColor, t, Color.lerp),
crossAxisMargin: lerpDouble(a?.crossAxisMargin, b?.crossAxisMargin, t),
mainAxisMargin: lerpDouble(a?.mainAxisMargin, b?.mainAxisMargin, t),
minThumbLength: lerpDouble(a?.minThumbLength, b?.minThumbLength, t),
......@@ -272,35 +272,6 @@ class ScrollbarThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<double>('mainAxisMargin', mainAxisMargin, defaultValue: null));
properties.add(DiagnosticsProperty<double>('minThumbLength', minThumbLength, defaultValue: null));
}
static MaterialStateProperty<T>? _lerpProperties<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T Function(T?, T?, double) lerpFunction,
) {
// Avoid creating a _LerpProperties object for a common case.
if (a == null && b == null) {
return null;
}
return _LerpProperties<T>(a, b, t, lerpFunction);
}
}
class _LerpProperties<T> implements MaterialStateProperty<T> {
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
final MaterialStateProperty<T>? a;
final MaterialStateProperty<T>? b;
final double t;
final T Function(T?, T?, double) lerpFunction;
@override
T resolve(Set<MaterialState> states) {
final T? resolvedA = a?.resolve(states);
final T? resolvedB = b?.resolve(states);
return lerpFunction(resolvedA, resolvedB, t);
}
}
bool? _lerpBool(bool? a, bool? b, double t) => t < 0.5 ? a : b;
......
......@@ -98,11 +98,11 @@ class SwitchThemeData with Diagnosticable {
/// {@macro dart.ui.shadow.lerp}
static SwitchThemeData lerp(SwitchThemeData? a, SwitchThemeData? b, double t) {
return SwitchThemeData(
thumbColor: _lerpProperties<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
trackColor: _lerpProperties<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
thumbColor: MaterialStateProperty.lerp<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
trackColor: MaterialStateProperty.lerp<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
overlayColor: _lerpProperties<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t),
);
}
......@@ -144,35 +144,6 @@ class SwitchThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
properties.add(DoubleProperty('splashRadius', splashRadius, defaultValue: null));
}
static MaterialStateProperty<T>? _lerpProperties<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T Function(T?, T?, double) lerpFunction,
) {
// Avoid creating a _LerpProperties object for a common case.
if (a == null && b == null) {
return null;
}
return _LerpProperties<T>(a, b, t, lerpFunction);
}
}
class _LerpProperties<T> implements MaterialStateProperty<T> {
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
final MaterialStateProperty<T>? a;
final MaterialStateProperty<T>? b;
final double t;
final T Function(T?, T?, double) lerpFunction;
@override
T resolve(Set<MaterialState> states) {
final T? resolvedA = a?.resolve(states);
final T? resolvedB = b?.resolve(states);
return lerpFunction(resolvedA, resolvedB, t);
}
}
/// Applies a switch theme to descendant [Switch] widgets.
......
......@@ -566,6 +566,45 @@ abstract class OutlinedBorder extends ShapeBorder {
/// Returns a copy of this OutlinedBorder that draws its outline with the
/// specified [side], if [side] is non-null.
OutlinedBorder copyWith({ BorderSide? side });
@override
ShapeBorder scale(double t);
@override
ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
if (a == null) {
return scale(t);
}
return null;
}
@override
ShapeBorder? lerpTo(ShapeBorder? b, double t) {
if (b == null) {
return scale(1.0 - t);
}
return null;
}
/// Linearly interpolates between two [OutlinedBorder]s.
///
/// This defers to `b`'s [lerpTo] function if `b` is not null. If `b` is
/// null or if its [lerpTo] returns null, it uses `a`'s [lerpFrom]
/// function instead. If both return null, it returns `a` before `t=0.5`
/// and `b` after `t=0.5`.
///
/// {@macro dart.ui.shadow.lerp}
static OutlinedBorder? lerp(OutlinedBorder? a, OutlinedBorder? b, double t) {
assert(t != null);
ShapeBorder? result;
if (b != null) {
result = b.lerpFrom(a, t);
}
if (result == null && a != null) {
result = a.lerpTo(b, t);
}
return result as OutlinedBorder? ?? (t < 0.5 ? a : b);
}
}
/// Represents the addition of two otherwise-incompatible borders.
......
......@@ -42,4 +42,42 @@ void main() {
expect(value.resolve(<MaterialState>{MaterialState.disabled}), 123);
expect(value.resolve(<MaterialState>{MaterialState.error}), 123);
});
test("Can interpolate between two MaterialStateProperty's", () {
const MaterialStateProperty<TextStyle?> textStyle1 = MaterialStatePropertyAll<TextStyle?>(
TextStyle(fontSize: 14.0),
);
const MaterialStateProperty<TextStyle?> textStyle2 = MaterialStatePropertyAll<TextStyle?>(
TextStyle(fontSize: 20.0),
);
// Using `0.0` interpolation value.
TextStyle textStyle = MaterialStateProperty.lerp<TextStyle?>(
textStyle1,
textStyle2,
0.0,
TextStyle.lerp,
)!.resolve(enabled)!;
expect(textStyle.fontSize, 14.0);
// Using `0.5` interpolation value.
textStyle = MaterialStateProperty.lerp<TextStyle?>(
textStyle1,
textStyle2,
0.5,
TextStyle.lerp,
)!.resolve(enabled)!;
expect(textStyle.fontSize, 17.0);
// Using `1.0` interpolation value.
textStyle = MaterialStateProperty.lerp<TextStyle?>(
textStyle1,
textStyle2,
1.0,
TextStyle.lerp,
)!.resolve(enabled)!;
expect(textStyle.fontSize, 20.0);
});
}
Set<MaterialState> enabled = <MaterialState>{};
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