mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 19:12:31 +08:00
Abstracting text API to enable custom renderers (#772)
* Abstracting text API to enable custom renderers * Addressing comments * Lint * Update doc/text.md Co-authored-by: Lukas Klingsbo <lukas.klingsbo@gmail.com> * Adding dartdoc about TextRenderer Co-authored-by: Lukas Klingsbo <lukas.klingsbo@gmail.com>
This commit is contained in:
37
doc/text.md
37
doc/text.md
@ -2,15 +2,28 @@
|
||||
|
||||
Flame has some dedicated classes to help you render text.
|
||||
|
||||
## TextConfig
|
||||
## TextRenderer
|
||||
|
||||
A Text Config contains all typographical information required to render text; i.e., font size and
|
||||
color, family, etc.
|
||||
`TextRenderer` is the abstract class used by Flame to render text. Flame provides one
|
||||
implementation for this called `TextPaint` but anyone can implement this abstraction
|
||||
and create a custom way to render text.
|
||||
|
||||
## TextPaint
|
||||
|
||||
A Text Paint is the built in implementation of text rendering on Flame, it is based on top of
|
||||
Flutter's `TextPainter` class (hence the name), it can be configured by its config class
|
||||
`TextPaintConfig` which contains all typographical information required to render text; i.e., font
|
||||
size and color, family, etc.
|
||||
|
||||
Example usage:
|
||||
|
||||
```dart
|
||||
const TextConfig config = TextConfig(fontSize: 48.0, fontFamily: 'Awesome Font');
|
||||
const TextPaint textPaint = TextPaint(
|
||||
config: TextPaintConfig(
|
||||
fontSize: 48.0,
|
||||
fontFamily: 'Awesome Font',
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
- `fontFamily`: a commonly available font, like Arial (default), or a custom font added in your
|
||||
@ -22,17 +35,17 @@ const TextConfig config = TextConfig(fontSize: 48.0, fontFamily: 'Awesome Font')
|
||||
For more information regarding colors and how to create then, see the
|
||||
[Colors and the Palette](palette.md) guide.
|
||||
|
||||
After the creation of the config you can use its `render` method to draw some string on a canvas:
|
||||
After the creation of the text paint you can use its `render` method to draw some string on a canvas:
|
||||
|
||||
```dart
|
||||
config.render(canvas, "Flame is awesome", Position(10, 10));
|
||||
textPaint.render(canvas, "Flame is awesome", Vector2(10, 10));
|
||||
```
|
||||
|
||||
If you want to set the anchor of the text you can also do that in the render call, with the optional
|
||||
`anchor` parameter:
|
||||
|
||||
```dart
|
||||
config.render(canvas, 'Flame is awesome', Vector2(10, 10), anchor: Anchor.topCenter);
|
||||
textPaint.render(canvas, 'Flame is awesome', Vector2(10, 10), anchor: Anchor.topCenter);
|
||||
```
|
||||
|
||||
## Text Components
|
||||
@ -47,12 +60,12 @@ Flame provides two text components that make it even easier to render text in yo
|
||||
Example usage:
|
||||
|
||||
```dart
|
||||
TextConfig regular = TextConfig(color: BasicPalette.white.color);
|
||||
TextPaint regular = TextPaint(color: BasicPalette.white.color);
|
||||
|
||||
class MyGame extends BaseGame {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
add(TextComponent('Hello, Flame', config: regular)
|
||||
add(TextComponent('Hello, Flame', textRenderer: regular)
|
||||
..anchor = Anchor.topCenter
|
||||
..x = size.width / 2 // size is a property from game
|
||||
..y = 32.0);
|
||||
@ -74,15 +87,15 @@ Example usage:
|
||||
|
||||
```dart
|
||||
class MyTextBox extends TextBoxComponent {
|
||||
MyTextBox(String text) : super(text, config: tiny, boxConfig: TextBoxConfig(timePerChar: 0.05));
|
||||
MyTextBox(String text) : super(text, textRenderer: tiny, boxConfig: TextBoxConfig(timePerChar: 0.05));
|
||||
|
||||
@override
|
||||
void drawBackground(Canvas c) {
|
||||
Rect rect = Rect.fromLTWH(0, 0, width, height);
|
||||
c.drawRect(rect, new Paint()..color = Color(0xFFFF00FF));
|
||||
c.drawRect(rect, Paint()..color = Color(0xFFFF00FF));
|
||||
c.drawRect(
|
||||
rect.deflate(boxConfig.margin),
|
||||
new Paint()
|
||||
Paint()
|
||||
..color = BasicPalette.black.color
|
||||
..style = PaintingStyle.stroke);
|
||||
}
|
||||
|
||||
@ -14,7 +14,11 @@ final R = Random();
|
||||
class MovableSquare extends SquareComponent
|
||||
with Hitbox, Collidable, HasGameRef<CameraAndViewportGame> {
|
||||
static const double speed = 300;
|
||||
static final TextConfig config = TextConfig(fontSize: 12);
|
||||
static final TextPaint textRenderer = TextPaint(
|
||||
config: const TextPaintConfig(
|
||||
fontSize: 12,
|
||||
),
|
||||
);
|
||||
|
||||
final Vector2 velocity = Vector2.zero();
|
||||
late Timer timer;
|
||||
@ -41,7 +45,7 @@ class MovableSquare extends SquareComponent
|
||||
void render(Canvas c) {
|
||||
super.render(c);
|
||||
final text = '(${x.toInt()}, ${y.toInt()})';
|
||||
config.render(c, text, size / 2, anchor: Anchor.center);
|
||||
textRenderer.render(c, text, size / 2, anchor: Anchor.center);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -167,8 +167,10 @@ class CollidableSnowman extends MyCollidable {
|
||||
|
||||
class MultipleShapes extends BaseGame
|
||||
with HasCollidables, HasDraggableComponents {
|
||||
final TextConfig fpsTextConfig = TextConfig(
|
||||
final TextPaint fpsTextPaint = TextPaint(
|
||||
config: TextPaintConfig(
|
||||
color: BasicPalette.white.color,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
@ -229,7 +231,7 @@ class MultipleShapes extends BaseGame
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
super.render(canvas);
|
||||
fpsTextConfig.render(
|
||||
fpsTextPaint.render(
|
||||
canvas,
|
||||
'${fps(120).toStringAsFixed(2)}fps',
|
||||
Vector2(0, size.y - 24),
|
||||
|
||||
@ -34,7 +34,11 @@ class LogoCompomnent extends SpriteComponent with HasGameRef<DebugGame> {
|
||||
}
|
||||
|
||||
class DebugGame extends BaseGame {
|
||||
static final fpsTextConfig = TextConfig(color: const Color(0xFFFFFFFF));
|
||||
static final fpsTextPaint = TextPaint(
|
||||
config: const TextPaintConfig(
|
||||
color: Color(0xFFFFFFFF),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
bool debugMode = true;
|
||||
@ -67,7 +71,7 @@ class DebugGame extends BaseGame {
|
||||
super.render(canvas);
|
||||
|
||||
if (debugMode) {
|
||||
fpsTextConfig.render(canvas, fps(120).toString(), Vector2(0, 50));
|
||||
fpsTextPaint.render(canvas, fps(120).toString(), Vector2(0, 50));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,9 @@ import 'package:flame/game.dart';
|
||||
import 'package:flame/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final _regular = TextConfig(color: BasicPalette.white.color);
|
||||
final _tiny = _regular.withFontSize(12.0);
|
||||
final _regularTextConfig = TextPaintConfig(color: BasicPalette.white.color);
|
||||
final _regular = TextPaint(config: _regularTextConfig);
|
||||
final _tiny = TextPaint(config: _regularTextConfig.withFontSize(12.0));
|
||||
|
||||
final _white = Paint()
|
||||
..color = BasicPalette.white.color
|
||||
@ -16,7 +17,7 @@ class MyTextBox extends TextBoxComponent {
|
||||
MyTextBox(String text)
|
||||
: super(
|
||||
text,
|
||||
config: _tiny,
|
||||
textRenderer: _tiny,
|
||||
boxConfig: TextBoxConfig(
|
||||
timePerChar: 0.05,
|
||||
growingBox: true,
|
||||
@ -43,20 +44,20 @@ class TextGame extends BaseGame {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
add(
|
||||
TextComponent('Hello, Flame', config: _regular)
|
||||
TextComponent('Hello, Flame', textRenderer: _regular)
|
||||
..anchor = Anchor.topCenter
|
||||
..x = size.x / 2
|
||||
..y = 32.0,
|
||||
);
|
||||
|
||||
add(
|
||||
TextComponent('center', config: _tiny)
|
||||
TextComponent('center', textRenderer: _tiny)
|
||||
..anchor = Anchor.center
|
||||
..position.setFrom(size / 2),
|
||||
);
|
||||
|
||||
add(
|
||||
TextComponent('bottomRight', config: _tiny)
|
||||
TextComponent('bottomRight', textRenderer: _tiny)
|
||||
..anchor = Anchor.bottomRight
|
||||
..position.setFrom(size),
|
||||
);
|
||||
|
||||
@ -21,8 +21,10 @@ class ParticlesGame extends BaseGame {
|
||||
final Random rnd = Random();
|
||||
final StepTween steppedTween = StepTween(begin: 0, end: 5);
|
||||
final trafficLight = TrafficLightComponent();
|
||||
final TextConfig fpsTextConfig = TextConfig(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
final TextPaint fpsTextPaint = TextPaint(
|
||||
config: const TextPaintConfig(
|
||||
color: Color(0xFFFFFFFF),
|
||||
),
|
||||
);
|
||||
|
||||
/// Defines the lifespan of all the particles in these examples
|
||||
@ -474,7 +476,7 @@ class ParticlesGame extends BaseGame {
|
||||
super.render(canvas);
|
||||
|
||||
if (debugMode) {
|
||||
fpsTextConfig.render(
|
||||
fpsTextPaint.render(
|
||||
canvas,
|
||||
'${fps(120).toStringAsFixed(2)}fps',
|
||||
Vector2(0, size.y - 24),
|
||||
|
||||
@ -4,7 +4,11 @@ import 'package:flame/timer.dart';
|
||||
import 'package:flame/gestures.dart';
|
||||
|
||||
class TimerGame extends Game with TapDetector {
|
||||
final TextConfig textConfig = TextConfig(color: const Color(0xFFFFFFFF));
|
||||
final TextPaint textConfig = TextPaint(
|
||||
config: const TextPaintConfig(
|
||||
color: Color(0xFFFFFFFF),
|
||||
),
|
||||
);
|
||||
late Timer countdown;
|
||||
late Timer interval;
|
||||
|
||||
|
||||
@ -4,13 +4,17 @@ import 'package:flame/timer.dart';
|
||||
import 'package:flame/gestures.dart';
|
||||
|
||||
class RenderedTimeComponent extends TimerComponent {
|
||||
final TextConfig textConfig = TextConfig(color: const Color(0xFFFFFFFF));
|
||||
final TextPaint textPaint = TextPaint(
|
||||
config: const TextPaintConfig(
|
||||
color: Color(0xFFFFFFFF),
|
||||
),
|
||||
);
|
||||
|
||||
RenderedTimeComponent(Timer timer) : super(timer);
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
textConfig.render(
|
||||
textPaint.render(
|
||||
canvas,
|
||||
'Elapsed time: ${timer.current}',
|
||||
Vector2(10, 150),
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
- Add a new renderRect method to Sprite
|
||||
- Addresses the TODO to change the camera public APIs to take Anchors for relativePositions
|
||||
- Adds methods to support moving the camera relative to its current position
|
||||
- Abstracting the text api to allow custom text renderers on the framework
|
||||
|
||||
## [1.0.0-rc9]
|
||||
- Fix input bug with other anchors than center
|
||||
|
||||
@ -21,5 +21,5 @@ export 'src/components/sprite_component.dart';
|
||||
export 'src/components/text_box_component.dart';
|
||||
export 'src/components/text_component.dart';
|
||||
export 'src/extensions/vector2.dart';
|
||||
export 'src/text_config.dart';
|
||||
export 'src/text.dart';
|
||||
export 'src/timer.dart';
|
||||
|
||||
@ -6,4 +6,4 @@ export 'src/game/game.dart';
|
||||
export 'src/game/game_widget/game_widget.dart';
|
||||
export 'src/game/projector.dart';
|
||||
export 'src/game/viewport.dart';
|
||||
export 'src/text_config.dart';
|
||||
export 'src/text.dart';
|
||||
|
||||
@ -9,7 +9,7 @@ import '../../game.dart';
|
||||
import '../effects/effects.dart';
|
||||
import '../effects/effects_handler.dart';
|
||||
import '../extensions/vector2.dart';
|
||||
import '../text_config.dart';
|
||||
import '../text.dart';
|
||||
import 'component.dart';
|
||||
import 'mixins/has_game_ref.dart';
|
||||
|
||||
@ -50,7 +50,12 @@ abstract class BaseComponent extends Component {
|
||||
..strokeWidth = 1
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
TextConfig get debugTextConfig => TextConfig(color: debugColor, fontSize: 12);
|
||||
TextPaint get debugTextPaint => TextPaint(
|
||||
config: TextPaintConfig(
|
||||
color: debugColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
);
|
||||
|
||||
/// This method is called periodically by the game engine to request that your component updates itself.
|
||||
///
|
||||
|
||||
@ -156,7 +156,7 @@ abstract class PositionComponent extends BaseComponent {
|
||||
(this as Hitbox).renderShapes(canvas);
|
||||
}
|
||||
canvas.drawRect(size.toRect(), debugPaint);
|
||||
debugTextConfig.render(
|
||||
debugTextPaint.render(
|
||||
canvas,
|
||||
'x: ${x.toStringAsFixed(2)} y:${y.toStringAsFixed(2)}',
|
||||
Vector2(-50, -15),
|
||||
@ -165,7 +165,7 @@ abstract class PositionComponent extends BaseComponent {
|
||||
final rect = toRect();
|
||||
final dx = rect.right;
|
||||
final dy = rect.bottom;
|
||||
debugTextConfig.render(
|
||||
debugTextPaint.render(
|
||||
canvas,
|
||||
'x:${dx.toStringAsFixed(2)} y:${dy.toStringAsFixed(2)}',
|
||||
Vector2(width - 50, height),
|
||||
|
||||
@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart' hide Image;
|
||||
|
||||
import '../extensions/vector2.dart';
|
||||
import '../palette.dart';
|
||||
import '../text_config.dart';
|
||||
import '../text.dart';
|
||||
import 'position_component.dart';
|
||||
|
||||
class TextBoxConfig {
|
||||
@ -31,7 +31,7 @@ class TextBoxComponent extends PositionComponent {
|
||||
Vector2 _gameSize = Vector2.zero();
|
||||
|
||||
final String _text;
|
||||
final TextConfig _config;
|
||||
final TextRenderer _textRenderer;
|
||||
final TextBoxConfig _boxConfig;
|
||||
|
||||
late List<String> _lines;
|
||||
@ -45,37 +45,37 @@ class TextBoxComponent extends PositionComponent {
|
||||
|
||||
String get text => _text;
|
||||
|
||||
TextConfig get config => _config;
|
||||
TextRenderer get renderer => _textRenderer;
|
||||
|
||||
TextBoxConfig get boxConfig => _boxConfig;
|
||||
|
||||
TextBoxComponent(
|
||||
String text, {
|
||||
TextConfig? config,
|
||||
TextRenderer? textRenderer,
|
||||
TextBoxConfig? boxConfig,
|
||||
Vector2? position,
|
||||
Vector2? size,
|
||||
}) : _text = text,
|
||||
_boxConfig = boxConfig ?? TextBoxConfig(),
|
||||
_config = config ?? TextConfig(),
|
||||
_textRenderer = textRenderer ?? TextPaint(),
|
||||
super(position: position, size: size) {
|
||||
_lines = [];
|
||||
double? lineHeight;
|
||||
text.split(' ').forEach((word) {
|
||||
final possibleLine = _lines.isEmpty ? word : '${_lines.last} $word';
|
||||
final painter = _config.toTextPainter(possibleLine);
|
||||
lineHeight ??= painter.height;
|
||||
if (painter.width <=
|
||||
_boxConfig.maxWidth - _boxConfig.margins.horizontal) {
|
||||
lineHeight ??= _textRenderer.measureTextHeight(possibleLine);
|
||||
|
||||
final textWidth = _textRenderer.measureTextWidth(possibleLine);
|
||||
if (textWidth <= _boxConfig.maxWidth - _boxConfig.margins.horizontal) {
|
||||
if (_lines.isNotEmpty) {
|
||||
_lines.last = possibleLine;
|
||||
} else {
|
||||
_lines.add(possibleLine);
|
||||
}
|
||||
_updateMaxWidth(painter.width);
|
||||
_updateMaxWidth(textWidth);
|
||||
} else {
|
||||
_lines.add(word);
|
||||
_updateMaxWidth(_config.toTextPainter(word).width);
|
||||
_updateMaxWidth(textWidth);
|
||||
}
|
||||
});
|
||||
_totalLines = _lines.length;
|
||||
@ -112,9 +112,12 @@ class TextBoxComponent extends PositionComponent {
|
||||
Vector2 get size => Vector2(width, height);
|
||||
|
||||
double getLineWidth(String line, int charCount) {
|
||||
return _config
|
||||
.toTextPainter(line.substring(0, math.min(charCount, line.length)))
|
||||
.width;
|
||||
return _textRenderer.measureTextWidth(
|
||||
line.substring(
|
||||
0,
|
||||
math.min(charCount, line.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double? _cachedWidth;
|
||||
@ -192,7 +195,7 @@ class TextBoxComponent extends PositionComponent {
|
||||
}
|
||||
|
||||
void _drawLine(Canvas c, String line, double dy) {
|
||||
_config.toTextPainter(line).paint(c, Offset(_boxConfig.margins.left, dy));
|
||||
_textRenderer.render(c, line, Vector2(_boxConfig.margins.left, dy));
|
||||
}
|
||||
|
||||
void redrawLater() async {
|
||||
|
||||
@ -4,14 +4,12 @@ import 'package:flutter/painting.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../extensions/vector2.dart';
|
||||
import '../text_config.dart';
|
||||
import '../text.dart';
|
||||
import 'position_component.dart';
|
||||
|
||||
class TextComponent extends PositionComponent {
|
||||
String _text;
|
||||
TextConfig _config;
|
||||
|
||||
late TextPainter _tp;
|
||||
TextRenderer _textRenderer;
|
||||
|
||||
String get text => _text;
|
||||
|
||||
@ -22,32 +20,32 @@ class TextComponent extends PositionComponent {
|
||||
}
|
||||
}
|
||||
|
||||
TextConfig get config => _config;
|
||||
TextRenderer get textRenderer => _textRenderer;
|
||||
|
||||
set config(TextConfig config) {
|
||||
_config = config;
|
||||
set textRenderer(TextRenderer textRenderer) {
|
||||
_textRenderer = textRenderer;
|
||||
_updateBox();
|
||||
}
|
||||
|
||||
TextComponent(
|
||||
this._text, {
|
||||
TextConfig? config,
|
||||
TextRenderer? textRenderer,
|
||||
Vector2? position,
|
||||
Vector2? size,
|
||||
}) : _config = config ?? TextConfig(),
|
||||
}) : _textRenderer = textRenderer ?? TextPaint(),
|
||||
super(position: position, size: size) {
|
||||
_updateBox();
|
||||
}
|
||||
|
||||
void _updateBox() {
|
||||
_tp = config.toTextPainter(_text);
|
||||
size.setValues(_tp.width, _tp.height);
|
||||
final size = textRenderer.measureText(_text);
|
||||
size.setValues(size.x, size.y);
|
||||
}
|
||||
|
||||
@mustCallSuper
|
||||
@override
|
||||
void render(Canvas c) {
|
||||
super.render(c);
|
||||
_tp.paint(c, Offset.zero);
|
||||
void render(Canvas canvas) {
|
||||
super.render(canvas);
|
||||
_textRenderer.render(canvas, text, Vector2.zero());
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,22 +8,76 @@ import 'extensions/size.dart';
|
||||
import 'extensions/vector2.dart';
|
||||
import 'memory_cache.dart';
|
||||
|
||||
/// A Text Config contains all typographical information required to render texts; i.e., font size and color, family, etc.
|
||||
/// [TextRenderer] is the abstract API that Flame uses for rendering text in its features
|
||||
/// this class can be extended to provide an implementation of text rendering in the engine.
|
||||
///
|
||||
/// It does not hold information regarding the position of the text to be render neither the text itself (the string).
|
||||
/// To hold all those information, use the Text component.
|
||||
/// See [TextPaint] for the default implementation offered by Flame
|
||||
abstract class TextRenderer<T extends BaseTextConfig> {
|
||||
final T config;
|
||||
|
||||
TextRenderer({required this.config});
|
||||
|
||||
/// Renders a given [text] in a given position [position] using the provided [canvas] and [anchor].
|
||||
///
|
||||
/// It is used by [TextComponent].
|
||||
class TextConfig {
|
||||
/// Renders it in the given position, considering the [anchor] specified.
|
||||
/// For example, if [Anchor.center] is specified, it's going to be drawn centered around [position].
|
||||
///
|
||||
/// Example usage (Using TextPaint implementation):
|
||||
///
|
||||
/// const TextPaint config = TextPaint(fontSize: 48.0, fontFamily: 'Awesome Font');
|
||||
/// config.render(canvas, Vector2(size.x - 10, size.y - 10, anchor: Anchor.bottomRight);
|
||||
void render(
|
||||
Canvas canvas,
|
||||
String text,
|
||||
Vector2 position, {
|
||||
Anchor anchor = Anchor.topLeft,
|
||||
});
|
||||
|
||||
/// Given a [text] String, returns the width of that [text].
|
||||
double measureTextWidth(String text);
|
||||
|
||||
/// Given a [text] String, returns the height of that [text].
|
||||
double measureTextHeight(String text);
|
||||
|
||||
/// Given a [text] String, returns a Vector2 with the size of that [text] has.
|
||||
Vector2 measureText(String text) {
|
||||
return Vector2(
|
||||
measureTextWidth(text),
|
||||
measureTextHeight(text),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A Text Config contains all typographical information required to render texts; i.e., font size, text direction, etc.
|
||||
abstract class BaseTextConfig {
|
||||
/// The font size to be used, in points.
|
||||
final double fontSize;
|
||||
|
||||
/// The direction to render this text (left to right or right to left).
|
||||
///
|
||||
/// Normally, leave this as is for most languages.
|
||||
/// For proper fonts of languages like Hebrew or Arabic, replace this with [TextDirection.rtl].
|
||||
final TextDirection textDirection;
|
||||
|
||||
/// The height of line, as a multiple of font size.
|
||||
final double? lineHeight;
|
||||
|
||||
const BaseTextConfig({
|
||||
this.fontSize = 24.0,
|
||||
this.textDirection = TextDirection.ltr,
|
||||
this.lineHeight,
|
||||
});
|
||||
}
|
||||
|
||||
/// An extension of the BaseTextConfig which includes more configs supported by
|
||||
/// TextPaint
|
||||
class TextPaintConfig extends BaseTextConfig {
|
||||
/// The font color to be used.
|
||||
///
|
||||
/// Dart's [Color] class is just a plain wrapper on top of ARGB color (0xAARRGGBB).
|
||||
/// For example,
|
||||
///
|
||||
/// const TextConfig config = TextConfig(color: const Color(0xFF00FF00)); // green
|
||||
/// const TextPaint config = TextPaint(color: const Color(0xFF00FF00)); // green
|
||||
///
|
||||
/// You can also use your Palette class to access colors used in your game.
|
||||
final Color color;
|
||||
@ -44,45 +98,97 @@ class TextConfig {
|
||||
/// The name you choose for the font family can be any name (it's not inside the TTF file and the filename doesn't need to match).
|
||||
final String fontFamily;
|
||||
|
||||
/// The [TextAlign] to be used when creating the [material.TextPainter].
|
||||
/// Creates a constant [TextPaint] with sensible defaults.
|
||||
///
|
||||
/// Beware: it's recommended to leave this with the default value of [TextAlign.left].
|
||||
/// Use the anchor parameter to [render] to specify a proper relative position.
|
||||
final TextAlign textAlign;
|
||||
/// Every parameter can be specified.
|
||||
const TextPaintConfig({
|
||||
this.color = const Color(0xFF000000),
|
||||
this.fontFamily = 'Arial',
|
||||
double fontSize = 24.0,
|
||||
TextDirection textDirection = TextDirection.rtl,
|
||||
double? lineHeight,
|
||||
}) : super(
|
||||
fontSize: fontSize,
|
||||
textDirection: textDirection,
|
||||
lineHeight: lineHeight,
|
||||
);
|
||||
|
||||
/// The direction to render this text (left to right or right to left).
|
||||
/// Creates a new [TextPaintConfig] changing only the [fontSize].
|
||||
///
|
||||
/// Normally, leave this as is for most languages.
|
||||
/// For proper fonts of languages like Hebrew or Arabic, replace this with [TextDirection.rtl].
|
||||
final TextDirection textDirection;
|
||||
/// This does not change the original (as it's immutable).
|
||||
TextPaintConfig withFontSize(double fontSize) {
|
||||
return TextPaintConfig(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
/// The height of line, as a multiple of font size.
|
||||
final double? lineHeight;
|
||||
/// Creates a new [TextPaintConfig] changing only the [color].
|
||||
///
|
||||
/// This does not change the original (as it's immutable).
|
||||
TextPaintConfig withColor(Color color) {
|
||||
return TextPaintConfig(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new [TextPaintConfig] changing only the [fontFamily].
|
||||
///
|
||||
/// This does not change the original (as it's immutable).
|
||||
TextPaintConfig withFontFamily(String fontFamily) {
|
||||
return TextPaintConfig(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new [TextPaintConfig] changing only the [textAlign].
|
||||
///
|
||||
/// This does not change the original (as it's immutable).
|
||||
TextPaintConfig withTextAlign(TextAlign textAlign) {
|
||||
return TextPaintConfig(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new [TextPaintConfig] changing only the [textDirection].
|
||||
///
|
||||
/// This does not change the original (as it's immutable).
|
||||
TextPaintConfig withTextDirection(TextDirection textDirection) {
|
||||
return TextPaintConfig(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A Text Config contains all typographical information required to render texts; i.e., font size and color, family, etc.
|
||||
///
|
||||
/// It does not hold information regarding the position of the text to be render neither the text itself (the string).
|
||||
/// To hold all those information, use the Text component.
|
||||
///
|
||||
/// It is used by [TextComponent].
|
||||
class TextPaint extends TextRenderer<TextPaintConfig> {
|
||||
final MemoryCache<String, material.TextPainter> _textPainterCache =
|
||||
MemoryCache();
|
||||
|
||||
/// Creates a constant [TextConfig] with sensible defaults.
|
||||
///
|
||||
/// Every parameter can be specified.
|
||||
TextConfig({
|
||||
this.fontSize = 24.0,
|
||||
this.color = const Color(0xFF000000),
|
||||
this.fontFamily = 'Arial',
|
||||
this.textAlign = TextAlign.left,
|
||||
this.textDirection = TextDirection.ltr,
|
||||
this.lineHeight,
|
||||
});
|
||||
TextPaint({
|
||||
TextPaintConfig config = const TextPaintConfig(),
|
||||
}) : super(config: config);
|
||||
|
||||
/// Renders a given [text] in a given position [p] using the provided [canvas] and [anchor].
|
||||
///
|
||||
/// It creates a [material.TextPainter] instance using the [toTextPainter] method, and renders it in the given position, considering the [anchor] specified.
|
||||
/// For example, if [Anchor.center] is specified, it's going to be drawn centered around [p].
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// const TextConfig config = TextConfig(fontSize: 48.0, fontFamily: 'Awesome Font');
|
||||
/// config.render(c, Offset(size.width - 10, size.height - 10, anchor: Anchor.bottomRight);
|
||||
@override
|
||||
void render(
|
||||
Canvas canvas,
|
||||
String text,
|
||||
@ -94,13 +200,23 @@ class TextConfig {
|
||||
tp.paint(canvas, translatedPosition.toOffset());
|
||||
}
|
||||
|
||||
@override
|
||||
double measureTextWidth(String text) {
|
||||
return toTextPainter(text).width;
|
||||
}
|
||||
|
||||
@override
|
||||
double measureTextHeight(String text) {
|
||||
return toTextPainter(text).height;
|
||||
}
|
||||
|
||||
/// Returns a [material.TextPainter] that allows for text rendering and size measuring.
|
||||
///
|
||||
/// A [material.TextPainter] has three important properties: paint, width and height (or size).
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// const TextConfig config = TextConfig(fontSize: 48.0, fontFamily: 'Awesome Font');
|
||||
/// const TextPaint config = TextPaint(fontSize: 48.0, fontFamily: 'Awesome Font');
|
||||
/// final tp = config.toTextPainter('Score: $score');
|
||||
/// tp.paint(c, Offset(size.width - p.width - 10, size.height - p.height - 10));
|
||||
///
|
||||
@ -109,10 +225,10 @@ class TextConfig {
|
||||
material.TextPainter toTextPainter(String text) {
|
||||
if (!_textPainterCache.containsKey(text)) {
|
||||
final style = material.TextStyle(
|
||||
color: color,
|
||||
fontSize: fontSize,
|
||||
fontFamily: fontFamily,
|
||||
height: lineHeight,
|
||||
color: config.color,
|
||||
fontSize: config.fontSize,
|
||||
fontFamily: config.fontFamily,
|
||||
height: config.lineHeight,
|
||||
);
|
||||
final span = material.TextSpan(
|
||||
style: style,
|
||||
@ -120,8 +236,7 @@ class TextConfig {
|
||||
);
|
||||
final tp = material.TextPainter(
|
||||
text: span,
|
||||
textAlign: textAlign,
|
||||
textDirection: textDirection,
|
||||
textDirection: config.textDirection,
|
||||
);
|
||||
tp.layout();
|
||||
|
||||
@ -129,69 +244,4 @@ class TextConfig {
|
||||
}
|
||||
return _textPainterCache.getValue(text)!;
|
||||
}
|
||||
|
||||
/// Creates a new [TextConfig] changing only the [fontSize].
|
||||
///
|
||||
/// This does not change the original (as it's immutable).
|
||||
TextConfig withFontSize(double fontSize) {
|
||||
return TextConfig(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
textAlign: textAlign,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new [TextConfig] changing only the [color].
|
||||
///
|
||||
/// This does not change the original (as it's immutable).
|
||||
TextConfig withColor(Color color) {
|
||||
return TextConfig(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
textAlign: textAlign,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new [TextConfig] changing only the [fontFamily].
|
||||
///
|
||||
/// This does not change the original (as it's immutable).
|
||||
TextConfig withFontFamily(String fontFamily) {
|
||||
return TextConfig(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
textAlign: textAlign,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new [TextConfig] changing only the [textAlign].
|
||||
///
|
||||
/// This does not change the original (as it's immutable).
|
||||
TextConfig withTextAlign(TextAlign textAlign) {
|
||||
return TextConfig(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
textAlign: textAlign,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new [TextConfig] changing only the [textDirection].
|
||||
///
|
||||
/// This does not change the original (as it's immutable).
|
||||
TextConfig withTextDirection(TextDirection textDirection) {
|
||||
return TextConfig(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
textAlign: textAlign,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user