Skip to content

Commit

Permalink
feat: Add accessor to determine a TextElement size (#3130)
Browse files Browse the repository at this point in the history
Add accessor to determine a `TextElement` size.

Currently, there is no way to figure out the actual size of a
`TextElement`.

This is quite important as the element will have the lines laid out and
broken down, so we need a way to determine the height of the resulting
bounding box.

Before:
<img
src="https://github.com/flame-engine/flame/assets/882703/5d69d9dd-068f-4deb-8dd6-19c90aca01db"
width="30%" />

After:
<img
src="https://github.com/flame-engine/flame/assets/882703/a235badd-734f-4eb6-880a-3ebe0edc9433"
width="30%" />
  • Loading branch information
luanpotter committed Apr 18, 2024
1 parent 7b706d5 commit 8a63a07
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 12 deletions.
1 change: 1 addition & 0 deletions .github/.cspell/flame_dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Dashbook # UI development tool for Flutter https://github.com/bluefireteam/dashb
emberquest # Ember Quest, our platformer tutorial game
Hermione # A character from the book Harry Potter
Kawabunga # Word expressing exhilaration, of unclear origins but popularized by the show Teenage Mutant Ninja Turtles
Kenobi # Eminent Jedi Master, General of the Republic Army, Obi-Wan Kenobi
Mocktail # A library for Dart that automatically creates mocks for tests https://github.com/felangel/mocktail
Nakama # An open-source server designed to power modern games and apps https://github.com/Allan-Nava/nakama-flutter
Overmind # A character in the game StarCraft
Expand Down
11 changes: 7 additions & 4 deletions packages/flame/lib/src/components/text_element_component.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import 'dart:ui';

import 'package:flame/components.dart';
import 'package:flame/extensions.dart';
import 'package:flame/text.dart';

class TextElementComponent extends PositionComponent {
final Vector2? documentSize;
TextElement element;

TextElementComponent({
required this.element,
super.position,
this.documentSize,
super.size,
super.position,
super.scale,
super.angle,
super.anchor,
Expand Down Expand Up @@ -37,10 +38,12 @@ class TextElementComponent extends PositionComponent {
width: effectiveSize.x,
height: effectiveSize.y,
);
final box = element.boundingBox;
return TextElementComponent(
element: element,
position: position,
size: effectiveSize,
documentSize: effectiveSize,
size: box.bottomRight.toVector2(),
scale: scale,
angle: angle,
anchor: anchor,
Expand Down
14 changes: 13 additions & 1 deletion packages/flame/lib/src/text/elements/group_element.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flame/extensions.dart';
import 'package:flame/text.dart';
import 'package:flutter/rendering.dart' hide TextStyle;

class GroupElement extends BlockElement {
GroupElement({
Expand All @@ -19,4 +19,16 @@ class GroupElement extends BlockElement {
void draw(Canvas canvas) {
children.forEach((child) => child.draw(canvas));
}

@override
Rect get boundingBox {
return children.fold<Rect?>(
null,
(previousValue, element) {
final box = element.boundingBox;
return previousValue?.expandToInclude(box) ?? box;
},
) ??
Rect.zero;
}
}
3 changes: 3 additions & 0 deletions packages/flame/lib/src/text/elements/inline_text_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ abstract class InlineTextElement extends TextElement {
);
draw(canvas);
}

@override
Rect get boundingBox => metrics.toRect();
}
3 changes: 3 additions & 0 deletions packages/flame/lib/src/text/elements/rect_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ class RectElement extends TextElement {
void draw(Canvas canvas) {
canvas.drawRect(_rect, _paint);
}

@override
Rect get boundingBox => _rect;
}
3 changes: 3 additions & 0 deletions packages/flame/lib/src/text/elements/rrect_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ class RRectElement extends TextElement {
void draw(Canvas canvas) {
canvas.drawRRect(_rrect, _paint);
}

@override
Rect get boundingBox => _rrect.outerRect;
}
3 changes: 3 additions & 0 deletions packages/flame/lib/src/text/elements/text_element.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:ui';
import 'package:flame/extensions.dart';
import 'package:flame/text.dart';

/// An [TextElement] is a basic rendering block of a rich-text document.
Expand All @@ -21,4 +22,6 @@ abstract class TextElement {
/// calling the [translate] method, or applying a translation transform to the
/// canvas itself.
void draw(Canvas canvas);

Rect get boundingBox;
}
33 changes: 26 additions & 7 deletions packages/flame/test/components/element_component_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,35 @@ import 'package:test/test.dart';

void main() {
group('ElementComponent', () {
test('size can be specified via the size parameter', () {
test('document 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)));
expect(c.documentSize, equals(Vector2(100, 200)));
expect(c.size, equals(Vector2.zero()));
});
test('size can be specified via the style', () {

test('document 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)));
expect(c.documentSize, equals(Vector2(100, 200)));
expect(c.size, equals(Vector2.zero()));
});
test('size can be super-specified if matching', () {

test('document 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)));
expect(c.documentSize, equals(Vector2(100, 200)));
expect(c.size, equals(Vector2.zero()));
});
test('size must be specified', () {

test('document size must be specified', () {
expect(
() {
TextElementComponent.fromDocument(
Expand All @@ -42,6 +48,7 @@ void main() {
),
);
});

test('size cannot be over-specified if mismatched', () {
expect(
() {
Expand All @@ -60,5 +67,17 @@ void main() {
),
);
});

test('size is computed from the element bounding box', () {
final c = TextElementComponent.fromDocument(
document: DocumentRoot([
ParagraphNode.simple('line 1'),
ParagraphNode.simple('line 2'),
]),
size: Vector2(800, 160), // oversized
);
expect(c.documentSize, equals(Vector2(800, 160)));
expect(c.size, equals(Vector2(96, 44)));
});
});
}
68 changes: 68 additions & 0 deletions packages/flame/test/text/text_element_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:flame/src/text/elements/group_element.dart';
import 'package:flame/text.dart';
import 'package:flutter/rendering.dart';
import 'package:test/test.dart';

void main() {
group('text elements', () {
test('bounding box for empty group', () {
final emptyGroup = GroupElement(width: 0, height: 0, children: []);
expect(emptyGroup.boundingBox, Rect.zero);
});

test('bounding box for inline elements', () {
final document = DocumentRoot([
ParagraphNode.group([
PlainTextNode('Hello'),
]),
]);

final element1 = document.format(
DocumentStyle(
paragraph: const BlockStyle(
margin: EdgeInsets.zero,
padding: EdgeInsets.zero,
),
),
width: 80,
height: 16,
);
const expected = Rect.fromLTWH(0, 0, 80, 16);

expect(element1.boundingBox, expected);
final element2 = element1.children.single as GroupElement;
expect(element2.boundingBox, expected);
final element3 = element2.children.single as InlineTextElement;
expect(element3.boundingBox, expected);
});

test('bounding box is composed', () {
final document = DocumentRoot([
ParagraphNode.group([
PlainTextNode('Hello, '),
PlainTextNode('there'),
]),
ParagraphNode.group([
ItalicTextNode.simple('General '),
BoldTextNode.simple('Kenobi'),
]),
]);

final element1 = document.format(
DocumentStyle(
paragraph: const BlockStyle(
margin: EdgeInsets.zero,
padding: EdgeInsets.zero,
),
),
width: 600,
height: 400,
);
expect(element1.boundingBox, const Rect.fromLTWH(0, 0, 224, 32));
final element2 = element1.children[0] as GroupElement;
expect(element2.boundingBox, const Rect.fromLTWH(0, 0, 192, 16));
final element3 = element1.children[1] as GroupElement;
expect(element3.boundingBox, const Rect.fromLTWH(0, 16, 224, 16));
});
});
}

0 comments on commit 8a63a07

Please sign in to comment.