667 lines
22 KiB
Dart
667 lines
22 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../protocol/runtime_protocol.dart';
|
|
|
|
class RuntimeNode {
|
|
const RuntimeNode({
|
|
required this.id,
|
|
required this.type,
|
|
this.parent,
|
|
this.asset,
|
|
this.sourceX,
|
|
this.sourceY,
|
|
this.sourceWidth,
|
|
this.sourceHeight,
|
|
this.sliceLeft,
|
|
this.sliceTop,
|
|
this.sliceRight,
|
|
this.sliceBottom,
|
|
this.pressedAsset,
|
|
this.disabledAsset,
|
|
this.animation,
|
|
this.skin,
|
|
this.loop = true,
|
|
this.text,
|
|
this.x = 0,
|
|
this.y = 0,
|
|
this.width,
|
|
this.height,
|
|
this.paddingLeft = 0,
|
|
this.paddingTop = 0,
|
|
this.paddingRight = 0,
|
|
this.paddingBottom = 0,
|
|
this.anchor = RuntimeAnchorValue.topLeft,
|
|
this.layer = 0,
|
|
this.visible = true,
|
|
this.alpha = 1,
|
|
this.scale = 1,
|
|
this.rotation = 0,
|
|
this.color,
|
|
this.fontSize,
|
|
this.textAlign = RuntimeTextAlignValue.center,
|
|
this.textShadowColor,
|
|
this.textShadowOffsetX,
|
|
this.textShadowOffsetY,
|
|
this.textShadowBlur,
|
|
this.radius,
|
|
this.strokeWidth,
|
|
this.value,
|
|
this.scrollX = 0,
|
|
this.scrollY = 0,
|
|
this.contentWidth,
|
|
this.contentHeight,
|
|
this.virtualized = false,
|
|
this.cacheExtent = 0,
|
|
this.inertia = true,
|
|
this.scrollbarThumbColor,
|
|
this.scrollbarTrackColor,
|
|
this.scrollbarThickness,
|
|
this.scrollbarVisible = true,
|
|
this.interactive = false,
|
|
this.onTap,
|
|
this.onScroll,
|
|
this.preset,
|
|
this.count,
|
|
this.duration,
|
|
this.speedMin,
|
|
this.speedMax,
|
|
this.gravityX,
|
|
this.gravityY,
|
|
this.spread,
|
|
this.colorTo,
|
|
this.radiusTo,
|
|
this.autoRemove = true,
|
|
this.fadeOut = true,
|
|
});
|
|
|
|
final String id;
|
|
final String type;
|
|
final String? parent;
|
|
final String? asset;
|
|
final double? sourceX;
|
|
final double? sourceY;
|
|
final double? sourceWidth;
|
|
final double? sourceHeight;
|
|
final double? sliceLeft;
|
|
final double? sliceTop;
|
|
final double? sliceRight;
|
|
final double? sliceBottom;
|
|
final String? pressedAsset;
|
|
final String? disabledAsset;
|
|
final String? animation;
|
|
final String? skin;
|
|
final bool loop;
|
|
final String? text;
|
|
final double x;
|
|
final double y;
|
|
final double? width;
|
|
final double? height;
|
|
final double paddingLeft;
|
|
final double paddingTop;
|
|
final double paddingRight;
|
|
final double paddingBottom;
|
|
final String anchor;
|
|
final int layer;
|
|
final bool visible;
|
|
final double alpha;
|
|
final double scale;
|
|
final double rotation;
|
|
final Color? color;
|
|
final double? fontSize;
|
|
final String textAlign;
|
|
final Color? textShadowColor;
|
|
final double? textShadowOffsetX;
|
|
final double? textShadowOffsetY;
|
|
final double? textShadowBlur;
|
|
final double? radius;
|
|
final double? strokeWidth;
|
|
final double? value;
|
|
final double scrollX;
|
|
final double scrollY;
|
|
final double? contentWidth;
|
|
final double? contentHeight;
|
|
final bool virtualized;
|
|
final double cacheExtent;
|
|
final bool inertia;
|
|
final Color? scrollbarThumbColor;
|
|
final Color? scrollbarTrackColor;
|
|
final double? scrollbarThickness;
|
|
final bool scrollbarVisible;
|
|
final bool interactive;
|
|
final String? onTap;
|
|
final String? onScroll;
|
|
final String? preset;
|
|
final int? count;
|
|
final double? duration;
|
|
final double? speedMin;
|
|
final double? speedMax;
|
|
final double? gravityX;
|
|
final double? gravityY;
|
|
final double? spread;
|
|
final Color? colorTo;
|
|
final double? radiusTo;
|
|
final bool autoRemove;
|
|
final bool fadeOut;
|
|
|
|
RuntimeNode copyWithProps(Map<String, Object?> props) {
|
|
RuntimeProtocolSchema.ensureKnownKeys(
|
|
props,
|
|
allowed: RuntimeProtocolSchema.nodePropsFields,
|
|
context: 'RuntimeNode.props',
|
|
);
|
|
final nextType = _stringProp(props, RuntimeProtocolField.type) ?? type;
|
|
if (!RuntimeNodeType.isSupported(nextType)) {
|
|
throw FormatException('RuntimeNode.type is unsupported: $nextType');
|
|
}
|
|
final nextAnchor =
|
|
_stringProp(props, RuntimeProtocolField.anchor) ?? anchor;
|
|
if (!RuntimeAnchorValue.isSupported(nextAnchor)) {
|
|
throw FormatException('RuntimeNode.anchor is unsupported: $nextAnchor');
|
|
}
|
|
final nextTextAlign =
|
|
_stringProp(props, RuntimeProtocolField.textAlign) ?? textAlign;
|
|
if (!RuntimeTextAlignValue.isSupported(nextTextAlign)) {
|
|
throw FormatException(
|
|
'RuntimeNode.textAlign is unsupported: $nextTextAlign',
|
|
);
|
|
}
|
|
|
|
final nextPreset =
|
|
_stringProp(props, RuntimeProtocolField.preset) ?? preset;
|
|
_validateParticlePreset(nextPreset);
|
|
|
|
final nextWidth = _doubleProp(props, RuntimeProtocolField.width) ?? width;
|
|
final nextHeight =
|
|
_doubleProp(props, RuntimeProtocolField.height) ?? height;
|
|
final nextContentWidth =
|
|
_doubleProp(props, RuntimeProtocolField.contentWidth) ?? contentWidth;
|
|
final nextContentHeight =
|
|
_doubleProp(props, RuntimeProtocolField.contentHeight) ?? contentHeight;
|
|
final nextPaddingLeft =
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.paddingLeft) ??
|
|
paddingLeft;
|
|
final nextPaddingTop =
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.paddingTop) ??
|
|
paddingTop;
|
|
final nextPaddingRight =
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.paddingRight) ??
|
|
paddingRight;
|
|
final nextPaddingBottom =
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.paddingBottom) ??
|
|
paddingBottom;
|
|
final nextViewportWidth = nextWidth == null
|
|
? null
|
|
: (nextWidth - nextPaddingLeft - nextPaddingRight)
|
|
.clamp(0.0, nextWidth)
|
|
.toDouble();
|
|
final nextViewportHeight = nextHeight == null
|
|
? null
|
|
: (nextHeight - nextPaddingTop - nextPaddingBottom)
|
|
.clamp(0.0, nextHeight)
|
|
.toDouble();
|
|
final nextScrollX = props.containsKey(RuntimeProtocolField.scrollX)
|
|
? _scrollProp(
|
|
props,
|
|
RuntimeProtocolField.scrollX,
|
|
contentExtent: nextContentWidth,
|
|
viewportExtent: nextViewportWidth,
|
|
)!
|
|
: _clampScroll(
|
|
scrollX,
|
|
contentExtent: nextContentWidth,
|
|
viewportExtent: nextViewportWidth,
|
|
);
|
|
final nextScrollY = props.containsKey(RuntimeProtocolField.scrollY)
|
|
? _scrollProp(
|
|
props,
|
|
RuntimeProtocolField.scrollY,
|
|
contentExtent: nextContentHeight,
|
|
viewportExtent: nextViewportHeight,
|
|
)!
|
|
: _clampScroll(
|
|
scrollY,
|
|
contentExtent: nextContentHeight,
|
|
viewportExtent: nextViewportHeight,
|
|
);
|
|
|
|
return RuntimeNode(
|
|
id: id,
|
|
type: nextType,
|
|
parent: _parentProp(props, currentParent: parent, nodeId: id),
|
|
asset: _stringProp(props, RuntimeProtocolField.asset) ?? asset,
|
|
sourceX: _nonNegativeDoubleProp(props, RuntimeProtocolField.sourceX) ?? sourceX,
|
|
sourceY: _nonNegativeDoubleProp(props, RuntimeProtocolField.sourceY) ?? sourceY,
|
|
sourceWidth:
|
|
_positiveDoubleProp(props, RuntimeProtocolField.sourceWidth) ??
|
|
sourceWidth,
|
|
sourceHeight:
|
|
_positiveDoubleProp(props, RuntimeProtocolField.sourceHeight) ??
|
|
sourceHeight,
|
|
sliceLeft:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.sliceLeft) ??
|
|
sliceLeft,
|
|
sliceTop:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.sliceTop) ??
|
|
sliceTop,
|
|
sliceRight:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.sliceRight) ??
|
|
sliceRight,
|
|
sliceBottom:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.sliceBottom) ??
|
|
sliceBottom,
|
|
pressedAsset:
|
|
_stringProp(props, RuntimeProtocolField.pressedAsset) ?? pressedAsset,
|
|
disabledAsset:
|
|
_stringProp(props, RuntimeProtocolField.disabledAsset) ??
|
|
disabledAsset,
|
|
animation:
|
|
_stringProp(props, RuntimeProtocolField.animation) ?? animation,
|
|
skin: _stringProp(props, RuntimeProtocolField.skin) ?? skin,
|
|
loop: _boolProp(props, RuntimeProtocolField.loop) ?? loop,
|
|
text: _stringProp(props, RuntimeProtocolField.text) ?? text,
|
|
x: _doubleProp(props, RuntimeProtocolField.x) ?? x,
|
|
y: _doubleProp(props, RuntimeProtocolField.y) ?? y,
|
|
width: nextWidth,
|
|
height: nextHeight,
|
|
paddingLeft: nextPaddingLeft,
|
|
paddingTop: nextPaddingTop,
|
|
paddingRight: nextPaddingRight,
|
|
paddingBottom: nextPaddingBottom,
|
|
anchor: nextAnchor,
|
|
layer: _intProp(props, RuntimeProtocolField.layer) ?? layer,
|
|
visible: _boolProp(props, RuntimeProtocolField.visible) ?? visible,
|
|
alpha: _doubleProp(props, RuntimeProtocolField.alpha) ?? alpha,
|
|
scale: _doubleProp(props, RuntimeProtocolField.scale) ?? scale,
|
|
rotation: _doubleProp(props, RuntimeProtocolField.rotation) ?? rotation,
|
|
color: _colorProp(props, RuntimeProtocolField.color) ?? color,
|
|
fontSize: _doubleProp(props, RuntimeProtocolField.fontSize) ?? fontSize,
|
|
textAlign: nextTextAlign,
|
|
textShadowColor:
|
|
_colorProp(props, RuntimeProtocolField.textShadowColor) ??
|
|
textShadowColor,
|
|
textShadowOffsetX:
|
|
_doubleProp(props, RuntimeProtocolField.textShadowOffsetX) ??
|
|
textShadowOffsetX,
|
|
textShadowOffsetY:
|
|
_doubleProp(props, RuntimeProtocolField.textShadowOffsetY) ??
|
|
textShadowOffsetY,
|
|
textShadowBlur:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.textShadowBlur) ??
|
|
textShadowBlur,
|
|
radius: _doubleProp(props, RuntimeProtocolField.radius) ?? radius,
|
|
strokeWidth:
|
|
_doubleProp(props, RuntimeProtocolField.strokeWidth) ?? strokeWidth,
|
|
value: _normalizedValueProp(props, RuntimeProtocolField.value) ?? value,
|
|
scrollX: nextScrollX,
|
|
scrollY: nextScrollY,
|
|
contentWidth: nextContentWidth,
|
|
contentHeight: nextContentHeight,
|
|
virtualized:
|
|
_boolProp(props, RuntimeProtocolField.virtualized) ?? virtualized,
|
|
cacheExtent:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.cacheExtent) ??
|
|
cacheExtent,
|
|
inertia: _boolProp(props, RuntimeProtocolField.inertia) ?? inertia,
|
|
scrollbarThumbColor:
|
|
_colorProp(props, RuntimeProtocolField.scrollbarThumbColor) ??
|
|
scrollbarThumbColor,
|
|
scrollbarTrackColor:
|
|
_colorProp(props, RuntimeProtocolField.scrollbarTrackColor) ??
|
|
scrollbarTrackColor,
|
|
scrollbarThickness:
|
|
_nonNegativeDoubleProp(
|
|
props,
|
|
RuntimeProtocolField.scrollbarThickness,
|
|
) ??
|
|
scrollbarThickness,
|
|
scrollbarVisible:
|
|
_boolProp(props, RuntimeProtocolField.scrollbarVisible) ??
|
|
scrollbarVisible,
|
|
interactive:
|
|
_boolProp(props, RuntimeProtocolField.interactive) ?? interactive,
|
|
onTap: _stringProp(props, RuntimeProtocolField.onTap) ?? onTap,
|
|
onScroll: _stringProp(props, RuntimeProtocolField.onScroll) ?? onScroll,
|
|
preset: nextPreset,
|
|
count: _positiveIntProp(props, RuntimeProtocolField.count) ?? count,
|
|
duration:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.duration) ??
|
|
duration,
|
|
speedMin:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.speedMin) ??
|
|
speedMin,
|
|
speedMax:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.speedMax) ??
|
|
speedMax,
|
|
gravityX: _doubleProp(props, RuntimeProtocolField.gravityX) ?? gravityX,
|
|
gravityY: _doubleProp(props, RuntimeProtocolField.gravityY) ?? gravityY,
|
|
spread:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.spread) ?? spread,
|
|
colorTo: _colorProp(props, RuntimeProtocolField.colorTo) ?? colorTo,
|
|
radiusTo:
|
|
_nonNegativeDoubleProp(props, RuntimeProtocolField.radiusTo) ??
|
|
radiusTo,
|
|
autoRemove:
|
|
_boolProp(props, RuntimeProtocolField.autoRemove) ?? autoRemove,
|
|
fadeOut: _boolProp(props, RuntimeProtocolField.fadeOut) ?? fadeOut,
|
|
);
|
|
}
|
|
|
|
static RuntimeNode fromMap(Map<String, Object?> map) {
|
|
RuntimeProtocolSchema.ensureKnownKeys(
|
|
map,
|
|
allowed: RuntimeProtocolSchema.nodeFields,
|
|
context: 'RuntimeNode',
|
|
);
|
|
final type = _requiredString(map, RuntimeProtocolField.type);
|
|
if (!RuntimeNodeType.isSupported(type)) {
|
|
throw FormatException('RuntimeNode.type is unsupported: $type');
|
|
}
|
|
final anchor =
|
|
_stringProp(map, RuntimeProtocolField.anchor) ??
|
|
RuntimeAnchorValue.topLeft;
|
|
if (!RuntimeAnchorValue.isSupported(anchor)) {
|
|
throw FormatException('RuntimeNode.anchor is unsupported: $anchor');
|
|
}
|
|
final textAlign =
|
|
_stringProp(map, RuntimeProtocolField.textAlign) ??
|
|
RuntimeTextAlignValue.center;
|
|
if (!RuntimeTextAlignValue.isSupported(textAlign)) {
|
|
throw FormatException('RuntimeNode.textAlign is unsupported: $textAlign');
|
|
}
|
|
|
|
final preset = _stringProp(map, RuntimeProtocolField.preset);
|
|
_validateParticlePreset(preset);
|
|
|
|
return RuntimeNode(
|
|
id: _requiredString(map, RuntimeProtocolField.id),
|
|
type: type,
|
|
parent: _parentProp(
|
|
map,
|
|
currentParent: null,
|
|
nodeId: _requiredString(map, RuntimeProtocolField.id),
|
|
),
|
|
asset: _stringProp(map, RuntimeProtocolField.asset),
|
|
sourceX: _nonNegativeDoubleProp(map, RuntimeProtocolField.sourceX),
|
|
sourceY: _nonNegativeDoubleProp(map, RuntimeProtocolField.sourceY),
|
|
sourceWidth: _positiveDoubleProp(map, RuntimeProtocolField.sourceWidth),
|
|
sourceHeight: _positiveDoubleProp(
|
|
map,
|
|
RuntimeProtocolField.sourceHeight,
|
|
),
|
|
sliceLeft: _nonNegativeDoubleProp(map, RuntimeProtocolField.sliceLeft),
|
|
sliceTop: _nonNegativeDoubleProp(map, RuntimeProtocolField.sliceTop),
|
|
sliceRight: _nonNegativeDoubleProp(map, RuntimeProtocolField.sliceRight),
|
|
sliceBottom: _nonNegativeDoubleProp(
|
|
map,
|
|
RuntimeProtocolField.sliceBottom,
|
|
),
|
|
pressedAsset: _stringProp(map, RuntimeProtocolField.pressedAsset),
|
|
disabledAsset: _stringProp(map, RuntimeProtocolField.disabledAsset),
|
|
animation: _stringProp(map, RuntimeProtocolField.animation),
|
|
skin: _stringProp(map, RuntimeProtocolField.skin),
|
|
loop: _boolProp(map, RuntimeProtocolField.loop) ?? true,
|
|
text: _stringProp(map, RuntimeProtocolField.text),
|
|
x: _doubleProp(map, RuntimeProtocolField.x) ?? 0,
|
|
y: _doubleProp(map, RuntimeProtocolField.y) ?? 0,
|
|
width: _doubleProp(map, RuntimeProtocolField.width),
|
|
height: _doubleProp(map, RuntimeProtocolField.height),
|
|
paddingLeft:
|
|
_nonNegativeDoubleProp(map, RuntimeProtocolField.paddingLeft) ?? 0,
|
|
paddingTop:
|
|
_nonNegativeDoubleProp(map, RuntimeProtocolField.paddingTop) ?? 0,
|
|
paddingRight:
|
|
_nonNegativeDoubleProp(map, RuntimeProtocolField.paddingRight) ?? 0,
|
|
paddingBottom:
|
|
_nonNegativeDoubleProp(map, RuntimeProtocolField.paddingBottom) ?? 0,
|
|
anchor: anchor,
|
|
layer: _intProp(map, RuntimeProtocolField.layer) ?? 0,
|
|
visible: _boolProp(map, RuntimeProtocolField.visible) ?? true,
|
|
alpha: _doubleProp(map, RuntimeProtocolField.alpha) ?? 1,
|
|
scale: _doubleProp(map, RuntimeProtocolField.scale) ?? 1,
|
|
rotation: _doubleProp(map, RuntimeProtocolField.rotation) ?? 0,
|
|
color: _colorProp(map, RuntimeProtocolField.color),
|
|
fontSize: _doubleProp(map, RuntimeProtocolField.fontSize),
|
|
textAlign: textAlign,
|
|
textShadowColor: _colorProp(map, RuntimeProtocolField.textShadowColor),
|
|
textShadowOffsetX: _doubleProp(
|
|
map,
|
|
RuntimeProtocolField.textShadowOffsetX,
|
|
),
|
|
textShadowOffsetY: _doubleProp(
|
|
map,
|
|
RuntimeProtocolField.textShadowOffsetY,
|
|
),
|
|
textShadowBlur: _nonNegativeDoubleProp(
|
|
map,
|
|
RuntimeProtocolField.textShadowBlur,
|
|
),
|
|
radius: _doubleProp(map, RuntimeProtocolField.radius),
|
|
strokeWidth: _doubleProp(map, RuntimeProtocolField.strokeWidth),
|
|
value: _normalizedValueProp(map, RuntimeProtocolField.value),
|
|
scrollX:
|
|
_scrollProp(
|
|
map,
|
|
RuntimeProtocolField.scrollX,
|
|
contentExtent: _doubleProp(map, RuntimeProtocolField.contentWidth),
|
|
viewportExtent: _doubleProp(map, RuntimeProtocolField.width),
|
|
) ??
|
|
0,
|
|
scrollY:
|
|
_scrollProp(
|
|
map,
|
|
RuntimeProtocolField.scrollY,
|
|
contentExtent: _doubleProp(map, RuntimeProtocolField.contentHeight),
|
|
viewportExtent: _doubleProp(map, RuntimeProtocolField.height),
|
|
) ??
|
|
0,
|
|
contentWidth: _doubleProp(map, RuntimeProtocolField.contentWidth),
|
|
contentHeight: _doubleProp(map, RuntimeProtocolField.contentHeight),
|
|
virtualized: _boolProp(map, RuntimeProtocolField.virtualized) ?? false,
|
|
cacheExtent:
|
|
_nonNegativeDoubleProp(map, RuntimeProtocolField.cacheExtent) ?? 0,
|
|
inertia: _boolProp(map, RuntimeProtocolField.inertia) ?? true,
|
|
scrollbarThumbColor: _colorProp(
|
|
map,
|
|
RuntimeProtocolField.scrollbarThumbColor,
|
|
),
|
|
scrollbarTrackColor: _colorProp(
|
|
map,
|
|
RuntimeProtocolField.scrollbarTrackColor,
|
|
),
|
|
scrollbarThickness: _nonNegativeDoubleProp(
|
|
map,
|
|
RuntimeProtocolField.scrollbarThickness,
|
|
),
|
|
scrollbarVisible:
|
|
_boolProp(map, RuntimeProtocolField.scrollbarVisible) ?? true,
|
|
interactive: _boolProp(map, RuntimeProtocolField.interactive) ?? false,
|
|
onTap: _stringProp(map, RuntimeProtocolField.onTap),
|
|
onScroll: _stringProp(map, RuntimeProtocolField.onScroll),
|
|
preset: preset,
|
|
count: _positiveIntProp(map, RuntimeProtocolField.count),
|
|
duration: _nonNegativeDoubleProp(map, RuntimeProtocolField.duration),
|
|
speedMin: _nonNegativeDoubleProp(map, RuntimeProtocolField.speedMin),
|
|
speedMax: _nonNegativeDoubleProp(map, RuntimeProtocolField.speedMax),
|
|
gravityX: _doubleProp(map, RuntimeProtocolField.gravityX),
|
|
gravityY: _doubleProp(map, RuntimeProtocolField.gravityY),
|
|
spread: _nonNegativeDoubleProp(map, RuntimeProtocolField.spread),
|
|
colorTo: _colorProp(map, RuntimeProtocolField.colorTo),
|
|
radiusTo: _nonNegativeDoubleProp(map, RuntimeProtocolField.radiusTo),
|
|
autoRemove: _boolProp(map, RuntimeProtocolField.autoRemove) ?? true,
|
|
fadeOut: _boolProp(map, RuntimeProtocolField.fadeOut) ?? true,
|
|
);
|
|
}
|
|
|
|
static String _requiredString(Map<String, Object?> map, String key) {
|
|
final value = map[key];
|
|
if (value is String && value.isNotEmpty) {
|
|
return value;
|
|
}
|
|
throw FormatException('RuntimeNode.$key must be a non-empty string');
|
|
}
|
|
|
|
static void _validateParticlePreset(String? preset) {
|
|
if (preset != null && !RuntimeParticlePresetValue.isSupported(preset)) {
|
|
throw FormatException('RuntimeNode.preset is unsupported: $preset');
|
|
}
|
|
}
|
|
|
|
static String? _stringProp(Map<String, Object?> map, String key) {
|
|
final value = map[key];
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value is String) {
|
|
return value;
|
|
}
|
|
throw FormatException('RuntimeNode.$key must be a string');
|
|
}
|
|
|
|
static String? _parentProp(
|
|
Map<String, Object?> map, {
|
|
required String? currentParent,
|
|
required String nodeId,
|
|
}) {
|
|
if (!map.containsKey(RuntimeProtocolField.parent)) {
|
|
return currentParent;
|
|
}
|
|
|
|
final value = _stringProp(map, RuntimeProtocolField.parent);
|
|
if (value == null || value.isEmpty) {
|
|
return null;
|
|
}
|
|
if (value == nodeId) {
|
|
throw const FormatException('RuntimeNode.parent cannot reference itself');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static bool? _boolProp(Map<String, Object?> map, String key) {
|
|
final value = map[key];
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value is bool) {
|
|
return value;
|
|
}
|
|
throw FormatException('RuntimeNode.$key must be a boolean');
|
|
}
|
|
|
|
static double? _doubleProp(Map<String, Object?> map, String key) {
|
|
final value = map[key];
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value is num) {
|
|
return value.toDouble();
|
|
}
|
|
throw FormatException('RuntimeNode.$key must be a number');
|
|
}
|
|
|
|
static double? _normalizedValueProp(Map<String, Object?> map, String key) {
|
|
final value = _doubleProp(map, key);
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value < 0 || value > 1) {
|
|
throw FormatException('RuntimeNode.$key must be between 0 and 1');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static double? _nonNegativeDoubleProp(Map<String, Object?> map, String key) {
|
|
final value = _doubleProp(map, key);
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value < 0) {
|
|
throw FormatException('RuntimeNode.$key must be >= 0');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static double? _positiveDoubleProp(Map<String, Object?> map, String key) {
|
|
final value = _doubleProp(map, key);
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value <= 0) {
|
|
throw FormatException('RuntimeNode.$key must be > 0');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static double? _scrollProp(
|
|
Map<String, Object?> map,
|
|
String key, {
|
|
required double? contentExtent,
|
|
required double? viewportExtent,
|
|
}) {
|
|
final value = _doubleProp(map, key);
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value < 0) {
|
|
throw FormatException('RuntimeNode.$key must be >= 0');
|
|
}
|
|
return _clampScroll(
|
|
value,
|
|
contentExtent: contentExtent,
|
|
viewportExtent: viewportExtent,
|
|
);
|
|
}
|
|
|
|
static double _clampScroll(
|
|
double value, {
|
|
required double? contentExtent,
|
|
required double? viewportExtent,
|
|
}) {
|
|
final maxScroll = (contentExtent ?? 0) - (viewportExtent ?? 0);
|
|
if (maxScroll <= 0) {
|
|
return 0;
|
|
}
|
|
return value.clamp(0, maxScroll).toDouble();
|
|
}
|
|
|
|
static int? _positiveIntProp(Map<String, Object?> map, String key) {
|
|
final value = _intProp(map, key);
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value <= 0) {
|
|
throw FormatException('RuntimeNode.$key must be > 0');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static int? _intProp(Map<String, Object?> map, String key) {
|
|
final value = map[key];
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value is num) {
|
|
return value.toInt();
|
|
}
|
|
throw FormatException('RuntimeNode.$key must be an integer');
|
|
}
|
|
|
|
static Color? _colorProp(Map<String, Object?> map, String key) {
|
|
final value = map[key];
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value is! String || !value.startsWith('#')) {
|
|
throw FormatException('RuntimeNode.$key must be a hex color');
|
|
}
|
|
|
|
final hex = value.substring(1);
|
|
if (hex.length == 6) {
|
|
return Color(int.parse('ff$hex', radix: 16));
|
|
}
|
|
if (hex.length == 8) {
|
|
return Color(int.parse(hex, radix: 16));
|
|
}
|
|
throw FormatException('RuntimeNode.$key must be #RRGGBB or #AARRGGBB');
|
|
}
|
|
}
|