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:
Erick
2021-04-29 09:24:17 -03:00
committed by GitHub
parent 91864d0b88
commit d9e0acd3ff
16 changed files with 266 additions and 175 deletions

View File

@ -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);
}

View File

@ -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

View File

@ -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),

View File

@ -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));
}
}
}

View File

@ -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),
);

View File

@ -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),

View File

@ -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;

View File

@ -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),

View File

@ -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

View File

@ -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';

View File

@ -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';

View File

@ -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.
///

View File

@ -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),

View File

@ -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 {

View File

@ -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());
}
}

View File

@ -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,
);
}
}