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
context.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5#include <ranges>
6
8{
9 _focusables.clear();
10 _focused_found = false;
11 dfs(root);
12 // Keep current focus if still exists. Nothing is focused until the
13 // player navigates or clicks, so no widget looks selected by default.
14 if (_focused != nullptr && !_focused_found) {
15 set_focus(ctx, nullptr);
16 }
17}
18
20{
21 if (_focused != nullptr || _focusables.empty()) {
22 return false;
23 }
24
25 const bool has_default = default_focus != nullptr
26 && std::ranges::find(_focusables, default_focus) != _focusables.end();
27 set_focus(ctx, has_default ? default_focus : _focusables.front());
28 return true;
29}
30
32{
33 _lane_x.reset();
34 _lane_y.reset();
35
36 if (_focused == w) {
37 return;
38 }
39 if (_focused != nullptr) {
40 _focused->_focused = false;
41 _focused->on_focus_changed(ctx, false);
42 }
43 _focused = w;
44 if (_focused != nullptr) {
45 _focused->_focused = true;
46 _focused->on_focus_changed(ctx, true);
47 }
48}
49
51{
52 if (_focusables.empty()) {
53 return;
54 }
55
56 if (focus_start(ctx)) {
57 return;
58 }
59 auto it = std::ranges::find(_focusables, _focused);
60 if (it == _focusables.end()) {
61 set_focus(ctx, _focusables.front());
62 return;
63 }
64 ++it;
65 if (it == _focusables.end()) {
66 it = _focusables.begin();
67 }
68 set_focus(ctx, *it);
69}
70
72{
73 if (_focusables.empty()) {
74 return;
75 }
76
77 if (focus_start(ctx)) {
78 return;
79 }
80
81 auto it = std::ranges::find(_focusables, _focused);
82 if (it == _focusables.end()) {
83 set_focus(ctx, _focusables.front());
84 return;
85 }
86
87 if (it == _focusables.begin()) {
88 it = _focusables.end();
89 }
90 --it;
91 set_focus(ctx, *it);
92}
93
94void asw::ui::FocusManager::focus_dir(Context& ctx, int dx, int dy)
95{
96 if (_focusables.empty()) {
97 return;
98 }
99
100 if (focus_start(ctx)) {
101 return;
102 }
103
104 Widget* const from_widget = _focused;
105 const bool vertical = dy != 0;
106 const int dir = vertical ? dy : dx;
107
108 // An explicit neighbour wins, if it can take focus
109 Widget* override_target = nullptr;
110 if (dy < 0) {
111 override_target = from_widget->nav_up;
112 } else if (dy > 0) {
113 override_target = from_widget->nav_down;
114 } else if (dx < 0) {
115 override_target = from_widget->nav_left;
116 } else if (dx > 0) {
117 override_target = from_widget->nav_right;
118 }
119 if (override_target != nullptr
120 && std::ranges::find(_focusables, override_target) != _focusables.end()) {
121 set_focus(ctx, override_target);
122 return;
123 }
124
125 // Along is the direction of travel, across the other axis
126 const auto along_lo = [vertical](const asw::Quad<float>& q) {
127 return vertical ? q.position.y : q.position.x;
128 };
129 const auto along_hi = [vertical](const asw::Quad<float>& q) {
130 return vertical ? q.position.y + q.size.y : q.position.x + q.size.x;
131 };
132 const auto across_lo = [vertical](const asw::Quad<float>& q) {
133 return vertical ? q.position.x : q.position.y;
134 };
135 const auto across_hi = [vertical](const asw::Quad<float>& q) {
136 return vertical ? q.position.x + q.size.x : q.position.y + q.size.y;
137 };
138
139 const auto& from = from_widget->transform;
140
141 // Repeated presses one way keep to the lane they started in, so down then
142 // up comes back to the same column
143 std::optional<float>& lane = vertical ? _lane_x : _lane_y;
144 const float anchor = lane.value_or((across_lo(from) + across_hi(from)) / 2.0F);
145
146 Widget* best = nullptr;
147 bool best_in_lane = false;
148 float best_distance = 0.0F;
149 float best_offset = 0.0F;
150
151 for (Widget* w : _focusables) {
152 if (w == from_widget) {
153 continue;
154 }
155 const auto& to = w->transform;
156
157 // Its centre must lie past the edge we leave from, so a wide widget
158 // does not move sideways into the row below it
159 const float to_center = (along_lo(to) + along_hi(to)) / 2.0F;
160 const float leading_edge = dir > 0 ? along_hi(from) : along_lo(from);
161 if ((to_center - leading_edge) * static_cast<float>(dir) <= 0.0F) {
162 continue;
163 }
164
165 // Edge to edge gap along the direction, 0 when they overlap
166 const float gap = dir > 0 ? along_lo(to) - along_hi(from) : along_lo(from) - along_hi(to);
167 const float along = std::max(0.0F, gap);
168
169 // Sharing a lane: the ranges across the direction overlap
170 const float overlap = std::min(across_hi(from), across_hi(to))
171 - std::max(across_lo(from), across_lo(to));
172 const bool in_lane = overlap > 0.0F;
173 const float across_gap = in_lane ? 0.0F : -overlap;
174
175 // Out of lane widgets pay for how far off to the side they are
176 const float distance = in_lane ? along : along + (across_gap * 2.0F);
177 const float offset = std::abs(((across_lo(to) + across_hi(to)) / 2.0F) - anchor);
178
179 const bool better = best == nullptr || (in_lane && !best_in_lane)
180 || (in_lane == best_in_lane
181 && (distance < best_distance
182 || (distance == best_distance && offset < best_offset)));
183 if (better) {
184 best = w;
185 best_in_lane = in_lane;
186 best_distance = distance;
187 best_offset = offset;
188 }
189 }
190
191 if (best != nullptr) {
192 set_focus(ctx, best);
193 lane = anchor;
194 }
195}
196
198{
199 // A hidden or disabled widget hides or disables its children too
200 if (!w.visible || !w.enabled) {
201 return;
202 }
203 if (w.focusable) {
204 _focusables.push_back(&w);
205 _focused_found = _focused_found || &w == _focused;
206 }
207 for (auto const& c : w.children()) {
208 dfs(*c);
209 }
210}
A 2D rectangle in space.
Definition geometry.h:504
Shared state for the UI system.
Definition context.h:97
void focus_prev(Context &ctx)
Move focus to the previous focusable widget.
Definition context.cpp:71
void focus_dir(Context &ctx, int dx, int dy)
Move focus in a direction based on widget positions.
Definition context.cpp:94
std::vector< Widget * > _focusables
Definition context.h:84
bool focus_start(Context &ctx)
Focus default_focus, or the first focusable widget, when nothing has focus.
Definition context.cpp:19
void set_focus(Context &ctx, Widget *w)
Set focus to a specific widget.
Definition context.cpp:31
void dfs(Widget &w)
Definition context.cpp:197
void focus_next(Context &ctx)
Move focus to the next focusable widget.
Definition context.cpp:50
void rebuild(Context &ctx, Widget &root)
Definition context.cpp:7
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
Widget * nav_up
Widget to focus on up, instead of the nearest one. Optional.
Definition widget.h:91
Widget * nav_right
Widget to focus on right, instead of the nearest one. Optional.
Definition widget.h:100
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
Widget * nav_left
Widget to focus on left, instead of the nearest one. Optional.
Definition widget.h:97
bool focusable
Whether the widget can receive focus. Focusable widgets are also the ones the pointer can press.
Definition widget.h:88
bool visible
Whether the widget is visible.
Definition widget.h:81
Widget * nav_down
Widget to focus on down, instead of the nearest one. Optional.
Definition widget.h:94
UI context and focus management for the ASW UI module.