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
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 "../GUI/GUI.hpp"
18#include "../macros.hpp"
19#include <memory>
20#include <vector>
21#include <string>
22#include <ranges>
23
24hi_export_module(hikogui.widgets.widget);
25
26hi_export namespace hi { inline namespace v1 {
27
37class widget : public widget_intf {
38public:
42
45 observer<extent2> maximum = extent2::large();
46
49 explicit widget(widget_intf const* parent) noexcept : widget_intf(parent)
50 {
51 }
52
55 explicit widget() noexcept : widget_intf(nullptr)
56 {
57 }
58
59 virtual ~widget() {}
60 widget(const widget&) = delete;
61 widget& operator=(const widget&) = delete;
62 widget(widget&&) = delete;
63 widget& operator=(widget&&) = delete;
64
66 [[nodiscard]] generator<widget_intf&> children(bool include_invisible) noexcept override
67 {
68 co_return;
69 }
70
78 [[nodiscard]] hitbox hitbox_test(point2 position) const noexcept override
79 {
80 return {};
81 }
82
89 [[nodiscard]] virtual hitbox hitbox_test_from_parent(point2 position) const noexcept
90 {
91 return hitbox_test(_layout.from_parent * position);
92 }
93
101 [[nodiscard]] virtual hitbox hitbox_test_from_parent(point2 position, hitbox sibling_hitbox) const noexcept
102 {
103 return std::max(sibling_hitbox, hitbox_test(_layout.from_parent * position));
104 }
105
109 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
110 {
111 hi_axiom(loop::main().on_thread());
112 return false;
113 }
114
115 [[nodiscard]] box_constraints update_constraints() noexcept override
116 {
117 _layout = {};
118 return {*minimum, *minimum, *maximum};
119 }
120
121 void set_layout(widget_layout const& context) noexcept override
122 {
123 _layout = context;
124 }
125
126 void draw(draw_context const& context) noexcept override {}
127
130 bool process_event(gui_event const& event) const noexcept override
131 {
132 if (parent != nullptr) {
133 return parent->process_event(event);
134 } else {
135 return true;
136 }
137 }
138
145
150 bool handle_event(gui_event const& event) noexcept override
151 {
152 hi_axiom(loop::main().on_thread());
153
154 switch (event.type()) {
155 case gui_event_type::keyboard_enter:
156 set_focus(true);
157 this->scroll_to_show();
158 return true;
159
160 case gui_event_type::keyboard_exit:
161 set_focus(false);
162 return true;
163
164 case gui_event_type::mouse_enter:
165 set_hover(true);
166 return true;
167
168 case gui_event_type::mouse_exit:
169 set_hover(false);
170 return true;
171
173 set_active(true);
174 // All widgets need the active value set.
175 return false;
176
178 set_active(false);
179 // All widgets need the active value unset.
180 return false;
181
182 case gui_event_type::gui_activate_stay:
183 process_event(gui_event_type::gui_activate);
184 if (accepts_keyboard_focus(keyboard_focus_group::menu)) {
185 // By going forward and backward we select the current parent,
186 // the widget that opened the menu-stack.
187 process_event(gui_event_type::gui_widget_next);
188 process_event(gui_event_type::gui_widget_prev);
189 }
190 return true;
191
192 case gui_event_type::gui_activate_next:
193 process_event(gui_event_type::gui_activate);
194 return process_event(gui_event_type::gui_widget_next);
195
196 case gui_event_type::gui_widget_next:
198 gui_event::window_set_keyboard_target(id, keyboard_focus_group::normal, keyboard_focus_direction::forward));
199 return true;
200
201 case gui_event_type::gui_widget_prev:
203 gui_event::window_set_keyboard_target(id, keyboard_focus_group::normal, keyboard_focus_direction::backward));
204 return true;
205
206 case gui_event_type::gui_menu_next:
207 if (mode() >= widget_mode::partial and accepts_keyboard_focus(keyboard_focus_group::menu)) {
209 gui_event::window_set_keyboard_target(id, keyboard_focus_group::menu, keyboard_focus_direction::forward));
210 return true;
211 }
212 break;
213
214 case gui_event_type::gui_menu_prev:
215 if (mode() >= widget_mode::partial and accepts_keyboard_focus(keyboard_focus_group::menu)) {
217 gui_event::window_set_keyboard_target(id, keyboard_focus_group::menu, keyboard_focus_direction::backward));
218 return true;
219 }
220 break;
221
222 case gui_event_type::gui_toolbar_next:
223 if (mode() >= widget_mode::partial and accepts_keyboard_focus(keyboard_focus_group::toolbar)) {
225 gui_event::window_set_keyboard_target(id, keyboard_focus_group::toolbar, keyboard_focus_direction::forward));
226 return true;
227 }
228 break;
229
230 case gui_event_type::gui_toolbar_prev:
231 if (mode() >= widget_mode::partial and accepts_keyboard_focus(keyboard_focus_group::toolbar)) {
233 gui_event::window_set_keyboard_target(id, keyboard_focus_group::toolbar, keyboard_focus_direction::backward));
234 return true;
235 }
236 break;
237
238 default:;
239 }
240
241 return false;
242 }
243
245 gui_event const& event,
246 std::vector<widget_id> const& reject_list = std::vector<widget_id>{}) noexcept override
247 {
248 hi_axiom(loop::main().on_thread());
249
250 auto handled = false;
251
252 for (auto& child : this->children(false)) {
253 handled |= child.handle_event_recursive(event, reject_list);
254 }
255
256 if (!std::ranges::any_of(reject_list, [&](auto const& x) {
257 return x == id;
258 })) {
259 handled |= handle_event(event);
260 }
261
262 return handled;
263 }
264
265 [[nodiscard]] virtual widget_id find_next_widget(
266 widget_id current_keyboard_widget,
267 keyboard_focus_group group,
268 keyboard_focus_direction direction) const noexcept override
269 {
270 hi_axiom(loop::main().on_thread());
271
272 auto found = false;
273
274 if (current_keyboard_widget == 0 and accepts_keyboard_focus(group)) {
275 // If there was no current_keyboard_widget, then return this if it accepts focus.
276 return id;
277
278 } else if (current_keyboard_widget == id) {
279 found = true;
280 }
281
282 auto children_ = std::vector<widget_intf const *>{};
283 for (auto& child : children(false)) {
284 children_.push_back(std::addressof(child));
285 }
286
287 if (direction == keyboard_focus_direction::backward) {
288 std::reverse(begin(children_), end(children_));
289 }
290
291 for (auto *child : children_) {
292 hi_axiom_not_null(child);
293
294 if (found) {
295 // Find the first focus accepting widget.
296 if (auto tmp = child->find_next_widget({}, group, direction); tmp != 0) {
297 return tmp;
298 }
299
300 } else {
301 auto tmp = child->find_next_widget(current_keyboard_widget, group, direction);
302 if (tmp == current_keyboard_widget) {
303 // The current widget was found, but no next widget available in the child.
304 // Try the first widget that does accept keyboard focus.
305 found = true;
306
307 } else if (tmp != 0) {
308 // Return the next widget that was found in the child-widget.
309 return tmp;
310 }
311 }
312 }
313
314 if (found) {
315 // Either:
316 // 1. current_keyboard_widget was nullptr; this widget, nor its child widgets accept focus.
317 // 2. current_keyboard_wigget was this; none of the child widgets accept focus.
318 // 3. current_keyboard_widget is a child; none of the following widgets accept focus.
319 return current_keyboard_widget;
320 }
321
322 return {};
323 }
324
327 {
328 hi_axiom(loop::main().on_thread());
329
330 if (parent) {
331 parent->scroll_to_show(_layout.to_parent * rectangle);
332 }
333 }
334
335 void set_window(gui_window *window) noexcept override
336 {
337 if (parent) {
338 return parent->set_window(window);
339 } else {
340 return;
341 }
342 }
343
344 [[nodiscard]] gui_window *window() const noexcept override
345 {
346 if (parent) {
347 return parent->window();
348 } else {
349 return nullptr;
350 }
351 }
352
353 [[nodiscard]] hi::theme const& theme() const noexcept
354 {
355 auto const w = window();
356 hi_assert_not_null(w);
357 return w->theme;
358 }
359
360 [[nodiscard]] gfx_surface const *surface() const noexcept
361 {
362 if (auto w = window()) {
363 return w->surface.get();
364 } else {
365 return nullptr;
366 }
367 }
368
369 [[nodiscard]] virtual color background_color() const noexcept
370 {
371 if (mode() >= widget_mode::partial) {
372 if (phase() == widget_phase::hover) {
373 return theme().color(semantic_color::fill, _layout.layer + 1);
374 } else {
375 return theme().color(semantic_color::fill, _layout.layer);
376 }
377 } else {
378 return theme().color(semantic_color::fill, _layout.layer - 1);
379 }
380 }
381
382 [[nodiscard]] virtual color foreground_color() const noexcept
383 {
384 if (mode() >= widget_mode::partial) {
385 if (phase() == widget_phase::hover) {
386 return theme().color(semantic_color::border, _layout.layer + 1);
387 } else {
388 return theme().color(semantic_color::border, _layout.layer);
389 }
390 } else {
391 return theme().color(semantic_color::border, _layout.layer - 1);
392 }
393 }
394
395 [[nodiscard]] virtual color focus_color() const noexcept
396 {
397 if (mode() >= widget_mode::partial) {
398 if (focus()) {
399 return theme().color(semantic_color::accent);
400 } else if (phase() == widget_phase::hover) {
401 return theme().color(semantic_color::border, _layout.layer + 1);
402 } else {
403 return theme().color(semantic_color::border, _layout.layer);
404 }
405 } else {
406 return theme().color(semantic_color::border, _layout.layer - 1);
407 }
408 }
409
410 [[nodiscard]] virtual color accent_color() const noexcept
411 {
412 if (mode() >= widget_mode::partial) {
413 return theme().color(semantic_color::accent);
414 } else {
415 return theme().color(semantic_color::border, _layout.layer - 1);
416 }
417 }
418
419 [[nodiscard]] virtual color label_color() const noexcept
420 {
421 if (mode() >= widget_mode::partial) {
422 return theme().text_style(semantic_text_style::label)->color;
423 } else {
424 return theme().color(semantic_color::border, _layout.layer - 1);
425 }
426 }
427
428protected:
438 [[nodiscard]] aarectangle make_overlay_rectangle(aarectangle requested_rectangle) const noexcept
439 {
440 hi_axiom(loop::main().on_thread());
441
442 // Move the request_rectangle to window coordinates.
443 auto const requested_window_rectangle = translate2{layout().clipping_rectangle_on_window()} * requested_rectangle;
444 auto const window_bounds = aarectangle{layout().window_size} - theme().margin<float>();
445 auto const response_window_rectangle = fit(window_bounds, requested_window_rectangle);
446 return layout().from_window * response_window_rectangle;
447 }
448};
449
450}} // namespace hi::v1
@ end
Start from the end of the file.
@ begin
Start from the beginning of the file.
@ window_redraw
Request that part of the window gets redrawn on the next frame.
@ window_deactivate
The window is not longer the top-window.
@ window_activate
The window becomes the top-window.
@ partial
A widget is partially enabled.
The HikoGUI namespace.
Definition array_generic.hpp:20
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
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
Definition widget_intf.hpp:24
virtual gui_window * window() const noexcept=0
Get the window that the widget is owned by.
widget_id id
The numeric identifier of a widget.
Definition widget_intf.hpp:30
widget_layout const & layout() const noexcept
Get the current layout for this widget.
Definition widget_intf.hpp:206
void scroll_to_show() noexcept
Scroll to show the important part of the widget.
Definition widget_intf.hpp:312
widget_intf * parent
Pointer to the parent widget.
Definition widget_intf.hpp:35
virtual bool process_event(gui_event const &event) const noexcept=0
Send a event to the window.
virtual void scroll_to_show(hi::aarectangle rectangle) noexcept=0
Scroll to show the given rectangle on the window.
virtual generator< widget_intf & > children(bool include_invisible) noexcept=0
Get a list of child widgets.
virtual void set_window(gui_window *window) noexcept=0
Set the window for this tree of widgets.
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:37
bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
Check if the widget will accept keyboard focus.
Definition widget.hpp:109
observer< extent2 > minimum
The minimum size this widget is allowed to be.
Definition widget.hpp:41
virtual hitbox hitbox_test_from_parent(point2 position, hitbox sibling_hitbox) const noexcept
Call hitbox_test from a parent widget.
Definition widget.hpp:101
void scroll_to_show() noexcept
Scroll to show the important part of the widget.
Definition widget_intf.hpp:312
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:244
gui_window * window() const noexcept override
Get the window that the widget is owned by.
Definition widget.hpp:344
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:265
void scroll_to_show(hi::aarectangle rectangle) noexcept override
Scroll to show the given rectangle on the window.
Definition widget.hpp:326
void set_layout(widget_layout const &context) noexcept override
Update the internal layout of the widget.
Definition widget.hpp:121
generator< widget_intf & > children(bool include_invisible) noexcept override
Get a list of child widgets.
Definition widget.hpp:66
void draw(draw_context const &context) noexcept override
Draw the widget.
Definition widget.hpp:126
void request_redraw() const noexcept override
Request the widget to be redrawn on the next frame.
Definition widget.hpp:141
virtual hitbox hitbox_test_from_parent(point2 position) const noexcept
Call hitbox_test from a parent widget.
Definition widget.hpp:89
widget() noexcept
Constructor for creating sub views.
Definition widget.hpp:55
hitbox hitbox_test(point2 position) const noexcept override
Find the widget that is under the mouse cursor.
Definition widget.hpp:78
box_constraints update_constraints() noexcept override
Update the constraints of the widget.
Definition widget.hpp:115
void set_window(gui_window *window) noexcept override
Set the window for this tree of widgets.
Definition widget.hpp:335
widget(widget_intf const *parent) noexcept
Constructor for creating sub views.
Definition widget.hpp:49
bool process_event(gui_event const &event) const noexcept override
Send a event to the window.
Definition widget.hpp:130
bool handle_event(gui_event const &event) noexcept override
Handle command.
Definition widget.hpp:150
observer< extent2 > maximum
The maximum size this widget is allowed to be.
Definition widget.hpp:45
T addressof(T... args)
T max(T... args)
T push_back(T... args)
T reverse(T... args)