15 if (sample !=
nullptr) {
26 for (; w !=
nullptr; w = w->
parent) {
39 for (
auto* w = hit; w !=
nullptr; w = w->
parent) {
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))) {
63 = { 0.0F, 0.0F,
static_cast<float>(screen.x),
static_cast<float>(screen.y) };
65 _modals.transform = root.transform;
70 auto r = root.transform;
74 _modals.transform = r;
81 while (w !=
nullptr && w != &root && w != &_modals) {
89 while (w !=
nullptr && w != &root) {
97 for (
auto it = _modals.children().rbegin(); it != _modals.children().rend(); ++it) {
99 return dynamic_cast<Modal*
>(it->get());
107 if (
Modal* modal = top_modal(); modal !=
nullptr) {
115 return top_modal() !=
nullptr;
122 if (_focus_before_modal ==
nullptr) {
123 if (
Widget* f = ctx.focus.focused(); in_root(f)) {
124 _focus_before_modal = f;
131 while (
Modal* modal = top_modal()) {
139 for (
auto const& c : _modals.children()) {
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) };
153 Modal* modal = top_modal();
156 if (modal !=
nullptr && modal != _focused_modal) {
157 _focused_modal = modal;
158 ctx.focus.focus_start(ctx);
164 if (modal ==
nullptr) {
165 Widget* back = std::exchange(_focus_before_modal,
nullptr);
166 if (_focused_modal ==
nullptr) {
169 _focused_modal =
nullptr;
171 && enabled_in_tree(back)) {
172 ctx.focus.set_focus(ctx, back);
182 if (ctx.hover !=
nullptr && !attached(ctx.hover)) {
183 ctx.hover->_hovered =
false;
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;
191 if (ctx.focus.default_focus !=
nullptr && !attached(ctx.focus.default_focus)) {
192 ctx.focus.default_focus =
nullptr;
194 if (_focus_before_modal !=
nullptr && !attached(_focus_before_modal)) {
195 _focus_before_modal =
nullptr;
200 ctx.focus.rebuild(ctx, active_root());
201 update_modal_focus();
207 for (
auto const& c : w.
children()) {
219 for (
int i = (
int)w.
children().size() - 1; i >= 0; --i) {
224 if (!c->transform.contains(pointer_pos)) {
227 if (
auto* hit = hit_test(*c, pointer_pos)) {
239 if (target ==
nullptr) {
245 std::vector<Widget*> chain;
252 bool handled =
false;
257 if (w->on_event(ctx, e)) {
272 if (ctx.pointer_capture !=
nullptr) {
273 target = ctx.pointer_capture;
278 return bubble(target, e);
283 Widget* f = ctx.focus.focused();
292 if (enabled_in_tree(&w)) {
293 play_ui_sound(ctx.theme.sound_activate);
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;
314 Widget* new_hover = interactive(hit);
315 if (new_hover != ctx.hover) {
316 if (ctx.hover !=
nullptr) {
317 Widget* old_hover = ctx.hover;
321 .pointer_pos = mouse.position };
326 if (!attached(new_hover)) {
330 ctx.hover = new_hover;
331 if (ctx.hover !=
nullptr) {
332 ctx.hover->_hovered =
true;
334 .pointer_pos = mouse.position };
335 ctx.hover->on_event(ctx, enter);
342 if (ctx.pointer_capture !=
nullptr || over_widget) {
349 ctx.show_focus =
false;
354 for (
const auto button : { MouseButton::Left, MouseButton::Right, MouseButton::Middle }) {
355 const bool left = button == MouseButton::Left;
357 if (get_mouse_button_down(button)) {
358 ctx.show_focus =
false;
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);
369 .pointer_pos = mouse.position,
370 .mouse_button = button };
374 if (get_mouse_button_up(button)) {
375 ctx.show_focus =
false;
378 .pointer_pos = mouse.position,
379 .mouse_button = button };
384 Widget* pressed = left ? ctx.pointer_capture :
nullptr;
387 if (pressed !=
nullptr) {
388 const bool over = pressed == ctx.hover;
391 ctx.pointer_capture =
nullptr;
400 if (ctx.pointer_capture !=
nullptr) {
401 ctx.pointer_capture->_pressed = ctx.pointer_capture == ctx.hover;
413 if (dispatch_to_focused(ti)) {
420 const auto& nav = ctx.navigation;
421 Widget*
const focused_before = ctx.focus.focused();
426 const auto pressed = [typed](
const std::string& action,
Key key) {
430 return action.empty() ? get_key_down(key) : get_action_down(action);
434 const auto repeating = [typed](
const std::string& action,
Key key) {
438 return action.empty() ? get_key_repeat(key) : get_action_down(action);
441 const auto shift = get_key(Key::LShift) || get_key(Key::RShift);
444 const bool next = pressed(nav.next, Key::Tab);
445 const bool prev = !typed && !nav.prev.empty() && get_action_down(nav.prev);
448 ctx.focus.focus_prev(ctx);
450 ctx.focus.focus_next(ctx);
453 ctx.show_focus =
true;
458 const auto direction = [&](
const std::string& action,
Key key,
int dx,
int dy) {
459 if (!repeating(action, key)) {
463 if (!dispatch_to_focused(e)) {
464 ctx.focus.focus_dir(ctx, dx, dy);
466 ctx.show_focus =
true;
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);
474 if (ctx.focus.focused() != focused_before) {
475 play_ui_sound(ctx.theme.sound_move);
479 for (
const auto key : { Key::Backspace, Key::Delete, Key::Home, Key::End }) {
480 if (get_key_repeat(key)) {
482 if (dispatch_to_focused(e)) {
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;
496 }
else if (
Widget* f = ctx.focus.focused(); f !=
nullptr) {
497 ctx.show_focus =
true;
503 if (pressed(nav.back, Key::Escape)) {
505 if (dispatch_to_focused(b)) {
507 }
else if (
Modal* modal = top_modal(); modal !=
nullptr) {
508 if (modal->close_on_back) {
513 }
else if (on_back) {
533 free_removed(_modals);
554 toast(
Toast { .text = text });
559 _toasts.push_back({ .toast = std::move(message), .age = 0.0F });
569 const auto now = std::chrono::steady_clock::now();
573 dt = std::min(std::chrono::duration<float>(now - *_last_update).count(), 0.25F);
577 const auto& style = ctx.theme.toast;
578 std::size_t shown = 0;
579 for (
auto& t : _toasts) {
580 if (shown++ >= style.max_visible) {
586 std::erase_if(_toasts, [&style](
const ShownToast& t) {
593 const auto& style = ctx.theme.toast;
594 const auto& font = ctx.theme.font;
595 if (font ==
nullptr) {
599 const auto faded = [](
asw::Color c,
float alpha) {
600 c.
a =
static_cast<uint8_t
>(
static_cast<float>(c.
a) * alpha);
604 const auto& area = root.transform;
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) {
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)
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;
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);
633 x = area.position.x + style.margin;
634 }
else if (right_side) {
635 x = area.position.x + area.size.x - w - style.margin;
642 if (style.bg.a > 0) {
645 if (style.border_width > 0.0F && style.border.a > 0) {
648 if (t.icon !=
nullptr) {
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));
654 y = from_bottom ? y - style.gap : y + h + style.gap;
660 ctx.focus.set_focus(ctx, &w);
661 ctx.show_focus = show;
666 ctx.focus.set_focus(ctx,
nullptr);
667 ctx.show_focus =
false;
676 if (
Modal* top = top_modal(); top !=
nullptr) {
677 for (
auto const& c : _modals.children()) {
681 if (c.get() == top) {
682 if (
const auto dim = top->get_style(ctx).dim; dim.a > 0) {
692 Widget* f = ctx.focus.focused();
693 if (ctx.show_focus && f !=
nullptr && f->
visible && f->
focus_ring && attached(f)) {
Action binding system for the ASW library.
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...
A box over the rest of the UI that has to be dealt with first, e.g. a question or a login code.
asw::Color bg
The background color.
void focus(Widget &w, bool show=false)
Focus a widget.
bool in_root(const Widget *w) const
Panel root
The root panel widget. See through by default, set bg for a background.
bool attached(const Widget *w) const
Root()
Default constructor.
bool dispatch_pointer(const UIEvent &e)
void clear_toasts()
Remove every message, shown or waiting.
void close_modals()
Close every open modal, top first.
Widget * hit_test(Widget &w, const asw::Vec2< float > &pointer_pos)
bool update()
Process input for this frame.
bool dispatch_to_focused(const UIEvent &e)
void clear_focus()
Remove focus from every widget.
void update_modal_focus()
void toast(const std::string &text)
Show a message at the top of the screen for a few seconds, e.g. "Achievement unlocked"....
void free_removed(Widget &w)
bool has_modal() const
True while a modal is open.
void draw()
Draw the UI tree, then the focus ring.
void set_size(float w, float h)
Set a fixed size for the root panel.
Modal * top_modal() const
bool bubble(Widget *target, const UIEvent &e)
Display and window routines for the ASW library.
Routines for drawing sprites and primitives to the screen.
asw::ui::Widget * interactive(asw::ui::Widget *hit)
void play_ui_sound(const asw::Sample &sample)
bool enabled_in_tree(const asw::ui::Widget *w)
asw::Vec2< int > get_logical_size()
Get the logical size of the window. This may differ from the actual size if scaling is enabled.
void rect(const asw::Quad< float > &position, asw::Color color, float thickness=1.0F)
Draw a rectangle.
void stretch_sprite(const asw::Texture &tex, const asw::Quad< float > &position)
Draw a sprite with the option to stretch it.
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....
void rect_fill(const asw::Quad< float > &position, asw::Color color)
Draw a filled rectangle.
SoundHandle play(const asw::Sample &sample, float volume=1.0F, float pan=0.0F, bool loop=false)
Play a sample.
void draw_focus_ring(const FocusRingStyle &style, const asw::Quad< float > &bounds)
Draw a focus ring around a widget.
asw::Vec2< int > get_text_size(const asw::Font &font, std::string_view text)
Get text size.
std::shared_ptr< MIX_Audio > Sample
Alias for a shared pointer to an MIX_Audio.
Root UI container and event dispatcher for the ASW UI module.
Sound module for the ASW library.
RGBA color struct with 8-bit channels.
Options for playing a sound.
A short message shown at the top of the screen, see Root::toast.
float seconds
How long it stays, the theme toast seconds when 0.
Event structure for UI interactions.
asw::Vec2< float > pointer_pos
The pointer position.
General utility functions.