mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 03:15:43 +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).
|
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
|
## Infrastructure
|
||||||
|
|
||||||
If you are not using the Flame Component System, want to understand the infrastructure behind text
|
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.
|
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
|
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.
|
to create an entire document (multiple blocks or paragraphs), enriched with formatted text. In order
|
||||||
Currently, these extra features of the system are not exposed through FCS; but can be used directly.
|
to render an arbitrary TextElement, you can alternatively use the `TextElementComponent` (see above).
|
||||||
|
|
||||||
An example of such usages can be seen in [this
|
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).
|
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
|
@override
|
||||||
Color backgroundColor() => const Color(0xFF888888);
|
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
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
final style = DocumentStyle(
|
final style = DocumentStyle(
|
||||||
@ -68,11 +59,13 @@ class MyTextComponent extends PositionComponent {
|
|||||||
'minds, truly happens.',
|
'minds, truly happens.',
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
element = document.format(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
add(
|
||||||
void render(Canvas canvas) {
|
TextElementComponent.fromDocument(
|
||||||
element.draw(canvas);
|
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/sprite_group_component.dart';
|
||||||
export 'src/components/text_box_component.dart';
|
export 'src/components/text_box_component.dart';
|
||||||
export 'src/components/text_component.dart';
|
export 'src/components/text_component.dart';
|
||||||
|
export 'src/components/text_element_component.dart';
|
||||||
export 'src/components/timer_component.dart';
|
export 'src/components/timer_component.dart';
|
||||||
export 'src/extensions/vector2.dart';
|
export 'src/extensions/vector2.dart';
|
||||||
export 'src/geometry/circle_component.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