mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 09:39:12 +08:00
@ -113,6 +113,63 @@ You can find all the options under [TextBoxComponent's
|
||||
API](https://pub.dev/documentation/flame/latest/components/TextBoxComponent-class.html).
|
||||
|
||||
|
||||
### TextElementComponent
|
||||
|
||||
If you want to render an arbitrary TextElement, ranging from a single InlineTextElement to a
|
||||
formatted DocumentRoot, you can use the `TextElementComponent`.
|
||||
|
||||
A simple example is to create a DocumentRoot to render a sequence of block elements (think of an
|
||||
HTML "div") containing rich text:
|
||||
|
||||
```dart
|
||||
final document = DocumentRoot([
|
||||
HeaderNode.simple('1984', level: 1),
|
||||
ParagraphNode.simple(
|
||||
'Anything could be true. The so-called laws of nature were nonsense.',
|
||||
),
|
||||
// ...
|
||||
]);
|
||||
final element = TextElementComponent.fromDocument(
|
||||
document: document,
|
||||
position: Vector2(100, 50),
|
||||
size: Vector2(400, 200),
|
||||
);
|
||||
```
|
||||
|
||||
Note that the size can be specified in two ways; either via:
|
||||
|
||||
- the size property common to all `PositionComponents`; or
|
||||
- the width/height included within the `DocumentStyle` applied.
|
||||
|
||||
An example applying a style to the document (which can include the size but other parameters as
|
||||
well):
|
||||
|
||||
```dart
|
||||
final style = DocumentStyle(
|
||||
width: 400,
|
||||
height: 200,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
|
||||
background: BackgroundStyle(
|
||||
color: const Color(0xFF4E322E),
|
||||
borderColor: const Color(0xFF000000),
|
||||
borderWidth: 2.0,
|
||||
),
|
||||
);
|
||||
final document = DocumentRoot([ ... ]);
|
||||
final element = TextElementComponent.fromDocument(
|
||||
document: document,
|
||||
style: style,
|
||||
position: Vector2(100, 50),
|
||||
);
|
||||
```
|
||||
|
||||
For a more elaborate example of rich-text, formatted text blocks rendering, check [this
|
||||
example](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/rendering/rich_text_example.dart).
|
||||
|
||||
For more details about the underlying mechanics of the text rendering pipeline, see "Text Elements,
|
||||
Text Nodes, and Text Styles" below.
|
||||
|
||||
|
||||
## Infrastructure
|
||||
|
||||
If you are not using the Flame Component System, want to understand the infrastructure behind text
|
||||
@ -322,8 +379,8 @@ element is that it exposes a LineMetrics that can be used for advanced rendering
|
||||
elements only expose a simpler `draw` method which is unaware of sizing and positioning.
|
||||
|
||||
However, the other types of Text Elements, Text Nodes, and Text Styles must be used if the intent is
|
||||
to create an entire document (multiple blocks or paragraphs), enriched with formatted text.
|
||||
Currently, these extra features of the system are not exposed through FCS; but can be used directly.
|
||||
to create an entire document (multiple blocks or paragraphs), enriched with formatted text. In order
|
||||
to render an arbitrary TextElement, you can alternatively use the `TextElementComponent` (see above).
|
||||
|
||||
An example of such usages can be seen in [this
|
||||
example](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/rendering/rich_text_example.dart).
|
||||
|
||||
@ -10,15 +10,6 @@ class RichTextExample extends FlameGame {
|
||||
@override
|
||||
Color backgroundColor() => const Color(0xFF888888);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
add(MyTextComponent()..position = Vector2(100, 50));
|
||||
}
|
||||
}
|
||||
|
||||
class MyTextComponent extends PositionComponent {
|
||||
late final TextElement element;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
final style = DocumentStyle(
|
||||
@ -68,11 +59,13 @@ class MyTextComponent extends PositionComponent {
|
||||
'minds, truly happens.',
|
||||
),
|
||||
]);
|
||||
element = document.format(style);
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
element.draw(canvas);
|
||||
add(
|
||||
TextElementComponent.fromDocument(
|
||||
document: document,
|
||||
style: style,
|
||||
position: Vector2(100, 50),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ export 'src/components/sprite_component.dart';
|
||||
export 'src/components/sprite_group_component.dart';
|
||||
export 'src/components/text_box_component.dart';
|
||||
export 'src/components/text_component.dart';
|
||||
export 'src/components/text_element_component.dart';
|
||||
export 'src/components/timer_component.dart';
|
||||
export 'src/extensions/vector2.dart';
|
||||
export 'src/geometry/circle_component.dart';
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
class TextElementComponent extends PositionComponent {
|
||||
TextElement element;
|
||||
|
||||
TextElementComponent({
|
||||
required this.element,
|
||||
super.position,
|
||||
super.size,
|
||||
super.scale,
|
||||
super.angle,
|
||||
super.anchor,
|
||||
super.children,
|
||||
super.priority,
|
||||
super.key,
|
||||
});
|
||||
|
||||
factory TextElementComponent.fromDocument({
|
||||
required DocumentRoot document,
|
||||
DocumentStyle? style,
|
||||
Vector2? position,
|
||||
Vector2? size,
|
||||
Vector2? scale,
|
||||
double? angle,
|
||||
Anchor? anchor,
|
||||
List<Component>? children,
|
||||
int priority = 0,
|
||||
ComponentKey? key,
|
||||
}) {
|
||||
final effectiveStyle = style ?? DocumentStyle();
|
||||
final effectiveSize = _coalesceSize(effectiveStyle, size);
|
||||
final element = document.format(
|
||||
effectiveStyle,
|
||||
width: effectiveSize.x,
|
||||
height: effectiveSize.y,
|
||||
);
|
||||
return TextElementComponent(
|
||||
element: element,
|
||||
position: position,
|
||||
size: effectiveSize,
|
||||
scale: scale,
|
||||
angle: angle,
|
||||
anchor: anchor,
|
||||
children: children,
|
||||
priority: priority,
|
||||
key: key,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
element.draw(canvas);
|
||||
}
|
||||
|
||||
static Vector2 _coalesceSize(DocumentStyle style, Vector2? size) {
|
||||
final width = style.width ?? size?.x;
|
||||
final height = style.height ?? size?.y;
|
||||
if (width == null || height == null) {
|
||||
throw ArgumentError('Either style.width or size.x must be provided.');
|
||||
}
|
||||
if ((style.width != null && style.width != width) ||
|
||||
(size?.x != null && size?.x != width)) {
|
||||
throw ArgumentError(
|
||||
'style.width and size.x, if both provided, must match.',
|
||||
);
|
||||
}
|
||||
if ((style.height != null && style.height != height) ||
|
||||
(size?.y != null && size?.y != height)) {
|
||||
throw ArgumentError(
|
||||
'style.height and size.y, if both provided, must match.',
|
||||
);
|
||||
}
|
||||
return Vector2(width, height);
|
||||
}
|
||||
}
|
||||
64
packages/flame/test/components/element_component_test.dart
Normal file
64
packages/flame/test/components/element_component_test.dart
Normal file
@ -0,0 +1,64 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('ElementComponent', () {
|
||||
test('size can be specified via the size parameter', () {
|
||||
final c = TextElementComponent.fromDocument(
|
||||
document: DocumentRoot([]),
|
||||
size: Vector2(100, 200),
|
||||
);
|
||||
expect(c.size, equals(Vector2(100, 200)));
|
||||
});
|
||||
test('size can be specified via the style', () {
|
||||
final c = TextElementComponent.fromDocument(
|
||||
document: DocumentRoot([]),
|
||||
style: DocumentStyle(width: 100, height: 200),
|
||||
);
|
||||
expect(c.size, equals(Vector2(100, 200)));
|
||||
});
|
||||
test('size can be super-specified if matching', () {
|
||||
final c = TextElementComponent.fromDocument(
|
||||
document: DocumentRoot([]),
|
||||
style: DocumentStyle(width: 100, height: 200),
|
||||
size: Vector2(100, 200),
|
||||
);
|
||||
expect(c.size, equals(Vector2(100, 200)));
|
||||
});
|
||||
test('size must be specified', () {
|
||||
expect(
|
||||
() {
|
||||
TextElementComponent.fromDocument(
|
||||
document: DocumentRoot([]),
|
||||
style: DocumentStyle(),
|
||||
);
|
||||
},
|
||||
throwsA(
|
||||
predicate((e) {
|
||||
return e is ArgumentError &&
|
||||
e.message == 'Either style.width or size.x must be provided.';
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
test('size cannot be over-specified if mismatched', () {
|
||||
expect(
|
||||
() {
|
||||
TextElementComponent.fromDocument(
|
||||
document: DocumentRoot([]),
|
||||
style: DocumentStyle(width: 100, height: 200),
|
||||
size: Vector2(100, 300),
|
||||
);
|
||||
},
|
||||
throwsA(
|
||||
predicate((e) {
|
||||
return e is ArgumentError &&
|
||||
e.message ==
|
||||
'style.height and size.y, if both provided, must match.';
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user