./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 / 211
0.0%
Branches:
0 / 299
0.0%
Functions:
0 / 16
0.0%

libs/mrgui/src/eu/mrgui/widgets.cc
Line Branch Exec Source
1 #include "eu/mrgui/widgets.h"
2
3 #include <SDL_timer.h>
4
5 #include "eu/render/canvas.h"
6 #include "eu/render/font.h"
7
8 namespace eu::mrgui
9 {
10 namespace
11 {
12 /// djb2 initial hash
13 constexpr u32 initial_hash = 5381;
14
15 /// djb2 hash function
16 ✗ u32 hash(const unsigned char* str, std::size_t length, u32 seed)
17 {
18 ✗ u32 hash = seed;
19
20
0/2
✗ Branch 4 → 3 not taken.
✗ Branch 4 → 5 not taken.
✗ for (std::size_t index = 0; index < length; index += 1)
21 {
22 ✗ auto c = str[index];
23 ✗ hash = ((hash << 5) + hash) + c;
24 }
25
26 ✗ return hash;
27 }
28 }
29
30 ✗ [[nodiscard]] u32 IdStack::get(const std::string& str) const
31 {
32 ✗ return hash(reinterpret_cast<const unsigned char*>(str.data()), str.size(), last_id());
33 }
34
35 ✗ [[nodiscard]] u32 IdStack::get(int id) const
36 {
37 ✗ return hash(reinterpret_cast<unsigned char*>(&id), sizeof(int), last_id());
38 }
39 ✗ void IdStack::push(const std::string& str)
40 {
41
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ stack.push_back(get(str));
42 ✗ }
43 ✗ void IdStack::push(int id)
44 {
45
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ stack.push_back(get(id));
46 ✗ }
47 ✗ void IdStack::pop()
48 {
49 ✗ stack.pop_back();
50 ✗ }
51 ✗ [[nodiscard]] u32 IdStack::last_id() const
52 {
53
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ return stack.empty() ? initial_hash : *stack.rbegin();
54 }
55
56
57
58 ✗ void UiState::begin()
59 {
60 ✗ hot_item = std::nullopt;
61 ✗ }
62
63 ✗ void UiState::end()
64 {
65
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 7 not taken.
✗ if (mousedown)
66 {
67
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 9 not taken.
✗ if (active_item == std::nullopt)
68 {
69 ✗ active_item = std::nullopt;
70 ✗ active_item_locked = true;
71 }
72 }
73 else
74 {
75 ✗ active_item = std::nullopt;
76 ✗ active_item_locked = false;
77 }
78
79
0/6
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 14 not taken.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
✗ if (key && key->key == SDLK_TAB)
80 {
81 ✗ kbd_item = std::nullopt;
82 }
83 ✗ key = std::nullopt;
84 ✗ keychar = std::nullopt;
85 ✗ }
86
87 ✗ [[nodiscard]] bool UiState::is_active_free() const
88 {
89
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
✗ return active_item_locked == false && active_item.has_value() == false;
90 }
91
92 // Simple button IMGUI widget
93 ✗ bool button_logic(u32 id, const Rect& rect, UiState& uistate)
94 {
95
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 13 not taken.
✗ if (is_within(uistate.mouse, rect))
96 {
97 ✗ uistate.hot_item = id;
98
0/6
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 9 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 13 not taken.
✗ if (uistate.is_active_free() && uistate.mousedown)
99 {
100 ✗ uistate.active_item = id;
101 }
102 }
103
104
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 17 not taken.
✗ if (uistate.kbd_item == std::nullopt)
105 {
106 ✗ uistate.kbd_item = id;
107 }
108
109 // keyboard interaction
110
0/2
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 31 not taken.
✗ if (uistate.kbd_item == id)
111 {
112
0/2
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 31 not taken.
✗ if (uistate.key.has_value())
113 {
114 ✗ const auto k = *uistate.key;
115 ✗ uistate.key = std::nullopt;
116
0/3
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 28 not taken.
✗ Branch 23 → 29 not taken.
✗ switch (k.key)
117 {
118 ✗ case SDLK_TAB:
119 ✗ uistate.kbd_item = std::nullopt;
120
0/2
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
✗ if ((k.mod & SDL_KMOD_SHIFT) != 0)
121 {
122 ✗ uistate.kbd_item = uistate.last_widget;
123 }
124 ✗ break;
125 ✗ case SDLK_RETURN:
126 ✗ return true;
127 ✗ default:
128 // ignore
129 ✗ break;
130 }
131 }
132 }
133
134 ✗ uistate.last_widget = id;
135
136 // If button is hot and active, but mouse button is not
137 // down, the user must have clicked the button.
138
0/2
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 38 not taken.
✗ if (uistate.mousedown == false &&
139
0/6
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 38 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
✗ uistate.hot_item == id &&
140 ✗ uistate.active_item == id)
141 {
142 ✗ return true;
143 }
144
145 // Otherwise, no clicky.
146 ✗ return false;
147 }
148
149 ✗ bool basic_button(u32 id, const Rect& rect, UiState& uistate, render::SpriteBatch* batch)
150 {
151 ✗ const auto ret = button_logic(id, rect, uistate);
152
153
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 14 not taken.
✗ if (uistate.kbd_item == id)
154 {
155 ✗ render::Quad{
156 .tint = colors::red_vermillion
157
0/6
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 44 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 43 not taken.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 43 not taken.
✗ }.draw(batch, rect.with_inset(Lrtb{ -4 }).with_offset({ 8, -8 }));
158 }
159
160 // shadow
161 ✗ render::Quad{
162 .tint = colors::black
163
0/4
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 48 not taken.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 48 not taken.
✗ }.draw(batch, rect.with_offset({ 8.0f, -8.0f }));
164
165
0/2
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 36 not taken.
✗ if (uistate.hot_item == id)
166 {
167
0/2
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 31 not taken.
✗ if (uistate.active_item == id)
168 {
169 // Button is both 'hot' and 'active'
170
0/4
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 51 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 51 not taken.
✗ render::Quad{ .tint = colors::white }.draw(batch, rect.with_offset({ 2.0f, -2.0f }));
171 }
172 else
173 {
174 // Button is merely 'hot'
175
0/2
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 54 not taken.
✗ render::Quad{ .tint = colors::white }.draw(batch, rect);
176 }
177 }
178 else
179 {
180 // button is not hot, but it may be active
181
0/2
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 55 not taken.
✗ render::Quad{ .tint = colors::orange }.draw(batch, rect);
182 // todo(Gustav): add grays
183 }
184
185 ✗ return ret;
186 }
187
188 ✗ bool button_texture(u32 id, const Rect& rect, UiState& uistate, render::SpriteBatch* batch, const render::Texture2d* texture, const Rect& active, const Rect& hot, const Rect& normal)
189 {
190 ✗ const auto ret = button_logic(id, rect, uistate);
191
192
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 14 not taken.
✗ if (uistate.kbd_item == id)
193 {
194 // keep?
195 ✗ render::Quad{
196 .tint = colors::red_vermillion
197
0/6
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 39 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 38 not taken.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 38 not taken.
✗ }.draw(batch, rect.with_inset(Lrtb{ -4 }).with_offset({ 8, -8 }));
198 }
199
200
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 30 not taken.
✗ if (uistate.hot_item == id)
201 {
202
0/2
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 24 not taken.
✗ if (uistate.active_item == id)
203 {
204 // Button is both 'hot' and 'active'
205
0/2
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 43 not taken.
✗ render::Quad{ .texture = texture, .texturecoord = active }.draw(batch, rect);
206 }
207 else
208 {
209 // Button is merely 'hot'
210
0/2
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 44 not taken.
✗ render::Quad{ .texture = texture, .texturecoord = hot }.draw(batch, rect);
211 }
212 }
213 else
214 {
215 // button is not hot, but it may be active
216
0/2
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 45 not taken.
✗ render::Quad{ .texture = texture, .texturecoord = normal }.draw(batch, rect);
217 // todo(Gustav): add grays
218 }
219
220 ✗ return ret;
221 }
222
223 ✗ bool slider(u32 id, float* val, const Rect& rect, UiState& uistate, render::SpriteBatch* batch)
224 {
225 ✗ const auto grab_size = 16.0f;
226
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 82 not taken.
✗ const auto inner = rect.with_inset(Lrtb{ 3.0f });
227
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 92 not taken.
✗ const auto avail = inner.get_size().x - grab_size;
228 ✗ const auto pos = avail * *val;
229
230
0/6
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 84 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 84 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 83 not taken.
✗ const auto grabber_rect = Rect::from_size({ grab_size, inner.get_size().y }).with_bottom_left_at({ inner.left + pos, inner.bottom });
231
232
0/4
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 92 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 21 not taken.
✗ if (is_within(uistate.mouse, inner))
233 {
234 ✗ uistate.hot_item = id;
235
0/6
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 17 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 21 not taken.
✗ if (uistate.is_active_free() && uistate.mousedown)
236 {
237 ✗ uistate.active_item = id;
238 }
239 }
240
241
0/2
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 25 not taken.
✗ if (uistate.kbd_item == std::nullopt)
242 {
243 ✗ uistate.kbd_item = id;
244 }
245
246
0/2
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 34 not taken.
✗ if (uistate.kbd_item == id)
247 {
248
0/4
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 86 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 86 not taken.
✗ render::Quad{ .tint = colors::red_vermillion }.draw(batch, rect.with_inset(Lrtb{ -6 }));
249 }
250
251
0/2
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 89 not taken.
✗ render::Quad{ .tint = colors::orange }.draw(batch, rect);
252
0/6
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 45 not taken.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 90 not taken.
✗ render::Quad{ .tint = uistate.active_item == id && uistate.hot_item == id ? colors::white : colors::green_bluish }.draw(batch, grabber_rect);
253
0/2
✗ Branch 50 → 51 not taken.
✗ Branch 50 → 71 not taken.
✗ if (uistate.kbd_item == id)
254 {
255
0/2
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 71 not taken.
✗ if (uistate.key.has_value())
256 {
257 ✗ const auto k = *uistate.key;
258 ✗ uistate.key = std::nullopt;
259 ✗ constexpr float change = 0.1f;
260
261
0/4
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 60 not taken.
✗ Branch 55 → 64 not taken.
✗ Branch 55 → 68 not taken.
✗ switch (k.key)
262 {
263 ✗ case SDLK_TAB:
264 ✗ uistate.kbd_item = std::nullopt;
265
0/2
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
✗ if ((k.mod & SDL_KMOD_SHIFT) != 0)
266 {
267 ✗ uistate.kbd_item = uistate.last_widget;
268 }
269 ✗ break;
270 ✗ case SDLK_LEFT:
271
0/2
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 63 not taken.
✗ if (*val > 0)
272 {
273 ✗ *val = std::max(0.0f, *val - change);
274 ✗ return true;
275 }
276 ✗ break;
277 ✗ case SDLK_RIGHT:
278
0/2
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 67 not taken.
✗ if (*val < 1)
279 {
280 ✗ *val = std::min(1.0f, *val + change);
281 ✗ return true;
282 }
283 ✗ break;
284 ✗ default:
285 // ignore
286 ✗ break;
287 }
288 }
289 }
290
291 ✗ uistate.last_widget = id;
292
293
0/2
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 79 not taken.
✗ if (uistate.active_item == id)
294 {
295
0/2
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 91 not taken.
✗ const auto new_range = to_01(inner, uistate.mouse);
296 ✗ const auto new_value = keep_within01(new_range.x);
297
0/2
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 78 not taken.
✗ if (*val != new_value)
298 {
299 ✗ *val = new_value;
300 ✗ return true;
301 }
302 }
303
304 ✗ return false;
305 }
306
307
308 ✗ void draw_text(render::SpriteBatch* batch, render::DrawableFont* font, const std::string& str, float size, const v2& pos, const Rgb& color)
309 {
310
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 11 not taken.
✗ render::DrawableText text{ font };
311
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 9 not taken.
✗ text.set_text(str);
312
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 9 not taken.
✗ text.set_size(size);
313
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 9 not taken.
✗ text.compile();
314
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 9 not taken.
✗ text.draw(batch, pos, color, color);
315 ✗ }
316
317 ✗ bool textfield(u32 id, std::string* val, render::DrawableFont* font, float size, const v2& pos, UiState& uistate, render::SpriteBatch* batch)
318 {
319 using namespace std;
320 ✗ bool changed = false;
321
322 ✗ auto& text_state = uistate.text_state;
323
324 // Layout
325
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 174 not taken.
✗ render::DrawableText text{ font };
326
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 172 not taken.
✗ text.set_text(*val);
327
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 172 not taken.
✗ text.set_size(size);
328
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 172 not taken.
✗ text.compile();
329
0/6
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 145 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 145 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 144 not taken.
✗ const auto rect = text.get_extents().with_offset(pos).with_inset(Lrtb{ -6 });
330
331
0/4
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 172 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 21 not taken.
✗ if (is_within(uistate.mouse, rect)) {
332 ✗ uistate.hot_item = id;
333
0/6
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 17 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 21 not taken.
✗ if (uistate.active_item == std::nullopt && uistate.mousedown)
334 {
335 ✗ uistate.active_item = id;
336
0/2
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 172 not taken.
✗ text_state.focus(val);
337 }
338 }
339
0/2
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 25 not taken.
✗ if (uistate.kbd_item == std::nullopt)
340 {
341 ✗ uistate.kbd_item = id;
342 }
343
344
0/2
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 34 not taken.
✗ if (uistate.kbd_item == id) {
345
0/4
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 147 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 147 not taken.
✗ render::Quad{ .tint = colors::red_vermillion }.draw(batch, rect.with_inset(Lrtb{ -6 }));
346 }
347
0/6
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 38 not taken.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 39 not taken.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 48 not taken.
✗ if (uistate.active_item == id || uistate.hot_item == id) {
348
0/4
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 150 not taken.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 150 not taken.
✗ render::Quad{ .tint = colors::white }.draw(batch, rect.with_inset(Lrtb{ -6 }));
349 }
350 else {
351
0/4
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 153 not taken.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 153 not taken.
✗ render::Quad{ .tint = colors::green_bluish }.draw(batch, rect.with_inset(Lrtb{ -6 }));
352 }
353
354 // Handle input
355
0/2
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 92 not taken.
✗ if (uistate.kbd_item == id) {
356
0/2
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 80 not taken.
✗ if (uistate.key.has_value()) {
357
0/2
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 156 not taken.
✗ auto k = uistate.key.value();
358 ✗ const auto shift = k.mod & SDL_KMOD_SHIFT;
359
0/8
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 63 not taken.
✗ Branch 60 → 65 not taken.
✗ Branch 60 → 67 not taken.
✗ Branch 60 → 69 not taken.
✗ Branch 60 → 71 not taken.
✗ Branch 60 → 73 not taken.
✗ Branch 60 → 78 not taken.
✗ switch (k.key) {
360
0/2
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 156 not taken.
✗ case SDLK_LEFT: text_state.onKeyLeft(shift != 0, val); changed = true; break;
361
0/2
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 156 not taken.
✗ case SDLK_RIGHT: text_state.onKeyRight(shift != 0, val); changed = true; break;
362
0/2
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 156 not taken.
✗ case SDLK_HOME: text_state.onKeyLineStart(shift != 0, val); changed = true; break;
363
0/2
✗ Branch 67 → 68 not taken.
✗ Branch 67 → 156 not taken.
✗ case SDLK_END: text_state.onKeyLineEnd(shift != 0, val); changed = true; break;
364
0/2
✗ Branch 69 → 70 not taken.
✗ Branch 69 → 156 not taken.
✗ case SDLK_BACKSPACE: text_state.onKeyBackspace(shift != 0, val); changed = true; break;
365
0/2
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 156 not taken.
✗ case SDLK_DELETE: text_state.onKeyDelete(shift != 0, val); changed = true; break;
366 ✗ case SDLK_RETURN: // ignore
367 case SDLK_TAB:
368 ✗ uistate.kbd_item = std::nullopt;
369
0/2
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
✗ if (shift != 0)
370 {
371 ✗ uistate.kbd_item = uistate.last_widget;
372 }
373 ✗ uistate.key = std::nullopt;
374 ✗ break;
375 ✗ default: break;
376 }
377 }
378
0/8
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 87 not taken.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 87 not taken.
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 87 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 92 not taken.
✗ if (uistate.keychar.has_value() && *uistate.keychar >= 32 && *uistate.keychar < 127) {
379 ✗ const char c = *uistate.keychar;
380
0/2
✗ Branch 90 → 91 not taken.
✗ Branch 90 → 172 not taken.
✗ text_state.onChar(c, val);
381 ✗ changed = true;
382 }
383 }
384
385 // Mouse click to set cursor
386
0/8
✗ Branch 92 → 93 not taken.
✗ Branch 92 → 98 not taken.
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 98 not taken.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 98 not taken.
✗ Branch 99 → 100 not taken.
✗ Branch 99 → 103 not taken.
✗ if (uistate.mousedown == false && uistate.hot_item == id && uistate.active_item == id) {
387 // Map mouse x to character index
388
0/2
✗ Branch 100 → 101 not taken.
✗ Branch 100 → 172 not taken.
✗ text_state.click(val, uistate.mouse.x - rect.left, uistate.mouse.y - rect.top);
389 ✗ uistate.kbd_item = id;
390 }
391
392 ✗ uistate.last_widget = id;
393
394 static int last_cursor = -10;
395 ✗ const auto current_cursor = text_state.state.cursor;
396
0/2
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 110 not taken.
✗ if (last_cursor != current_cursor)
397 {
398
0/6
✗ Branch 105 → 106 not taken.
✗ Branch 105 → 172 not taken.
✗ Branch 106 → 107 not taken.
✗ Branch 106 → 159 not taken.
✗ Branch 107 → 108 not taken.
✗ Branch 107 → 157 not taken.
✗ LOG_INFO("Cursor position: {}", current_cursor);
399 ✗ last_cursor = current_cursor;
400 }
401
402 // Draw text
403
0/2
✗ Branch 110 → 111 not taken.
✗ Branch 110 → 172 not taken.
✗ text.draw(batch, pos, colors::black, colors::black);
404
405 // Draw cursor
406
0/2
✗ Branch 112 → 113 not taken.
✗ Branch 112 → 141 not taken.
✗ if (uistate.kbd_item == id) {
407
0/2
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 172 not taken.
✗ const auto is_cursor_blink_visible = (SDL_GetTicks() >> 8) & 1;
408
0/2
✗ Branch 114 → 115 not taken.
✗ Branch 114 → 141 not taken.
✗ if (is_cursor_blink_visible != 0) {
409 // Compute cursor x
410 ✗ float cursor_x = rect.left;
411
0/6
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 120 not taken.
✗ Branch 118 → 119 not taken.
✗ Branch 118 → 120 not taken.
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 132 not taken.
✗ if (text_state.state.cursor > 0 && std::cmp_less_equal(text_state.state.cursor, val->size())) {
412
0/2
✗ Branch 122 → 123 not taken.
✗ Branch 122 → 166 not taken.
✗ const std::string substr = val->substr(0, text_state.state.cursor);
413
0/2
✗ Branch 123 → 124 not taken.
✗ Branch 123 → 164 not taken.
✗ render::DrawableText t{ font };
414
0/2
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 162 not taken.
✗ t.set_text(substr);
415
0/2
✗ Branch 125 → 126 not taken.
✗ Branch 125 → 162 not taken.
✗ t.set_size(size);
416
0/2
✗ Branch 126 → 127 not taken.
✗ Branch 126 → 162 not taken.
✗ t.compile();
417
0/4
✗ Branch 127 → 128 not taken.
✗ Branch 127 → 161 not taken.
✗ Branch 128 → 129 not taken.
✗ Branch 128 → 161 not taken.
✗ cursor_x = rect.left + t.get_extents().get_size().x;
418 ✗ }
419 ✗ const auto ascent = font->ascent * size;
420 ✗ const auto descent = font->descent * size;
421
0/6
✗ Branch 136 → 137 not taken.
✗ Branch 136 → 168 not taken.
✗ Branch 138 → 139 not taken.
✗ Branch 138 → 167 not taken.
✗ Branch 139 → 140 not taken.
✗ Branch 139 → 167 not taken.
✗ render::Quad{ .tint = colors::black }.draw(batch, Rect::from_size({ 1, ascent + -descent }).with_bottom_left_at({ cursor_x, pos.y + descent }));
422 }
423 }
424
425 ✗ return changed;
426 ✗ }
427
428
429 }
430