HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
widget.hpp
Go to the documentation of this file.
1// Copyright Take Vos 2019-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
8
9#pragma once
10
11#include "../layout/layout.hpp"
12#include "../geometry/geometry.hpp"
13#include "../observer/observer.hpp"
14#include "../time/time.hpp"
15#include "../settings/settings.hpp"
16#include "../numeric/numeric.hpp"
17#include "../theme/theme.hpp"
18#include "../GUI/GUI.hpp"
19#include "../macros.hpp"
20#include <memory>
21#include <vector>
22#include <string>
23#include <ranges>
24
25hi_export_module(hikogui.widgets.widget);
26
27hi_export namespace hi { inline namespace v1 {
28
38class widget : public widget_intf {
39public:
43
46 observer<extent2> maximum = extent2::large();
47
50 explicit widget() noexcept : widget_intf()
51 {
52 }
53
54 virtual ~widget() {}
55 widget(const widget&) = delete;
56 widget& operator=(const widget&) = delete;
57 widget(widget&&) = delete;
58 widget& operator=(widget&&) = delete;
59
61 [[nodiscard]] generator<widget_intf&> children(bool include_invisible) noexcept override
62 {
63 co_return;
64 }
65
73 [[nodiscard]] hitbox hitbox_test(point2 position) const noexcept override
74 {
75 return {};
76 }
77
84 [[nodiscard]] virtual hitbox hitbox_test_from_parent(point2 position) const noexcept
85 {
86 return hitbox_test(_layout.from_parent * position);
87 }
88
96 [[nodiscard]] virtual hitbox hitbox_test_from_parent(point2 position, hitbox sibling_hitbox) const noexcept
97 {
98 return std::max(sibling_hitbox, hitbox_test(_layout.from_parent * position));
99 }
100
104 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
105 {
106 hi_axiom(loop::main().on_thread());
107 return false;
108 }
109
110 [[nodiscard]] box_constraints update_constraints() noexcept override
111 {
112 _layout = {};
113 return {*minimum, *minimum, *maximum};
114 }
115
116 void set_layout(widget_layout const& context) noexcept override
117 {
118 _layout = context;
119 }
120
121 void draw(draw_context const& context) noexcept override {}
122
125 bool process_event(gui_event const& event) const noexcept override
126 {
127 if (auto *p = parent()) {
128 return p->process_event(event);
129 } else {
130 return true;
131 }
132 }
133
140
145 bool handle_event(gui_event const& event) noexcept override
146 {
147 hi_axiom(loop::main().on_thread());
148
149 switch (event.type()) {
150 case gui_event_type::keyboard_enter:
151 set_focus(true);
152 this->scroll_to_show();
153 return true;
154
155 case gui_event_type::keyboard_exit:
156 set_focus(false);
157 return true;
158
159 case gui_event_type::mouse_enter:
160 set_hover(true);
161 return true;
162
163 case gui_event_type::mouse_exit:
164 set_hover(false);
165 return true;
166
168 set_active(true);
169 // All widgets need the active value set.
170 return false;
171
173 set_active(false);
174 // All widgets need the active value unset.
175 return false;
176
177 case gui_event_type::gui_activate_stay:
178 process_event(gui_event_type::gui_activate);
179 if (accepts_keyboard_focus(keyboard_focus_group::menu)) {
180 // By going forward and backward we select the current parent,
181 // the widget that opened the menu-stack.
182 process_event(gui_event_type::gui_widget_next);
183 process_event(gui_event_type::gui_widget_prev);
184 }
185 return true;
186
187 case gui_event_type::gui_activate_next:
188 process_event(gui_event_type::gui_activate);
189 return process_event(gui_event_type::gui_widget_next);
190
191 case gui_event_type::gui_widget_next:
193 gui_event::window_set_keyboard_target(id, keyboard_focus_group::normal, keyboard_focus_direction::forward));
194 return true;
195
196 case gui_event_type::gui_widget_prev:
198 gui_event::window_set_keyboard_target(id, keyboard_focus_group::normal, keyboard_focus_direction::backward));
199 return true;
200
201 case gui_event_type::gui_menu_next:
202 if (mode() >= widget_mode::partial and accepts_keyboard_focus(keyboard_focus_group::menu)) {
204 gui_event::window_set_keyboard_target(id, keyboard_focus_group::menu, keyboard_focus_direction::forward));
205 return true;
206 }
207 break;
208
209 case gui_event_type::gui_menu_prev:
210 if (mode() >= widget_mode::partial and accepts_keyboard_focus(keyboard_focus_group::menu)) {
212 gui_event::window_set_keyboard_target(id, keyboard_focus_group::menu, keyboard_focus_direction::backward));
213 return true;
214 }
215 break;
216
217 case gui_event_type::gui_toolbar_next:
218 if (mode() >= widget_mode::partial and accepts_keyboard_focus(keyboard_focus_group::toolbar)) {
220 gui_event::window_set_keyboard_target(id, keyboard_focus_group::toolbar, keyboard_focus_direction::forward));
221 return true;
222 }
223 break;
224
225 case gui_event_type::gui_toolbar_prev:
226 if (mode() >= widget_mode::partial and accepts_keyboard_focus(keyboard_focus_group::toolbar)) {
228 gui_event::window_set_keyboard_target(id, keyboard_focus_group::toolbar, keyboard_focus_direction::backward));
229 return true;
230 }
231 break;
232
233 default:;
234 }
235
236 return false;
237 }
238
240 gui_event const& event,
241 std::vector<widget_id> const& reject_list = std::vector<widget_id>{}) noexcept override
242 {
243 hi_axiom(loop::main().on_thread());
244
245 auto handled = false;
246
247 for (auto& child : this->children(false)) {
248 handled |= child.handle_event_recursive(event, reject_list);
249 }
250
251 if (!std::ranges::any_of(reject_list, [&](auto const& x) {
252 return x == id;
253 })) {
254 handled |= handle_event(event);
255 }
256
257 return handled;
258 }
259
260 [[nodiscard]] virtual widget_id find_next_widget(
261 widget_id current_keyboard_widget,
262 keyboard_focus_group group,
263 keyboard_focus_direction direction) const noexcept override
264 {
265 hi_axiom(loop::main().on_thread());
266
267 auto found = false;
268
269 if (current_keyboard_widget == 0 and accepts_keyboard_focus(group)) {
270 // If there was no current_keyboard_widget, then return this if it accepts focus.
271 return id;
272
273 } else if (current_keyboard_widget == id) {
274 found = true;
275 }
276
277 auto children_ = std::vector<widget_intf const *>{};
278 for (auto& child : children(false)) {
279 children_.push_back(std::addressof(child));
280 }
281
282 if (direction == keyboard_focus_direction::backward) {
283 std::reverse(begin(children_), end(children_));
284 }
285
286 for (auto *child : children_) {
287 hi_axiom_not_null(child);
288
289 if (found) {
290 // Find the first focus accepting widget.
291 if (auto tmp = child->find_next_widget({}, group, direction); tmp != 0) {
292 return tmp;
293 }
294
295 } else {
296 auto tmp = child->find_next_widget(current_keyboard_widget, group, direction);
297 if (tmp == current_keyboard_widget) {
298 // The current widget was found, but no next widget available in the child.
299 // Try the first widget that does accept keyboard focus.
300 found = true;
301
302 } else if (tmp != 0) {
303 // Return the next widget that was found in the child-widget.
304 return tmp;
305 }
306 }
307 }
308
309 if (found) {
310 // Either:
311 // 1. current_keyboard_widget was nullptr; this widget, nor its child widgets accept focus.
312 // 2. current_keyboard_wigget was this; none of the child widgets accept focus.
313 // 3. current_keyboard_widget is a child; none of the following widgets accept focus.
314 return current_keyboard_widget;
315 }
316
317 return {};
318 }
319
322 {
323 hi_axiom(loop::main().on_thread());
324
325 if (auto p = parent()) {
326 p->scroll_to_show(_layout.to_parent * rectangle);
327 }
328 }
329
330 [[nodiscard]] hi::theme const& theme() const noexcept
331 {
332 hi_assert_not_null(window);
333 return window->theme;
334 }
335
336 [[nodiscard]] gfx_surface const *surface() const noexcept
337 {
338 if (window) {
339 return window->surface.get();
340 } else {
341 return nullptr;
342 }
343 }
344
345 [[nodiscard]] virtual color background_color() const noexcept
346 {
347 if (mode() >= widget_mode::partial) {
348 if (phase() == widget_phase::hover) {
349 return theme().fill_color(_layout.layer + 1);
350 } else {
351 return theme().fill_color(_layout.layer);
352 }
353 } else {
354 return theme().fill_color(_layout.layer - 1);
355 }
356 }
357
358 [[nodiscard]] virtual color foreground_color() const noexcept
359 {
360 if (mode() >= widget_mode::partial) {
361 if (phase() == widget_phase::hover) {
362 return theme().border_color(_layout.layer + 1);
363 } else {
364 return theme().border_color(_layout.layer);
365 }
366 } else {
367 return theme().border_color(_layout.layer - 1);
368 }
369 }
370
371 [[nodiscard]] virtual color focus_color() const noexcept
372 {
373 if (mode() >= widget_mode::partial) {
374 if (focus()) {
375 return theme().accent_color();
376 } else if (phase() == widget_phase::hover) {
377 return theme().border_color(_layout.layer + 1);
378 } else {
379 return theme().border_color(_layout.layer);
380 }
381 } else {
382 return theme().border_color(_layout.layer - 1);
383 }
384 }
385
386 [[nodiscard]] virtual color accent_color() const noexcept
387 {
388 if (mode() >= widget_mode::partial) {
389 return theme().accent_color();
390 } else {
391 return theme().border_color(_layout.layer - 1);
392 }
393 }
394
395protected:
405 [[nodiscard]] aarectangle make_overlay_rectangle(aarectangle requested_rectangle) const noexcept
406 {
407 hi_axiom(loop::main().on_thread());
408
409 // Move the request_rectangle to window coordinates.
410 auto const requested_window_rectangle = translate2{layout().clipping_rectangle_on_window()} * requested_rectangle;
411 auto const window_bounds = aarectangle{layout().window_size} - theme().margin<float>();
412 auto const response_window_rectangle = fit(window_bounds, requested_window_rectangle);
413 return layout().from_window * response_window_rectangle;
414 }
415};
416
417}} // namespace hi::v1
@ end
Start from the end of the file.
Definition seek_whence.hpp:17
@ begin
Start from the beginning of the file.
Definition seek_whence.hpp:15
@ window_redraw
Request that part of the window gets redrawn on the next frame.
Definition gui_event_type.hpp:46
@ window_deactivate
The window is not longer the top-window.
Definition gui_event_type.hpp:58
@ window_activate
The window becomes the top-window.
Definition gui_event_type.hpp:57
@ partial
A widget is partially enabled.
Definition widget_state.hpp:73
The HikoGUI namespace.
Definition array_generic.hpp:21
The HikoGUI API version 1.
Definition array_generic.hpp:22
@ color
A color value was modified.
Definition style_modify_mask.hpp:27
Class which represents an axis-aligned rectangle.
Definition aarectangle.hpp:33
A high-level geometric extent.
Definition extent2.hpp:32
A rectangle / parallelogram in 3D space.
Definition rectangle.hpp:25
Draw context for drawing using the HikoGUI shaders.
Definition draw_context_intf.hpp:209
A user interface event.
Definition gui_event.hpp:82
widget_id id
The numeric identifier of a widget.
Definition widget_intf.hpp:31
widget_layout const & layout() const noexcept
Get the current layout for this widget.
Definition widget_intf.hpp:241
virtual generator< widget_intf & > children(bool include_invisible=true) noexcept=0
Get a list of child widgets.
widget_intf * parent() const noexcept
Pointer to the parent widget.
Definition widget_intf.hpp:113
virtual void scroll_to_show(hi::aarectangle rectangle) noexcept=0
Scroll to show the given rectangle on the window.
The layout of a widget.
Definition widget_layout.hpp:56
constexpr aarectangle clipping_rectangle_on_window() const noexcept
Get the clipping rectangle in window coordinate system.
Definition widget_layout.hpp:198
translate2 from_window
This matrix transforms window coordinates to local coordinates.
Definition widget_layout.hpp:83
extent2 window_size
Size of the window.
Definition widget_layout.hpp:87
2D constraints.
Definition box_constraints.hpp:25
A observer pointing to the whole or part of a observed_base.
Definition observer_intf.hpp:32
An interactive graphical object as part of the user-interface.
Definition widget.hpp:38
bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
Check if the widget will accept keyboard focus.
Definition widget.hpp:104
observer< extent2 > minimum
The minimum size this widget is allowed to be.
Definition widget.hpp:42
virtual hitbox hitbox_test_from_parent(point2 position, hitbox sibling_hitbox) const noexcept
Call hitbox_test from a parent widget.
Definition widget.hpp:96
void scroll_to_show() noexcept
Scroll to show the important part of the widget.
Definition widget_intf.hpp:347
bool handle_event_recursive(gui_event const &event, std::vector< widget_id > const &reject_list=std::vector< widget_id >{}) noexcept override
Handle command recursive.
Definition widget.hpp:239
virtual widget_id find_next_widget(widget_id current_keyboard_widget, keyboard_focus_group group, keyboard_focus_direction direction) const noexcept override
Find the next widget that handles keyboard focus.
Definition widget.hpp:260
void scroll_to_show(hi::aarectangle rectangle) noexcept override
Scroll to show the given rectangle on the window.
Definition widget.hpp:321
void set_layout(widget_layout const &context) noexcept override
Update the internal layout of the widget.
Definition widget.hpp:116
generator< widget_intf & > children(bool include_invisible) noexcept override
Get a list of child widgets.
Definition widget.hpp:61
void draw(draw_context const &context) noexcept override
Draw the widget.
Definition widget.hpp:121
void request_redraw() const noexcept override
Request the widget to be redrawn on the next frame.
Definition widget.hpp:136
virtual hitbox hitbox_test_from_parent(point2 position) const noexcept
Call hitbox_test from a parent widget.
Definition widget.hpp:84
widget() noexcept
Constructor for creating sub views.
Definition widget.hpp:50
hitbox hitbox_test(point2 position) const noexcept override
Find the widget that is under the mouse cursor.
Definition widget.hpp:73
box_constraints update_constraints() noexcept override
Update the constraints of the widget.
Definition widget.hpp:110
bool process_event(gui_event const &event) const noexcept override
Send a event to the window.
Definition widget.hpp:125
bool handle_event(gui_event const &event) noexcept override
Handle command.
Definition widget.hpp:145
observer< extent2 > maximum
The maximum size this widget is allowed to be.
Definition widget.hpp:46
T addressof(T... args)
T max(T... args)
T reverse(T... args)