HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
window_traffic_lights_widget.hpp
Go to the documentation of this file.
1// Copyright Take Vos 2020-2021.
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 "../GUI/module.hpp"
12#include "../font/module.hpp"
13#include <memory>
14#include <string>
15#include <array>
16
17namespace hi { inline namespace v1 {
18
26template<fixed_string Name = "">
28public:
29 using super = widget;
30 constexpr static auto prefix = Name / "traffic-lights";
31
33
35 [[nodiscard]] box_constraints update_constraints() noexcept override
36 {
37 if (operating_system::current == operating_system::windows) {
38 hilet theme_size = theme<prefix / "windows.size", int>{}(this);
39 hilet size = extent2i{theme_size * 3, theme_size};
40 return {size, size, size};
41
42 } else if (operating_system::current == operating_system::macos) {
43 hilet theme_size = theme<prefix / "macos.size", int>{}(this);
44 hilet margin = theme<prefix / "margin", int>{}(this);
45 hilet spacing = theme<prefix>.int_spacing_horizontal(this);
46 hilet size = extent2i{theme_size * 3 + 2 * margin + 2 * spacing, theme_size + 2 * spacing};
47 return {size, size, size};
48
49 } else {
51 }
52 }
53
54 void set_layout(widget_layout const& context) noexcept override
55 {
56 if (compare_store(layout, context)) {
57 auto extent = context.size();
58 auto y = context.height() - extent.height();
59
60 if (operating_system::current == operating_system::windows) {
61 closeRectangle =
62 aarectanglei{point2i(extent.width() * 2 / 3, y), extent2i{extent.width() * 1 / 3, extent.height()}};
63
64 maximizeRectangle =
65 aarectanglei{point2i(extent.width() * 1 / 3, y), extent2i{extent.width() * 1 / 3, extent.height()}};
66
67 minimizeRectangle = aarectanglei{point2i(0, y), extent2i{extent.width() * 1 / 3, extent.height()}};
68
69 } else if (operating_system::current == operating_system::macos) {
70 hilet size = theme<prefix / "macos.size", int>{}(this);
71 hilet margin = theme<prefix / "margin", int>{}(this);
72 hilet spacing = theme<prefix>.int_spacing_horizontal(this);
73
74 closeRectangle = aarectanglei{point2i(margin, (extent.height() - size) / 2), extent2i{size, size}};
75
76 minimizeRectangle =
77 aarectanglei{point2i(margin + size + spacing, (extent.height() - size) / 2), extent2i{size, size}};
78
79 maximizeRectangle = aarectanglei{
80 point2i(margin + size + spacing + size + spacing, (extent.height() - size) / 2), extent2i{size, size}};
81 } else {
83 }
84
85 closeWindowGlyph = find_glyph(hikogui_icon::CloseWindow);
86 minimizeWindowGlyph = find_glyph(hikogui_icon::MinimizeWindow);
87
88 if (operating_system::current == operating_system::windows) {
89 maximizeWindowGlyph = find_glyph(hikogui_icon::MaximizeWindowMS);
90 restoreWindowGlyph = find_glyph(hikogui_icon::RestoreWindowMS);
91
92 } else if (operating_system::current == operating_system::macos) {
93 maximizeWindowGlyph = find_glyph(hikogui_icon::MaximizeWindowMacOS);
94 restoreWindowGlyph = find_glyph(hikogui_icon::RestoreWindowMacOS);
95 } else {
97 }
98
99 hilet glyph_size = operating_system::current == operating_system::macos ?
100 theme<prefix / "macos.icon.size", float>{}(this) :
101 theme<prefix / "windows.icon.size", float>{}(this);
102
103 hilet closeWindowGlyphBB = narrow_cast<aarectanglei>(closeWindowGlyph.get_bounding_rectangle() * glyph_size);
104 hilet minimizeWindowGlyphBB = narrow_cast<aarectanglei>(minimizeWindowGlyph.get_bounding_rectangle() * glyph_size);
105 hilet maximizeWindowGlyphBB = narrow_cast<aarectanglei>(maximizeWindowGlyph.get_bounding_rectangle() * glyph_size);
106 hilet restoreWindowGlyphBB = narrow_cast<aarectanglei>(restoreWindowGlyph.get_bounding_rectangle() * glyph_size);
107
108 closeWindowGlyphRectangle = align(closeRectangle, closeWindowGlyphBB, alignment::middle_center());
109 minimizeWindowGlyphRectangle = align(minimizeRectangle, minimizeWindowGlyphBB, alignment::middle_center());
110 maximizeWindowGlyphRectangle = align(maximizeRectangle, maximizeWindowGlyphBB, alignment::middle_center());
111 restoreWindowGlyphRectangle = align(maximizeRectangle, restoreWindowGlyphBB, alignment::middle_center());
112 }
113 }
114
115 void draw(widget_draw_context const& context) noexcept override
116 {
117 if (*mode > widget_mode::invisible and overlaps(context, layout)) {
118 if (operating_system::current == operating_system::macos) {
119 drawMacOS(context);
120
121 } else if (operating_system::current == operating_system::windows) {
122 drawWindows(context);
123
124 } else {
126 }
127 }
128 }
129
130 bool handle_event(gui_event const& event) noexcept override
131 {
132 switch (event.type()) {
133 case gui_event_type::mouse_move:
134 case gui_event_type::mouse_drag:
135 {
136 // Check the hover states of each button.
137 auto state_has_changed = false;
138 state_has_changed |= compare_store(hoverClose, closeRectangle.contains(event.mouse().position));
139 state_has_changed |= compare_store(hoverMinimize, minimizeRectangle.contains(event.mouse().position));
140 state_has_changed |= compare_store(hoverMaximize, maximizeRectangle.contains(event.mouse().position));
141 if (state_has_changed) {
143 }
144 }
145 break;
146
147 case gui_event_type::mouse_exit:
148 hoverClose = false;
149 hoverMinimize = false;
150 hoverMaximize = false;
152 return super::handle_event(event);
153
154 case gui_event_type::mouse_down:
155 if (event.mouse().cause.left_button) {
156 if (closeRectangle.contains(event.mouse().position)) {
157 pressedClose = true;
158
159 } else if (minimizeRectangle.contains(event.mouse().position)) {
160 pressedMinimize = true;
161
162 } else if (maximizeRectangle.contains(event.mouse().position)) {
163 pressedMaximize = true;
164 }
166 return true;
167 }
168 break;
169
170 case gui_event_type::mouse_up:
171 if (event.mouse().cause.left_button) {
172 pressedClose = false;
173 pressedMinimize = false;
174 pressedMaximize = false;
176
177 if (closeRectangle.contains(event.mouse().position)) {
178 return process_event({gui_event_type::window_close});
179
180 } else if (minimizeRectangle.contains(event.mouse().position)) {
181 return process_event({gui_event_type::window_minimize});
182
183 } else if (maximizeRectangle.contains(event.mouse().position)) {
184 switch (layout.window_size_state) {
185 case gui_window_size::normal:
186 return process_event({gui_event_type::window_maximize});
187
188 case gui_window_size::maximized:
189 return process_event({gui_event_type::window_normalize});
190
191 default:
193 }
194 }
195 return true;
196 }
197 break;
198
199 default:;
200 }
201 return super::handle_event(event);
202 }
203
204 [[nodiscard]] hitbox hitbox_test(point2i position) const noexcept override
205 {
206 hi_axiom(loop::main().on_thread());
207
208 if (*mode >= widget_mode::partial and layout.contains(position) and
209 (closeRectangle.contains(position) or minimizeRectangle.contains(position) or maximizeRectangle.contains(position))) {
210 return hitbox{id, layout.elevation, hitbox_type::button};
211 } else {
212 return {};
213 }
214 }
216private:
217 aarectanglei closeRectangle;
218 aarectanglei minimizeRectangle;
219 aarectanglei maximizeRectangle;
220
221 font_book::font_glyph_type closeWindowGlyph;
222 font_book::font_glyph_type minimizeWindowGlyph;
223 font_book::font_glyph_type maximizeWindowGlyph;
224 font_book::font_glyph_type restoreWindowGlyph;
225
226 aarectanglei closeWindowGlyphRectangle;
227 aarectanglei minimizeWindowGlyphRectangle;
228 aarectanglei maximizeWindowGlyphRectangle;
229 aarectanglei restoreWindowGlyphRectangle;
230
231 bool hoverClose = false;
232 bool hoverMinimize = false;
233 bool hoverMaximize = false;
234
235 bool pressedClose = false;
236 bool pressedMinimize = false;
237 bool pressedMaximize = false;
238
239 void drawMacOS(widget_draw_context const& context) noexcept
240 {
241 context.draw_box(
242 layout,
243 closeRectangle,
244 theme<prefix / "windows.close.fill.color", color>{}(this),
245 corner_radii{closeRectangle.height() / 2.0f});
246
247 context.draw_box(
248 layout,
249 minimizeRectangle,
250 theme<prefix / "windows.minimize.fill.color", color>{}(this),
251 corner_radii{minimizeRectangle.height() / 2.0f});
252
253 context.draw_box(
254 layout,
255 maximizeRectangle,
256 theme<prefix / "windows.maximize.fill.color", color>{}(this),
257 corner_radii{maximizeRectangle.height() / 2.0f});
258
259 if (*hover) {
260 context.draw_glyph(
261 layout,
262 translate_z(0.1f) * narrow_cast<aarectangle>(closeWindowGlyphRectangle),
263 closeWindowGlyph,
264 theme<prefix / "windows.close.icon.color", color>{}(this));
265 context.draw_glyph(
266 layout,
267 translate_z(0.1f) * narrow_cast<aarectangle>(minimizeWindowGlyphRectangle),
268 minimizeWindowGlyph,
269 theme<prefix / "windows.minimize.icon.color", color>{}(this));
270
271 if (layout.window_size_state == gui_window_size::maximized) {
272 context.draw_glyph(
273 layout,
274 translate_z(0.1f) * narrow_cast<aarectangle>(restoreWindowGlyphRectangle),
275 restoreWindowGlyph,
276 theme<prefix / "windows.maximize.icon.color", color>{}(this));
277 } else {
278 context.draw_glyph(
279 layout,
280 translate_z(0.1f) * narrow_cast<aarectangle>(maximizeWindowGlyphRectangle),
281 maximizeWindowGlyph,
282 theme<prefix / "windows.maximize.icon.color", color>{}(this));
283 }
284 }
285 }
286
287 void drawWindows(widget_draw_context const& context) noexcept
288 {
289 context.draw_box(layout, closeRectangle, theme<prefix / "windows.close.fill.color", color>{}(this));
290 context.draw_box(layout, minimizeRectangle, theme<prefix / "windows.minimize.fill.color", color>{}(this));
291 context.draw_box(layout, maximizeRectangle, theme<prefix / "windows.maximize.fill.color", color>{}(this));
292
293 context.draw_glyph(
294 layout,
295 translate_z(0.1f) * narrow_cast<aarectangle>(closeWindowGlyphRectangle),
296 closeWindowGlyph,
297 theme<prefix / "windows.close.icon.color", color>{}(this));
298
299 context.draw_glyph(
300 layout,
301 translate_z(0.1f) * narrow_cast<aarectangle>(minimizeWindowGlyphRectangle),
302 minimizeWindowGlyph,
303 theme<prefix / "windows.minimize.icon.color", color>{}(this));
304
305 if (layout.window_size_state == gui_window_size::maximized) {
306 context.draw_glyph(
307 layout,
308 translate_z(0.1f) * narrow_cast<aarectangle>(restoreWindowGlyphRectangle),
309 restoreWindowGlyph,
310 theme<prefix / "windows.maximize.icon.color", color>{}(this));
311 } else {
312 context.draw_glyph(
313 layout,
314 translate_z(0.1f) * narrow_cast<aarectangle>(maximizeWindowGlyphRectangle),
315 maximizeWindowGlyph,
316 theme<prefix / "windows.maximize.icon.color", color>{}(this));
317 }
318 }
319};
320
321}} // namespace hi::v1
#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 hilet
Invariant should be the default for variables.
Definition utility.hpp:23
@ window_normalize
Request the window to be restored to the original size after a minimize and maximize commands.
@ window_minimize
Request the window to minimize.
@ window_close
Request the window to be closed.
@ window_maximize
Request the window to maximize.
@ partial
A widget is partially enabled.
@ invisible
The widget is invisible.
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
bool compare_store(T &lhs, U &&rhs) noexcept
Compare then store if there was a change.
Definition utility.hpp:212
auto theme
A tagged global variable to a theme model for a widget's component.
Definition theme_model.hpp:433
This is a RGBA floating point color.
Definition color.hpp:44
constexpr bool contains(point< value_type, 2 > const &rhs) const noexcept
Check if a 2D coordinate is inside the rectangle.
Definition axis_aligned_rectangle.hpp:265
constexpr value_type & width() noexcept
Access the x-as-width element from the extent.
Definition extent.hpp:166
point2i position
The current position of the mouse pointer.
Definition gui_event.hpp:37
mouse_buttons cause
Buttons which have caused this event.
Definition gui_event.hpp:55
A user interface event.
Definition gui_event.hpp:75
constexpr gui_event_type type() const noexcept
Get the event type.
Definition gui_event.hpp:214
mouse_event_data & mouse() noexcept
Get the mouse event information.
Definition gui_event.hpp:257
Definition widget.hpp:26
widget_id id
The numeric identifier of a widget.
Definition widget.hpp:35
observer< bool > hover
Mouse cursor is hovering over the widget.
Definition widget.hpp:57
virtual void request_redraw() const noexcept
Request the widget to be redrawn on the next frame.
Definition widget.hpp:270
virtual bool handle_event(gui_event const &event) noexcept
Handle command.
Definition widget.hpp:279
widget * parent
Pointer to the parent widget.
Definition widget.hpp:40
observer< widget_mode > mode
The widget mode.
Definition widget.hpp:53
Draw context for drawing using the HikoGUI shaders.
Definition widget_draw_context.hpp:204
The layout of a widget.
Definition widget_layout.hpp:37
constexpr bool contains(point3i mouse_position) const noexcept
Check if the mouse position is inside the widget.
Definition widget_layout.hpp:126
float elevation
The elevation of the widget above the window.
Definition widget_layout.hpp:72
2D constraints.
Definition box_constraints.hpp:22
Window control button widget.
Definition window_traffic_lights_widget.hpp:27