Add image atlas and nine-slice support

This commit is contained in:
gem
2026-06-09 12:30:44 +08:00
parent 409942b4af
commit e2a584d4dc
12 changed files with 453 additions and 24 deletions

View File

@@ -4,6 +4,7 @@ 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/rendering.dart' show Rect;
import 'package:flutter_test/flutter_test.dart';
void main() {
@@ -138,6 +139,50 @@ void main() {
);
});
test('computes atlas source region and clamps to image bounds', () {
expect(
runtimeImageSourceRect(
imageWidth: 100,
imageHeight: 80,
sourceX: 10,
sourceY: 12,
sourceWidth: 30,
sourceHeight: 20,
),
const Rect.fromLTWH(10, 12, 30, 20),
);
expect(
runtimeImageSourceRect(
imageWidth: 100,
imageHeight: 80,
sourceX: 90,
sourceY: 70,
sourceWidth: 30,
sourceHeight: 20,
),
const Rect.fromLTWH(90, 70, 10, 10),
);
});
test('computes nine-slice source and destination rects', () {
final parts = runtimeNineSliceRects(
source: const Rect.fromLTWH(10, 20, 30, 40),
destination: const Rect.fromLTWH(0, 0, 90, 120),
sliceLeft: 5,
sliceTop: 6,
sliceRight: 7,
sliceBottom: 8,
);
expect(parts, hasLength(9));
expect(parts.first.source, const Rect.fromLTRB(10, 20, 15, 26));
expect(parts.first.destination, const Rect.fromLTRB(0, 0, 5, 6));
expect(parts[4].source, const Rect.fromLTRB(15, 26, 33, 52));
expect(parts[4].destination, const Rect.fromLTRB(5, 6, 83, 112));
expect(parts.last.source, const Rect.fromLTRB(33, 52, 40, 60));
expect(parts.last.destination, const Rect.fromLTRB(83, 112, 90, 120));
});
test('updates text alpha style without rebuilding text component', () {
final component = RuntimeComponent(
node: const RuntimeNode(
@@ -161,27 +206,32 @@ void main() {
expect(((updatedText.textRenderer as TextPaint).style.color!).a, 1);
});
test('updates button text alpha style without rebuilding text component', () {
final component = RuntimeComponent(
node: const RuntimeNode(
id: 'button',
type: RuntimeNodeType.button,
text: 'Fade me',
alpha: 0,
),
resources: GameResourceManager(),
onNodeTap: (_, __) {},
);
test(
'updates button text alpha style without rebuilding text component',
() {
final component = RuntimeComponent(
node: const RuntimeNode(
id: 'button',
type: RuntimeNodeType.button,
text: 'Fade me',
alpha: 0,
),
resources: GameResourceManager(),
onNodeTap: (_, __) {},
);
final text = component.children.whereType<TextComponent>().single;
expect(((text.textRenderer as TextPaint).style.color!).a, 0);
final text = component.children.whereType<TextComponent>().single;
expect(((text.textRenderer as TextPaint).style.color!).a, 0);
component.setRuntimeAlpha(1);
component.setRuntimeAlpha(1);
final updatedText = component.children.whereType<TextComponent>().single;
expect(identical(updatedText, text), isTrue);
expect(((updatedText.textRenderer as TextPaint).style.color!).a, 1);
});
final updatedText = component.children
.whereType<TextComponent>()
.single;
expect(identical(updatedText, text), isTrue);
expect(((updatedText.textRenderer as TextPaint).style.color!).a, 1);
},
);
test('applies text shadow style', () {
final component = RuntimeComponent(