HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
window_widget.hpp
Go to the documentation of this file.
1// Copyright Take Vos 2020-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 "toolbar_widget.hpp"
13#include "grid_widget.hpp"
14// #include "window_traffic_lights_widget.hpp"
15#include "../GUI/module.hpp"
16#include "../label.hpp"
17#include <memory>
18
19namespace hi { inline namespace v1 {
20
27template<fixed_string Name = "">
28class window_widget final : public widget {
29public:
30 using super = widget;
31 constexpr static auto prefix = Name / "window";
32
33 observer<label> title;
34
35 window_widget(forward_of<observer<label>> auto&& title) noexcept : super(nullptr), title(hi_forward(title))
36 {
37 _toolbar = std::make_unique<toolbar_widget<prefix>>(this);
38
39 if (operating_system::current == operating_system::windows) {
40#if HI_OPERATING_SYSTEM == HI_OS_WINDOWS
41 _system_menu = &_toolbar->make_widget<system_menu_widget<prefix>>();
42 this->_system_menu->icon = this->title.get<"icon">();
43#endif
44 //_toolbar->make_widget<window_traffic_lights_widget<prefix>, horizontal_alignment::right>();
45 } else if (operating_system::current == operating_system::macos) {
46 //_toolbar->make_widget<window_traffic_lights_widget<prefix>>();
47 } else {
49 }
50
51 _content = std::make_unique<grid_widget<prefix>>(this);
52 }
53
54 void set_window(gui_window& window) noexcept
55 {
56 _window = std::addressof(window);
57 }
58
63 [[nodiscard]] grid_widget<prefix>& content() noexcept
64 {
65 hi_axiom(loop::main().on_thread());
66 hi_assert_not_null(_content);
67 return *_content;
68 }
69
74 [[nodiscard]] toolbar_widget<prefix>& toolbar() noexcept
75 {
76 hi_axiom(loop::main().on_thread());
77 hi_assert_not_null(_toolbar);
78 return *_toolbar;
79 }
80
82 [[nodiscard]] generator<widget const&> children(bool include_invisible) const noexcept override
83 {
84 co_yield *_toolbar;
85 co_yield *_content;
86 }
87
88 [[nodiscard]] box_constraints update_constraints() noexcept override
89 {
90 hi_assert_not_null(_content);
91 hi_assert_not_null(_toolbar);
92
93 _content_constraints = _content->update_constraints();
94 _toolbar_constraints = _toolbar->update_constraints();
95
96 auto r = box_constraints{};
97 r.minimum.width() = std::max(
98 _toolbar_constraints.margins.left() + _toolbar_constraints.minimum.width() + _toolbar_constraints.margins.right(),
99 _content_constraints.margins.left() + _content_constraints.minimum.width() + _content_constraints.margins.right());
100 r.preferred.width() = std::max(
101 _toolbar_constraints.margins.left() + _toolbar_constraints.preferred.width() + _toolbar_constraints.margins.right(),
102 _content_constraints.margins.left() + _content_constraints.preferred.width() + _content_constraints.margins.right());
103 r.maximum.width() = std::min(
104 _toolbar_constraints.margins.left() + _toolbar_constraints.maximum.width() + _toolbar_constraints.margins.right(),
105 _content_constraints.margins.left() + _content_constraints.maximum.width() + _content_constraints.margins.right());
106
107 // clang-format off
108 r.minimum.height() =
109 _toolbar_constraints.margins.top() +
110 _toolbar_constraints.preferred.height() +
111 std::max(_toolbar_constraints.margins.bottom(), _content_constraints.margins.top()) +
112 _content_constraints.minimum.height() +
113 _content_constraints.margins.bottom();
114 r.preferred.height() =
115 _toolbar_constraints.margins.top() +
116 _toolbar_constraints.preferred.height() +
117 std::max(_toolbar_constraints.margins.bottom(), _content_constraints.margins.top()) +
118 _content_constraints.preferred.height() +
119 _content_constraints.margins.bottom();
120 r.maximum.height() =
121 _toolbar_constraints.margins.top() +
122 _toolbar_constraints.preferred.height() +
123 std::max(_toolbar_constraints.margins.bottom(), _content_constraints.margins.top()) +
124 _content_constraints.maximum.height() +
125 _content_constraints.margins.bottom();
126 // clang-format on
127
128 // The operating system also has a minimum and maximum size, these sizes
129 // are more important than the calculated sizes.
130 inplace_max(r.minimum.width(), os_settings::minimum_window_width());
131 inplace_max(r.minimum.height(), os_settings::minimum_window_height());
132
133 inplace_clamp(r.maximum.width(), r.minimum.width(), os_settings::maximum_window_width());
134 inplace_clamp(r.maximum.height(), r.minimum.height(), os_settings::maximum_window_height());
135
136 inplace_clamp(r.preferred.width(), r.minimum.width(), r.maximum.width());
137 inplace_clamp(r.preferred.height(), r.minimum.height(), r.maximum.height());
138
139 _can_resize_width = r.minimum.width() != r.maximum.width();
140 _can_resize_height = r.minimum.height() != r.maximum.height();
141
142 return r;
143 }
144
145 void set_layout(widget_layout const& context) noexcept override
146 {
147 if (compare_store(layout, context)) {
148 hilet toolbar_height = _toolbar_constraints.preferred.height();
149 hilet between_margin = std::max(_toolbar_constraints.margins.bottom(), _content_constraints.margins.top());
150
151 hilet toolbar_rectangle = aarectangle{
152 point2{
153 _toolbar_constraints.margins.left(), context.height() - toolbar_height - _toolbar_constraints.margins.top()},
154 point2{
155 context.width() - _toolbar_constraints.margins.right(),
156 context.height() - _toolbar_constraints.margins.top()}};
157 _toolbar_shape = box_shape{_toolbar_constraints, toolbar_rectangle, theme<prefix>.cap_height(this)};
158
159 hilet content_rectangle = aarectangle{
160 point2{_content_constraints.margins.left(), _content_constraints.margins.bottom()},
161 point2{context.width() - _content_constraints.margins.right(), toolbar_rectangle.bottom() - between_margin}};
162 _content_shape = box_shape{_content_constraints, content_rectangle, theme<prefix>.cap_height(this)};
163 }
164 _toolbar->set_layout(context.transform(_toolbar_shape));
165 _content->set_layout(context.transform(_content_shape));
166 }
167
168 void draw(widget_draw_context& context) noexcept override
169 {
171 context.draw_box(
172 layout,
173 layout.shape.rectangle,
174 theme<prefix>.background_color(this),
175 theme<prefix>.border_color(this),
176 theme<prefix>.border_width(this),
178 theme<prefix>.border_radius(this)
179 );
180
181 _toolbar->draw(context);
182 _content->draw(context);
183 }
184 }
185
186 [[nodiscard]] hitbox hitbox_test(point2 position) const noexcept override
187 {
188 constexpr float BORDER_WIDTH = 10.0f;
189
190 hi_axiom(loop::main().on_thread());
191
192 auto r = _toolbar->hitbox_test_from_parent(position);
193 r = _content->hitbox_test_from_parent(position, r);
194
195 hilet is_on_l_edge = position.x() <= BORDER_WIDTH;
196 hilet is_on_r_edge = position.x() >= (layout.width() - BORDER_WIDTH);
197 hilet is_on_b_edge = position.y() <= BORDER_WIDTH;
198 hilet is_on_t_edge = position.y() >= (layout.height() - BORDER_WIDTH);
199
200 // Corner resize has always priority.
201 if (is_on_l_edge and is_on_b_edge) {
202 if (_can_resize_width and _can_resize_height) {
203 return {id, layout.elevation, hitbox_type::bottom_left_resize_corner};
204 } else if (_can_resize_width) {
205 return {id, layout.elevation, hitbox_type::left_resize_border};
206 } else if (_can_resize_height) {
207 return {id, layout.elevation, hitbox_type::bottom_resize_border};
208 }
209 } else if (is_on_r_edge and is_on_b_edge) {
210 if (_can_resize_width and _can_resize_height) {
211 return {id, layout.elevation, hitbox_type::bottom_right_resize_corner};
212 } else if (_can_resize_width) {
213 return {id, layout.elevation, hitbox_type::right_resize_border};
214 } else if (_can_resize_height) {
215 return {id, layout.elevation, hitbox_type::bottom_resize_border};
216 }
217 } else if (is_on_l_edge and is_on_t_edge) {
218 if (_can_resize_width and _can_resize_height) {
219 return {id, layout.elevation, hitbox_type::top_left_resize_corner};
220 } else if (_can_resize_width) {
221 return {id, layout.elevation, hitbox_type::left_resize_border};
222 } else if (_can_resize_height) {
223 return {id, layout.elevation, hitbox_type::top_resize_border};
224 }
225 } else if (is_on_r_edge and is_on_t_edge) {
226 if (_can_resize_width and _can_resize_height) {
227 return {id, layout.elevation, hitbox_type::top_right_resize_corner};
228 } else if (_can_resize_width) {
229 return {id, layout.elevation, hitbox_type::right_resize_border};
230 } else if (_can_resize_height) {
231 return {id, layout.elevation, hitbox_type::top_resize_border};
232 }
233 }
234
235 // Border resize only has priority if there is no scroll-bar in the way.
236 if (r.type != hitbox_type::scroll_bar) {
237 if (is_on_l_edge and _can_resize_width) {
238 return {id, layout.elevation, hitbox_type::left_resize_border};
239 } else if (is_on_r_edge and _can_resize_width) {
240 return {id, layout.elevation, hitbox_type::right_resize_border};
241 } else if (is_on_b_edge and _can_resize_height) {
242 return {id, layout.elevation, hitbox_type::bottom_resize_border};
243 } else if (is_on_t_edge and _can_resize_height) {
244 return {id, layout.elevation, hitbox_type::top_resize_border};
245 }
246 }
247
248 return r;
249 }
250
251 bool handle_event(gui_event const& event) noexcept override
252 {
253 using enum gui_event_type;
254
255 switch (event.type()) {
256 case gui_toolbar_open:
257 process_event(
258 gui_event::window_set_keyboard_target(id, keyboard_focus_group::toolbar, keyboard_focus_direction::forward));
259 return true;
260 }
261 return super::handle_event(event);
262 }
263
264 bool process_event(gui_event const& event) const noexcept override
265 {
266 if (_window == nullptr) {
267 // Widgets may start sending messages such as request_reconstrain
268 // before a window is assigned. Capture the messages for replay.
269 _events_before_window.push_back(event);
270 return true;
271
272 } else {
273 if (not _events_before_window.empty()) {
274 for (hilet& e : _events_before_window) {
275 _window->process_event(e);
276 }
277 _events_before_window.clear();
278 _events_before_window.shrink_to_fit();
279 }
280 return _window->process_event(event);
281 }
282 }
284private:
287 gui_window *_window = nullptr;
288
289 mutable std::vector<gui_event> _events_before_window = {};
290
292 box_constraints _content_constraints;
293 box_shape _content_shape;
294
296 box_constraints _toolbar_constraints;
297 box_shape _toolbar_shape;
298
299 mutable bool _can_resize_width;
300 mutable bool _can_resize_height;
301
302#if HI_OPERATING_SYSTEM == HI_OS_WINDOWS
303 system_menu_widget<prefix> *_system_menu = nullptr;
304#endif
305};
306
307}} // namespace hi::v1
Functionality for labels, text and icons.
Defines system_menu_widget.
Defines toolbar_widget.
Defines grid_widget.
#define hi_no_default(...)
This part of the code should not be reachable, unless a programming bug.
Definition assert.hpp:279
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:253
#define hi_assert_not_null(x,...)
Assert if an expression is not nullptr.
Definition assert.hpp:238
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition utility.hpp:29
gui_event_type
GUI event type.
Definition gui_event_type.hpp:21
@ invisible
The widget is invisible.
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
@ outside
The border is drawn outside the edge of a quad.
bool compare_store(T &lhs, U &&rhs) noexcept
Compare then store if there was a change.
Definition utility.hpp:212
constexpr value_type & width() noexcept
Access the x-as-width element from the extent.
Definition extent.hpp:166
constexpr value_type & height() noexcept
Access the y-as-height element from the extent.
Definition extent.hpp:177
Definition widget.hpp:26
widget_id id
The numeric identifier of a widget.
Definition widget.hpp:35
virtual bool handle_event(gui_event const &event) noexcept
Handle command.
Definition widget.hpp:236
observer< widget_mode > mode
The widget mode.
Definition widget.hpp:49
2D constraints.
Definition box_constraints.hpp:22
A GUI widget that lays out child-widgets in a grid with variable sized cells.
Definition grid_widget.hpp:41
The system menu widget.
Definition system_menu_widget.hpp:27
A toolbar widget is located at the top of a window and lays out its children horizontally.
Definition toolbar_widget.hpp:37
The top-level window widget.
Definition window_widget.hpp:28
toolbar_widget< prefix > & toolbar() noexcept
Get a reference to window's toolbar widget.
Definition window_widget.hpp:74
grid_widget< prefix > & content() noexcept
Get a reference to the window's content widget.
Definition window_widget.hpp:63
T addressof(T... args)
T clear(T... args)
T empty(T... args)
T max(T... args)
T min(T... args)
T push_back(T... args)
T shrink_to_fit(T... args)