Add runtime text shadow support
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Added Runtime text shadow fields for text-capable nodes.
|
||||||
- Fixed Runtime node color alpha composition so `#AARRGGBB` alpha now multiplies with node/runtime alpha instead of being overwritten.
|
- Fixed Runtime node color alpha composition so `#AARRGGBB` alpha now multiplies with node/runtime alpha instead of being overwritten.
|
||||||
|
|
||||||
## 0.1.0
|
## 0.1.0
|
||||||
|
|||||||
@@ -44,6 +44,16 @@ Final opacity = color alpha × node alpha × runtime animation alpha
|
|||||||
|
|
||||||
`#RRGGBB` colors behave as fully opaque colors. `#00000000` is fully transparent.
|
`#RRGGBB` colors behave as fully opaque colors. `#00000000` is fully transparent.
|
||||||
|
|
||||||
|
### Text shadow
|
||||||
|
|
||||||
|
Text-capable nodes may use flat shadow fields:
|
||||||
|
|
||||||
|
- `textShadowColor`: `#RRGGBB` or `#AARRGGBB` shadow color.
|
||||||
|
- `textShadowOffsetX` / `textShadowOffsetY`: shadow offset in runtime pixels.
|
||||||
|
- `textShadowBlur`: non-negative blur radius.
|
||||||
|
|
||||||
|
The shadow color alpha is multiplied by `RuntimeNode.alpha` and any runtime animation alpha.
|
||||||
|
|
||||||
## RuntimeCommand
|
## RuntimeCommand
|
||||||
|
|
||||||
Runtime commands request generic side effects owned by Dart/Flame.
|
Runtime commands request generic side effects owned by Dart/Flame.
|
||||||
|
|||||||
@@ -109,6 +109,10 @@
|
|||||||
---@field color? string
|
---@field color? string
|
||||||
---@field fontSize? number
|
---@field fontSize? number
|
||||||
---@field textAlign? RuntimeTextAlign
|
---@field textAlign? RuntimeTextAlign
|
||||||
|
---@field textShadowColor? string
|
||||||
|
---@field textShadowOffsetX? number
|
||||||
|
---@field textShadowOffsetY? number
|
||||||
|
---@field textShadowBlur? number
|
||||||
---@field radius? number
|
---@field radius? number
|
||||||
---@field strokeWidth? number
|
---@field strokeWidth? number
|
||||||
---@field value? number
|
---@field value? number
|
||||||
@@ -166,6 +170,10 @@
|
|||||||
---@field color? string
|
---@field color? string
|
||||||
---@field fontSize? number
|
---@field fontSize? number
|
||||||
---@field textAlign? RuntimeTextAlign
|
---@field textAlign? RuntimeTextAlign
|
||||||
|
---@field textShadowColor? string
|
||||||
|
---@field textShadowOffsetX? number
|
||||||
|
---@field textShadowOffsetY? number
|
||||||
|
---@field textShadowBlur? number
|
||||||
---@field radius? number
|
---@field radius? number
|
||||||
---@field strokeWidth? number
|
---@field strokeWidth? number
|
||||||
---@field value? number
|
---@field value? number
|
||||||
|
|||||||
@@ -109,6 +109,10 @@
|
|||||||
---@field color? string
|
---@field color? string
|
||||||
---@field fontSize? number
|
---@field fontSize? number
|
||||||
---@field textAlign? RuntimeTextAlign
|
---@field textAlign? RuntimeTextAlign
|
||||||
|
---@field textShadowColor? string
|
||||||
|
---@field textShadowOffsetX? number
|
||||||
|
---@field textShadowOffsetY? number
|
||||||
|
---@field textShadowBlur? number
|
||||||
---@field radius? number
|
---@field radius? number
|
||||||
---@field strokeWidth? number
|
---@field strokeWidth? number
|
||||||
---@field value? number
|
---@field value? number
|
||||||
@@ -166,6 +170,10 @@
|
|||||||
---@field color? string
|
---@field color? string
|
||||||
---@field fontSize? number
|
---@field fontSize? number
|
||||||
---@field textAlign? RuntimeTextAlign
|
---@field textAlign? RuntimeTextAlign
|
||||||
|
---@field textShadowColor? string
|
||||||
|
---@field textShadowOffsetX? number
|
||||||
|
---@field textShadowOffsetY? number
|
||||||
|
---@field textShadowBlur? number
|
||||||
---@field radius? number
|
---@field radius? number
|
||||||
---@field strokeWidth? number
|
---@field strokeWidth? number
|
||||||
---@field value? number
|
---@field value? number
|
||||||
|
|||||||
@@ -109,6 +109,10 @@
|
|||||||
---@field color? string
|
---@field color? string
|
||||||
---@field fontSize? number
|
---@field fontSize? number
|
||||||
---@field textAlign? RuntimeTextAlign
|
---@field textAlign? RuntimeTextAlign
|
||||||
|
---@field textShadowColor? string
|
||||||
|
---@field textShadowOffsetX? number
|
||||||
|
---@field textShadowOffsetY? number
|
||||||
|
---@field textShadowBlur? number
|
||||||
---@field radius? number
|
---@field radius? number
|
||||||
---@field strokeWidth? number
|
---@field strokeWidth? number
|
||||||
---@field value? number
|
---@field value? number
|
||||||
@@ -166,6 +170,10 @@
|
|||||||
---@field color? string
|
---@field color? string
|
||||||
---@field fontSize? number
|
---@field fontSize? number
|
||||||
---@field textAlign? RuntimeTextAlign
|
---@field textAlign? RuntimeTextAlign
|
||||||
|
---@field textShadowColor? string
|
||||||
|
---@field textShadowOffsetX? number
|
||||||
|
---@field textShadowOffsetY? number
|
||||||
|
---@field textShadowBlur? number
|
||||||
---@field radius? number
|
---@field radius? number
|
||||||
---@field strokeWidth? number
|
---@field strokeWidth? number
|
||||||
---@field value? number
|
---@field value? number
|
||||||
|
|||||||
@@ -109,6 +109,10 @@
|
|||||||
---@field color? string
|
---@field color? string
|
||||||
---@field fontSize? number
|
---@field fontSize? number
|
||||||
---@field textAlign? RuntimeTextAlign
|
---@field textAlign? RuntimeTextAlign
|
||||||
|
---@field textShadowColor? string
|
||||||
|
---@field textShadowOffsetX? number
|
||||||
|
---@field textShadowOffsetY? number
|
||||||
|
---@field textShadowBlur? number
|
||||||
---@field radius? number
|
---@field radius? number
|
||||||
---@field strokeWidth? number
|
---@field strokeWidth? number
|
||||||
---@field value? number
|
---@field value? number
|
||||||
@@ -166,6 +170,10 @@
|
|||||||
---@field color? string
|
---@field color? string
|
||||||
---@field fontSize? number
|
---@field fontSize? number
|
||||||
---@field textAlign? RuntimeTextAlign
|
---@field textAlign? RuntimeTextAlign
|
||||||
|
---@field textShadowColor? string
|
||||||
|
---@field textShadowOffsetX? number
|
||||||
|
---@field textShadowOffsetY? number
|
||||||
|
---@field textShadowBlur? number
|
||||||
---@field radius? number
|
---@field radius? number
|
||||||
---@field strokeWidth? number
|
---@field strokeWidth? number
|
||||||
---@field value? number
|
---@field value? number
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ class RuntimeNode {
|
|||||||
this.color,
|
this.color,
|
||||||
this.fontSize,
|
this.fontSize,
|
||||||
this.textAlign = RuntimeTextAlignValue.center,
|
this.textAlign = RuntimeTextAlignValue.center,
|
||||||
|
this.textShadowColor,
|
||||||
|
this.textShadowOffsetX,
|
||||||
|
this.textShadowOffsetY,
|
||||||
|
this.textShadowBlur,
|
||||||
this.radius,
|
this.radius,
|
||||||
this.strokeWidth,
|
this.strokeWidth,
|
||||||
this.value,
|
this.value,
|
||||||
@@ -89,6 +93,10 @@ class RuntimeNode {
|
|||||||
final Color? color;
|
final Color? color;
|
||||||
final double? fontSize;
|
final double? fontSize;
|
||||||
final String textAlign;
|
final String textAlign;
|
||||||
|
final Color? textShadowColor;
|
||||||
|
final double? textShadowOffsetX;
|
||||||
|
final double? textShadowOffsetY;
|
||||||
|
final double? textShadowBlur;
|
||||||
final double? radius;
|
final double? radius;
|
||||||
final double? strokeWidth;
|
final double? strokeWidth;
|
||||||
final double? value;
|
final double? value;
|
||||||
@@ -232,6 +240,18 @@ class RuntimeNode {
|
|||||||
color: _colorProp(props, RuntimeProtocolField.color) ?? color,
|
color: _colorProp(props, RuntimeProtocolField.color) ?? color,
|
||||||
fontSize: _doubleProp(props, RuntimeProtocolField.fontSize) ?? fontSize,
|
fontSize: _doubleProp(props, RuntimeProtocolField.fontSize) ?? fontSize,
|
||||||
textAlign: nextTextAlign,
|
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,
|
radius: _doubleProp(props, RuntimeProtocolField.radius) ?? radius,
|
||||||
strokeWidth:
|
strokeWidth:
|
||||||
_doubleProp(props, RuntimeProtocolField.strokeWidth) ?? strokeWidth,
|
_doubleProp(props, RuntimeProtocolField.strokeWidth) ?? strokeWidth,
|
||||||
@@ -352,6 +372,19 @@ class RuntimeNode {
|
|||||||
color: _colorProp(map, RuntimeProtocolField.color),
|
color: _colorProp(map, RuntimeProtocolField.color),
|
||||||
fontSize: _doubleProp(map, RuntimeProtocolField.fontSize),
|
fontSize: _doubleProp(map, RuntimeProtocolField.fontSize),
|
||||||
textAlign: textAlign,
|
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),
|
radius: _doubleProp(map, RuntimeProtocolField.radius),
|
||||||
strokeWidth: _doubleProp(map, RuntimeProtocolField.strokeWidth),
|
strokeWidth: _doubleProp(map, RuntimeProtocolField.strokeWidth),
|
||||||
value: _normalizedValueProp(map, RuntimeProtocolField.value),
|
value: _normalizedValueProp(map, RuntimeProtocolField.value),
|
||||||
|
|||||||
@@ -161,6 +161,10 @@ class RuntimeProtocolField {
|
|||||||
static const color = 'color';
|
static const color = 'color';
|
||||||
static const fontSize = 'fontSize';
|
static const fontSize = 'fontSize';
|
||||||
static const textAlign = 'textAlign';
|
static const textAlign = 'textAlign';
|
||||||
|
static const textShadowColor = 'textShadowColor';
|
||||||
|
static const textShadowOffsetX = 'textShadowOffsetX';
|
||||||
|
static const textShadowOffsetY = 'textShadowOffsetY';
|
||||||
|
static const textShadowBlur = 'textShadowBlur';
|
||||||
static const radius = 'radius';
|
static const radius = 'radius';
|
||||||
static const strokeWidth = 'strokeWidth';
|
static const strokeWidth = 'strokeWidth';
|
||||||
static const value = 'value';
|
static const value = 'value';
|
||||||
@@ -244,6 +248,10 @@ class RuntimeProtocolSchema {
|
|||||||
RuntimeProtocolField.color,
|
RuntimeProtocolField.color,
|
||||||
RuntimeProtocolField.fontSize,
|
RuntimeProtocolField.fontSize,
|
||||||
RuntimeProtocolField.textAlign,
|
RuntimeProtocolField.textAlign,
|
||||||
|
RuntimeProtocolField.textShadowColor,
|
||||||
|
RuntimeProtocolField.textShadowOffsetX,
|
||||||
|
RuntimeProtocolField.textShadowOffsetY,
|
||||||
|
RuntimeProtocolField.textShadowBlur,
|
||||||
RuntimeProtocolField.radius,
|
RuntimeProtocolField.radius,
|
||||||
RuntimeProtocolField.strokeWidth,
|
RuntimeProtocolField.strokeWidth,
|
||||||
RuntimeProtocolField.value,
|
RuntimeProtocolField.value,
|
||||||
@@ -309,6 +317,10 @@ class RuntimeProtocolSchema {
|
|||||||
RuntimeProtocolField.color,
|
RuntimeProtocolField.color,
|
||||||
RuntimeProtocolField.fontSize,
|
RuntimeProtocolField.fontSize,
|
||||||
RuntimeProtocolField.textAlign,
|
RuntimeProtocolField.textAlign,
|
||||||
|
RuntimeProtocolField.textShadowColor,
|
||||||
|
RuntimeProtocolField.textShadowOffsetX,
|
||||||
|
RuntimeProtocolField.textShadowOffsetY,
|
||||||
|
RuntimeProtocolField.textShadowBlur,
|
||||||
RuntimeProtocolField.radius,
|
RuntimeProtocolField.radius,
|
||||||
RuntimeProtocolField.strokeWidth,
|
RuntimeProtocolField.strokeWidth,
|
||||||
RuntimeProtocolField.value,
|
RuntimeProtocolField.value,
|
||||||
|
|||||||
@@ -968,6 +968,7 @@ class RuntimeComponent extends PositionComponent
|
|||||||
fontWeight: node.type == RuntimeNodeType.button
|
fontWeight: node.type == RuntimeNodeType.button
|
||||||
? FontWeight.w600
|
? FontWeight.w600
|
||||||
: FontWeight.normal,
|
: FontWeight.normal,
|
||||||
|
shadows: _textShadows(node),
|
||||||
);
|
);
|
||||||
|
|
||||||
final component = _textComponent;
|
final component = _textComponent;
|
||||||
@@ -1033,6 +1034,23 @@ class RuntimeComponent extends PositionComponent
|
|||||||
return (node.text ?? '').contains('\n');
|
return (node.text ?? '').contains('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Shadow>? _textShadows(RuntimeNode node) {
|
||||||
|
final color = node.textShadowColor;
|
||||||
|
if (color == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
Shadow(
|
||||||
|
color: composeRuntimeColorAlpha(color, renderAlpha),
|
||||||
|
offset: Offset(
|
||||||
|
node.textShadowOffsetX ?? 0,
|
||||||
|
node.textShadowOffsetY ?? 0,
|
||||||
|
),
|
||||||
|
blurRadius: node.textShadowBlur ?? 0,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
Color _textColor(RuntimeNode node) {
|
Color _textColor(RuntimeNode node) {
|
||||||
if (node.type == RuntimeNodeType.button) {
|
if (node.type == RuntimeNodeType.button) {
|
||||||
return Colors.white;
|
return Colors.white;
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ void main() {
|
|||||||
'color': '#112233',
|
'color': '#112233',
|
||||||
'fontSize': 18,
|
'fontSize': 18,
|
||||||
'textAlign': 'left',
|
'textAlign': 'left',
|
||||||
|
'textShadowColor': '#80000000',
|
||||||
|
'textShadowOffsetX': 2,
|
||||||
|
'textShadowOffsetY': 3,
|
||||||
|
'textShadowBlur': 4,
|
||||||
'radius': 10,
|
'radius': 10,
|
||||||
'strokeWidth': 3,
|
'strokeWidth': 3,
|
||||||
'value': 0.6,
|
'value': 0.6,
|
||||||
@@ -91,6 +95,10 @@ void main() {
|
|||||||
expect(node.color, const Color(0xff112233));
|
expect(node.color, const Color(0xff112233));
|
||||||
expect(node.fontSize, 18);
|
expect(node.fontSize, 18);
|
||||||
expect(node.textAlign, 'left');
|
expect(node.textAlign, 'left');
|
||||||
|
expect(node.textShadowColor, const Color(0x80000000));
|
||||||
|
expect(node.textShadowOffsetX, 2);
|
||||||
|
expect(node.textShadowOffsetY, 3);
|
||||||
|
expect(node.textShadowBlur, 4);
|
||||||
expect(node.radius, 10);
|
expect(node.radius, 10);
|
||||||
expect(node.strokeWidth, 3);
|
expect(node.strokeWidth, 3);
|
||||||
expect(node.value, 0.6);
|
expect(node.value, 0.6);
|
||||||
@@ -135,6 +143,10 @@ void main() {
|
|||||||
expect(node.rotation, 0);
|
expect(node.rotation, 0);
|
||||||
expect(node.loop, isTrue);
|
expect(node.loop, isTrue);
|
||||||
expect(node.textAlign, 'center');
|
expect(node.textAlign, 'center');
|
||||||
|
expect(node.textShadowColor, isNull);
|
||||||
|
expect(node.textShadowOffsetX, isNull);
|
||||||
|
expect(node.textShadowOffsetY, isNull);
|
||||||
|
expect(node.textShadowBlur, isNull);
|
||||||
expect(node.scrollbarVisible, isTrue);
|
expect(node.scrollbarVisible, isTrue);
|
||||||
expect(node.paddingLeft, 0);
|
expect(node.paddingLeft, 0);
|
||||||
expect(node.paddingTop, 0);
|
expect(node.paddingTop, 0);
|
||||||
@@ -175,6 +187,10 @@ void main() {
|
|||||||
'scrollX': 90,
|
'scrollX': 90,
|
||||||
'scrollY': 80,
|
'scrollY': 80,
|
||||||
'textAlign': 'right',
|
'textAlign': 'right',
|
||||||
|
'textShadowColor': '#40000000',
|
||||||
|
'textShadowOffsetX': 1,
|
||||||
|
'textShadowOffsetY': 2,
|
||||||
|
'textShadowBlur': 3,
|
||||||
'preset': 'trail',
|
'preset': 'trail',
|
||||||
'count': 12,
|
'count': 12,
|
||||||
});
|
});
|
||||||
@@ -202,6 +218,10 @@ void main() {
|
|||||||
expect(updated.scrollX, 68);
|
expect(updated.scrollX, 68);
|
||||||
expect(updated.scrollY, 60);
|
expect(updated.scrollY, 60);
|
||||||
expect(updated.textAlign, 'right');
|
expect(updated.textAlign, 'right');
|
||||||
|
expect(updated.textShadowColor, const Color(0x40000000));
|
||||||
|
expect(updated.textShadowOffsetX, 1);
|
||||||
|
expect(updated.textShadowOffsetY, 2);
|
||||||
|
expect(updated.textShadowBlur, 3);
|
||||||
expect(updated.preset, 'trail');
|
expect(updated.preset, 'trail');
|
||||||
expect(updated.count, 12);
|
expect(updated.count, 12);
|
||||||
});
|
});
|
||||||
@@ -243,6 +263,14 @@ void main() {
|
|||||||
}),
|
}),
|
||||||
throwsFormatException,
|
throwsFormatException,
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
() => RuntimeNode.fromMap({
|
||||||
|
'id': 'a',
|
||||||
|
'type': 'text',
|
||||||
|
'textShadowBlur': -1,
|
||||||
|
}),
|
||||||
|
throwsFormatException,
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
() =>
|
() =>
|
||||||
RuntimeNode.fromMap({'id': 'a', 'type': 'listView', 'scrollY': -1}),
|
RuntimeNode.fromMap({'id': 'a', 'type': 'listView', 'scrollY': -1}),
|
||||||
|
|||||||
@@ -138,6 +138,31 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('applies text shadow style', () {
|
||||||
|
final component = RuntimeComponent(
|
||||||
|
node: const RuntimeNode(
|
||||||
|
id: 'text',
|
||||||
|
type: RuntimeNodeType.text,
|
||||||
|
text: 'Shadowed',
|
||||||
|
alpha: 0.5,
|
||||||
|
textShadowColor: Color(0x80000000),
|
||||||
|
textShadowOffsetX: 2,
|
||||||
|
textShadowOffsetY: 3,
|
||||||
|
textShadowBlur: 4,
|
||||||
|
),
|
||||||
|
resources: GameResourceManager(),
|
||||||
|
onNodeTap: (_, __) {},
|
||||||
|
);
|
||||||
|
|
||||||
|
final text = component.children.whereType<TextComponent>().single;
|
||||||
|
final style = (text.textRenderer as TextPaint).style;
|
||||||
|
final shadow = style.shadows!.single;
|
||||||
|
expect(shadow.color.a, closeTo(0.25, 0.003));
|
||||||
|
expect(shadow.offset.dx, 2);
|
||||||
|
expect(shadow.offset.dy, 3);
|
||||||
|
expect(shadow.blurRadius, 4);
|
||||||
|
});
|
||||||
|
|
||||||
test('multi-line non-button text is top aligned', () {
|
test('multi-line non-button text is top aligned', () {
|
||||||
final component = RuntimeComponent(
|
final component = RuntimeComponent(
|
||||||
node: const RuntimeNode(
|
node: const RuntimeNode(
|
||||||
|
|||||||
@@ -109,6 +109,10 @@
|
|||||||
---@field color? string
|
---@field color? string
|
||||||
---@field fontSize? number
|
---@field fontSize? number
|
||||||
---@field textAlign? RuntimeTextAlign
|
---@field textAlign? RuntimeTextAlign
|
||||||
|
---@field textShadowColor? string
|
||||||
|
---@field textShadowOffsetX? number
|
||||||
|
---@field textShadowOffsetY? number
|
||||||
|
---@field textShadowBlur? number
|
||||||
---@field radius? number
|
---@field radius? number
|
||||||
---@field strokeWidth? number
|
---@field strokeWidth? number
|
||||||
---@field value? number
|
---@field value? number
|
||||||
@@ -166,6 +170,10 @@
|
|||||||
---@field color? string
|
---@field color? string
|
||||||
---@field fontSize? number
|
---@field fontSize? number
|
||||||
---@field textAlign? RuntimeTextAlign
|
---@field textAlign? RuntimeTextAlign
|
||||||
|
---@field textShadowColor? string
|
||||||
|
---@field textShadowOffsetX? number
|
||||||
|
---@field textShadowOffsetY? number
|
||||||
|
---@field textShadowBlur? number
|
||||||
---@field radius? number
|
---@field radius? number
|
||||||
---@field strokeWidth? number
|
---@field strokeWidth? number
|
||||||
---@field value? number
|
---@field value? number
|
||||||
|
|||||||
Reference in New Issue
Block a user