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 }