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
4
6{
7 root.transform = { 0, 0, 128, 128 };
9}
10
11void asw::ui::Root::set_size(float w, float h)
12{
13 auto r = root.transform;
14 r.size.x = w;
15 r.size.y = h;
16 root.transform = r;
17 ctx.need_focus_rebuild = true;
18}
19
21{
22 if (!ctx.need_focus_rebuild) {
23 return;
24 }
25 ctx.focus.rebuild(ctx, root);
26 ctx.hover = nullptr;
27 ctx.pointer_capture = nullptr;
28 ctx.need_focus_rebuild = false;
29}
30
32{
33 if (!w.visible) {
34 return nullptr;
35 }
36
37 // traverse children in reverse for top-most
38 for (int i = (int)w.children.size() - 1; i >= 0; --i) {
39 auto const& c = w.children[i];
40 if (!c->visible) {
41 continue;
42 }
43 if (!c->transform.contains(pointer_pos)) {
44 continue;
45 }
46 if (auto* hit = hit_test(*c, pointer_pos)) {
47 return hit;
48 }
49 return c.get();
50 }
51 return w.transform.contains(pointer_pos) ? &w : nullptr;
52}
53
55{
56 Widget* target = nullptr;
57
58 if (ctx.pointer_capture != nullptr) {
59 target = ctx.pointer_capture;
60 } else {
61 target = hit_test(root, e.pointer_pos);
62 }
63
64 // Let target handle; if not handled, bubble up to parents
65 for (Widget* w = target; w != nullptr; w = w->parent) {
66 if (w->on_event(ctx, e)) {
67 return true;
68 }
69 }
70 return false;
71}
72
74{
75 Widget* f = ctx.focus.focused();
76 if (f == nullptr) {
77 return false;
78 }
79 for (Widget* w = f; w != nullptr; w = w->parent) {
80 if (w->on_event(ctx, e)) {
81 return true;
82 }
83 }
84 return false;
85}
86
88{
89 using namespace asw::input;
90
91 // Rebuild focus list if needed
92 rebuild_focus_if_needed();
93
94 // Arrange
95 root.layout(ctx);
96
97 // --- Mouse ---
98 const auto& mouse = get_mouse();
99
100 // Hover and Unhover events
101 if (mouse.change.x != 0.0F || mouse.change.y != 0.0F) {
102 // Send leave/enter when the hovered widget changes
103 if (Widget* new_hover = hit_test(root, mouse.position); new_hover != ctx.hover) {
104 if (ctx.hover != nullptr) {
105 const UIEvent leave { .type = UIEvent::Type::PointerLeave,
106 .pointer_pos = mouse.position };
107 ctx.hover->on_event(ctx, leave);
108 }
109 ctx.hover = new_hover;
110 if (ctx.hover != nullptr) {
111 const UIEvent enter { .type = UIEvent::Type::PointerEnter,
112 .pointer_pos = mouse.position };
113 ctx.hover->on_event(ctx, enter);
114 }
115 }
116
117 // Also dispatch the regular move event
118 const UIEvent e { .type = UIEvent::Type::PointerMove, .pointer_pos = mouse.position };
119 dispatch_pointer(e);
120 ctx.theme.show_focus = false;
121 }
122
123 // --- Button events ---
124
125 // Button Down
126 if (get_mouse_button_down(MouseButton::Left) || get_mouse_button_down(MouseButton::Right)
127 || get_mouse_button_down(MouseButton::Middle)) {
128 const UIEvent e { .type = UIEvent::Type::PointerDown,
129 .pointer_pos = mouse.position,
130 .mouse_button = MouseButton::Left };
131 dispatch_pointer(e);
132 ctx.theme.show_focus = false;
133 }
134
135 // Button Up
136 if (get_mouse_button_up(MouseButton::Left) || get_mouse_button_up(MouseButton::Right)
137 || get_mouse_button_up(MouseButton::Middle)) {
138 const UIEvent e { .type = UIEvent::Type::PointerUp,
139 .pointer_pos = mouse.position,
140 .mouse_button = MouseButton::Left };
141 dispatch_pointer(e);
142 ctx.theme.show_focus = false;
143 }
144
145 // --- Text Input ---
146 if (!input::get_text_input().empty()) {
147 const UIEvent ti { .type = UIEvent::Type::TextInput, .text = input::get_text_input() };
148 dispatch_to_focused(ti);
149 }
150
151 // --- Focus Events ---
152 const auto shift = get_key(Key::LShift) || get_key(Key::RShift);
153
154 // Global focus handling first (keyboard-first UX)
155 if (get_key_down(Key::Tab)) {
156 if (shift) {
157 ctx.focus.focus_prev(ctx);
158 } else {
159 ctx.focus.focus_next(ctx);
160 }
161
162 ctx.theme.show_focus = true;
163 }
164
165 // Arrow keys: dispatch KeyDown to focused widget first, fall back to focus navigation
166 if (get_key_down(Key::Up)) {
167 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Up };
168 if (!dispatch_to_focused(e)) {
169 ctx.focus.focus_dir(ctx, 0, -1);
170 ctx.theme.show_focus = true;
171 }
172 }
173 if (get_key_down(Key::Down)) {
174 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Down };
175 if (!dispatch_to_focused(e)) {
176 ctx.focus.focus_dir(ctx, 0, +1);
177 ctx.theme.show_focus = true;
178 }
179 }
180 if (get_key_down(Key::Left)) {
181 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Left };
182 if (!dispatch_to_focused(e)) {
183 ctx.focus.focus_dir(ctx, -1, 0);
184 ctx.theme.show_focus = true;
185 }
186 }
187 if (get_key_down(Key::Right)) {
188 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Right };
189 if (!dispatch_to_focused(e)) {
190 ctx.focus.focus_dir(ctx, +1, 0);
191 ctx.theme.show_focus = true;
192 }
193 }
194
195 // Editing keys dispatched to focused widget
196 if (get_key_down(Key::Backspace)) {
197 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Backspace };
198 dispatch_to_focused(e);
199 }
200 if (get_key_down(Key::Delete)) {
201 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Delete };
202 dispatch_to_focused(e);
203 }
204 if (get_key_down(Key::Home)) {
205 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Home };
206 dispatch_to_focused(e);
207 }
208 if (get_key_down(Key::End)) {
209 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::End };
210 dispatch_to_focused(e);
211 }
212
213 // Activate/back routed to focused widget
214 if (get_key_down(Key::Return) || get_key_down(Key::Space)) {
215 const UIEvent a { .type = UIEvent::Type::Activate };
216 dispatch_to_focused(a);
217 }
218 if (get_key_down(Key::Escape)) {
219 const UIEvent b { .type = UIEvent::Type::Back };
220 dispatch_to_focused(b);
221 }
222}
223
225{
226 root.draw(ctx);
227}
bool contains(const Vec2< T > &point) const
Check if a point is inside the rectangle.
Definition geometry.h:501
A 2D vector in space.
Definition geometry.h:21
Theme theme
The current UI theme.
Definition context.h:82
asw::Color bg
The background color.
Definition panel.h:30
Panel root
The root panel widget.
Definition root.h:29
Root()
Default constructor.
Definition root.cpp:5
bool dispatch_pointer(const UIEvent &e)
Route a pointer event to the appropriate widget.
Definition root.cpp:54
Widget * hit_test(Widget &w, const asw::Vec2< float > &pointer_pos)
Find the deepest widget at a given pointer position.
Definition root.cpp:31
bool dispatch_to_focused(const UIEvent &e)
Dispatch an event to the currently focused widget.
Definition root.cpp:73
void rebuild_focus_if_needed()
Rebuild the focus list if the tree has changed.
Definition root.cpp:20
void update()
Process input and dispatch UI events.
Definition root.cpp:87
Context ctx
The UI context.
Definition root.h:26
void draw()
Draw the UI tree.
Definition root.cpp:224
void set_size(float w, float h)
Set the size of the root panel.
Definition root.cpp:11
Base class for all UI widgets.
Definition widget.h:28
asw::Quad< float > transform
The transform (position and size) of the widget.
Definition widget.h:119
std::vector< std::unique_ptr< Widget > > children
Child widgets.
Definition widget.h:72
bool visible
Whether the widget is visible.
Definition widget.h:60
Widget * parent
Pointer to the parent widget.
Definition widget.h:69
Input module for the ASW library.
const std::string & get_text_input()
Get the text input received this frame.
Definition input.cpp:51
Root UI container and event dispatcher for the ASW UI module.
asw::Color panel_bg
Panel background color.
Definition theme.h:26
Event structure for UI interactions.
Definition event.h:21
asw::Vec2< float > pointer_pos
The pointer position.
Definition event.h:46