Fix runtime color alpha composition
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Fixed Runtime node color alpha composition so `#AARRGGBB` alpha now multiplies with node/runtime alpha instead of being overwritten.
|
||||
|
||||
## 0.1.0
|
||||
|
||||
- Initial extracted package skeleton for `flame_lua_runtime`.
|
||||
|
||||
@@ -34,6 +34,16 @@ Supported node concepts include:
|
||||
|
||||
Lua may compose higher-level widgets, but those widgets must normalize into supported runtime nodes.
|
||||
|
||||
### Color and alpha
|
||||
|
||||
`RuntimeNode.color` supports `#RRGGBB` and `#AARRGGBB`. When the alpha channel is present in the color, it is multiplied with `RuntimeNode.alpha` and any runtime animation alpha.
|
||||
|
||||
```text
|
||||
Final opacity = color alpha × node alpha × runtime animation alpha
|
||||
```
|
||||
|
||||
`#RRGGBB` colors behave as fully opaque colors. `#00000000` is fully transparent.
|
||||
|
||||
## RuntimeCommand
|
||||
|
||||
Runtime commands request generic side effects owned by Dart/Flame.
|
||||
|
||||
@@ -12,6 +12,11 @@ import '../models/runtime_node.dart';
|
||||
import '../protocol/runtime_protocol.dart';
|
||||
import '../resources/game_resource_manager.dart';
|
||||
|
||||
@visibleForTesting
|
||||
Color composeRuntimeColorAlpha(Color color, double alpha) {
|
||||
return color.withValues(alpha: color.a * alpha.clamp(0.0, 1.0));
|
||||
}
|
||||
|
||||
class RuntimeComponent extends PositionComponent
|
||||
with HasVisibility, TapCallbacks {
|
||||
RuntimeComponent({
|
||||
@@ -156,7 +161,10 @@ class RuntimeComponent extends PositionComponent
|
||||
}
|
||||
|
||||
final paint = Paint()
|
||||
..color = (_node.color ?? _defaultColor()).withValues(alpha: renderAlpha);
|
||||
..color = composeRuntimeColorAlpha(
|
||||
_node.color ?? _defaultColor(),
|
||||
renderAlpha,
|
||||
);
|
||||
|
||||
canvas.save();
|
||||
canvas.transform(Float64List.fromList(transform.transformMatrix.storage));
|
||||
@@ -177,7 +185,10 @@ class RuntimeComponent extends PositionComponent
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
final paint = Paint()
|
||||
..color = (_node.color ?? _defaultColor()).withValues(alpha: renderAlpha);
|
||||
..color = composeRuntimeColorAlpha(
|
||||
_node.color ?? _defaultColor(),
|
||||
renderAlpha,
|
||||
);
|
||||
|
||||
switch (_node.type) {
|
||||
case RuntimeNodeType.circle:
|
||||
@@ -224,7 +235,7 @@ class RuntimeComponent extends PositionComponent
|
||||
final rect = Rect.fromLTWH(0, 0, size.x, size.y);
|
||||
final radius = Radius.circular(_node.radius ?? 4);
|
||||
final backgroundPaint = Paint()
|
||||
..color = const Color(0x33475569).withValues(alpha: renderAlpha);
|
||||
..color = composeRuntimeColorAlpha(const Color(0x33475569), renderAlpha);
|
||||
canvas.drawRRect(RRect.fromRectAndRadius(rect, radius), backgroundPaint);
|
||||
|
||||
final value = _node.value ?? 0;
|
||||
@@ -327,11 +338,15 @@ class RuntimeComponent extends PositionComponent
|
||||
final contentRect = _listViewContentRect();
|
||||
final scrollbars = _listViewScrollbarVisibility();
|
||||
final trackPaint = Paint()
|
||||
..color = (_node.scrollbarTrackColor ?? const Color(0x33475569))
|
||||
.withValues(alpha: renderAlpha);
|
||||
..color = composeRuntimeColorAlpha(
|
||||
_node.scrollbarTrackColor ?? const Color(0x33475569),
|
||||
renderAlpha,
|
||||
);
|
||||
final thumbPaint = Paint()
|
||||
..color = (_node.scrollbarThumbColor ?? const Color(0xaa94a3b8))
|
||||
.withValues(alpha: renderAlpha);
|
||||
..color = composeRuntimeColorAlpha(
|
||||
_node.scrollbarThumbColor ?? const Color(0xaa94a3b8),
|
||||
renderAlpha,
|
||||
);
|
||||
|
||||
final contentHeight = _node.contentHeight;
|
||||
if (scrollbars.vertical &&
|
||||
@@ -415,7 +430,7 @@ class RuntimeComponent extends PositionComponent
|
||||
image,
|
||||
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()),
|
||||
rect,
|
||||
Paint()..color = Colors.white.withValues(alpha: renderAlpha),
|
||||
Paint()..color = composeRuntimeColorAlpha(Colors.white, renderAlpha),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -948,7 +963,7 @@ class RuntimeComponent extends PositionComponent
|
||||
final text = label ?? '';
|
||||
final color = _textColor(node);
|
||||
final style = TextStyle(
|
||||
color: color.withValues(alpha: renderAlpha),
|
||||
color: composeRuntimeColorAlpha(color, renderAlpha),
|
||||
fontSize: node.fontSize ?? 18,
|
||||
fontWeight: node.type == RuntimeNodeType.button
|
||||
? FontWeight.w600
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flame_lua_runtime/runtime/models/runtime_node.dart';
|
||||
import 'package:flame_lua_runtime/runtime/protocol/runtime_protocol.dart';
|
||||
import 'package:flame_lua_runtime/runtime/rendering/runtime_component.dart';
|
||||
import 'package:flame_lua_runtime/runtime/resources/game_resource_manager.dart';
|
||||
import 'package:flutter/material.dart' show Color;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
@@ -114,6 +115,29 @@ void main() {
|
||||
expect(component.renderAlpha, 1);
|
||||
});
|
||||
|
||||
test('multiplies color alpha with node and runtime alpha', () {
|
||||
expect(
|
||||
composeRuntimeColorAlpha(const Color(0xffffffff), 1).a,
|
||||
closeTo(1, 0.001),
|
||||
);
|
||||
expect(
|
||||
composeRuntimeColorAlpha(const Color(0x80ffffff), 1).a,
|
||||
closeTo(0.5, 0.003),
|
||||
);
|
||||
expect(
|
||||
composeRuntimeColorAlpha(const Color(0x80ffffff), 0.5).a,
|
||||
closeTo(0.25, 0.003),
|
||||
);
|
||||
expect(
|
||||
composeRuntimeColorAlpha(const Color(0x00ffffff), 1).a,
|
||||
closeTo(0, 0.001),
|
||||
);
|
||||
expect(
|
||||
composeRuntimeColorAlpha(const Color(0x80ffffff), 0.25).a,
|
||||
closeTo(0.125, 0.003),
|
||||
);
|
||||
});
|
||||
|
||||
test('multi-line non-button text is top aligned', () {
|
||||
final component = RuntimeComponent(
|
||||
node: const RuntimeNode(
|
||||
|
||||
Reference in New Issue
Block a user