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