1 /** 2 * Copyright © Novelate 2020 3 * License: MIT (https://github.com/Novelate/NovelateEngine/blob/master/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 * Website: https://novelate.com/ 6 * ------ 7 * Novelate is a free and open-source visual novel engine and framework written in the D programming language. 8 * It can be used freely for both personal and commercial projects. 9 * ------ 10 * Module Description: 11 * A timed text component is useful for displaying text that is rendered over time looking like an automated typing animation. 12 */ 13 module novelate.timedtext; 14 15 import novelate.component; 16 import novelate.fonts; 17 import novelate.config; 18 import novelate.textwrap; 19 import novelate.queue; 20 21 /// Alias for delegate handler. 22 private alias _DELEGATE = void delegate(MouseButton button, ref bool stopEvent); 23 24 /// Wrapper around timed text, which is text that is gradually rendered over time. 25 final class TimedText : Component 26 { 27 private: 28 /// The text component. 29 ExternalText _textComponent; 30 /// The original text without wrapping. 31 dstring _originalText; 32 /// The text with wrapping. 33 dstring _text; 34 /// The text count. 35 size_t _textCount; 36 /// The font name. 37 string _fontName; 38 /// The font. 39 ExternalFont _font; 40 /// The font size. 41 size_t _fontSize; 42 /// The box width. 43 size_t _boxWidth; 44 /// Boolean determining whether the text has finished rendering. 45 bool _hasFinished; 46 /// The color. 47 Paint _color; 48 49 public: 50 final: 51 /// Creates a new timed text component. 52 this() 53 { 54 super(); 55 56 globalMouseReleaseTextFinished = new Queue!_DELEGATE; 57 58 _fontName = config.defaultFont; 59 _font = retrieveFont(_fontName, FontStyle.normal); 60 _fontSize = config.defaultFontSize; 61 62 _textComponent = new ExternalText; 63 _textComponent.setFont(_font); 64 _textComponent.setString(""); 65 _textComponent.setCharacterSize(_fontSize); 66 67 _textCount = 1; 68 69 super.globalKeyRelease = (k, ref s) 70 { 71 if (k == KeyboardKey.returnKey || k == KeyboardKey.space) 72 { 73 super.globalMouseRelease(MouseButton.left, s); 74 } 75 }; 76 77 super.globalMouseRelease = (b, ref s) 78 { 79 if (b != MouseButton.left) 80 { 81 return; 82 } 83 84 if (_hasFinished) 85 { 86 if (globalMouseReleaseTextFinished.has) 87 { 88 auto action = globalMouseReleaseTextFinished.dequeue(); 89 90 if (action) 91 { 92 action(b, s); 93 } 94 } 95 } 96 else 97 { 98 if (!_text || !_text.length) 99 { 100 return; 101 } 102 103 _textComponent.setString(_text); 104 _textCount = _text.length; 105 _hasFinished = true; 106 } 107 }; 108 } 109 110 /// A queue of handlers for when the text has finished rendering by mouse release. 111 Queue!_DELEGATE globalMouseReleaseTextFinished; 112 113 @property 114 { 115 /// Gets the text. 116 dstring text() { return _text; } 117 118 /// Sets the text. 119 void text(dstring newText) 120 { 121 _originalText = newText; 122 _text = wrapableText(_originalText, _fontName, _fontSize, _boxWidth); 123 _textCount = 1; 124 _hasFinished = false; 125 } 126 127 /// Gets the font name. 128 string fontName() { return _fontName; } 129 130 /// Sets the font name. 131 void fontName(string newFontName) 132 { 133 _fontName = newFontName; 134 135 _font = retrieveFont(_fontName, FontStyle.normal); 136 _textComponent.setFont(_font); 137 } 138 139 /// Gets the font size. 140 size_t fontSize() { return _fontSize; } 141 142 /// Sets the font size. 143 void fontSize(size_t newFontSize) 144 { 145 _fontSize = newFontSize; 146 147 _textComponent.setCharacterSize(_fontSize); 148 } 149 150 /// Gets the color. 151 Paint color() { return _color; } 152 153 /// Sets the color. 154 void color(Paint newColor) 155 { 156 _color = newColor; 157 158 _textComponent.setColor(_color); 159 } 160 } 161 162 /// Fps counter for when the text is rendering. 163 private size_t _fpsCounter = 0; 164 165 /// See: Component.render() 166 override void render(ExternalWindow window) 167 { 168 if (!_hasFinished) 169 { 170 _fpsCounter++; 171 172 if (_fpsCounter >= 2) 173 { 174 _fpsCounter = 0; 175 176 if (_text && _text.length) 177 { 178 _textComponent.setString(_text[0 .. _textCount]); 179 180 _textCount++; 181 182 _hasFinished = _textCount >= _text.length; 183 184 if (_hasFinished) 185 { 186 _textComponent.setString(_text); 187 } 188 } 189 } 190 } 191 192 _textComponent.draw(window); 193 } 194 195 /// See: Component.refresh() 196 override void refresh(size_t width, size_t height) 197 { 198 _boxWidth = width; 199 _boxWidth -= ((config.defaultDialoguePadding * 2) + (config.defaultDialogueMargin * 2)); 200 201 size_t boxHeight; 202 if (width == 800) 203 { 204 boxHeight = config.defaultDialogueHeight800; 205 } 206 else if (width == 1024) 207 { 208 boxHeight = config.defaultDialogueHeight1024; 209 } 210 else if (width == 1280) 211 { 212 boxHeight = config.defaultDialogueHeight1280; 213 } 214 215 boxHeight -= cast(size_t)(cast(double)config.defaultDialogueNameFontSize * 1.5); 216 217 _textComponent.position = FloatVector(config.defaultDialogueMargin + config.defaultDialoguePadding, ((height + config.defaultDialoguePadding) - boxHeight)); 218 219 _text = wrapableText(_originalText, _fontName, _fontSize, _boxWidth); 220 221 if (_text && _text.length) 222 { 223 if (_hasFinished) 224 { 225 _textComponent.setString(_text); 226 } 227 else 228 { 229 _textComponent.setString(_text[0 .. _textCount]); 230 } 231 } 232 233 updateInternalPosition(_textComponent.position); 234 } 235 236 /// See: Component.updateSize() 237 override void updateSize() 238 { 239 } 240 241 /// See: Component.updatePosition() 242 override void updatePosition() 243 { 244 _textComponent.position = super.position; 245 } 246 }