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 "widget.hpp"
12#include "toolbar_widget.hpp"
14#include "grid_widget.hpp"
17#include "../l10n/l10n.hpp"
18#include "../macros.hpp"
19#include <coroutine>
20
21hi_export_module(hikogui.widgets.window_widget);
22
23hi_export namespace hi {
24inline namespace v1 {
25
32class window_widget : public widget {
33public:
34 using super = widget;
35
36 observer<label> title;
37
38 template<forward_of<observer<label>> Title>
39 window_widget(Title&& title) noexcept : super(), title(std::forward<Title>(title))
40 {
41 _toolbar = std::make_unique<toolbar_widget>(this);
42
43#if HI_OPERATING_SYSTEM == HI_OS_WINDOWS
44 _system_menu = &_toolbar->emplace<system_menu_widget>();
45 this->_system_menu->icon = this->title.sub<"icon">();
47
48#elif HI_OPERATING_SYSTEM == HI_OS_MACOS
49 _toolbar->emplace<window_controls_macos_widget>();
50
51#else
52#error "Not implemented"
53#endif
54
55 _content = std::make_unique<grid_widget>(this);
56 }
57
62 [[nodiscard]] color background_color() noexcept
63 {
64 hi_axiom(loop::main().on_thread());
65 return theme().color(semantic_color::fill, _layout.layer);
66 }
67
72 [[nodiscard]] grid_widget& content() noexcept
73 {
74 hi_axiom(loop::main().on_thread());
75 hi_assert_not_null(_content);
76 return *_content;
77 }
78
84 [[nodiscard]] toolbar_widget& toolbar() noexcept
85 {
86 hi_axiom(loop::main().on_thread());
87 hi_assert_not_null(_toolbar);
88 return *_toolbar;
89 }
90
92 [[nodiscard]] generator<widget_intf&> children(bool include_invisible) noexcept override
93 {
94 co_yield *_toolbar;
95 co_yield *_content;
96 }
97 [[nodiscard]] box_constraints update_constraints() noexcept override
98 {
99 hi_assert_not_null(_content);
100 hi_assert_not_null(_toolbar);
101
102 _layout = {};
103 _content_constraints = _content->update_constraints();
104 _toolbar_constraints = _toolbar->update_constraints();
105
106 auto r = box_constraints{};
107 r.minimum.width() = std::max(
108 _toolbar_constraints.margins.left() + _toolbar_constraints.minimum.width() + _toolbar_constraints.margins.right(),
109 _content_constraints.margins.left() + _content_constraints.minimum.width() + _content_constraints.margins.right());
110 r.preferred.width() = std::max(
111 _toolbar_constraints.margins.left() + _toolbar_constraints.preferred.width() + _toolbar_constraints.margins.right(),
112 _content_constraints.margins.left() + _content_constraints.preferred.width() + _content_constraints.margins.right());
113 r.maximum.width() = std::min(
114 _toolbar_constraints.margins.left() + _toolbar_constraints.maximum.width() + _toolbar_constraints.margins.right(),
115 _content_constraints.margins.left() + _content_constraints.maximum.width() + _content_constraints.margins.right());
116
117 // clang-format off
118 r.minimum.height() =
119 _toolbar_constraints.margins.top() +
120 _toolbar_constraints.preferred.height() +
121 std::max(_toolbar_constraints.margins.bottom(), _content_constraints.margins.top()) +
122 _content_constraints.minimum.height() +
123 _content_constraints.margins.bottom();
124 r.preferred.height() =
125 _toolbar_constraints.margins.top() +
126 _toolbar_constraints.preferred.height() +
127 std::max(_toolbar_constraints.margins.bottom(), _content_constraints.margins.top()) +
128 _content_constraints.preferred.height() +
129 _content_constraints.margins.bottom();
130 r.maximum.height() =
131 _toolbar_constraints.margins.top() +
132 _toolbar_constraints.preferred.height() +
133 std::max(_toolbar_constraints.margins.bottom(), _content_constraints.margins.top()) +
134 _content_constraints.maximum.height() +
135 _content_constraints.margins.bottom();
136 // clang-format on
137
138 // The operating system also has a minimum and maximum size, these sizes
139 // are more important than the calculated sizes.
140 inplace_max(r.minimum.width(), os_settings::minimum_window_width());
141 inplace_max(r.minimum.height(), os_settings::minimum_window_height());
142
143 inplace_clamp(r.maximum.width(), r.minimum.width(), os_settings::maximum_window_width());
144 inplace_clamp(r.maximum.height(), r.minimum.height(), os_settings::maximum_window_height());
145
146 inplace_clamp(r.preferred.width(), r.minimum.width(), r.maximum.width());
147 inplace_clamp(r.preferred.height(), r.minimum.height(), r.maximum.height());
148
149 _can_resize_width = r.minimum.width() != r.maximum.width();
150 _can_resize_height = r.minimum.height() != r.maximum.height();
151
152 return r;
153 }
154
155 void set_layout(widget_layout const& context) noexcept override
156 {
157 if (compare_store(_layout, context)) {
158 auto const toolbar_height = _toolbar_constraints.preferred.height();
159 auto const between_margin = std::max(_toolbar_constraints.margins.bottom(), _content_constraints.margins.top());
160
161 auto const toolbar_rectangle = aarectangle{
162 point2{
163 _toolbar_constraints.margins.left(), context.height() - toolbar_height - _toolbar_constraints.margins.top()},
164 point2{
165 context.width() - _toolbar_constraints.margins.right(),
166 context.height() - _toolbar_constraints.margins.top()}};
167 _toolbar_shape = box_shape{_toolbar_constraints, toolbar_rectangle, theme().baseline_adjustment()};
168
169 auto const content_rectangle = aarectangle{
170 point2{_content_constraints.margins.left(), _content_constraints.margins.bottom()},
171 point2{context.width() - _content_constraints.margins.right(), toolbar_rectangle.bottom() - between_margin}};
172 _content_shape = box_shape{_content_constraints, content_rectangle, theme().baseline_adjustment()};
173 }
174 _toolbar->set_layout(context.transform(_toolbar_shape));
175 _content->set_layout(context.transform(_content_shape));
176 }
177 void draw(draw_context const& context) noexcept override
178 {
179 if (mode() > widget_mode::invisible) {
180 context.draw_box(_layout, _layout.rectangle(), background_color(), background_color());
181
182 _toolbar->draw(context);
183 _content->draw(context);
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 auto const is_on_l_edge = position.x() <= BORDER_WIDTH;
196 auto const is_on_r_edge = position.x() >= (layout().width() - BORDER_WIDTH);
197 auto const is_on_b_edge = position.y() <= BORDER_WIDTH;
198 auto const 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 bool handle_event(gui_event const& event) noexcept override
251 {
252 using enum gui_event_type;
253
254 switch (event.type()) {
255 case gui_toolbar_open:
256 process_event(
257 gui_event::window_set_keyboard_target(id, keyboard_focus_group::toolbar, keyboard_focus_direction::forward));
258 return true;
259 default:;
260 }
261 return super::handle_event(event);
262 }
263 bool process_event(gui_event const& event) const noexcept override
264 {
265 if (_window) {
266 return _window->process_event(event);
267 } else {
268 // Since there is no window, pretend that the message was handled.
269 return true;
270 }
271 }
272 void set_window(gui_window* window) noexcept override
273 {
274 _window = window;
275 if (_window) {
276 _window->set_title(*title);
277 }
278 }
279 [[nodiscard]] gui_window* window() const noexcept override
280 {
281 return _window;
282 }
284private:
285 gui_window* _window = nullptr;
286
288 box_constraints _content_constraints;
289 box_shape _content_shape;
290
292 box_constraints _toolbar_constraints;
293 box_shape _toolbar_shape;
294
295 mutable bool _can_resize_width;
296 mutable bool _can_resize_height;
297
298#if HI_OPERATING_SYSTEM == HI_OS_WINDOWS
299 system_menu_widget* _system_menu = nullptr;
300#endif
301};
302
303} // namespace v1
304} // namespace hi::v1
Defines widget.
Defines system_menu_widget.
Defines window_controls_macos_widget.
Defines window_controls_win32_widget.
Defines toolbar_widget.
Defines grid_widget.
@ right
Align the text to the right side.
gui_event_type
GUI event type.
Definition gui_event_type.hpp:24
@ invisible
The widget is invisible.
The HikoGUI namespace.
Definition array_generic.hpp:20
bool compare_store(T &lhs, U &&rhs) noexcept
Compare then store if there was a change.
Definition misc.hpp:53
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
This is a RGBA floating point color.
Definition color_intf.hpp:49
constexpr float & width() noexcept
Access the x-as-width element from the extent.
Definition extent2.hpp:107
constexpr float & height() noexcept
Access the y-as-height element from the extent.
Definition extent2.hpp:118
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
2D constraints.
Definition box_constraints.hpp:25
A observer pointing to the whole or part of a observed_base.
Definition observer_intf.hpp:32
auto sub(auto const &index) const noexcept
Create a sub-observer by indexing into the value.
Definition observer_intf.hpp:471
A GUI widget that lays out child-widgets in a grid with variable sized cells.
Definition grid_widget.hpp:43
The system menu widget.
Definition system_menu_widget.hpp:30
A toolbar widget is located at the top of a window and lays out its children horizontally.
Definition toolbar_widget.hpp:38
An interactive graphical object as part of the user-interface.
Definition widget.hpp:37
widget() noexcept
Constructor for creating sub views.
Definition widget.hpp:55
bool handle_event(gui_event const &event) noexcept override
Handle command.
Definition widget.hpp:150
Window control button widget.
Definition window_controls_macos_widget.hpp:29
Window control button widget.
Definition window_controls_win32_widget.hpp:29
The top-level window widget.
Definition window_widget.hpp:32
grid_widget & content() noexcept
Get a reference to the window's content widget.
Definition window_widget.hpp:72
toolbar_widget & toolbar() noexcept
Get a reference to window's toolbar widget.
Definition window_widget.hpp:84
color background_color() noexcept
The background color of the window.
Definition window_widget.hpp:62
T max(T... args)
T min(T... args)