mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 10:38:17 +08:00
211 lines
5.3 KiB
Dart
211 lines
5.3 KiB
Dart
import 'dart:async';
|
|
import 'dart:math' as math;
|
|
import 'dart:ui';
|
|
|
|
import 'package:flutter/widgets.dart' hide Image;
|
|
|
|
import '../palette.dart';
|
|
import '../text_config.dart';
|
|
import '../extensions/vector2.dart';
|
|
import 'position_component.dart';
|
|
|
|
class TextBoxConfig {
|
|
final double maxWidth;
|
|
final EdgeInsets margins;
|
|
final double timePerChar;
|
|
final double dismissDelay;
|
|
final bool growingBox;
|
|
|
|
TextBoxConfig({
|
|
this.maxWidth = 200.0,
|
|
this.margins = const EdgeInsets.all(8.0),
|
|
this.timePerChar = 0.0,
|
|
this.dismissDelay = 0.0,
|
|
this.growingBox = false,
|
|
});
|
|
}
|
|
|
|
class TextBoxComponent extends PositionComponent {
|
|
static final Paint _imagePaint = BasicPalette.white.paint
|
|
..filterQuality = FilterQuality.high;
|
|
Vector2 _gameSize = Vector2.zero();
|
|
|
|
final String _text;
|
|
final TextConfig _config;
|
|
final TextBoxConfig _boxConfig;
|
|
|
|
List<String> _lines;
|
|
double _maxLineWidth = 0.0;
|
|
double _lineHeight;
|
|
int _totalLines;
|
|
|
|
double _lifeTime = 0.0;
|
|
Image _cache;
|
|
int _previousChar;
|
|
|
|
String get text => _text;
|
|
|
|
TextConfig get config => _config;
|
|
|
|
TextBoxConfig get boxConfig => _boxConfig;
|
|
|
|
TextBoxComponent(
|
|
String text, {
|
|
TextConfig config,
|
|
TextBoxConfig boxConfig,
|
|
}) : _text = text,
|
|
_boxConfig = boxConfig ?? TextBoxConfig(),
|
|
_config = config ?? TextConfig() {
|
|
_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) {
|
|
if (_lines.isNotEmpty) {
|
|
_lines.last = possibleLine;
|
|
} else {
|
|
_lines.add(possibleLine);
|
|
}
|
|
_updateMaxWidth(painter.width);
|
|
} else {
|
|
_lines.add(word);
|
|
_updateMaxWidth(_config.toTextPainter(word).width);
|
|
}
|
|
});
|
|
_totalLines = _lines.length;
|
|
_lineHeight = lineHeight ?? 0.0;
|
|
}
|
|
|
|
void _updateMaxWidth(double w) {
|
|
if (w > _maxLineWidth) {
|
|
_maxLineWidth = w;
|
|
}
|
|
}
|
|
|
|
double get totalCharTime => _text.length * _boxConfig.timePerChar;
|
|
|
|
bool get finished => _lifeTime > totalCharTime + _boxConfig.dismissDelay;
|
|
|
|
int get currentChar => _boxConfig.timePerChar == 0.0
|
|
? _text.length
|
|
: math.min(_lifeTime ~/ _boxConfig.timePerChar, _text.length);
|
|
|
|
int get currentLine {
|
|
int totalCharCount = 0;
|
|
final int _currentChar = currentChar;
|
|
for (int i = 0; i < _lines.length; i++) {
|
|
totalCharCount += _lines[i].length;
|
|
if (totalCharCount > _currentChar) {
|
|
return i;
|
|
}
|
|
}
|
|
return _lines.length - 1;
|
|
}
|
|
|
|
@override
|
|
Vector2 get size => Vector2(width, height);
|
|
|
|
double getLineWidth(String line, int charCount) {
|
|
return _config
|
|
.toTextPainter(line.substring(0, math.min(charCount, line.length)))
|
|
.width;
|
|
}
|
|
|
|
double _cachedWidth;
|
|
|
|
@override
|
|
double get width {
|
|
if (_cachedWidth != null) {
|
|
return _cachedWidth;
|
|
}
|
|
if (_boxConfig.growingBox) {
|
|
int i = 0;
|
|
int totalCharCount = 0;
|
|
final int _currentChar = currentChar;
|
|
final int _currentLine = currentLine;
|
|
final double textWidth = _lines.sublist(0, _currentLine + 1).map((line) {
|
|
final int charCount =
|
|
(i < _currentLine) ? line.length : (_currentChar - totalCharCount);
|
|
totalCharCount += line.length;
|
|
i++;
|
|
return getLineWidth(line, charCount);
|
|
}).reduce(math.max);
|
|
_cachedWidth = textWidth + _boxConfig.margins.horizontal;
|
|
} else {
|
|
_cachedWidth = _boxConfig.maxWidth + _boxConfig.margins.horizontal;
|
|
}
|
|
return _cachedWidth;
|
|
}
|
|
|
|
@override
|
|
double get height {
|
|
if (_boxConfig.growingBox) {
|
|
return _lineHeight * _lines.length + _boxConfig.margins.vertical;
|
|
} else {
|
|
return _lineHeight * _totalLines + _boxConfig.margins.vertical;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void render(Canvas c) {
|
|
if (_cache == null) {
|
|
return;
|
|
}
|
|
super.render(c);
|
|
c.drawImage(_cache, Offset.zero, _imagePaint);
|
|
}
|
|
|
|
@override
|
|
void onGameResize(Vector2 gameSize) {
|
|
super.onGameResize(gameSize);
|
|
_gameSize = gameSize;
|
|
}
|
|
|
|
Future<Image> _redrawCache() {
|
|
final PictureRecorder recorder = PictureRecorder();
|
|
final Canvas c = Canvas(recorder, _gameSize.toRect());
|
|
_fullRender(c);
|
|
return recorder.endRecording().toImage(width.toInt(), height.toInt());
|
|
}
|
|
|
|
void drawBackground(Canvas c) {}
|
|
|
|
void _fullRender(Canvas c) {
|
|
drawBackground(c);
|
|
|
|
final int _currentLine = currentLine;
|
|
int charCount = 0;
|
|
double dy = _boxConfig.margins.top;
|
|
for (int line = 0; line < _currentLine; line++) {
|
|
charCount += _lines[line].length;
|
|
_drawLine(c, _lines[line], dy);
|
|
dy += _lineHeight;
|
|
}
|
|
final int max =
|
|
math.min(currentChar - charCount, _lines[_currentLine].length);
|
|
_drawLine(c, _lines[_currentLine].substring(0, max), dy);
|
|
}
|
|
|
|
void _drawLine(Canvas c, String line, double dy) {
|
|
_config.toTextPainter(line).paint(c, Offset(_boxConfig.margins.left, dy));
|
|
}
|
|
|
|
void redrawLater() async {
|
|
_cache = await _redrawCache();
|
|
}
|
|
|
|
@override
|
|
void update(double dt) {
|
|
super.update(dt);
|
|
_lifeTime += dt;
|
|
if (_previousChar != currentChar) {
|
|
_cachedWidth = null;
|
|
redrawLater();
|
|
}
|
|
_previousChar = currentChar;
|
|
}
|
|
}
|