blob: 855441328f1fd4c18e753a00816e36b0fa77ebcd [file] [log] [blame]
// Copyright 2013 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.
// There's a lot of <Object>[] lists in this file so to avoid making this
// file even less readable we relax our usual stance on verbose typing.
// ignore_for_file: always_specify_types
// This file is hand-formatted.
import 'dart:math' as math show pi;
import 'dart:ui' show FontFeature; // TODO(ianh): https://github.com/flutter/flutter/issues/87235
import 'package:flutter/material.dart';
import 'runtime.dart';
/// Default duration and curve for animations in remote flutter widgets.
///
/// This inherited widget allows a duration and a curve (defaulting to 200ms and
/// [Curves.fastOutSlowIn]) to be set as the default to use when local widgets
/// use the [ArgumentsDecoder.curve] and [ArgumentsDecoder.duration] methods and
/// find that the [DataSource] has no explicit curve or duration.
class AnimationDefaults extends InheritedWidget {
/// Configures an [AnimanionDefaults] widget.
///
/// The [duration] and [curve] are optional, and default to 200ms and
/// [Curves.fastOutSlowIn] respectively.
const AnimationDefaults({
Key? key,
this.duration,
this.curve,
required Widget child,
}) : super(key: key, child: child);
/// The default duration that [ArgumentsDecoder.duration] should use.
///
/// Defaults to 200ms when this is null.
final Duration? duration;
/// The default curve that [ArgumentsDecoder.curve] should use.
///
/// Defaults to [Curves.fastOutSlowIn] when this is null.
final Curve? curve;
/// Return the ambient [AnimationDefaults.duration], or 200ms if there is no
/// ambient [AnimationDefaults] or if the nearest [AnimationDefaults] has a
/// null [duration].
static Duration durationOf(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AnimationDefaults>()?.duration ?? const Duration(milliseconds: 200);
}
/// Return the ambient [AnimationDefaults.curve], or [Curves.fastOutSlowIn] if
/// there is no ambient [AnimationDefaults] or if the nearest
/// [AnimationDefaults] has a null [curve].
static Curve curveOf(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AnimationDefaults>()?.curve ?? Curves.fastOutSlowIn;
}
@override
bool updateShouldNotify(AnimationDefaults oldWidget) => duration != oldWidget.duration || curve != oldWidget.curve;
}
/// Signature for methods that decode structured values from a [DataSource],
/// such as the static methods of [ArgumentDecoders].
///
/// Used to make some of the methods of that class extensible.
typedef ArgumentDecoder<T> = T Function(DataSource source, List<Object> key);
/// A set of methods for decoding structured values from a [DataSource].
///
/// Specifically, these methods decode types that are used by local widgets
/// (q.v. [createCoreWidgets]).
///
/// These methods take a [DataSource] and a `key`. The `key` is a path to the
/// part of the [DataSource] that the value should be read from. This may
/// identify a map, a list, or a leaf value, depending on the needs of the
/// method.
class ArgumentDecoders {
const ArgumentDecoders._();
/// This is a workaround for https://github.com/dart-lang/sdk/issues/47021
static const ArgumentDecoders __ = ArgumentDecoders._(); // ignore: unused_field
// (in alphabetical order)
/// Decodes an [AlignmentDirectional] or [Alignment] object out of the
/// specified map.
///
/// If the map has `start` and `y` keys, then it is interpreted as an
/// [AlignmentDirectional] with those values. Otherwise if it has `x` and `y`
/// it's an [Alignment] with those values. Otherwise it returns null.
static AlignmentGeometry? alignment(DataSource source, List<Object> key) {
if (!source.isMap(key)) {
return null;
}
final double? x = source.v<double>([...key, 'x']);
final double? start = source.v<double>([...key, 'start']);
final double? y = source.v<double>([...key, 'y']);
if (x == null && start == null) {
return null;
}
if (y == null) {
return null;
}
if (start != null) {
return AlignmentDirectional(start, y);
}
x!;
return Alignment(x, y);
}
/// Decodes the specified map into a [BoxConstraints].
///
/// The keys used are `minWidth`, `maxWidth`, `minHeight`, and `maxHeight`.
/// Omitted keys are defaulted to 0.0 for minimums and infinity for maximums.
static BoxConstraints? boxConstraints(DataSource source, List<Object> key) {
if (!source.isMap(key)) {
return null;
}
return BoxConstraints(
minWidth: source.v<double>([...key, 'minWidth']) ?? 0.0,
maxWidth: source.v<double>([...key, 'maxWidth']) ?? double.infinity,
minHeight: source.v<double>([...key, 'minHeight']) ?? 0.0,
maxHeight: source.v<double>([...key, 'maxHeight']) ?? double.infinity,
);
}
/// Returns a [BorderDirectional] from the specified list.
///
/// The list is a list of values as interpreted by [borderSide]. An empty or
/// missing list results in a null return value. The list should have one
/// through four items. Extra items are ignored.
///
/// The values are interpreted as follows:
///
/// * start: first value.
/// * top: second value, defaulting to same as start.
/// * end: third value, defaulting to same as start.
/// * bottom: fourth value, defaulting to same as top.
static BoxBorder? border(DataSource source, List<Object> key) {
final BorderSide? a = borderSide(source, [...key, 0]);
if (a == null) {
return null;
}
final BorderSide? b = borderSide(source, [...key, 1]);
final BorderSide? c = borderSide(source, [...key, 2]);
final BorderSide? d = borderSide(source, [...key, 3]);
return BorderDirectional(
start: a,
top: b ?? a,
end: c ?? a,
bottom: d ?? b ?? a,
);
}
/// Returns a [BorderRadiusDirectional] from the specified list.
///
/// The list is a list of values as interpreted by [radius]. An empty or
/// missing list results in a null return value. The list should have one
/// through four items. Extra items are ignored.
///
/// The values are interpreted as follows:
///
/// * topStart: first value.
/// * topEnd: second value, defaulting to same as topStart.
/// * bottomStart: third value, defaulting to same as topStart.
/// * bottomEnd: fourth value, defaulting to same as topEnd.
static BorderRadiusGeometry? borderRadius(DataSource source, List<Object> key) {
final Radius? a = radius(source, [...key, 0]);
if (a == null) {
return null;
}
final Radius? b = radius(source, [...key, 1]);
final Radius? c = radius(source, [...key, 2]);
final Radius? d = radius(source, [...key, 3]);
return BorderRadiusDirectional.only(
topStart: a,
topEnd: b ?? a,
bottomStart: c ?? a,
bottomEnd: d ?? b ?? a,
);
}
/// Returns a [BorderSide] from the specified map.
///
/// If the map is absent, returns null.
///
/// Otherwise (even if it has no keys), the [BorderSide] is created from the
/// keys `color` (see [color], defaults to black), `width` (a double, defaults
/// to 1.0), and `style` (see [enumValue] for [BorderStyle], defaults to
/// [BorderStyle.solid]).
static BorderSide? borderSide(DataSource source, List<Object> key) {
if (!source.isMap(key)) {
return null;
}
return BorderSide(
color: color(source, [...key, 'color']) ?? const Color(0xFF000000),
width: source.v<double>([...key, 'width']) ?? 1.0,
style: enumValue<BorderStyle>(BorderStyle.values, source, [...key, 'style']) ?? BorderStyle.solid,
);
}
/// Returns a [BoxShadow] from the specified map.
///
/// If the map is absent, returns null.
///
/// Otherwise (even if it has no keys), the [BoxShadow] is created from the
/// keys `color` (see [color], defaults to black), `offset` (see [offset],
/// defaults to [Offset.zero]), `blurRadius` (double, defaults to zero), and
/// `spreadRadius` (double, defaults to zero).
static BoxShadow boxShadow(DataSource source, List<Object> key) {
if (!source.isMap(key)) {
return const BoxShadow();
}
return BoxShadow(
color: color(source, [...key, 'color']) ?? const Color(0xFF000000),
offset: offset(source, [...key, 'offset']) ?? Offset.zero,
blurRadius: source.v<double>([...key, 'blurRadius']) ?? 0.0,
spreadRadius: source.v<double>([...key, 'spreadRadius']) ?? 0.0,
);
}
/// Returns a [Color] from the specified integer.
///
/// Returns null if it's not an integer; otherwise, passes it to the [new
/// Color] constructor.
static Color? color(DataSource source, List<Object> key) {
final int? value = source.v<int>(key);
if (value == null) {
return null;
}
return Color(value);
}
/// Returns a [ColorFilter] from the specified map.
///
/// The `type` key specifies the kind of filter.
///
/// A type of `linearToSrgbGamma` creates a [ColorFilter.linearToSrgbGamma].
///
/// A type of `matrix` creates a [ColorFilter.matrix], parsing the `matrix`
/// key as per [colorMatrix]). If there is no `matrix` key, returns null.
///
/// A type of `mode` creates a [ColorFilter.mode], using the `color` key
/// (see[color], defaults to black) and the `blendMode` key (see [enumValue] for
/// [BlendMdoe], defaults to [BlendMode.srcOver])
///
/// A type of `srgbToLinearGamma` creates a [ColorFilter.srgbToLinearGamma].
///
/// If the type is none of these, but is not null, then the type is looked up
/// in [colorFilterDecoders], and if an entry is found, this method defers to
/// that callback.
///
/// Otherwise, returns null.
static ColorFilter? colorFilter(DataSource source, List<Object> key) {
final String? type = source.v<String>([...key, 'type']);
switch (type) {
case null:
return null;
case 'linearToSrgbGamma':
return const ColorFilter.linearToSrgbGamma();
case 'matrix':
final List<double>? matrix = colorMatrix(source, [...key, 'matrix']);
if (matrix == null) {
return null;
}
return ColorFilter.matrix(matrix);
case 'mode':
return ColorFilter.mode(
color(source, [...key, 'color']) ?? const Color(0xFF000000),
enumValue<BlendMode>(BlendMode.values, source, [...key, 'blendMode']) ?? BlendMode.srcOver,
);
case 'srgbToLinearGamma':
return const ColorFilter.srgbToLinearGamma();
default:
final ArgumentDecoder<ColorFilter?>? decoder = colorFilterDecoders[type];
if (decoder == null) {
return null;
}
return decoder(source, key);
}
}
/// Extension mechanism for [colorFilter].
static final Map<String, ArgumentDecoder<ColorFilter?>> colorFilterDecoders = <String, ArgumentDecoder<ColorFilter?>>{};
/// Returns a list of 20 doubles from the specified list.
///
/// If the specified key does not identify a list, returns null instead.
///
/// If the list has fewer than 20 entries or if any of the entries are not
/// doubles, any entries that could not be obtained are replaced by zero.
///
/// Used by [colorFilter] in the `matrix` mode.
static List<double>? colorMatrix(DataSource source, List<Object> key) {
if (!source.isList(key)) {
return null;
}
return <double>[
source.v<double>([...key, 0]) ?? 0.0,
source.v<double>([...key, 1]) ?? 0.0,
source.v<double>([...key, 2]) ?? 0.0,
source.v<double>([...key, 3]) ?? 0.0,
source.v<double>([...key, 4]) ?? 0.0,
source.v<double>([...key, 5]) ?? 0.0,
source.v<double>([...key, 6]) ?? 0.0,
source.v<double>([...key, 7]) ?? 0.0,
source.v<double>([...key, 8]) ?? 0.0,
source.v<double>([...key, 9]) ?? 0.0,
source.v<double>([...key, 10]) ?? 0.0,
source.v<double>([...key, 11]) ?? 0.0,
source.v<double>([...key, 12]) ?? 0.0,
source.v<double>([...key, 13]) ?? 0.0,
source.v<double>([...key, 14]) ?? 0.0,
source.v<double>([...key, 15]) ?? 0.0,
source.v<double>([...key, 16]) ?? 0.0,
source.v<double>([...key, 17]) ?? 0.0,
source.v<double>([...key, 18]) ?? 0.0,
source.v<double>([...key, 19]) ?? 0.0,
];
}
/// Returns a [Color] from the specified integer.
///
/// Returns black if it's not an integer; otherwise, passes it to the [new
/// Color] constructor.
///
/// This is useful in situations where null is not acceptable, for example,
/// when providing a decoder to [list]. Otherwise, prefer using [DataSource.v]
/// directly.
static Color colorOrBlack(DataSource source, List<Object> key) {
return color(source, key) ?? const Color(0xFF000000);
}
/// Returns a [Curve] from the specified string.
///
/// The given key should specify a string. If that string matches one of the
/// names of static curves defined in the [Curves] class, then that curve is
/// returned. Otherwise, if the string was not null, and is present as a key
/// in the [curveDecoders] map, then the matching decoder from that map is
/// invoked. Otherwise, the default obtained from [AnimationDefaults.curveOf]
/// is used (which is why a [BuildContext] is required).
static Curve curve(DataSource source, List<Object> key, BuildContext context) {
final String? type = source.v<String>(key);
switch (type) {
case 'linear':
return Curves.linear;
case 'decelerate':
return Curves.decelerate;
case 'fastLinearToSlowEaseIn':
return Curves.fastLinearToSlowEaseIn;
case 'ease':
return Curves.ease;
case 'easeIn':
return Curves.easeIn;
case 'easeInToLinear':
return Curves.easeInToLinear;
case 'easeInSine':
return Curves.easeInSine;
case 'easeInQuad':
return Curves.easeInQuad;
case 'easeInCubic':
return Curves.easeInCubic;
case 'easeInQuart':
return Curves.easeInQuart;
case 'easeInQuint':
return Curves.easeInQuint;
case 'easeInExpo':
return Curves.easeInExpo;
case 'easeInCirc':
return Curves.easeInCirc;
case 'easeInBack':
return Curves.easeInBack;
case 'easeOut':
return Curves.easeOut;
case 'linearToEaseOut':
return Curves.linearToEaseOut;
case 'easeOutSine':
return Curves.easeOutSine;
case 'easeOutQuad':
return Curves.easeOutQuad;
case 'easeOutCubic':
return Curves.easeOutCubic;
case 'easeOutQuart':
return Curves.easeOutQuart;
case 'easeOutQuint':
return Curves.easeOutQuint;
case 'easeOutExpo':
return Curves.easeOutExpo;
case 'easeOutCirc':
return Curves.easeOutCirc;
case 'easeOutBack':
return Curves.easeOutBack;
case 'easeInOut':
return Curves.easeInOut;
case 'easeInOutSine':
return Curves.easeInOutSine;
case 'easeInOutQuad':
return Curves.easeInOutQuad;
case 'easeInOutCubic':
return Curves.easeInOutCubic;
case 'easeInOutCubicEmphasized':
return Curves.easeInOutCubicEmphasized;
case 'easeInOutQuart':
return Curves.easeInOutQuart;
case 'easeInOutQuint':
return Curves.easeInOutQuint;
case 'easeInOutExpo':
return Curves.easeInOutExpo;
case 'easeInOutCirc':
return Curves.easeInOutCirc;
case 'easeInOutBack':
return Curves.easeInOutBack;
case 'fastOutSlowIn':
return Curves.fastOutSlowIn;
case 'slowMiddle':
return Curves.slowMiddle;
case 'bounceIn':
return Curves.bounceIn;
case 'bounceOut':
return Curves.bounceOut;
case 'bounceInOut':
return Curves.bounceInOut;
case 'elasticIn':
return Curves.elasticIn;
case 'elasticOut':
return Curves.elasticOut;
case 'elasticInOut':
return Curves.elasticInOut;
default:
if (type != null) {
final ArgumentDecoder<Curve>? decoder = curveDecoders[type];
if (decoder != null) {
return decoder(source, key);
}
}
return AnimationDefaults.curveOf(context);
}
}
/// Extension mechanism for [curve].
///
/// The decoders must not return null.
///
/// The given key will specify a string, which is known to not match any of
/// the values in [Curves].
static final Map<String, ArgumentDecoder<Curve>> curveDecoders = <String, ArgumentDecoder<Curve>>{};
/// Returns a [Decoration] from the specified map.
///
/// The `type` key specifies the kind of decoration.
///
/// A type of `box` creates a [BoxDecoration] using the keys `color`
/// ([color]), `image` ([decorationImage]), `border` ([border]),
/// `borderRadius` ([borderRadius]), `boxShadow` (a [list] of [boxShadow]),
/// `gradient` ([gradient]), `backgroundBlendMode` (an [enumValue] of [BlendMode]),
/// and `shape` (an [enumValue] of [BoxShape]), these keys each corresponding to
/// the properties of [BoxDecoration] with the same name.
///
/// A type of `flutterLogo` creates a [FlutterLogoDecoration] using the keys
/// `color` ([color], corresponds to [FlutterLogoDecoration.textColor]),
/// `style` ([enumValue] of [FlutterLogoStyle], defaults to
/// [FlutterLogoStyle.markOnly]), and `margin` ([edgeInsets], always with a
/// left-to-right direction), the latter two keys corresponding to
/// the properties of [FlutterLogoDecoration] with the same name.
///
/// A type of `shape` creates a [ShapeDecoration] using the keys `color`
/// ([color]), `image` ([decorationImage]), `gradient` ([gradient]), `shadows`
/// (a [list] of [boxShadow]), and `shape` ([shapeBorder]), these keys each
/// corresponding to the properties of [ShapeDecoration] with the same name.
///
/// If the type is none of these, but is not null, then the type is looked up
/// in [decorationDecoders], and if an entry is found, this method defers to
/// that callback.
///
/// Otherwise, returns null.
static Decoration? decoration(DataSource source, List<Object> key) {
final String? type = source.v<String>([...key, 'type']);
switch (type) {
case null:
return null;
case 'box':
return BoxDecoration(
color: color(source, [...key, 'color']),
image: decorationImage(source, [...key, 'image']),
border: border(source, [...key, 'border']),
borderRadius: borderRadius(source, [...key, 'borderRadius']),
boxShadow: list<BoxShadow>(source, [...key, 'boxShadow'], boxShadow),
gradient: gradient(source, [...key, 'gradient']),
backgroundBlendMode: enumValue<BlendMode>(BlendMode.values, source, [...key, 'backgroundBlendMode']),
shape: enumValue<BoxShape>(BoxShape.values, source, [...key, 'shape']) ?? BoxShape.rectangle,
);
case 'flutterLogo':
return FlutterLogoDecoration(
textColor: color(source, [...key, 'color']) ?? const Color(0xFF757575),
style: enumValue<FlutterLogoStyle>(FlutterLogoStyle.values, source, [...key, 'style']) ?? FlutterLogoStyle.markOnly,
margin: (edgeInsets(source, [...key, 'margin']) ?? EdgeInsets.zero).resolve(TextDirection.ltr),
);
case 'shape':
return ShapeDecoration(
color: color(source, [...key, 'color']),
image: decorationImage(source, [...key, 'image']),
gradient: gradient(source, [...key, 'gradient']),
shadows: list<BoxShadow>(source, [...key, 'shadows'], boxShadow),
shape: shapeBorder(source, [...key, 'shape']) ?? const Border(),
);
default:
final ArgumentDecoder<Decoration?>? decoder = decorationDecoders[type];
if (decoder == null) {
return null;
}
return decoder(source, key);
}
}
/// Extension mechanism for [decoration].
static final Map<String, ArgumentDecoder<Decoration?>> decorationDecoders = <String, ArgumentDecoder<Decoration?>>{};
/// Returns a [DecorationImage] from the specified map.
///
/// The [DecorationImage.image] is determined by interpreting the same key as
/// per [imageProvider]. If that method returns null, then this returns null
/// also. Otherwise, the return value is used as the provider and additional
/// keys map to the identically-named properties of [DecorationImage]:
/// `onError` (must be an event handler; the payload map is augmented by an
/// `exception` key that contains the text serialization of the exception and
/// a `stackTrace` key that contains the stack trace, also as a string),
/// `colorFilter` ([colorFilter]), `fit` ([enumValue] of [BoxFit]), `alignment`
/// ([alignment], defaults to [Alignment.center]), `centerSlice` ([rect]),
/// `repeat` ([enumValue] of [ImageRepeat], defaults to [ImageRepeat.noRepeat]),
/// `matchTextDirection` (boolean, defaults to false).
static DecorationImage? decorationImage(DataSource source, List<Object> key) {
final ImageProvider? provider = imageProvider(source, key);
if (provider == null) {
return null;
}
return DecorationImage(
image: provider,
onError: (Object exception, StackTrace? stackTrace) {
final VoidCallback? handler = source.voidHandler([...key, 'onError'], { 'exception': exception.toString(), 'stackTrack': stackTrace.toString() });
if (handler != null) {
handler();
}
},
colorFilter: colorFilter(source, [...key, 'colorFilter']),
fit: enumValue<BoxFit>(BoxFit.values, source, [...key, 'fit']),
alignment: alignment(source, [...key, 'alignment']) ?? Alignment.center,
centerSlice: rect(source, [...key, 'centerSlice']),
repeat: enumValue<ImageRepeat>(ImageRepeat.values, source, [...key, 'repeat']) ?? ImageRepeat.noRepeat,
matchTextDirection: source.v<bool>([...key, 'matchTextDirection']) ?? false,
);
}
/// Returns a double from the specified double.
///
/// Returns 0.0 if it's not a double.
///
/// This is useful in situations where null is not acceptable, for example,
/// when providing a decoder to [list]. Otherwise, prefer using [DataSource.v]
/// directly.
static double doubleOrZero(DataSource source, List<Object> key) {
return source.v<double>(key) ?? 0.0;
}
/// Returns a [Duration] from the specified integer.
///
/// If it's not an integer, the default obtained from
/// [AnimationDefaults.durationOf] is used (which is why a [BuildContext] is
/// required).
static Duration duration(DataSource source, List<Object> key, BuildContext context) {
final int? value = source.v<int>(key);
if (value == null) {
return AnimationDefaults.durationOf(context);
}
return Duration(milliseconds: value);
}
/// Returns an [EdgeInsetsDirectional] from the specified list.
///
/// The list is a list of doubles. An empty or missing list results in a null
/// return value. The list should have one through four items. Extra items are
/// ignored.
///
/// The values are interpreted as follows:
///
/// * start: first value.
/// * top: second value, defaulting to same as start.
/// * end: third value, defaulting to same as start.
/// * bottom: fourth value, defaulting to same as top.
static EdgeInsetsGeometry? edgeInsets(DataSource source, List<Object> key) {
final double? a = source.v<double>([...key, 0]);
if (a == null) {
return null;
}
final double? b = source.v<double>([...key, 1]);
final double? c = source.v<double>([...key, 2]);
final double? d = source.v<double>([...key, 3]);
return EdgeInsetsDirectional.fromSTEB(
a,
b ?? a,
c ?? a,
d ?? b ?? a,
);
}
/// Returns one of the values of the specified enum `T`, from the specified string.
///
/// The string must match the name of the enum value, excluding the enum type
/// name (the part of its [toString] after the dot).
///
/// The first argument must be the `values` list for that enum; this is the
/// list of values that is searched.
///
/// For example, `enumValue<TileMode>(TileMode.values, source, ['tileMode']) ??
/// TileMode.clamp` reads the `tileMode` key of `source`, and looks for the
/// first match in [TileMode.values], defaulting to [TileMode.clamp] if
/// nothing matches; thus, the string `mirror` would return [TileMode.mirror].
static T? enumValue<T>(List<T> values, DataSource source, List<Object> key) {
final String? value = source.v<String>(key);
if (value == null) {
return null;
}
for (int index = 0; index < values.length; index += 1) {
if (value == values[index].toString().split('.').last) {
return values[index];
}
}
return null;
}
/// Returns a [FontFeature] from the specified map.
///
/// The `feature` key is used as the font feature name (defaulting to the
/// probably-useless private value "NONE"), and the `value` key is used as the
/// value (defaulting to 1, which typically means "enabled").
///
/// As this never returns null, it is possible to use it with [list].
static FontFeature fontFeature(DataSource source, List<Object> key) {
return FontFeature(source.v<String>([...key, 'feature']) ?? 'NONE', source.v<int>([...key, 'value']) ?? 1);
}
/// Returns a [Gradient] from the specified map.
///
/// The `type` key specifies the kind of gradient.
///
/// A type of `linear` creates a [LinearGradient] using the keys `begin`
/// ([alignment], defaults to [Alignment.centerLeft]), `end` ([alignment],
/// defaults to [Alignment.centerRight]), `colors` ([list] of [colorOrBlack],
/// defaults to a two-element list with black and white), `stops` ([list] of
/// [doubleOrZero]), and `tileMode` ([enumValue] of [TileMode], defaults to
/// [TileMode.clamp]), these keys each corresponding to the properties of
/// [BoxDecoration] with the same name.
///
/// A type of `radial` creates a [RadialGradient] using the keys `center`
/// ([alignment], defaults to [Alignment.center]), `radius' (double, defaults
/// to 0.5), `colors` ([list] of [colorOrBlack], defaults to a two-element
/// list with black and white), `stops` ([list] of [doubleOrZero]), `tileMode`
/// ([enumValue] of [TileMode], defaults to [TileMode.clamp]), `focal`
/// (([alignment]), and `focalRadius` (double, defaults to zero), these keys
/// each corresponding to the properties of [BoxDecoration] with the same
/// name.
///
/// A type of `linear` creates a [LinearGradient] using the keys `center`
/// ([alignment], defaults to [Alignment.center]), `startAngle` (double,
/// defaults to 0.0), `endAngle` (double, defaults to 2Ï€), `colors` ([list] of
/// [colorOrBlack], defaults to a two-element list with black and white),
/// `stops` ([list] of [doubleOrZero]), and `tileMode` ([enumValue] of [TileMode],
/// defaults to [TileMode.clamp]), these keys each corresponding to the
/// properties of [BoxDecoration] with the same name.
///
/// The `transform` property of these gradient classes is not supported.
// TODO(ianh): https://github.com/flutter/flutter/issues/87208
///
/// If the type is none of these, but is not null, then the type is looked up
/// in [gradientDecoders], and if an entry is found, this method defers to
/// that callback.
///
/// Otherwise, returns null.
static Gradient? gradient(DataSource source, List<Object> key) {
final String? type = source.v<String>([...key, 'type']);
switch (type) {
case null:
return null;
case 'linear':
return LinearGradient(
begin: alignment(source, [...key, 'begin']) ?? Alignment.centerLeft,
end: alignment(source, [...key, 'end']) ?? Alignment.centerRight,
colors: list<Color>(source, [...key, 'colors'], colorOrBlack) ?? const <Color>[Color(0xFF000000), Color(0xFFFFFFFF)],
stops: list<double>(source, [...key, 'stops'], doubleOrZero),
tileMode: enumValue<TileMode>(TileMode.values, source, [...key, 'tileMode']) ?? TileMode.clamp,
// transform: GradientTransformMatrix(matrix(source, [...key, 'transform'])), // blocked by https://github.com/flutter/flutter/issues/87208
);
case 'radial':
return RadialGradient(
center: alignment(source, [...key, 'center']) ?? Alignment.center,
radius: source.v<double>([...key, 'radius']) ?? 0.5,
colors: list<Color>(source, [...key, 'colors'], colorOrBlack) ?? const <Color>[Color(0xFF000000), Color(0xFFFFFFFF)],
stops: list<double>(source, [...key, 'stops'], doubleOrZero),
tileMode: enumValue<TileMode>(TileMode.values, source, [...key, 'tileMode']) ?? TileMode.clamp,
focal: alignment(source, [...key, 'focal']),
focalRadius: source.v<double>([...key, 'focalRadius']) ?? 0.0,
// transform: GradientTransformMatrix(matrix(source, [...key, 'transform'])), // blocked by https://github.com/flutter/flutter/issues/87208
);
case 'sweep':
return SweepGradient(
center: alignment(source, [...key, 'center']) ?? Alignment.center,
startAngle: source.v<double>([...key, 'startAngle']) ?? 0.0,
endAngle: source.v<double>([...key, 'endAngle']) ?? math.pi * 2,
colors: list<Color>(source, [...key, 'colors'], colorOrBlack) ?? const <Color>[Color(0xFF000000), Color(0xFFFFFFFF)],
stops: list<double>(source, [...key, 'stops'], doubleOrZero),
tileMode: enumValue<TileMode>(TileMode.values, source, [...key, 'tileMode']) ?? TileMode.clamp,
// transform: GradientTransformMatrix(matrix(source, [...key, 'transform'])), // blocked by https://github.com/flutter/flutter/issues/87208
);
default:
final ArgumentDecoder<Gradient?>? decoder = gradientDecoders[type];
if (decoder == null) {
return null;
}
return decoder(source, key);
}
}
/// Extension mechanism for [gradient].
static final Map<String, ArgumentDecoder<Gradient?>> gradientDecoders = <String, ArgumentDecoder<Gradient?>>{};
/// Returns a [SliverGridDelegate] from the specified map.
///
/// The `type` key specifies the kind of grid delegate.
///
/// A type of `fixedCrossAxisCount` creates a
/// [SliverGridDelegateWithFixedCrossAxisCount] using the keys
/// `crossAxisCount`, `mainAxisSpacing`, `crossAxisSpacing`,
/// `childAspectRatio`, and `mainAxisExtent`.
///
/// A type of `maxCrossAxisExtent` creates a
/// [SliverGridDelegateWithMaxCrossAxisExtent] using the keys
/// maxCrossAxisExtent:`, `mainAxisSpacing`, `crossAxisSpacing`,
/// `childAspectRatio`, and `mainAxisExtent`.
///
/// The types (int or double) and defaults for these keys match the
/// identically named arguments to the default constructors of those classes.
///
/// If the type is none of these, but is not null, then the type is looked up
/// in [gridDelegateDecoders], and if an entry is found, this method defers to
/// that callback.
///
/// Otherwise, returns null.
static SliverGridDelegate? gridDelegate(DataSource source, List<Object> key) {
final String? type = source.v<String>([...key, 'type']);
switch (type) {
case null:
return null;
case 'fixedCrossAxisCount':
return SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: source.v<int>([...key, 'crossAxisCount']) ?? 2,
mainAxisSpacing: source.v<double>([...key, 'mainAxisSpacing']) ?? 0.0,
crossAxisSpacing: source.v<double>([...key, 'crossAxisSpacing']) ?? 0.0,
childAspectRatio: source.v<double>([...key, 'childAspectRatio']) ?? 1.0,
mainAxisExtent: source.v<double>([...key, 'mainAxisExtent']),
);
case 'maxCrossAxisExtent':
return SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: source.v<double>([...key, 'maxCrossAxisExtent']) ?? 100.0,
mainAxisSpacing: source.v<double>([...key, 'mainAxisSpacing']) ?? 0.0,
crossAxisSpacing: source.v<double>([...key, 'crossAxisSpacing']) ?? 0.0,
childAspectRatio: source.v<double>([...key, 'childAspectRatio']) ?? 1.0,
mainAxisExtent: source.v<double>([...key, 'mainAxisExtent']),
);
default:
final ArgumentDecoder<SliverGridDelegate?>? decoder = gridDelegateDecoders[type];
if (decoder == null) {
return null;
}
return decoder(source, key);
}
}
/// Extension mechanism for [gridDelegate].
static final Map<String, ArgumentDecoder<SliverGridDelegate?>> gridDelegateDecoders = <String, ArgumentDecoder<SliverGridDelegate?>>{};
/// Returns an [IconData] from the specified map.
///
/// If the map does not have an `icon` key that is an integer, returns null.
///
/// Otherwise, returns an [IconData] with the [IconData.codePoint] set to the
/// integer from the `icon` key, the [IconData.fontFamily] set to the string
/// from the `fontFamily` key, and the [IconData.matchTextDirection] set to
/// the boolean from the `matchTextDirection` key (defaulting to false).
///
/// For Material Design icons (those from the [Icons] class), the code point
/// can be obtained from the documentation for the icon, and the font family
/// is `MaterialIcons`. For example, [Icons.chalet] would correspond to
/// `{ icon: 0xe14f, fontFamily: 'MaterialIcons' }`.
///
/// When building the release build of an application that uses the RFW
/// package, because this method creates non-const [IconData] objects
/// dynamically, the `--no-tree-shake-icons` option must be used.
static IconData? iconData(DataSource source, List<Object> key) {
final int? icon = source.v<int>([...key, 'icon']);
if (icon == null) {
return null;
}
return IconData(
icon,
fontFamily: source.v<String>([...key, 'fontFamily']),
matchTextDirection: source.v<bool>([...key, 'matchTextDirection']) ?? false,
);
}
/// Returns an [IconThemeData] from the specified map.
///
/// If the map is absent, returns null.
///
/// Otherwise (even if it has no keys), the [IconThemeData] is created from
/// the following keys: 'color` ([color]), `opacity` (double), `size`
/// (double).
static IconThemeData? iconThemeData(DataSource source, List<Object> key) {
if (!source.isMap(key)) {
return null;
}
return IconThemeData(
color: color(source, [...key, 'color']),
opacity: source.v<double>([...key, 'opacity']),
size: source.v<double>([...key, 'size']),
);
}
/// Returns an [ImageProvider] from the specifed map.
///
/// The `source` key of the specified map is controlling. It must be a string.
/// If its value is one of the keys in [imageProviderDecoders], then the
/// relevant decoder is invoked and its return value is used (even if it is
/// null).
///
/// Otherwise, if the `source` key gives an absolute URL (one with a scheme),
/// then a [NetworkImage] with that URL is returned. Its scale is given by the
/// `scale` key (double, defaults to 1.0).
///
/// Otherwise, if the `source` key gives a relative URL (i.e. it can be parsed
/// as a URL and has no scheme), an [AssetImage] with the name given by the
/// `source` key is returned.
///
/// Otherwise, if there is no `source` key in the map, or if that cannot be
/// parsed as a URL (absolute or relative), null is returned.
static ImageProvider? imageProvider(DataSource source, List<Object> key) {
final String? image = source.v<String>([...key, 'source']);
if (image == null) {
return null;
}
if (imageProviderDecoders.containsKey(image)) {
return imageProviderDecoders[image]!(source, key);
}
final Uri? imageUrl = Uri.tryParse(image);
if (imageUrl == null) {
return null;
}
if (!imageUrl.hasScheme) {
return AssetImage(image);
}
return NetworkImage(image, scale: source.v<double>([...key, 'scale']) ?? 1.0);
}
/// Extension mechanism for [imageProvider].
static final Map<String, ArgumentDecoder<ImageProvider?>> imageProviderDecoders = <String, ArgumentDecoder<ImageProvider?>>{};
/// Returns a [List] of `T` values from the specified list, using the given
/// `decoder` to parse each value.
///
/// If the list is absent _or empty_, returns null (not an empty list).
///
/// Otherwise, returns a list with as many items as the specified list, with
/// each entry in the list decoded using `decoder`.
///
/// If `T` is non-nullable, the decoder must also be non-nullable.
static List<T>? list<T>(DataSource source, List<Object> key, ArgumentDecoder<T> decoder) {
final int count = source.length(key);
if (count == 0) {
return null;
}
return List<T>.generate(count, (int index) {
return decoder(source, [...key, index]);
});
}
/// Returns a [Locale] from the specified string.
///
/// The string is split on hyphens ("-").
///
/// If the string is null, returns null.
///
/// If there is no hyphen in the list, uses the one-argument form of [new
/// Locale], passing the whole string.
///
/// If there is one hyphen in the list, uses the two-argument form of [new
/// Locale], passing the parts before and after the hyphen respectively.
///
/// If there are two or more hyphens, uses the [new Locale.fromSubtags]
/// constructor.
static Locale? locale(DataSource source, List<Object> key) {
final String? value = source.v<String>(key);
if (value == null) {
return null;
}
final List<String> subtags = value.split('-');
if (subtags.isEmpty) {
return null;
}
if (subtags.length == 1) {
return Locale(value);
}
if (subtags.length == 2) {
return Locale(subtags[0], subtags[1]);
}
// TODO(ianh): verify this is correct (I tried looking up the Unicode spec but it was... confusing)
return Locale.fromSubtags(languageCode: subtags[0], scriptCode: subtags[1], countryCode: subtags[2]);
}
/// Returns a list of 16 doubles from the specified list.
///
/// If the list is missing or has fewer than 16 entries, returns null.
///
/// Otherwise, returns a list of 16 entries, corresponding to the first 16
/// entries of the specified list, with any non-double values replaced by 0.0.
static Matrix4? matrix(DataSource source, List<Object> key) {
final double? arg15 = source.v<double>([...key, 15]);
if (arg15 == null) {
return null;
}
return Matrix4(
source.v<double>([...key, 0]) ?? 0.0,
source.v<double>([...key, 1]) ?? 0.0,
source.v<double>([...key, 2]) ?? 0.0,
source.v<double>([...key, 3]) ?? 0.0,
source.v<double>([...key, 4]) ?? 0.0,
source.v<double>([...key, 5]) ?? 0.0,
source.v<double>([...key, 6]) ?? 0.0,
source.v<double>([...key, 7]) ?? 0.0,
source.v<double>([...key, 8]) ?? 0.0,
source.v<double>([...key, 9]) ?? 0.0,
source.v<double>([...key, 10]) ?? 0.0,
source.v<double>([...key, 11]) ?? 0.0,
source.v<double>([...key, 12]) ?? 0.0,
source.v<double>([...key, 13]) ?? 0.0,
source.v<double>([...key, 14]) ?? 0.0,
arg15,
);
}
/// Returns a [MaskFilter] from the specified map.
///
/// The `type` key specifies the kind of mask filter.
///
/// A type of `blur` creates a [MaskFilter.blur]. The `style` key ([enumValue] of
/// [BlurStyle], defaults to [BlurStyle.normal]) is used as the blur style,
/// and the `sigma` key (double, defaults to 1.0) is used as the blur sigma.
///
/// If the type is none of these, but is not null, then the type is looked up
/// in [maskFilterDecoders], and if an entry is found, this method defers to
/// that callback.
///
/// Otherwise, returns null.
static MaskFilter? maskFilter(DataSource source, List<Object> key) {
final String? type = source.v<String>([...key, 'type']);
switch (type) {
case null:
return null;
case 'blur':
return MaskFilter.blur(
enumValue<BlurStyle>(BlurStyle.values, source, [...key, 'style']) ?? BlurStyle.normal,
source.v<double>([...key, 'sigma']) ?? 1.0,
);
default:
final ArgumentDecoder<MaskFilter?>? decoder = maskFilterDecoders[type];
if (decoder == null) {
return null;
}
return decoder(source, key);
}
}
/// Extension mechanism for [maskFilter].
static final Map<String, ArgumentDecoder<MaskFilter?>> maskFilterDecoders = <String, ArgumentDecoder<MaskFilter?>>{};
/// Returns an [Offset] from the specified map.
///
/// The map must have an `x` key and a `y` key, doubles.
static Offset? offset(DataSource source, List<Object> key) {
final double? x = source.v<double>([...key, 'x']);
if (x == null) {
return null;
}
final double? y = source.v<double>([...key, 'y']);
if (y == null) {
return null;
}
return Offset(x, y);
}
/// Returns a [Paint] from the specified map.
///
/// If the map is absent, returns null.
///
/// Otherwise (even if it has no keys), a new [Paint] is created and its
/// properties are set according to the identically-named properties of the
/// map, as follows:
///
/// * `blendMode`: [enumValue] of [BlendMode].
/// * `color`: [color].
/// * `colorFilter`: [colorFilter].
/// * `filterQuality`: [enumValue] of [FilterQuality].
// * `imageFilter`: [imageFilter].
// * `invertColors`: boolean.
/// * `isAntiAlias`: boolean.
/// * `maskFilter`: [maskFilter].
/// * `shader`: [shader].
// * `strokeCap`: [enumValue] of [StrokeCap].
// * `strokeJoin`: [enumValue] of [StrokeJoin].
// * `strokeMiterLimit`: double.
// * `strokeWidth`: double.
// * `style`: [enumValue] of [PaintingStyle].
///
/// (Some values are not supported, because there is no way for them to be
/// used currently in RFW contexts.)
static Paint? paint(DataSource source, List<Object> key) {
if (!source.isMap(key)) {
return null;
}
final Paint result = Paint();
final BlendMode? paintBlendMode = enumValue<BlendMode>(BlendMode.values, source, [...key, 'blendMode']);
if (paintBlendMode != null) {
result.blendMode = paintBlendMode;
}
final Color? paintColor = color(source, [...key, 'color']);
if (paintColor != null) {
result.color = paintColor;
}
final ColorFilter? paintColorFilter = colorFilter(source, [...key, 'colorFilter']);
if (paintColorFilter != null) {
result.colorFilter = paintColorFilter;
}
final FilterQuality? paintFilterQuality = enumValue<FilterQuality>(FilterQuality.values, source, [...key, 'filterQuality']);
if (paintFilterQuality != null) {
result.filterQuality = paintFilterQuality;
}
// final ImageFilter? paintImageFilter = imageFilter(source, [...key, 'imageFilter']);
// if (paintImageFilter != null) {
// result.imageFilter = paintImageFilter;
// }
// final bool? paintInvertColors = source.v<bool>([...key, 'invertColors']);
// if (paintInvertColors != null) {
// result.invertColors = paintInvertColors;
// }
final bool? paintIsAntiAlias = source.v<bool>([...key, 'isAntiAlias']);
if (paintIsAntiAlias != null) {
result.isAntiAlias = paintIsAntiAlias;
}
final MaskFilter? paintMaskFilter = maskFilter(source, [...key, 'maskFilter']);
if (paintMaskFilter != null) {
result.maskFilter = paintMaskFilter;
}
final Shader? paintShader = shader(source, [...key, 'shader']);
if (paintShader != null) {
result.shader = paintShader;
}
// final StrokeCap? paintStrokeCap = enumValue<StrokeCap>(StrokeCap.values, source, [...key, 'strokeCap']),
// if (paintStrokeCap != null) {
// result.strokeCap = paintStrokeCap;
// }
// final StrokeJoin? paintStrokeJoin = enumValue<StrokeJoin>(StrokeJoin.values, source, [...key, 'strokeJoin']),
// if (paintStrokeJoin != null) {
// result.strokeJoin = paintStrokeJoin;
// }
// final double paintStrokeMiterLimit? = source.v<double>([...key, 'strokeMiterLimit']),
// if (paintStrokeMiterLimit != null) {
// result.strokeMiterLimit = paintStrokeMiterLimit;
// }
// final double paintStrokeWidth? = source.v<double>([...key, 'strokeWidth']),
// if (paintStrokeWidth != null) {
// result.strokeWidth = paintStrokeWidth;
// }
// final PaintingStyle? paintStyle = enumValue<PaintingStyle>(PaintingStyle.values, source, [...key, 'style']),
// if (paintStyle != null) {
// result.style = paintStyle;
// }
return result;
}
/// Returns a [Radius] from the specified map.
///
/// The map must have an `x` value corresponding to [Radius.x], and may have a
/// `y` value corresponding to [Radius.y].
///
/// If the map only has an `x` key, the `y` value is assumed to be the same
/// (as in [Radius.circular]).
///
/// If the `x` key is absent, the returned value is null.
static Radius? radius(DataSource source, List<Object> key) {
final double? x = source.v<double>([...key, 'x']);
if (x == null) {
return null;
}
final double y = source.v<double>([...key, 'y']) ?? x;
return Radius.elliptical(x, y);
}
/// Returns a [Rect] from the specified map.
///
/// If the map is absent, returns null.
///
/// Otherwise, returns a [Rect.fromLTWH] whose x, y, width, and height
/// components are determined from the `x`, `y`, `w`, and `h` properties of
/// the map, with missing (or non-double) values replaced by zeros.
static Rect? rect(DataSource source, List<Object> key) {
if (!source.isMap(key)) {
return null;
}
final double x = source.v<double>([...key, 'x']) ?? 0.0;
final double y = source.v<double>([...key, 'y']) ?? 0.0;
final double w = source.v<double>([...key, 'w']) ?? 0.0;
final double h = source.v<double>([...key, 'h']) ?? 0.0;
return Rect.fromLTWH(x, y, w, h);
}
/// Returns a [ShapeBorder] from the specified map or list.
///
/// If the key identifies a list, then each entry in the list is decoded by
/// recursively invoking [shapeBorder], and the result is the combination of
/// those [ShapeBorder] values as obtained using the [ShapeBorder.+] operator.
///
/// Otherwise, if the key identifies a map with a `type` value, the map is
/// interpreted according to the `type` as follows:
///
/// * `box`: the map's `sides` key is interpreted as a list by [border] and
/// the resulting [BoxBorder] (actually, [BorderDirectional]) is returned.
///
/// * `beveled`: a [BeveledRectangleBorder] is returned; the `side` key is
/// interpreted by [borderSide] to set the [BeveledRectangleBorder.side]
/// (default of [BorderSide.none)), and the `borderRadius` key is
/// interpreted by [borderRadius] to set the
/// [BeveledRectangleBorder.borderRadius] (default [BorderRadius.zero]).
///
/// * `circle`: a [CircleBorder] is returned; the `side` key is interpreted
/// by [borderSide] to set the [BeveledRectangleBorder.side] (default of
/// [BorderSide.none)).
///
/// * `continuous`: a [ContinuousRectangleBorder] is returned; the `side` key
/// is interpreted by [borderSide] to set the [BeveledRectangleBorder.side]
/// (default of [BorderSide.none)), and the `borderRadius` key is
/// interpreted by [borderRadius] to set the
/// [BeveledRectangleBorder.borderRadius] (default [BorderRadius.zero]).
///
/// * `rounded`: a [RoundedRectangleBorder] is returned; the `side` key is
/// interpreted by [borderSide] to set the [BeveledRectangleBorder.side]
/// (default of [BorderSide.none)), and the `borderRadius` key is
/// interpreted by [borderRadius] to set the
/// [BeveledRectangleBorder.borderRadius] (default [BorderRadius.zero]).
///
/// * `stadium`: a [StadiumBorder] is returned; the `side` key is interpreted
/// by [borderSide] to set the [BeveledRectangleBorder.side] (default of
/// [BorderSide.none)).
///
/// If the type is none of these, then the type is looked up in
/// [shapeBorderDecoders], and if an entry is found, this method defers to
/// that callback.
///
/// Otherwise, if type is null or is not found in [shapeBorderDecoders], returns null.
static ShapeBorder? shapeBorder(DataSource source, List<Object> key) {
final List<ShapeBorder?>? shapes = list<ShapeBorder?>(source, key, shapeBorder);
if (shapes != null) {
return shapes.where((ShapeBorder? a) => a != null).reduce((ShapeBorder? a, ShapeBorder? b) => a! + b!);
}
final String? type = source.v<String>([...key, 'type']);
switch (type) {
case null:
return null;
case 'box':
return border(source, [...key, 'sides']) ?? const Border();
case 'beveled':
return BeveledRectangleBorder(
side: borderSide(source, [...key, 'side']) ?? BorderSide.none,
borderRadius: borderRadius(source, [...key, 'borderRadius']) ?? BorderRadius.zero,
);
case 'circle':
return CircleBorder(
side: borderSide(source, [...key, 'side']) ?? BorderSide.none,
);
case 'continuous':
return ContinuousRectangleBorder(
side: borderSide(source, [...key, 'side']) ?? BorderSide.none,
borderRadius: borderRadius(source, [...key, 'borderRadius']) ?? BorderRadius.zero,
);
case 'rounded':
return RoundedRectangleBorder(
side: borderSide(source, [...key, 'side']) ?? BorderSide.none,
borderRadius: borderRadius(source, [...key, 'borderRadius']) ?? BorderRadius.zero,
);
case 'stadium':
return StadiumBorder(
side: borderSide(source, [...key, 'side']) ?? BorderSide.none,
);
default:
final ArgumentDecoder<ShapeBorder>? decoder = shapeBorderDecoders[type];
if (decoder == null) {
return null;
}
return decoder(source, key);
}
}
/// Extension mechanism for [shapeBorder].
static final Map<String, ArgumentDecoder<ShapeBorder>> shapeBorderDecoders = <String, ArgumentDecoder<ShapeBorder>>{};
/// Returns a [Shader] based on the specified map.
///
/// The `type` key specifies the kind of shader.
///
/// A type of `linear`, `radial`, or `sweep` is interpreted as described by
/// [gradient]; then, the gradient is compiled to a shader by applying the
/// `rect` (interpreted by [rect]) and `textDirection` (interpreted as an
/// [enumValue] of [TextDirection]) using the [Gradient.createShader] method.
///
/// If the type is none of these, but is not null, then the type is looked up
/// in [shaderDecoders], and if an entry is found, this method defers to
/// that callback.
///
/// Otherwise, returns null.
static Shader? shader(DataSource source, List<Object> key) {
final String? type = source.v<String>([...key, 'type']);
switch (type) {
case null:
return null;
case 'linear':
case 'radial':
case 'sweep':
return gradient(source, key)!.createShader(
rect(source, [...key, 'rect']) ?? Rect.zero,
textDirection: enumValue<TextDirection>(TextDirection.values, source, ['textDirection']) ?? TextDirection.ltr,
);
default:
final ArgumentDecoder<Shader?>? decoder = shaderDecoders[type];
if (decoder == null) {
return null;
}
return decoder(source, key);
}
}
/// Extension mechanism for [shader].
static final Map<String, ArgumentDecoder<Shader?>> shaderDecoders = <String, ArgumentDecoder<Shader?>>{};
/// Returns a string from the specified string.
///
/// Returns the empty string if it's not a string.
///
/// This is useful in situations where null is not acceptable, for example,
/// when providing a decoder to [list]. Otherwise, prefer using [DataSource.v]
/// directly.
static String string(DataSource source, List<Object> key) {
return source.v<String>(key) ?? '';
}
/// Returns a [StrutStyle] from the specified map.
///
/// If the map is absent, returns null.
///
/// Otherwise (even if it has no keys), the [StrutStyle] is created from the
/// following keys: 'fontFamily` (string), `fontFamilyFallback` ([list] of
/// [string]), `fontSize` (double), `height` (double), `leadingDistribution`
/// ([enumValue] of [TextLeadingDistribution]), `leading` (double),
/// `fontWeight` ([enumValue] of [FontWeight]), `fontStyle` ([enumValue] of
/// [FontStyle]), `forceStrutHeight` (boolean).
static StrutStyle? strutStyle(DataSource source, List<Object> key) {
if (!source.isMap(key)) {
return null;
}
return StrutStyle(
fontFamily: source.v<String>([...key, 'fontFamily']),
fontFamilyFallback: list<String>(source, [...key, 'fontFamilyFallback'], string),
fontSize: source.v<double>([...key, 'fontSize']),
height: source.v<double>([...key, 'height']),
leadingDistribution: enumValue<TextLeadingDistribution>(TextLeadingDistribution.values, source, [...key, 'leadingDistribution']),
leading: source.v<double>([...key, 'leading']),
fontWeight: enumValue<FontWeight>(FontWeight.values, source, [...key, 'fontWeight']),
fontStyle: enumValue<FontStyle>(FontStyle.values, source, [...key, 'fontStyle']),
forceStrutHeight: source.v<bool>([...key, 'forceStrutHeight']),
);
}
/// Returns a [TextHeightBehavior] from the specified map.
///
/// If the map is absent, returns null.
///
/// Otherwise (even if it has no keys), the [TextHeightBehavior] is created
/// from the following keys: 'applyHeightToFirstAscent` (boolean; defaults to
/// true), `applyHeightToLastDescent` (boolean, defaults to true), and
/// `leadingDistribution` ([enumValue] of [TextLeadingDistribution], deafults
/// to [TextLeadingDistribution.proportional]).
static TextHeightBehavior? textHeightBehavior(DataSource source, List<Object> key) {
if (!source.isMap(key)) {
return null;
}
return TextHeightBehavior(
applyHeightToFirstAscent: source.v<bool>([...key, 'applyHeightToFirstAscent']) ?? true,
applyHeightToLastDescent: source.v<bool>([...key, 'applyHeightToLastDescent']) ?? true,
leadingDistribution: enumValue<TextLeadingDistribution>(TextLeadingDistribution.values, source, [...key, 'leadingDistribution']) ?? TextLeadingDistribution.proportional,
);
}
/// Returns a [TextDecoration] from the specified list or string.
///
/// If the key identifies a list, then each entry in the list is decoded by
/// recursively invoking [textDecoration], and the result is the combination
/// of those [TextDecoration] values as obtained using
/// [TextDecoration.combine].
///
/// Otherwise, if the key identifies a string, then the value `lineThrough` is
/// mapped to [TextDecoration.lineThrough], `overline` to
/// [TextDecoration.overline], and `underline` to [TextDecoration.underline].
/// Other values (and the abscence of a value) are interpreted as
/// [TextDecoration.none].
static TextDecoration textDecoration(DataSource source, List<Object> key) {
final List<TextDecoration>? decorations = list<TextDecoration>(source, key, textDecoration);
if (decorations != null) {
return TextDecoration.combine(decorations);
}
switch (source.v<String>([...key])) {
case 'lineThrough':
return TextDecoration.lineThrough;
case 'overline':
return TextDecoration.overline;
case 'underline':
return TextDecoration.underline;
default:
return TextDecoration.none;
}
}
/// Returns a [TextStyle] from the specified map.
///
/// If the map is absent, returns null.
///
/// Otherwise (even if it has no keys), the [TextStyle] is created from the
/// following keys: `color` ([color]), `backgroundColor` ([color]), `fontSize`
/// (double), `fontWeight` ([enumValue] of [FontWeight]), `fontStyle`
/// ([enumValue] of [FontStyle]), `letterSpacing` (double), `wordSpacing`
/// (double), `textBaseline` ([enumValue] of [TextBaseline]), `height`
/// (double), `leadingDistribution` ([enumValue] of
/// [TextLeadingDistribution]), `locale` ([locale]), `foreground` ([paint]),
/// `background` ([paint]), `shadows` ([list] of [boxShadow]s), `fontFeatures`
/// ([list] of [fontFeature]s), `decoration` ([textDecoration]),
/// `decorationColor` ([color]), `decorationStyle` ([enumValue] of
/// [TextDecorationStyle]), `decorationThickness` (double), 'fontFamily`
/// (string), `fontFamilyFallback` ([list] of [string]), and `overflow`
/// ([enumValue] of [TextOverflow]).
static TextStyle? textStyle(DataSource source, List<Object> key) {
if (!source.isMap(key)) {
return null;
}
return TextStyle(
color: color(source, [...key, 'color']),
backgroundColor: color(source, [...key, 'backgroundColor']),
fontSize: source.v<double>([...key, 'fontSize']),
fontWeight: enumValue<FontWeight>(FontWeight.values, source, [...key, 'fontWeight']),
fontStyle: enumValue<FontStyle>(FontStyle.values, source, [...key, 'fontStyle']),
letterSpacing: source.v<double>([...key, 'letterSpacing']),
wordSpacing: source.v<double>([...key, 'wordSpacing']),
textBaseline: enumValue<TextBaseline>(TextBaseline.values, source, ['textBaseline']),
height: source.v<double>([...key, 'height']),
leadingDistribution: enumValue<TextLeadingDistribution>(TextLeadingDistribution.values, source, [...key, 'leadingDistribution']),
locale: locale(source, [...key, 'locale']),
foreground: paint(source, [...key, 'foreground']),
background: paint(source, [...key, 'background']),
shadows: list<BoxShadow>(source, [...key, 'shadows'], boxShadow),
fontFeatures: list<FontFeature>(source, [...key, 'fontFeatures'], fontFeature),
decoration: textDecoration(source, [...key, 'decoration']),
decorationColor: color(source, [...key, 'decorationColor']),
decorationStyle: enumValue<TextDecorationStyle>(TextDecorationStyle.values, source, [...key, 'decorationStyle']),
decorationThickness: source.v<double>([...key, 'decorationThickness']),
fontFamily: source.v<String>([...key, 'fontFamily']),
fontFamilyFallback: list<String>(source, [...key, 'fontFamilyFallback'], string),
overflow: enumValue<TextOverflow>(TextOverflow.values, source, ['overflow']),
);
}
/// Returns a [VisualDensity] from the specified string or map.
///
/// If the specified value is a string, then it is interpreted as follows:
///
/// * `adaptivePlatformDensity`: returns
/// [VisualDensity.adaptivePlatformDensity] (which varies by platform).
/// * `comfortable`: returns [VisualDensity.comfortable].
/// * `compact`: returns [VisualDensity.compact].
/// * `standard`: returns [VisualDensity.standard].
///
/// Otherwise, if the specified value is a map, then the keys `horizontal` and
/// `vertical` (doubles) are used to create a custom [VisualDensity]. The
/// specified values must be in the range given by
/// [VisualDensity.minimumDensity] to [VisualDensity.maximumDensity]. Missing
/// values are interpreted as zero.
static VisualDensity? visualDensity(DataSource source, List<Object> key) {
final String? type = source.v<String>(key);
switch (type) {
case 'adaptivePlatformDensity':
return VisualDensity.adaptivePlatformDensity;
case 'comfortable':
return VisualDensity.comfortable;
case 'compact':
return VisualDensity.compact;
case 'standard':
return VisualDensity.standard;
default:
if (!source.isMap(key)) {
return null;
}
return VisualDensity(
horizontal: source.v<double>([...key, 'horizontal']) ?? 0.0,
vertical: source.v<double>([...key, 'vertical']) ?? 0.0,
);
}
}
}