Skip to content

Commit

Permalink
feat: Scrollable TextBoxComponent (#2901)
Browse files Browse the repository at this point in the history
This PR introduces a new ScrollTextBoxComponent, enhancing the existing
text box functionalities with scrollable text capabilities. This
component, built on top of the existing TextBoxComponent, is designed to
handle scrollable text, thereby providing a better user interface for
games that require displaying longer text content.

Added docs and and an example.

---------

Co-authored-by: Lukas Klingsbo <lukas.klingsbo@gmail.com>
Co-authored-by: Lukas Klingsbo <me@lukas.fyi>
  • Loading branch information
3 people committed Dec 8, 2023
1 parent 969a84f commit 8c3cb72
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 48 deletions.
37 changes: 34 additions & 3 deletions doc/flame/rendering/text_rendering.md
Expand Up @@ -10,10 +10,15 @@ components:

- `TextComponent` for rendering a single line of text
- `TextBoxComponent` for bounding multi-line text within a sized box, including the possibility of a
typing effect
typing effect
- `ScrollTextBoxComponent` enhances the functionality of `TextBoxComponent` by adding scrolling
capability when the text exceeds the boundaries of the enclosing box.

Both components are showcased in [this
example](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/rendering/text_example.dart).
Use the `onFinished` callback to get notified when the text is completely printed.


All components are showcased in
[this example](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/rendering/text_example.dart).


### TextComponent
Expand Down Expand Up @@ -109,10 +114,36 @@ class MyTextBox extends TextBoxComponent {
}
```


You can find all the options under [TextBoxComponent's
API](https://pub.dev/documentation/flame/latest/components/TextBoxComponent-class.html).


### ScrollTextBoxComponent

The `ScrollTextBoxComponent` is an advanced version of the `TextBoxComponent`,
designed for displaying scrollable text within a defined area.
This component is particularly useful for creating interfaces where large amounts of text
need to be presented in a constrained space, such as dialogues or information panels.

Note that the `align` property of `TextBoxComponent` is not available.


Example usage:


```dart
class MyScrollableText extends ScrollTextBoxComponent {
MyScrollableText(Vector2 frameSize, String text) : super(
size: frameSize,
text: text,
textRenderer: regular,
boxConfig: TextBoxConfig(timePerChar: 0.05),
);
}
```


### TextElementComponent

If you want to render an arbitrary TextElement, ranging from a single InlineTextElement to a
Expand Down
166 changes: 121 additions & 45 deletions examples/lib/stories/rendering/text_example.dart
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/palette.dart';
Expand All @@ -11,52 +13,87 @@ class TextExample extends FlameGame {

@override
Future<void> onLoad() async {
addAll([
TextComponent(text: 'Hello, Flame', textRenderer: _regular)
..anchor = Anchor.topCenter
..x = size.x / 2
..y = 32.0,
TextComponent(text: 'Text with shade', textRenderer: _shaded)
..anchor = Anchor.topRight
..position = size - Vector2.all(100),
TextComponent(text: 'center', textRenderer: _tiny)
..anchor = Anchor.center
..position.setFrom(size / 2),
TextComponent(text: 'bottomRight', textRenderer: _tiny)
..anchor = Anchor.bottomRight
..position.setFrom(size),
MyTextBox(
'"This is our world now. The world of the electron and the switch; '
'the beauty of the baud. We exist without nationality, skin color, '
'or religious bias. You wage wars, murder, cheat, lie to us and try '
"to make us believe it's for our own good, yet we're the "
'criminals. Yes, I am a criminal. My crime is that of curiosity."',
)
..anchor = Anchor.bottomLeft
..y = size.y,
MyTextBox(
'Let A be a finitely generated torsion-free abelian group. Then '
'A is free.',
align: Anchor.center,
size: Vector2(300, 200),
timePerChar: 0,
margins: 10,
)..position = Vector2(10, 50),
MyTextBox(
'Let A be a torsion abelian group. Then A is the direct sum of its '
'subgroups A(p) for all primes p such that A(p) ≠ 0.',
align: Anchor.bottomRight,
size: Vector2(300, 200),
timePerChar: 0,
margins: 10,
)..position = Vector2(10, 260),
]);
addAll(
[
TextComponent(text: 'Hello, Flame', textRenderer: _regular)
..anchor = Anchor.topCenter
..x = size.x / 2
..y = 32.0,
TextComponent(text: 'Text with shade', textRenderer: _shaded)
..anchor = Anchor.topRight
..position = size - Vector2.all(100),
TextComponent(text: 'center', textRenderer: _tiny)
..anchor = Anchor.center
..position.setFrom(size / 2),
TextComponent(text: 'bottomRight', textRenderer: _tiny)
..anchor = Anchor.bottomRight
..position.setFrom(size),
MyTextBox(
'"This is our world now. The world of the electron and the switch; '
'the beauty of the baud. We exist without nationality, skin color, '
'or religious bias. You wage wars, murder, cheat, lie to us and try '
"to make us believe it's for our own good, yet we're the "
'criminals. Yes, I am a criminal. My crime is that of curiosity."',
)
..anchor = Anchor.bottomLeft
..y = size.y,
MyTextBox(
'Let A be a finitely generated torsion-free abelian group. Then '
'A is free.',
align: Anchor.center,
size: Vector2(300, 200),
timePerChar: 0,
margins: 10,
)..position = Vector2(10, 50),
MyTextBox(
'Let A be a torsion abelian group. Then A is the direct sum of its '
'subgroups A(p) for all primes p such that A(p) ≠ 0.',
align: Anchor.bottomRight,
size: Vector2(300, 200),
timePerChar: 0,
margins: 10,
)..position = Vector2(10, 260),
TextComponent(
text: 'Scroll me when finished:',
position: Vector2(size.x / 2, size.y / 2 + 100),
anchor: Anchor.bottomCenter,
),
MyScrollTextBox(
'In a bustling city, a small team of developers set out to create '
'a mobile game using the Flame engine for Flutter. Their goal was '
'simple: to create an engaging, easy-to-play game that could reach '
'a wide audience on both iOS and Android platforms. '
'After weeks of brainstorming, they decided on a concept: '
'a fast-paced, endless runner game set in a whimsical, '
'ever-changing world. They named it "Swift Dash." '
"Using Flutter's versatility and the Flame engine's "
'capabilities, the team crafted a game with vibrant graphics, '
'smooth animations, and responsive controls. '
'The game featured a character dashing through various landscapes, '
'dodging obstacles, and collecting points. '
'As they launched "Swift Dash," the team was anxious but hopeful. '
'To their delight, the game was well-received. Players loved its '
'simplicity and charm, and the game quickly gained popularity.',
size: Vector2(200, 150),
position: Vector2(size.x / 2, size.y / 2 + 100),
anchor: Anchor.topCenter,
boxConfig: TextBoxConfig(
timePerChar: 0.005,
margins: const EdgeInsets.fromLTRB(10, 10, 10, 10),
),
),
],
);
}
}

final _regularTextStyle =
TextStyle(fontSize: 18, color: BasicPalette.white.color);
final _regular = TextPaint(style: _regularTextStyle);
final _regularTextStyle = TextStyle(
fontSize: 18,
color: BasicPalette.white.color,
);
final _regular = TextPaint(
style: _regularTextStyle,
);
final _tiny = TextPaint(style: _regularTextStyle.copyWith(fontSize: 14.0));
final _box = _regular.copyWith(
(style) => style.copyWith(
Expand All @@ -77,6 +114,9 @@ final _shaded = TextPaint(
);

class MyTextBox extends TextBoxComponent {
late Paint paint;
late Rect bgRect;

MyTextBox(
String text, {
super.align,
Expand All @@ -94,10 +134,46 @@ class MyTextBox extends TextBoxComponent {
),
);

@override
Future<void> onLoad() {
paint = Paint();
bgRect = Rect.fromLTWH(0, 0, width, height);

paint.color = Colors.white10;
return super.onLoad();
}

@override
void render(Canvas canvas) {
canvas.drawRect(bgRect, paint);
super.render(canvas);
}
}

class MyScrollTextBox extends ScrollTextBoxComponent {
late Paint paint;
late Rect bgRect;

MyScrollTextBox(
String text, {
required super.size,
super.boxConfig,
super.position,
super.anchor,
}) : super(text: text, textRenderer: _box);

@override
FutureOr<void> onLoad() {
paint = Paint();
bgRect = Rect.fromLTWH(0, 0, width, height);

paint.color = Colors.white10;
return super.onLoad();
}

@override
void render(Canvas canvas) {
final rect = Rect.fromLTWH(0, 0, width, height);
canvas.drawRect(rect, Paint()..color = Colors.white10);
canvas.drawRect(bgRect, paint);
super.render(canvas);
}
}
1 change: 1 addition & 0 deletions packages/flame/lib/components.dart
Expand Up @@ -39,6 +39,7 @@ export 'src/components/nine_tile_box_component.dart';
export 'src/components/parallax_component.dart';
export 'src/components/particle_system_component.dart';
export 'src/components/position_component.dart';
export 'src/components/scroll_text_box_component.dart';
export 'src/components/spawn_component.dart';
export 'src/components/sprite_animation_component.dart';
export 'src/components/sprite_animation_group_component.dart';
Expand Down

0 comments on commit 8c3cb72

Please sign in to comment.