LCOV - code coverage report
Current view: top level - lib/src/components - text_box_component.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 102 0.0 %
Date: 2021-08-10 15:50:53 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'dart:math' as math;
       3             : import 'dart:ui';
       4             : 
       5             : import 'package:flutter/widgets.dart' hide Image;
       6             : 
       7             : import '../extensions/vector2.dart';
       8             : import '../palette.dart';
       9             : import '../text.dart';
      10             : import 'position_component.dart';
      11             : 
      12             : /// A set of configurations for the [TextBoxComponent] itself (as opposed to
      13             : /// the [TextRenderer], that contains the configuration for how to render the
      14             : /// text only (font size, color, family, etc)).
      15             : class TextBoxConfig {
      16             :   /// Max width this paragraph can take. Lines will be broken trying to respect
      17             :   /// word boundaries in as many lines as necessary.
      18             :   final double maxWidth;
      19             : 
      20             :   /// Margins of the text box w.r.t the [TextBoxComponent.size].
      21             :   final EdgeInsets margins;
      22             : 
      23             :   /// Defaults to 0. If not zero the characters will appear one by one giving
      24             :   /// a typying effect to the text box, and this will be the delay in seconds
      25             :   /// between each char.
      26             :   final double timePerChar;
      27             : 
      28             :   /// Defaults to 9. If not zero, this component will disapear this amount of
      29             :   /// seconds after being completed (if [timePerChar] is set) or after first
      30             :   /// appearing (otherwise).
      31             :   final double dismissDelay;
      32             : 
      33             :   /// Only relevant if [timePerChar] is set. If true, the box will start with
      34             :   /// the size to fit the first character and grow as more lines are typed.
      35             :   /// If false, the box will start with the full necessary size from the
      36             :   /// beginning (both width and height).
      37             :   final bool growingBox;
      38             : 
      39           0 :   TextBoxConfig({
      40             :     this.maxWidth = 200.0,
      41             :     this.margins = const EdgeInsets.all(8.0),
      42             :     this.timePerChar = 0.0,
      43             :     this.dismissDelay = 0.0,
      44             :     this.growingBox = false,
      45             :   });
      46             : }
      47             : 
      48             : class TextBoxComponent<T extends TextRenderer> extends PositionComponent {
      49           0 :   static final Paint _imagePaint = BasicPalette.white.paint()
      50             :     ..filterQuality = FilterQuality.high;
      51             : 
      52             :   final String _text;
      53             :   final T _textRenderer;
      54             :   final TextBoxConfig _boxConfig;
      55             : 
      56             :   late List<String> _lines;
      57             :   double _maxLineWidth = 0.0;
      58             :   late double _lineHeight;
      59             :   late int _totalLines;
      60             : 
      61             :   double _lifeTime = 0.0;
      62             :   Image? _cache;
      63             :   int? _previousChar;
      64             : 
      65           0 :   String get text => _text;
      66             : 
      67           0 :   TextRenderer get renderer => _textRenderer;
      68             : 
      69           0 :   TextBoxConfig get boxConfig => _boxConfig;
      70             : 
      71           0 :   TextBoxComponent(
      72             :     String text, {
      73             :     T? textRenderer,
      74             :     TextBoxConfig? boxConfig,
      75             :     Vector2? position,
      76             :     Vector2? size,
      77             :     int? priority,
      78             :   })  : _text = text,
      79           0 :         _boxConfig = boxConfig ?? TextBoxConfig(),
      80           0 :         _textRenderer = textRenderer ?? TextRenderer.createDefault<T>(),
      81           0 :         super(position: position, size: size, priority: priority) {
      82           0 :     _lines = [];
      83             :     double? lineHeight;
      84           0 :     text.split(' ').forEach((word) {
      85           0 :       final possibleLine = _lines.isEmpty ? word : '${_lines.last} $word';
      86           0 :       lineHeight ??= _textRenderer.measureTextHeight(possibleLine);
      87             : 
      88           0 :       final textWidth = _textRenderer.measureTextWidth(possibleLine);
      89           0 :       if (textWidth <= _boxConfig.maxWidth - _boxConfig.margins.horizontal) {
      90           0 :         if (_lines.isNotEmpty) {
      91           0 :           _lines.last = possibleLine;
      92             :         } else {
      93           0 :           _lines.add(possibleLine);
      94             :         }
      95           0 :         _updateMaxWidth(textWidth);
      96             :       } else {
      97           0 :         _lines.add(word);
      98           0 :         _updateMaxWidth(textWidth);
      99             :       }
     100             :     });
     101           0 :     _totalLines = _lines.length;
     102           0 :     _lineHeight = lineHeight ?? 0.0;
     103             :   }
     104             : 
     105           0 :   void _updateMaxWidth(double w) {
     106           0 :     if (w > _maxLineWidth) {
     107           0 :       _maxLineWidth = w;
     108             :     }
     109             :   }
     110             : 
     111           0 :   double get totalCharTime => _text.length * _boxConfig.timePerChar;
     112             : 
     113           0 :   bool get finished => _lifeTime > totalCharTime + _boxConfig.dismissDelay;
     114             : 
     115           0 :   int get _actualTextLength {
     116           0 :     return _lines.map((e) => e.length).fold(0, (p, c) => p + c);
     117             :   }
     118             : 
     119           0 :   int get currentChar => _boxConfig.timePerChar == 0.0
     120           0 :       ? _actualTextLength
     121           0 :       : math.min(_lifeTime ~/ _boxConfig.timePerChar, _actualTextLength);
     122             : 
     123           0 :   int get currentLine {
     124             :     var totalCharCount = 0;
     125           0 :     final _currentChar = currentChar;
     126           0 :     for (var i = 0; i < _lines.length; i++) {
     127           0 :       totalCharCount += _lines[i].length;
     128           0 :       if (totalCharCount > _currentChar) {
     129             :         return i;
     130             :       }
     131             :     }
     132           0 :     return _lines.length - 1;
     133             :   }
     134             : 
     135           0 :   @override
     136           0 :   Vector2 get size => Vector2(width, height);
     137             : 
     138           0 :   double getLineWidth(String line, int charCount) {
     139           0 :     return _textRenderer.measureTextWidth(
     140           0 :       line.substring(0, math.min(charCount, line.length)),
     141             :     );
     142             :   }
     143             : 
     144             :   double? _cachedWidth;
     145             : 
     146           0 :   @override
     147             :   double get width {
     148           0 :     if (_cachedWidth != null) {
     149           0 :       return _cachedWidth!;
     150             :     }
     151           0 :     if (_boxConfig.growingBox) {
     152             :       var i = 0;
     153             :       var totalCharCount = 0;
     154           0 :       final _currentChar = currentChar;
     155           0 :       final _currentLine = currentLine;
     156           0 :       final textWidth = _lines.sublist(0, _currentLine + 1).map((line) {
     157             :         final charCount =
     158           0 :             (i < _currentLine) ? line.length : (_currentChar - totalCharCount);
     159           0 :         totalCharCount += line.length;
     160           0 :         i++;
     161           0 :         return getLineWidth(line, charCount);
     162           0 :       }).reduce(math.max);
     163           0 :       _cachedWidth = textWidth + _boxConfig.margins.horizontal;
     164             :     } else {
     165           0 :       _cachedWidth = _boxConfig.maxWidth + _boxConfig.margins.horizontal;
     166             :     }
     167           0 :     return _cachedWidth!;
     168             :   }
     169             : 
     170           0 :   @override
     171             :   double get height {
     172           0 :     if (_boxConfig.growingBox) {
     173           0 :       return _lineHeight * _lines.length + _boxConfig.margins.vertical;
     174             :     } else {
     175           0 :       return _lineHeight * _totalLines + _boxConfig.margins.vertical;
     176             :     }
     177             :   }
     178             : 
     179           0 :   @override
     180             :   void render(Canvas c) {
     181           0 :     if (_cache == null) {
     182             :       return;
     183             :     }
     184           0 :     super.render(c);
     185           0 :     final devicePixelRatio = window.devicePixelRatio;
     186           0 :     c.save();
     187           0 :     c.scale(1 / devicePixelRatio);
     188           0 :     c.drawImage(_cache!, Offset.zero, _imagePaint);
     189           0 :     c.restore();
     190             :   }
     191             : 
     192           0 :   Future<Image> _redrawCache() {
     193           0 :     final devicePixelRatio = window.devicePixelRatio;
     194           0 :     final recorder = PictureRecorder();
     195           0 :     final c = Canvas(recorder, size.toRect());
     196           0 :     c.scale(devicePixelRatio);
     197           0 :     _fullRender(c);
     198           0 :     return recorder.endRecording().toImage(
     199           0 :           (width * devicePixelRatio).ceil(),
     200           0 :           (height * devicePixelRatio).ceil(),
     201             :         );
     202             :   }
     203             : 
     204             :   /// Override this method to provide a custom background to the text box.
     205           0 :   void drawBackground(Canvas c) {}
     206             : 
     207           0 :   void _fullRender(Canvas c) {
     208           0 :     drawBackground(c);
     209             : 
     210           0 :     final _currentLine = currentLine;
     211             :     var charCount = 0;
     212           0 :     var dy = _boxConfig.margins.top;
     213           0 :     for (var line = 0; line < _currentLine; line++) {
     214           0 :       charCount += _lines[line].length;
     215           0 :       _drawLine(c, _lines[line], dy);
     216           0 :       dy += _lineHeight;
     217             :     }
     218           0 :     final max = math.min(currentChar - charCount, _lines[_currentLine].length);
     219           0 :     _drawLine(c, _lines[_currentLine].substring(0, max), dy);
     220             :   }
     221             : 
     222           0 :   void _drawLine(Canvas c, String line, double dy) {
     223           0 :     _textRenderer.render(c, line, Vector2(_boxConfig.margins.left, dy));
     224             :   }
     225             : 
     226           0 :   void redrawLater() async {
     227           0 :     _cache = await _redrawCache();
     228             :   }
     229             : 
     230           0 :   @override
     231             :   void update(double dt) {
     232           0 :     super.update(dt);
     233           0 :     _lifeTime += dt;
     234           0 :     if (_previousChar != currentChar) {
     235           0 :       _cachedWidth = null;
     236           0 :       redrawLater();
     237             :     }
     238           0 :     _previousChar = currentChar;
     239             :   }
     240             : }

Generated by: LCOV version 1.15