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.ui.timedtext; 14 15 import novelate.ui.component; 16 import novelate.ui.textwrap; 17 import novelate.fonts; 18 import novelate.config; 19 import novelate.queue; 20 import novelate.buildstate; 21 22 /// Alias for delegate handler. 23 private alias _DELEGATE = void delegate(MouseButton button, ref bool stopEvent); 24 25 /// Wrapper around timed text, which is text that is gradually rendered over time. 26 final class TimedText : Component 27 { 28 private: 29 /// The text component. 30 ExternalText _textComponent; 31 /// The original text without wrapping. 32 dstring _originalText; 33 /// The text with wrapping. 34 dstring _text; 35 /// The text count. 36 size_t _textCount; 37 /// The original text speed. 38 size_t _originalTextSpeed; 39 /// The text speed. 40 size_t _textSpeed; 41 /// The font name. 42 string _fontName; 43 /// The font. 44 ExternalFont _font; 45 /// The font size. 46 size_t _fontSize; 47 /// The box width. 48 size_t _boxWidth; 49 /// Boolean determining whether the text has finished rendering. 50 bool _hasFinished; 51 /// The color. 52 Paint _color; 53 /// Boolean determining whether shift is held or not. 54 bool _shiftHeld; 55 56 public: 57 final: 58 /// Creates a new timed text component. 59 this() 60 { 61 super(); 62 63 globalMouseReleaseTextFinished = new Queue!_DELEGATE; 64 65 _fontName = config.defaultFont; 66 _font = retrieveFont(_fontName, FontStyle.normal); 67 _fontSize = config.defaultFontSize; 68 69 _textComponent = new ExternalText; 70 _textComponent.setFont(_font); 71 _textComponent.setString(""); 72 _textComponent.setCharacterSize(_fontSize); 73 74 _textCount = 1; 75 _textSpeed = 1; 76 77 super.globalKeyPress = (k, ref s) 78 { 79 if (k == KeyboardKey.LShift) 80 { 81 _originalTextSpeed = _textSpeed; 82 83 _textSpeed *= 3; 84 85 _shiftHeld = true; 86 } 87 }; 88 89 super.globalKeyRelease = (k, ref s) 90 { 91 if (k == KeyboardKey.returnKey || k == KeyboardKey.space) 92 { 93 super.globalMouseRelease(MouseButton.left, s); 94 } 95 else if (k == KeyboardKey.LShift) 96 { 97 _textSpeed = _originalTextSpeed; 98 99 _shiftHeld = false; 100 } 101 }; 102 103 super.globalMouseRelease = (b, ref s) 104 { 105 if (b != MouseButton.left) 106 { 107 return; 108 } 109 110 if (_hasFinished) 111 { 112 if (globalMouseReleaseTextFinished.has) 113 { 114 auto action = globalMouseReleaseTextFinished.dequeue(); 115 116 if (action) 117 { 118 action(b, s); 119 } 120 } 121 } 122 else 123 { 124 if (!_text || !_text.length) 125 { 126 return; 127 } 128 129 _textComponent.setString(_text); 130 _textCount = _text.length; 131 _hasFinished = true; 132 } 133 }; 134 } 135 136 /// A queue of handlers for when the text has finished rendering by mouse release. 137 Queue!_DELEGATE globalMouseReleaseTextFinished; 138 139 @property 140 { 141 /// Gets the text. 142 dstring text() { return _text; } 143 144 /// Sets the text. 145 void text(dstring newText) 146 { 147 _originalText = newText; 148 149 _text = wrapableText(_originalText, _fontName, _fontSize, _boxWidth); 150 151 _textCount = 1; 152 _hasFinished = false; 153 } 154 155 /// Gets the font name. 156 string fontName() { return _fontName; } 157 158 /// Sets the font name. 159 void fontName(string newFontName) 160 { 161 _fontName = newFontName; 162 163 _font = retrieveFont(_fontName, FontStyle.normal); 164 _textComponent.setFont(_font); 165 } 166 167 /// Gets the font size. 168 size_t fontSize() { return _fontSize; } 169 170 /// Sets the font size. 171 void fontSize(size_t newFontSize) 172 { 173 _fontSize = newFontSize; 174 175 _textComponent.setCharacterSize(_fontSize); 176 } 177 178 /// Gets the color. 179 Paint color() { return _color; } 180 181 /// Sets the color. 182 void color(Paint newColor) 183 { 184 _color = newColor; 185 186 _textComponent.setColor(_color); 187 } 188 189 /// Gets the text speed. 190 size_t textSpeed() { return _textSpeed; } 191 192 /// Sets the text speed. 193 void textSpeed(size_t newTextSpeed) 194 { 195 _textSpeed = newTextSpeed; 196 _originalTextSpeed = _textSpeed; 197 198 if (_shiftHeld) 199 { 200 _textSpeed *= 3; 201 } 202 } 203 } 204 205 /// Fps counter for when the text is rendering. 206 private size_t _fpsCounter = 0; 207 208 /// See: Component.render() 209 override void render(ExternalWindow window) 210 { 211 if (!_hasFinished) 212 { 213 _fpsCounter++; 214 215 if (_fpsCounter >= 2) 216 { 217 _fpsCounter = 0; 218 219 if (_text && _text.length) 220 { 221 if (_textCount > _text.length) 222 { 223 _textCount = _text.length; 224 } 225 226 _textComponent.setString(_text[0 .. _textCount]); 227 228 _textCount += _textSpeed; 229 230 _hasFinished = _textCount >= _text.length; 231 232 if (_hasFinished) 233 { 234 _textComponent.setString(_text); 235 } 236 } 237 } 238 } 239 240 _textComponent.draw(window); 241 } 242 243 /// See: Component.refresh() 244 override void refresh(size_t width, size_t height) 245 { 246 _boxWidth = width; 247 _boxWidth -= ((config.defaultDialoguePadding * 2) + (config.defaultDialogueMargin * 2)); 248 249 size_t boxHeight; 250 if (width == 800) 251 { 252 boxHeight = config.defaultDialogueHeight800; 253 } 254 else if (width == 1024) 255 { 256 boxHeight = config.defaultDialogueHeight1024; 257 } 258 else if (width == 1280) 259 { 260 boxHeight = config.defaultDialogueHeight1280; 261 } 262 263 boxHeight -= cast(size_t)(cast(double)config.defaultDialogueNameFontSize * 1.5); 264 265 _textComponent.position = FloatVector(config.defaultDialogueMargin + config.defaultDialoguePadding, ((height + config.defaultDialoguePadding) - boxHeight)); 266 267 _text = wrapableText(_originalText, _fontName, _fontSize, _boxWidth); 268 269 if (_text && _text.length) 270 { 271 if (_hasFinished) 272 { 273 _textComponent.setString(_text); 274 } 275 else 276 { 277 if (_textCount > _text.length) 278 { 279 _textCount = _text.length; 280 } 281 282 _textComponent.setString(_text[0 .. _textCount]); 283 } 284 } 285 286 updateInternalPosition(_textComponent.position); 287 } 288 289 /// See: Component.updateSize() 290 override void updateSize() 291 { 292 } 293 294 /// See: Component.updatePosition() 295 override void updatePosition() 296 { 297 _textComponent.position = super.position; 298 } 299 300 static if (isManualMemory) 301 { 302 /// See: Component.clean() 303 override void clean() 304 { 305 if (_textComponent) 306 { 307 _textComponent.clean(); 308 } 309 } 310 } 311 }