HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
widget_intf.hpp
1// Copyright Take Vos 2023.
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
5#pragma once
6
7#include "hitbox.hpp"
8#include "widget_layout.hpp"
9#include "widget_id.hpp"
10#include "widget_state.hpp"
11#include "keyboard_focus_group.hpp"
12#include "../layout/layout.hpp"
13#include "../GFX/GFX.hpp"
14#include "../telemetry/telemetry.hpp"
15#include "../theme/theme.hpp"
16#include "../macros.hpp"
17#include <coroutine>
18
19hi_export_module(hikogui.GUI : widget_intf);
20
21hi_export namespace hi {
22inline namespace v1 {
23class gui_window;
24
25class widget_intf {
26public:
31 widget_id id = {};
32
40
41 gui_window *window = nullptr;
42
46
50
51 virtual ~widget_intf()
52 {
53 release_widget_id(id);
54 }
55
56 widget_intf() noexcept : id(make_widget_id())
57 {
58 _style_cbt = style.subscribe([&](style_modify_mask mask, bool path_has_changed) {
59 if (path_has_changed) {
60 // When the path has changed of a style, the style of the children
61 // may change as well. Therefor we update the parent-path of the
62 // children to trigger a re-evaluation of the style.
63 ++global_counter<"widget:style:path">;
64 for (auto &child : children()) {
65 child.style.set_parent_path(style.path());
66 }
67 }
68
69 if (to_bool(mask & style_modify_mask::layout)) {
70 // The layout has changed which means its size may have changed
71 // which would require a re-layout and re-constrain of the widget.
72 ++global_counter<"widget:style:reconstrain">;
74
75 } else if (to_bool(mask & style_modify_mask::redraw)) {
76 // The color attributes, or border magnitude has changed which
77 // should only change the widget visually and not the layout.
78 ++global_counter<"widget:style:redraw">;
80 }
81 });
82
83 // This lambda allows the state to be set once before it will trigger
84 // notifications.
85 _state_cbt = state.subscribe([&](widget_state new_state) {
86 style.set_pseudo_class(new_state.pseudo_class());
87
88 static std::optional<widget_state> old_state = std::nullopt;
89
90 if (old_state) {
91 if (need_reconstrain(*old_state, *state)) {
92 ++global_counter<"widget:state:reconstrain">;
94
95 } else if (need_relayout(*old_state, *state)) {
96 ++global_counter<"widget:state:relayout">;
98
99 } else if (need_redraw(*old_state, *state)) {
100 ++global_counter<"widget:state:redraw">;
102 }
103 }
104 old_state = *state;
105 });
106 }
107
113 [[nodiscard]] widget_intf *parent() const noexcept
114 {
115 return _parent;
116 }
117
123 virtual void set_parent(widget_intf *new_parent) noexcept;
124
127 template<forward_of<void()> Func>
128 [[nodiscard]] callback<void()> subscribe(Func&& func, callback_flags flags = callback_flags::synchronous) noexcept
129 {
130 return notifier.subscribe(std::forward<Func>(func), flags);
131 }
132
135 [[nodiscard]] auto operator co_await() const noexcept
136 {
137 return notifier.operator co_await();
138 }
139
140 [[nodiscard]] size_t layer() const noexcept
141 {
142 return state->layer();
143 }
144
145 void set_layer(size_t new_layer) noexcept
146 {
147 state->set_layer(new_layer);
148 }
149
150 [[nodiscard]] widget_mode mode() const noexcept
151 {
152 return state->mode();
153 }
154
155 void set_mode(widget_mode new_mode) noexcept
156 {
157 state->set_mode(new_mode);
158 }
159
160 [[nodiscard]] widget_value value() const noexcept
161 {
162 return state->value();
163 }
164
165 void set_value(widget_value new_value) noexcept
166 {
167 state->set_value(new_value);
168 }
169
170 [[nodiscard]] widget_phase phase() const noexcept
171 {
172 return state->phase();
173 }
174
175 void set_pressed(bool pressed) noexcept
176 {
177 state->set_pressed(pressed);
178 }
179
180 void set_hover(bool hover) noexcept
181 {
182 state->set_hover(hover);
183 }
184
185 void set_active(bool active) noexcept
186 {
187 state->set_active(active);
188 }
189
190 [[nodiscard]] bool focus() const noexcept
191 {
192 return state->focus();
193 }
194
195 void set_focus(bool new_focus) noexcept
196 {
197 state->set_focus(new_focus);
198 }
199
202 [[nodiscard]] virtual generator<widget_intf&> children(bool include_invisible = true) noexcept = 0;
203
206 [[nodiscard]] virtual generator<widget_intf const&> children(bool include_invisible = true) const noexcept final
207 {
208 for (auto& child : const_cast<widget_intf *>(this)->children(include_invisible)) {
209 co_yield child;
210 }
211 }
212
224 [[nodiscard]] virtual box_constraints update_constraints() noexcept = 0;
225
237 virtual void set_layout(widget_layout const& context) noexcept = 0;
238
241 [[nodiscard]] widget_layout const& layout() const noexcept
242 {
243 return _layout;
244 }
245
260 virtual void draw(draw_context const& context) noexcept = 0;
261
269 [[nodiscard]] virtual hitbox hitbox_test(point2 position) const noexcept = 0;
270
274 [[nodiscard]] virtual bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept = 0;
275
278 virtual void request_redraw() const noexcept = 0;
279
282 virtual bool process_event(gui_event const& event) const noexcept = 0;
283
288 virtual bool handle_event(gui_event const& event) noexcept = 0;
289
298 gui_event const& event,
299 std::vector<widget_id> const& reject_list = std::vector<widget_id>{}) noexcept = 0;
300
315 [[nodiscard]] virtual widget_id find_next_widget(
316 widget_id current_keyboard_widget,
317 keyboard_focus_group group,
318 keyboard_focus_direction direction) const noexcept = 0;
319
323 [[nodiscard]] std::vector<widget_id> parent_chain() const noexcept
324 {
326
327 if (auto w = this) {
328 chain.push_back(w->id);
329 while ((w = w->parent())) {
330 chain.push_back(w->id);
331 }
332 }
333
334 return chain;
335 }
336
343 virtual void scroll_to_show(hi::aarectangle rectangle) noexcept = 0;
344
347 void scroll_to_show() noexcept
348 {
350 }
351
352protected:
353 callback<void(widget_state)> _state_cbt;
354 hi::style::callback_type _style_cbt;
355
356 widget_layout _layout;
357
358private:
359 widget_intf *_parent = nullptr;
360};
361
362inline widget_intf *get_if(widget_intf *start, widget_id id, bool include_invisible) noexcept
363{
364 hi_assert_not_null(start);
365
366 if (start->id == id) {
367 return start;
368 }
369 for (auto& child : start->children(include_invisible)) {
370 if (auto const r = get_if(&child, id, include_invisible); r != nullptr) {
371 return r;
372 }
373 }
374 return nullptr;
375}
376
377inline widget_intf& get(widget_intf& start, widget_id id, bool include_invisible)
378{
379 if (auto r = get_if(std::addressof(start), id, include_invisible); r != nullptr) {
380 return *r;
381 }
382 throw not_found_error("get widget by id");
383}
384
385template<std::invocable<widget_intf&> Func>
386inline void apply(widget_intf& start, Func &&func, bool include_invisible = true)
387{
388 auto todo = std::vector<widget_intf *>{&start};
389
390 while (not todo.empty()) {
391 auto *tmp = todo.back();
392 todo.pop_back();
393
394 func(*tmp);
395
396 for (auto &child : tmp->children(include_invisible)) {
397 todo.push_back(&child);
398 }
399 }
400}
401
402inline void apply_window_data(widget_intf& start, gui_window *new_window, unit::pixel_density const& new_density, style::attributes_from_theme_type const& new_attributes_from_theme)
403{
404 apply(start, [&](widget_intf& w) {
405 w.window = new_window;
406 w.style.set_pixel_density(new_density);
407 w.style.set_attributes_from_theme(new_attributes_from_theme);
408 });
409}
410
411inline void widget_intf::set_parent(widget_intf *new_parent) noexcept
412{
413 _parent = new_parent;
414 apply_window_data(
415 *this,
416 new_parent ? new_parent->window : nullptr,
417 new_parent ? new_parent->style.pixel_density() : unit::pixel_density{},
418 new_parent ? new_parent->style.attributes_from_theme() : style::attributes_from_theme_type{});
419
420 // The path will automatically propagate to the child widgets.
421 style.set_parent_path(new_parent ? new_parent->style.path() : style_path{});
422}
423
424
425} // namespace v1
426} // namespace hi
@ window_relayout
Request that widgets get laid out on the next frame.
Definition gui_event_type.hpp:47
@ window_reconstrain
Request that widget get constraint on the next frame.
Definition gui_event_type.hpp:48
@ rectangle
The gui_event has rectangle data.
Definition gui_event_variant.hpp:44
widget_mode
The mode that the widget is operating at.
Definition widget_state.hpp:26
STL namespace.
The HikoGUI namespace.
Definition array_generic.hpp:21
The HikoGUI API version 1.
Definition array_generic.hpp:22
style_modify_mask
Definition style_modify_mask.hpp:16
@ layout
A layout (size, alignment) value was modified.
Definition style_modify_mask.hpp:55
@ redraw
Only visual changes.
Definition style_modify_mask.hpp:51
Definition callback.hpp:77
Class which represents an axis-aligned rectangle.
Definition aarectangle.hpp:33
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:25
notifier< void()> notifier
Notifier which is called after an action is completed by a widget.
Definition widget_intf.hpp:45
std::vector< widget_id > parent_chain() const noexcept
Get a list of parents of a given widget.
Definition widget_intf.hpp:323
virtual bool handle_event_recursive(gui_event const &event, std::vector< widget_id > const &reject_list=std::vector< widget_id >{}) noexcept=0
Handle command recursive.
virtual void request_redraw() const noexcept=0
Request the widget to be redrawn on the next frame.
widget_layout const & layout() const noexcept
Get the current layout for this widget.
Definition widget_intf.hpp:241
void scroll_to_show() noexcept
Scroll to show the important part of the widget.
Definition widget_intf.hpp:347
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 bool process_event(gui_event const &event) const noexcept=0
Send a event to the window.
virtual bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept=0
Check if the widget will accept keyboard focus.
virtual box_constraints update_constraints() noexcept=0
Update the constraints of the widget.
virtual widget_id find_next_widget(widget_id current_keyboard_widget, keyboard_focus_group group, keyboard_focus_direction direction) const noexcept=0
Find the next widget that handles keyboard focus.
virtual void set_layout(widget_layout const &context) noexcept=0
Update the internal layout of the widget.
virtual void scroll_to_show(hi::aarectangle rectangle) noexcept=0
Scroll to show the given rectangle on the window.
virtual bool handle_event(gui_event const &event) noexcept=0
Handle command.
virtual hitbox hitbox_test(point2 position) const noexcept=0
Find the widget that is under the mouse cursor.
virtual void draw(draw_context const &context) noexcept=0
Draw the widget.
virtual void set_parent(widget_intf *new_parent) noexcept
Set the parent widget.
Definition widget_intf.hpp:411
callback< void()> subscribe(Func &&func, callback_flags flags=callback_flags::synchronous) noexcept
Subscribe a callback to be called when an action is completed by the widget.
Definition widget_intf.hpp:128
hi::style style
The style of this widget.
Definition widget_intf.hpp:39
observer< widget_state > state
The current state of the widget.
Definition widget_intf.hpp:49
The layout of a widget.
Definition widget_layout.hpp:56
The state the widget is in.
Definition widget_state.hpp:105
2D constraints.
Definition box_constraints.hpp:25
A observer pointing to the whole or part of a observed_base.
Definition observer_intf.hpp:32
Definition style.hpp:22
callback_type subscribe(Func &&func, callback_flags flags=callback_flags::synchronous) noexcept
Add a callback to the style.
Definition style.hpp:246
Definition style_path.hpp:33
Definition pixel_density.hpp:18
Exception thrown when an item was not found.
Definition exception_intf.hpp:149
T addressof(T... args)
T forward(T... args)
T push_back(T... args)