]> git.localhorst.tv Git - blank.git/blob - src/ui/widgets.cpp
make gcc nag more
[blank.git] / src / ui / widgets.cpp
1 #include "FixedText.hpp"
2 #include "MessageBox.hpp"
3 #include "Progress.hpp"
4 #include "Text.hpp"
5 #include "TextInput.hpp"
6
7 #include "../graphics/Font.hpp"
8 #include "../graphics/Viewport.hpp"
9
10 #include <cstdio>
11 #include <cstring>
12 #include <limits>
13
14 using namespace std;
15
16
17 namespace blank {
18
19 MessageBox::MessageBox(const Font &f)
20 : font(f)
21 , lines()
22 , max_lines(10)
23 , pos(0.0f)
24 , adv(0.0f, font.LineSkip(), 0.0f)
25 , bg(1.0f, 1.0f, 1.0f, 0.0f)
26 , fg(1.0f, 1.0f, 1.0f, 1.0f)
27 , grav(Gravity::NORTH_WEST)
28 , dirty(true) {
29
30 }
31
32 void MessageBox::Position(const glm::vec3 &p, Gravity g) noexcept {
33         pos = p;
34         grav = g;
35         if (get_y(g) == Align::END) {
36                 adv.y = -font.LineSkip();
37         } else {
38                 adv.y = font.LineSkip();
39         }
40         for (Text &txt : lines) {
41                 txt.Pivot(g);
42         }
43         dirty = true;
44 }
45
46 void MessageBox::PushLine(const char *text) {
47         lines.emplace_front();
48         Text &txt = lines.front();
49         txt.Set(font, text);
50         txt.Pivot(grav);
51
52         while (lines.size() > max_lines) {
53                 lines.pop_back();
54         }
55         dirty = true;
56 }
57
58 namespace {
59
60 PrimitiveMesh::Buffer bg_buf;
61
62 }
63
64 void MessageBox::Recalc() {
65         size = glm::vec2(0.0f, 0.0f);
66         for (const Text &line : lines) {
67                 size.x = max(size.x, line.Size().x);
68                 size.y += line.Size().y;
69         }
70         bg_buf.FillRect(size.x, size.y, bg, align(grav, size));
71         bg_mesh.Update(bg_buf);
72         bg_buf.Clear();
73         dirty = false;
74 }
75
76 void MessageBox::Render(Viewport &viewport) noexcept {
77         viewport.SetCursor(pos, grav);
78         if (bg.a > numeric_limits<float>::epsilon()) {
79                 if (dirty) {
80                         Recalc();
81                 }
82                 PlainColor &prog = viewport.HUDColorProgram();
83                 prog.SetM(viewport.Cursor());
84                 bg_mesh.DrawTriangles();
85                 viewport.MoveCursor(glm::vec3(0.0f, 0.0f, -1.0f));
86         }
87         BlendedSprite &prog = viewport.SpriteProgram();
88         prog.SetBG(glm::vec4(0.0f));
89         prog.SetFG(glm::vec4(fg) * (1.0f / 255.0f));
90         for (Text &txt : lines) {
91                 prog.SetM(viewport.Cursor());
92                 txt.Render(viewport);
93                 viewport.MoveCursor(adv);
94         }
95 }
96
97
98 Progress::Progress(Font &font) noexcept
99 : font(font)
100 , text()
101 , tpl("%d/%d (%d%%)") {
102
103 }
104
105 namespace {
106
107 char buf[128] = { '\0' };
108
109 }
110
111 void Progress::Update(int current, int total) {
112         snprintf(buf, sizeof(buf), tpl, current, total, current * 100 / total);
113         text.Set(font, buf);
114 }
115
116 void Progress::Render(Viewport &viewport) noexcept {
117         text.Render(viewport);
118 }
119
120
121 Text::Text() noexcept
122 : tex()
123 , sprite()
124 , size(0.0f)
125 , pivot(Gravity::NORTH_WEST)
126 , dirty(false) {
127
128 }
129
130 FixedText::FixedText() noexcept
131 : Text()
132 , bg(1.0f, 1.0f, 1.0f, 0.0f)
133 , fg(1.0f, 1.0f, 1.0f, 1.0f)
134 , pos(0.0f)
135 , grav(Gravity::NORTH_WEST)
136 , visible(false) {
137
138 }
139
140 void Text::Set(const Font &font, const char *text) {
141         font.Render(text, tex);
142         size = font.TextSize(text);
143         dirty = true;
144 }
145
146 namespace {
147
148 SpriteMesh::Buffer sprite_buf;
149
150 }
151
152 void Text::Update() {
153         sprite_buf.LoadRect(size.x, size.y, align(pivot, size));
154         sprite.Update(sprite_buf);
155         dirty = false;
156 }
157
158 void FixedText::Render(Viewport &viewport) noexcept {
159         BlendedSprite &prog = viewport.SpriteProgram();
160         viewport.SetCursor(pos, grav);
161         prog.SetM(viewport.Cursor());
162         prog.SetBG(bg);
163         prog.SetFG(fg);
164         Text::Render(viewport);
165 }
166
167 void Text::Render(Viewport &viewport) noexcept {
168         if (dirty) {
169                 Update();
170         }
171         BlendedSprite &prog = viewport.SpriteProgram();
172         prog.SetTexture(tex);
173         sprite.Draw();
174 }
175
176
177 TextInput::TextInput(const Font &font)
178 : font(font)
179 , input()
180 , cursor(0)
181 , text()
182 , bg_mesh()
183 , cursor_mesh()
184 , bg(1.0f, 1.0f, 1.0f, 0.0f)
185 , fg(1.0f, 1.0f, 1.0f, 1.0f)
186 , position(0.0f)
187 , size(font.LineSkip())
188 , gravity(Gravity::NORTH_WEST)
189 , active(false)
190 , dirty_box(true)
191 , dirty_cursor(true)
192 , dirty_text(true) {
193
194 }
195
196 void TextInput::Focus(Viewport &viewport) noexcept {
197         SDL_StartTextInput();
198         active = true;
199
200         glm::vec2 p(viewport.GetPosition(glm::vec2(position), gravity));
201         SDL_Rect rect;
202         rect.x = p.x;
203         rect.y = p.y;
204         rect.w = size.x;
205         rect.h = size.y;
206         SDL_SetTextInputRect(&rect);
207 }
208
209 void TextInput::Blur() noexcept {
210         SDL_StopTextInput();
211         active = false;
212 }
213
214 void TextInput::Clear() noexcept {
215         input.clear();
216         cursor = 0;
217         dirty_text = true;
218 }
219
220 void TextInput::Backspace() noexcept {
221         string::size_type previous(cursor);
222         MoveBackward();
223         input.erase(cursor, previous - cursor);
224         dirty_text = true;
225 }
226
227 void TextInput::Delete() noexcept {
228         string::size_type previous(cursor);
229         MoveForward();
230         input.erase(previous, cursor - previous);
231         cursor = previous;
232         dirty_text = true;
233 }
234
235 void TextInput::MoveBegin() noexcept {
236         cursor = 0;
237 }
238
239 void TextInput::MoveBackward() noexcept {
240         if (AtBegin()) return;
241         --cursor;
242         while (cursor > 0 && (input[cursor] & 0xC0) == 0x80) {
243                 --cursor;
244         }
245 }
246
247 void TextInput::MoveForward() noexcept {
248         if (AtEnd()) return;
249         ++cursor;
250         while (cursor <= input.size() && (input[cursor] & 0xC0) == 0x80) {
251                 ++cursor;
252         }
253 }
254
255 void TextInput::MoveEnd() noexcept {
256         cursor = input.size();
257 }
258
259 void TextInput::Insert(const char *str) {
260         size_t len = strlen(str);
261         input.insert(cursor, str, len);
262         cursor += len;
263         dirty_text = true;
264 }
265
266 bool TextInput::AtBegin() const noexcept {
267         return cursor == 0;
268 }
269
270 bool TextInput::AtEnd() const noexcept {
271         return cursor == input.size();
272 }
273
274 void TextInput::Position(const glm::vec3 &p, Gravity g, Gravity pv) noexcept {
275         position = p;
276         gravity = g;
277         text.Pivot(pv);
278         dirty_box = true;
279 }
280
281 void TextInput::Width(float w) noexcept {
282         size.x = w;
283         dirty_box = true;
284 }
285
286 void TextInput::Handle(const SDL_TextInputEvent &e) {
287         Insert(e.text);
288 }
289
290 void TextInput::Handle(const SDL_TextEditingEvent &) {
291
292 }
293
294 void TextInput::Refresh() {
295         if (dirty_box) {
296                 bg_buf.FillRect(size.x, size.y, bg, align(gravity, size));
297                 bg_mesh.Update(bg_buf);
298                 bg_buf.Clear();
299                 dirty_box = false;
300         }
301         if (dirty_cursor) {
302                 bg_buf.Reserve(2, 2);
303                 bg_buf.vertices.emplace_back(0.0f, 0.0f, 0.0f);
304                 bg_buf.vertices.emplace_back(0.0f, float(font.LineSkip()), 0.0f);
305                 bg_buf.colors.resize(2, fg);
306                 bg_buf.indices.push_back(0);
307                 bg_buf.indices.push_back(1);
308                 cursor_mesh.Update(bg_buf);
309                 bg_buf.Clear();
310                 dirty_cursor = false;
311         }
312         if (dirty_text) {
313                 if (!input.empty()) {
314                         text.Set(font, input.c_str());
315                 }
316                 dirty_text = false;
317         }
318 }
319
320 void TextInput::Render(Viewport &viewport) {
321         Refresh();
322         viewport.SetCursor(position, gravity);
323         if (bg.a > numeric_limits<float>::epsilon()) {
324                 viewport.EnableAlphaBlending();
325                 PlainColor &prog = viewport.HUDColorProgram();
326                 prog.SetM(viewport.Cursor());
327                 bg_mesh.DrawTriangles();
328                 viewport.MoveCursor(glm::vec3(0.0f, 0.0f, -1.0f));
329         }
330         if (!input.empty()) {
331                 BlendedSprite &prog = viewport.SpriteProgram();
332                 prog.SetBG(glm::vec4(0.0f));
333                 prog.SetFG(glm::vec4(fg) * (1.0f / 255.0f));
334                 prog.SetM(viewport.Cursor());
335                 text.Render(viewport);
336         }
337         if (active) {
338                 glm::vec2 offset(0.0f);
339                 if (input.empty() || AtBegin()) {
340                         // a okay
341                         offset = -align(text.Pivot(), glm::vec2(0.0f, font.LineSkip()));
342                 } else if (AtEnd()) {
343                         offset = -align(text.Pivot(), text.Size(), glm::vec2(-text.Size().x, 0.0f));
344                 } else {
345                         offset = -align(text.Pivot(), text.Size(), glm::vec2(-font.TextSize(input.substr(0, cursor).c_str()).x, 0.0f));
346                 }
347                 viewport.MoveCursor(glm::vec3(offset, -1.0f));
348                 PlainColor &prog = viewport.HUDColorProgram();
349                 prog.SetM(viewport.Cursor());
350                 cursor_mesh.DrawLines();
351         }
352 }
353
354 }