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
99 // Hover and Unhover events
100 if (mouse.change.x != 0.0F || mouse.change.y != 0.0F) {
101 // Send leave/enter when the hovered widget changes
102 if (Widget* new_hover = hit_test(root, mouse.position); new_hover != ctx.hover) {
103 if (ctx.hover != nullptr) {
104 const UIEvent leave { .type = UIEvent::Type::PointerLeave,
105 .pointer_pos = mouse.position };
106 ctx.hover->on_event(ctx, leave);
107 }
108 ctx.hover = new_hover;
109 if (ctx.hover != nullptr) {
110 const UIEvent enter { .type = UIEvent::Type::PointerEnter,
111 .pointer_pos = mouse.position };
112 ctx.hover->on_event(ctx, enter);
113 }
114 }
115
116 // Also dispatch the regular move event
117 const UIEvent e { .type = UIEvent::Type::PointerMove, .pointer_pos = mouse.position };
118 dispatch_pointer(e);
119 ctx.theme.show_focus = false;
120 }
121
122 // --- Button events ---
123
124 // Button Down
125 if (get_mouse_button_down(MouseButton::Left) || get_mouse_button_down(MouseButton::Right)
126 || get_mouse_button_down(MouseButton::Middle)) {
127 const UIEvent e { .type = UIEvent::Type::PointerDown,
128 .pointer_pos = mouse.position,
129 .mouse_button = MouseButton::Left };
130 dispatch_pointer(e);
131 ctx.theme.show_focus = false;
132 }
133
134 // Button Up
135 if (get_mouse_button_up(MouseButton::Left) || get_mouse_button_up(MouseButton::Right)
136 || get_mouse_button_up(MouseButton::Middle)) {
137 const UIEvent e { .type = UIEvent::Type::PointerUp,
138 .pointer_pos = mouse.position,
139 .mouse_button = MouseButton::Left };
140 dispatch_pointer(e);
141 ctx.theme.show_focus = false;
142 }
143
144 // --- Text Input ---
145 if (!input::text_input.empty()) {
146 const UIEvent ti { .type = UIEvent::Type::TextInput, .text = input::text_input };
147 dispatch_to_focused(ti);
148 }
149
150 // --- Focus Events ---
151 const auto shift = get_key(Key::LShift) || get_key(Key::RShift);
152
153 // Global focus handling first (keyboard-first UX)
154 if (get_key_down(Key::Tab)) {
155 if (shift) {
156 ctx.focus.focus_prev(ctx);
157 } else {
158 ctx.focus.focus_next(ctx);
159 }
160
161 ctx.theme.show_focus = true;
162 }
163
164 // Arrow keys: dispatch KeyDown to focused widget first, fall back to focus navigation
165 if (get_key_down(Key::Up)) {
166 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Up, .shift = shift };
167 if (!dispatch_to_focused(e)) {
168 ctx.focus.focus_dir(ctx, 0, -1);
169 ctx.theme.show_focus = true;
170 }
171 }
172 if (get_key_down(Key::Down)) {
173 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Down, .shift = shift };
174 if (!dispatch_to_focused(e)) {
175 ctx.focus.focus_dir(ctx, 0, +1);
176 ctx.theme.show_focus = true;
177 }
178 }
179 if (get_key_down(Key::Left)) {
180 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Left, .shift = shift };
181 if (!dispatch_to_focused(e)) {
182 ctx.focus.focus_dir(ctx, -1, 0);
183 ctx.theme.show_focus = true;
184 }
185 }
186 if (get_key_down(Key::Right)) {
187 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Right, .shift = shift };
188 if (!dispatch_to_focused(e)) {
189 ctx.focus.focus_dir(ctx, +1, 0);
190 ctx.theme.show_focus = true;
191 }
192 }
193
194 // Editing keys dispatched to focused widget
195 if (get_key_down(Key::Backspace)) {
196 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Backspace, .shift = shift };
197 dispatch_to_focused(e);
198 }
199 if (get_key_down(Key::Delete)) {
200 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Delete, .shift = shift };
201 dispatch_to_focused(e);
202 }
203 if (get_key_down(Key::Home)) {
204 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::Home, .shift = shift };
205 dispatch_to_focused(e);
206 }
207 if (get_key_down(Key::End)) {
208 const UIEvent e { .type = UIEvent::Type::KeyDown, .key = Key::End, .shift = shift };
209 dispatch_to_focused(e);
210 }
211
212 // Activate/back routed to focused widget
213 if (get_key_down(Key::Return) || get_key_down(Key::Space)) {
214 const UIEvent a { .type = UIEvent::Type::Activate };
215 dispatch_to_focused(a);
216 }
217 if (get_key_down(Key::Escape)) {
218 const UIEvent b { .type = UIEvent::Type::Back };
219 dispatch_to_focused(b);
220 }
221}
222
224{
225 root.draw(ctx);
226}
bool contains(const Vec2< T > &point) const
Check if a point is inside the rectangle.
Definition geometry.h:456
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:223
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.
std::string text_input
Text input received this frame.
Definition input.cpp:15
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:49