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 }