ASW Lib
A.D.S. Games SDL Wrapper Library. A library targeted at Allegro4 users who want to switch to SDL3 and use modern c++.
Loading...
Searching...
No Matches
root.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
10#include "./asw/modules/util.h"
11
12namespace {
13void play_ui_sound(const asw::Sample& sample)
14{
15 if (sample != nullptr) {
17 options.bus = asw::sound::Bus::Ui;
18 asw::sound::play(sample, options);
19 }
20}
21
22// Whether a widget and every parent above it are enabled. A disabled widget
23// disables its children, as in keyboard navigation.
25{
26 for (; w != nullptr; w = w->parent) {
27 if (!w->enabled) {
28 return false;
29 }
30 }
31 return true;
32}
33
34// The widget a pointer acts on: the nearest focusable widget at or above
35// the hit, so a label inside a button still presses the button. Nothing
36// inside a disabled parent reacts to the pointer.
38{
39 for (auto* w = hit; w != nullptr; w = w->parent) {
40 if (w->focusable) {
41 return enabled_in_tree(w->parent) ? w : nullptr;
42 }
43 }
44 return nullptr;
45}
46} // namespace
47
49{
50 // See through and screen sized, so a UI can sit on top of a scene
51 root.bg = { 0, 0, 0, 0 };
52 _modals.bg = { 0, 0, 0, 0 };
54}
55
57{
58 const auto screen = asw::display::get_logical_size();
59 if (screen.x > 0 && screen.y > 0
60 && (root.transform.size.x != static_cast<float>(screen.x)
61 || root.transform.size.y != static_cast<float>(screen.y))) {
62 root.transform
63 = { 0.0F, 0.0F, static_cast<float>(screen.x), static_cast<float>(screen.y) };
64 }
65 _modals.transform = root.transform;
66}
67
68void asw::ui::Root::set_size(float w, float h)
69{
70 auto r = root.transform;
71 r.size.x = w;
72 r.size.y = h;
73 root.transform = r;
74 _modals.transform = r;
75 auto_size = false;
76}
77
78bool asw::ui::Root::attached(const Widget* w) const
79{
80 // Removed widgets have no parent, so the walk stops short of root
81 while (w != nullptr && w != &root && w != &_modals) {
82 w = w->parent;
83 }
84 return w != nullptr;
85}
86
87bool asw::ui::Root::in_root(const Widget* w) const
88{
89 while (w != nullptr && w != &root) {
90 w = w->parent;
91 }
92 return w != nullptr;
93}
94
96{
97 for (auto it = _modals.children().rbegin(); it != _modals.children().rend(); ++it) {
98 if ((*it)->visible) {
99 return dynamic_cast<Modal*>(it->get());
100 }
101 }
102 return nullptr;
103}
104
106{
107 if (Modal* modal = top_modal(); modal != nullptr) {
108 return *modal;
109 }
110 return root;
111}
112
114{
115 return top_modal() != nullptr;
116}
117
119{
120 // Remember focus from before the first modal only. A modal opened as
121 // another closes keeps that focus, not a widget of the closed modal.
122 if (_focus_before_modal == nullptr) {
123 if (Widget* f = ctx.focus.focused(); in_root(f)) {
124 _focus_before_modal = f;
125 }
126 }
127}
128
130{
131 while (Modal* modal = top_modal()) {
132 modal->close();
133 }
134 validate();
135}
136
138{
139 for (auto const& c : _modals.children()) {
140 if (!c->visible) {
141 continue;
142 }
143 c->measure(ctx);
144 auto& t = c->transform;
145 t.position = { _modals.transform.position.x + ((_modals.transform.size.x - t.size.x) / 2.0F),
146 _modals.transform.position.y + ((_modals.transform.size.y - t.size.y) / 2.0F) };
147 c->layout(ctx);
148 }
149}
150
152{
153 Modal* modal = top_modal();
154
155 // A new top modal: focus its first widget, so a controller can answer it
156 if (modal != nullptr && modal != _focused_modal) {
157 _focused_modal = modal;
158 ctx.focus.focus_start(ctx);
159 return;
160 }
161
162 // The last modal closed: focus goes back where it was. Forget it even if
163 // the modal closed before it took focus, so the next modal remembers anew.
164 if (modal == nullptr) {
165 Widget* back = std::exchange(_focus_before_modal, nullptr);
166 if (_focused_modal == nullptr) {
167 return;
168 }
169 _focused_modal = nullptr;
170 if (back != nullptr && attached(back) && back->focusable && back->visible
171 && enabled_in_tree(back)) {
172 ctx.focus.set_focus(ctx, back);
173 }
174 }
175}
176
178{
179 // Widgets can be removed at any time, often from inside a callback. They
180 // stay alive until the next update, so a pointer to one can still be read
181 // here and dropped.
182 if (ctx.hover != nullptr && !attached(ctx.hover)) {
183 ctx.hover->_hovered = false;
184 ctx.hover = nullptr;
185 }
186 if (ctx.pointer_capture != nullptr && !attached(ctx.pointer_capture)) {
187 ctx.pointer_capture->_pressed = false;
188 ctx.pointer_capture->_captured = false;
189 ctx.pointer_capture = nullptr;
190 }
191 if (ctx.focus.default_focus != nullptr && !attached(ctx.focus.default_focus)) {
192 ctx.focus.default_focus = nullptr;
193 }
194 if (_focus_before_modal != nullptr && !attached(_focus_before_modal)) {
195 _focus_before_modal = nullptr;
196 }
197
198 // Also drops focus from a removed widget, telling it so. While a modal is
199 // open only its widgets can have focus.
200 ctx.focus.rebuild(ctx, active_root());
201 update_modal_focus();
202}
203
205{
206 w._removed.clear();
207 for (auto const& c : w.children()) {
208 free_removed(*c);
209 }
210}
211
213{
214 if (!w.visible) {
215 return nullptr;
216 }
217
218 // traverse children in reverse for top-most
219 for (int i = (int)w.children().size() - 1; i >= 0; --i) {
220 auto const& c = w.children()[i];
221 if (!c->visible) {
222 continue;
223 }
224 if (!c->transform.contains(pointer_pos)) {
225 continue;
226 }
227 if (auto* hit = hit_test(*c, pointer_pos)) {
228 return hit;
229 }
230 return c.get();
231 }
232 return w.transform.contains(pointer_pos) ? &w : nullptr;
233}
234
235bool asw::ui::Root::bubble(Widget* target, const UIEvent& e)
236{
237 // No handler runs, so the tree cannot change and needs no validate. The
238 // pointer moving over empty space takes this path every frame.
239 if (target == nullptr) {
240 return false;
241 }
242
243 // Take the chain first, a handler that removes its own widget cuts it off
244 // from its parent
245 std::vector<Widget*> chain;
246 for (Widget* w = target; w != nullptr; w = w->parent) {
247 chain.push_back(w);
248 }
249
250 // Let target handle; if not handled, bubble up to parents. Removed
251 // widgets are still alive but take no more events.
252 bool handled = false;
253 for (Widget* w : chain) {
254 if (!attached(w)) {
255 continue;
256 }
257 if (w->on_event(ctx, e)) {
258 handled = true;
259 break;
260 }
261 }
262
263 // Handlers may have changed the tree
264 validate();
265 return handled;
266}
267
269{
270 Widget* target = nullptr;
271
272 if (ctx.pointer_capture != nullptr) {
273 target = ctx.pointer_capture;
274 } else {
275 target = hit_test(active_root(), e.pointer_pos);
276 }
277
278 return bubble(target, e);
279}
280
282{
283 Widget* f = ctx.focus.focused();
284 if (f == nullptr) {
285 return false;
286 }
287 return bubble(f, e);
288}
289
291{
292 if (enabled_in_tree(&w)) {
293 play_ui_sound(ctx.theme.sound_activate);
294 w.activate(ctx);
295 _used = true;
296 }
297
298 // The callback may have changed the tree
299 validate();
300}
301
303{
304 using namespace asw::input;
305
306 const auto& mouse = get_mouse();
307 const bool moved = mouse.change.x != 0.0F || mouse.change.y != 0.0F;
308 Widget& layer = active_root();
309 Widget* hit = hit_test(layer, mouse.position);
310 const bool over_widget = hit != nullptr && hit != &root;
311
312 // Hover every frame, so it is right after a scene change or when widgets
313 // move under a still mouse
314 Widget* new_hover = interactive(hit);
315 if (new_hover != ctx.hover) {
316 if (ctx.hover != nullptr) {
317 Widget* old_hover = ctx.hover;
318 old_hover->_hovered = false;
319 ctx.hover = nullptr;
320 const UIEvent leave { .type = UIEvent::Type::PointerLeave,
321 .pointer_pos = mouse.position };
322 old_hover->on_event(ctx, leave);
323
324 // The handler may have changed the tree, new_hover included
325 validate();
326 if (!attached(new_hover)) {
327 new_hover = nullptr;
328 }
329 }
330 ctx.hover = new_hover;
331 if (ctx.hover != nullptr) {
332 ctx.hover->_hovered = true;
333 const UIEvent enter { .type = UIEvent::Type::PointerEnter,
334 .pointer_pos = mouse.position };
335 ctx.hover->on_event(ctx, enter);
336
337 // The handler may have changed the tree
338 validate();
339 }
340 }
341
342 if (ctx.pointer_capture != nullptr || over_widget) {
343 _used = true;
344 }
345
346 if (moved) {
347 const UIEvent e { .type = UIEvent::Type::PointerMove, .pointer_pos = mouse.position };
348 dispatch_pointer(e);
349 ctx.show_focus = false;
350 }
351
352 // One event per button, carrying the real button so widgets can ignore
353 // the ones they do not use
354 for (const auto button : { MouseButton::Left, MouseButton::Right, MouseButton::Middle }) {
355 const bool left = button == MouseButton::Left;
356
357 if (get_mouse_button_down(button)) {
358 ctx.show_focus = false;
359
360 // Press, capture and focus the widget under the pointer
361 if (left && ctx.hover != nullptr && enabled_in_tree(ctx.hover)) {
362 ctx.pointer_capture = ctx.hover;
363 ctx.hover->_pressed = true;
364 ctx.hover->_captured = true;
365 ctx.focus.set_focus(ctx, ctx.hover);
366 }
367
368 const UIEvent e { .type = UIEvent::Type::PointerDown,
369 .pointer_pos = mouse.position,
370 .mouse_button = button };
371 dispatch_pointer(e);
372 }
373
374 if (get_mouse_button_up(button)) {
375 ctx.show_focus = false;
376
377 const UIEvent e { .type = UIEvent::Type::PointerUp,
378 .pointer_pos = mouse.position,
379 .mouse_button = button };
380 dispatch_pointer(e);
381
382 // Read after the dispatch, a handler may have removed the pressed
383 // widget and validate then dropped it, so it is not clicked
384 Widget* pressed = left ? ctx.pointer_capture : nullptr;
385
386 // Click: released over the widget that was pressed
387 if (pressed != nullptr) {
388 const bool over = pressed == ctx.hover;
389 pressed->_pressed = false;
390 pressed->_captured = false;
391 ctx.pointer_capture = nullptr;
392 if (over) {
393 activate(*pressed);
394 }
395 }
396 }
397 }
398
399 // Pressed look only while the pointer is still over the pressed widget
400 if (ctx.pointer_capture != nullptr) {
401 ctx.pointer_capture->_pressed = ctx.pointer_capture == ctx.hover;
402 }
403}
404
406{
407 using namespace asw::input;
408
409 // --- Text Input ---
410 bool typed = false;
411 if (!input::get_text_input().empty()) {
412 const UIEvent ti { .type = UIEvent::Type::TextInput, .text = input::get_text_input() };
413 if (dispatch_to_focused(ti)) {
414 _used = true;
415 typed = true;
416 }
417 }
418
419 // --- Focus Events ---
420 const auto& nav = ctx.navigation;
421 Widget* const focused_before = ctx.focus.focused();
422
423 // An action when one is named, else the built in key. Keys that typed
424 // text into the focused widget this frame do not also navigate, e.g.
425 // Space bound to activate or letters bound to move.
426 const auto pressed = [typed](const std::string& action, Key key) {
427 if (typed) {
428 return false;
429 }
430 return action.empty() ? get_key_down(key) : get_action_down(action);
431 };
432
433 // Like pressed, but a held built in key repeats, as in a text field or menu
434 const auto repeating = [typed](const std::string& action, Key key) {
435 if (typed) {
436 return false;
437 }
438 return action.empty() ? get_key_repeat(key) : get_action_down(action);
439 };
440
441 const auto shift = get_key(Key::LShift) || get_key(Key::RShift);
442
443 // Global focus handling first (keyboard-first UX)
444 const bool next = pressed(nav.next, Key::Tab);
445 const bool prev = !typed && !nav.prev.empty() && get_action_down(nav.prev);
446 if (next || prev) {
447 if (prev || shift) {
448 ctx.focus.focus_prev(ctx);
449 } else {
450 ctx.focus.focus_next(ctx);
451 }
452
453 ctx.show_focus = true;
454 _used = true;
455 }
456
457 // Directions: dispatch KeyDown to focused widget first, fall back to focus navigation
458 const auto direction = [&](const std::string& action, Key key, int dx, int dy) {
459 if (!repeating(action, key)) {
460 return;
461 }
462 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = key };
463 if (!dispatch_to_focused(e)) {
464 ctx.focus.focus_dir(ctx, dx, dy);
465 }
466 ctx.show_focus = true;
467 _used = true;
468 };
469 direction(nav.up, Key::Up, 0, -1);
470 direction(nav.down, Key::Down, 0, +1);
471 direction(nav.left, Key::Left, -1, 0);
472 direction(nav.right, Key::Right, +1, 0);
473
474 if (ctx.focus.focused() != focused_before) {
475 play_ui_sound(ctx.theme.sound_move);
476 }
477
478 // Editing keys dispatched to focused widget. Held keys repeat.
479 for (const auto key : { Key::Backspace, Key::Delete, Key::Home, Key::End }) {
480 if (get_key_repeat(key)) {
481 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = key };
482 if (dispatch_to_focused(e)) {
483 _used = true;
484 }
485 }
486 }
487
488 // Activate the focused widget. The first press only shows where focus is.
489 const bool activate_pressed = nav.activate.empty()
490 ? pressed({}, Key::Return) || pressed({}, Key::Space)
491 : pressed(nav.activate, Key::Return);
492 if (activate_pressed) {
493 if (ctx.focus.focus_start(ctx)) {
494 ctx.show_focus = true;
495 _used = true;
496 } else if (Widget* f = ctx.focus.focused(); f != nullptr) {
497 ctx.show_focus = true;
498 activate(*f);
499 }
500 }
501
502 // Back goes to the focused widget, then the top modal, then on_back
503 if (pressed(nav.back, Key::Escape)) {
504 const UIEvent b { .type = UIEvent::Type::Back };
505 if (dispatch_to_focused(b)) {
506 _used = true;
507 } else if (Modal* modal = top_modal(); modal != nullptr) {
508 if (modal->close_on_back) {
509 modal->close();
510 }
511 _used = true;
512 validate();
513 } else if (on_back) {
514 on_back();
515 _used = true;
516 validate();
517 }
518 }
519}
520
522{
523 _used = false;
524
525 if (auto_size) {
526 fit_to_screen();
527 }
528
529 // Catch tree changes made since the last update, then free the widgets
530 // removed since, nothing points to them any more
531 validate();
532 free_removed(root);
533 free_removed(_modals);
534
535 // Arrange
536 root.measure(ctx);
537 root.layout(ctx);
538 layout_modals();
539
540 update_pointer();
541 update_keys();
542 update_toasts();
543
544 // A modal takes all input while it is open
545 if (has_modal()) {
546 _used = true;
547 }
548
549 return _used;
550}
551
552void asw::ui::Root::toast(const std::string& text)
553{
554 toast(Toast { .text = text });
555}
556
558{
559 _toasts.push_back({ .toast = std::move(message), .age = 0.0F });
560}
561
563{
564 _toasts.clear();
565}
566
568{
569 const auto now = std::chrono::steady_clock::now();
570 float dt = 0.0F;
571 if (_last_update) {
572 // Capped, so a pause such as a loading screen does not skip messages
573 dt = std::min(std::chrono::duration<float>(now - *_last_update).count(), 0.25F);
574 }
575 _last_update = now;
576
577 const auto& style = ctx.theme.toast;
578 std::size_t shown = 0;
579 for (auto& t : _toasts) {
580 if (shown++ >= style.max_visible) {
581 break;
582 }
583 t.age += dt;
584 }
585
586 std::erase_if(_toasts, [&style](const ShownToast& t) {
587 return t.age >= (t.toast.seconds > 0.0F ? t.toast.seconds : style.seconds);
588 });
589}
590
592{
593 const auto& style = ctx.theme.toast;
594 const auto& font = ctx.theme.font;
595 if (font == nullptr) {
596 return;
597 }
598
599 const auto faded = [](asw::Color c, float alpha) {
600 c.a = static_cast<uint8_t>(static_cast<float>(c.a) * alpha);
601 return c;
602 };
603
604 const auto& area = root.transform;
605 const bool from_bottom = style.anchor == Anchor::Bottom || style.anchor == Anchor::BottomLeft
606 || style.anchor == Anchor::BottomRight;
607 const bool left_side = style.anchor == Anchor::TopLeft || style.anchor == Anchor::BottomLeft;
608 const bool right_side = style.anchor == Anchor::TopRight || style.anchor == Anchor::BottomRight;
609
610 float y = from_bottom ? area.position.y + area.size.y - style.margin : area.position.y + style.margin;
611 std::size_t shown = 0;
612 for (const auto& [t, age] : _toasts) {
613 if (shown++ >= style.max_visible) {
614 break;
615 }
616
617 const float seconds = t.seconds > 0.0F ? t.seconds : style.seconds;
618 const float left = seconds - age;
619 const float alpha = style.fade_seconds > 0.0F && left < style.fade_seconds
620 ? std::max(0.0F, left / style.fade_seconds)
621 : 1.0F;
622
623 const auto text_size = asw::util::get_text_size(font, t.text);
624 const float text_w = static_cast<float>(text_size.x);
625 const float text_h = static_cast<float>(text_size.y);
626 const float icon = t.icon != nullptr ? text_h : 0.0F;
627 const float icon_gap = t.icon != nullptr ? style.padding / 2.0F : 0.0F;
628
629 const float w = text_w + icon + icon_gap + (style.padding * 2.0F);
630 const float h = text_h + (style.padding * 2.0F);
631 float x = area.position.x + ((area.size.x - w) / 2.0F);
632 if (left_side) {
633 x = area.position.x + style.margin;
634 } else if (right_side) {
635 x = area.position.x + area.size.x - w - style.margin;
636 }
637 if (from_bottom) {
638 y -= h;
639 }
640 const asw::Quad<float> box { { x, y }, { w, h } };
641
642 if (style.bg.a > 0) {
643 asw::draw::rect_fill(box, faded(style.bg, alpha));
644 }
645 if (style.border_width > 0.0F && style.border.a > 0) {
646 asw::draw::rect(box, faded(style.border, alpha), style.border_width);
647 }
648 if (t.icon != nullptr) {
649 asw::draw::stretch_sprite(t.icon, { { x + style.padding, y + style.padding }, { icon, icon } });
650 }
651 asw::draw::text(font, t.text, { x + style.padding + icon + icon_gap, y + style.padding },
652 faded(t.color.value_or(style.text), alpha));
653
654 y = from_bottom ? y - style.gap : y + h + style.gap;
655 }
656}
657
658void asw::ui::Root::focus(Widget& w, bool show)
659{
660 ctx.focus.set_focus(ctx, &w);
661 ctx.show_focus = show;
662}
663
665{
666 ctx.focus.set_focus(ctx, nullptr);
667 ctx.show_focus = false;
668}
669
671{
672 root.draw(ctx);
673
674 // Modals, with the screen dimmed under the top one only, so a modal
675 // below it looks inactive too
676 if (Modal* top = top_modal(); top != nullptr) {
677 for (auto const& c : _modals.children()) {
678 if (!c->visible) {
679 continue;
680 }
681 if (c.get() == top) {
682 if (const auto dim = top->get_style(ctx).dim; dim.a > 0) {
683 asw::draw::rect_fill(_modals.transform, dim);
684 }
685 }
686 c->draw(ctx);
687 }
688 }
689
690 // Focus ring on top of everything, unless the focused widget was removed
691 // since the last update
692 Widget* f = ctx.focus.focused();
693 if (ctx.show_focus && f != nullptr && f->visible && f->focus_ring && attached(f)) {
694 draw_focus_ring(ctx.theme.focus_ring, f->transform);
695 }
696
697 // Messages over everything
698 draw_toasts();
699}
Action binding system for the ASW library.
A 2D rectangle in space.
Definition geometry.h:504
bool contains(const Vec2< T > &point) const
Check if a point is inside the rectangle. The left and top edges are inside, the right and bottom edg...
Definition geometry.h:574
A 2D vector in space.
Definition geometry.h:27
A box over the rest of the UI that has to be dealt with first, e.g. a question or a login code.
Definition modal.h:31
asw::Color bg
The background color.
Definition panel.h:30
void focus(Widget &w, bool show=false)
Focus a widget.
Definition root.cpp:658
bool in_root(const Widget *w) const
Definition root.cpp:87
void fit_to_screen()
Definition root.cpp:56
void draw_toasts()
Definition root.cpp:591
void update_keys()
Definition root.cpp:405
Panel root
The root panel widget. See through by default, set bg for a background.
Definition root.h:60
bool attached(const Widget *w) const
Definition root.cpp:78
Root()
Default constructor.
Definition root.cpp:48
bool dispatch_pointer(const UIEvent &e)
Definition root.cpp:268
void clear_toasts()
Remove every message, shown or waiting.
Definition root.cpp:562
void close_modals()
Close every open modal, top first.
Definition root.cpp:129
Widget * hit_test(Widget &w, const asw::Vec2< float > &pointer_pos)
Definition root.cpp:212
bool update()
Process input for this frame.
Definition root.cpp:521
void activate(Widget &w)
Definition root.cpp:290
bool dispatch_to_focused(const UIEvent &e)
Definition root.cpp:281
void clear_focus()
Remove focus from every widget.
Definition root.cpp:664
void update_modal_focus()
Definition root.cpp:151
void layout_modals()
Definition root.cpp:137
void opened_modal()
Definition root.cpp:118
void toast(const std::string &text)
Show a message at the top of the screen for a few seconds, e.g. "Achievement unlocked"....
Definition root.cpp:552
void free_removed(Widget &w)
Definition root.cpp:204
void validate()
Definition root.cpp:177
bool has_modal() const
True while a modal is open.
Definition root.cpp:113
void update_toasts()
Definition root.cpp:567
void update_pointer()
Definition root.cpp:302
Widget & active_root()
Definition root.cpp:105
void draw()
Draw the UI tree, then the focus ring.
Definition root.cpp:670
void set_size(float w, float h)
Set a fixed size for the root panel.
Definition root.cpp:68
Panel _modals
Definition root.h:182
Modal * top_modal() const
Definition root.cpp:95
bool bubble(Widget *target, const UIEvent &e)
Definition root.cpp:235
Base class for all UI widgets.
Definition widget.h:43
asw::Quad< float > transform
The transform (position and size) of the widget.
Definition widget.h:192
const std::vector< std::unique_ptr< Widget > > & children() const
Get the child widgets. Change them with add_child, remove_child and clear_children,...
Definition widget.h:115
bool enabled
Whether the widget is enabled.
Definition widget.h:84
virtual bool on_event(Context &ctx, const UIEvent &e)
Handle a UI event.
Definition widget.cpp:140
bool focus_ring
Draw the theme focus ring around this widget while it has visible focus. Turn off for widgets that sh...
Definition widget.h:104
bool focusable
Whether the widget can receive focus. Focusable widgets are also the ones the pointer can press.
Definition widget.h:88
virtual void activate(Context &ctx)
Called when the widget is clicked, or activated with the keyboard or a controller while focused....
Definition widget.cpp:153
bool visible
Whether the widget is visible.
Definition widget.h:81
Widget * parent
Pointer to the parent widget.
Definition widget.h:107
std::vector< std::unique_ptr< Widget > > _removed
Definition widget.h:252
Display and window routines for the ASW library.
Routines for drawing sprites and primitives to the screen.
Input module for the ASW library.
asw::ui::Widget * interactive(asw::ui::Widget *hit)
Definition root.cpp:37
void play_ui_sound(const asw::Sample &sample)
Definition root.cpp:13
bool enabled_in_tree(const asw::ui::Widget *w)
Definition root.cpp:24
asw::Vec2< int > get_logical_size()
Get the logical size of the window. This may differ from the actual size if scaling is enabled.
Definition display.cpp:198
void rect(const asw::Quad< float > &position, asw::Color color, float thickness=1.0F)
Draw a rectangle.
Definition draw.cpp:504
void stretch_sprite(const asw::Texture &tex, const asw::Quad< float > &position)
Draw a sprite with the option to stretch it.
Definition draw.cpp:232
void text(const asw::Font &font, const std::string &text, const asw::Vec2< float > &position, asw::Color color, asw::TextJustify justify=asw::TextJustify::Left)
Draw text. The position is rounded to whole pixels after justification so glyphs stay sharp....
Definition draw.cpp:357
void rect_fill(const asw::Quad< float > &position, asw::Color color)
Draw a filled rectangle.
Definition draw.cpp:536
Key
Enumeration of keys in the ASW library.
Definition input.h:38
const std::string & get_text_input()
Get the text input received this frame.
Definition input.cpp:206
SoundHandle play(const asw::Sample &sample, float volume=1.0F, float pan=0.0F, bool loop=false)
Play a sample.
Definition sound.cpp:609
void draw_focus_ring(const FocusRingStyle &style, const asw::Quad< float > &bounds)
Draw a focus ring around a widget.
Definition theme.cpp:5
asw::Vec2< int > get_text_size(const asw::Font &font, std::string_view text)
Get text size.
Definition util.cpp:81
std::shared_ptr< MIX_Audio > Sample
Alias for a shared pointer to an MIX_Audio.
Definition types.h:82
Root UI container and event dispatcher for the ASW UI module.
Sound module for the ASW library.
RGBA color struct with 8-bit channels.
Definition color.h:18
uint8_t a
Definition color.h:33
Options for playing a sound.
Definition sound.h:67
Bus bus
Bus to play on.
Definition sound.h:88
A short message shown at the top of the screen, see Root::toast.
Definition root.h:29
float seconds
How long it stays, the theme toast seconds when 0.
Definition root.h:37
Event structure for UI interactions.
Definition event.h:21
asw::Vec2< float > pointer_pos
The pointer position.
Definition event.h:45
General utility functions.