27 HWND win32Window =
nullptr;
49 mouse_cursor current_mouse_cursor = mouse_cursor::None;
55 bool resizing =
false;
85 if (not os_settings::start_subsystem()) {
86 hi_log_fatal(
"Could not start the os_settings subsystem.");
91 register_font_directories(font_dirs());
93 register_theme_directories(theme_dirs());
96 load_system_keyboard_bindings(URL{
"resource:win32.keybinds.json"});
98 hi_log_fatal(
"Could not load keyboard bindings. \"{}\"", e.
what());
101 _first_window =
true;
104 SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
106 _widget->set_window(
this);
111 _widget_constraints = _widget->update_constraints();
112 auto const new_size = _widget_constraints.preferred;
115 update_keyboard_target({});
119 _setting_change_cbt = os_settings::subscribe(
121 ++global_counter<
"gui_window:os_setting:constrain">;
122 this->process_event({gui_event_type::window_reconstrain});
124 callback_flags::main);
127 _selected_theme_cbt = theme_book::global().selected_theme.subscribe(
129 ++global_counter<
"gui_window:selected_theme:constrain">;
130 this->process_event({gui_event_type::window_reconstrain});
132 callback_flags::main);
134 _render_cbt = loop::main().subscribe_render([
this](utc_nanoseconds display_time) {
135 this->render(display_time);
140 create_window(new_size);
146 if (win32Window !=
nullptr) {
147 DestroyWindow(win32Window);
148 hi_assert(win32Window ==
nullptr);
153 hi_log_fatal(
"Could not properly destruct gui_window. '{}'", e.
what());
161 hi_log_info(
"Window '{}' has been properly destructed.", _title);
164 hi_log_fatal(
"Could not properly destruct gui_window. '{}'", e.
what());
168 template<
typename W
idget>
169 [[nodiscard]] Widget& widget() const noexcept
171 return up_cast<Widget>(*_widget);
174 void set_title(label title)
noexcept
182 void render(utc_nanoseconds display_time_point)
184 if (surface->device() ==
nullptr) {
189 auto const t1 =
trace<
"window::render">();
191 hi_axiom(loop::main().on_thread());
192 hi_assert_not_null(surface);
193 hi_assert_not_null(_widget);
197 auto need_reconstrain = _reconstrain.exchange(
false, std::memory_order_relaxed);
201 need_reconstrain =
true;
204 if (need_reconstrain) {
205 auto const t2 =
trace<
"window::constrain">();
209 _widget_constraints = _widget->update_constraints();
221 if (_resize.exchange(
false, std::memory_order::relaxed)) {
223 auto const current_size = rectangle.size();
224 auto const new_size = _widget_constraints.preferred;
225 if (new_size != current_size) {
226 hi_log_info(
"A new preferred window size {} was requested by one of the widget.", new_size);
227 set_window_size(new_size);
232 auto const current_size = rectangle.size();
233 auto const new_size = clamp(current_size, _widget_constraints.minimum, _widget_constraints.maximum);
234 if (new_size != current_size and size_state() != gui_window_size::minimized) {
235 hi_log_info(
"The current window size {} must grow or shrink to {} to fit the widgets.", current_size, new_size);
236 set_window_size(new_size);
240 if (rectangle.size() < _widget_constraints.minimum or rectangle.size() > _widget_constraints.maximum) {
248 surface->update(rectangle.size());
251 auto need_relayout = _relayout.exchange(
false, std::memory_order_relaxed);
255 need_relayout =
true;
258 if (need_reconstrain or need_relayout or widget_size != rectangle.size()) {
259 auto const t2 =
trace<
"window::layout">();
260 widget_size = rectangle.size();
264 auto const widget_layout_size = max(_widget_constraints.minimum, widget_size);
265 _widget->set_layout(widget_layout{widget_layout_size, _size_state,
subpixel_orientation(), display_time_point});
268 _redraw_rectangle = aarectangle{widget_size};
273 _redraw_rectangle = aarectangle{widget_size};
277 if (
auto draw_context = surface->render_start(_redraw_rectangle)) {
278 _redraw_rectangle = aarectangle{};
279 draw_context.display_time_point = display_time_point;
281 draw_context.saturation = 1.0f;
284 auto const t2 =
trace<
"window::draw">();
285 _widget->draw(draw_context);
288 auto const t2 =
trace<
"window::submit">();
289 surface->render_finish(draw_context);
298 hi_axiom(loop::main().on_thread());
300 if (current_mouse_cursor == cursor) {
303 current_mouse_cursor = cursor;
305 if (cursor == mouse_cursor::None) {
309 static auto idcAppStarting = LoadCursorW(
nullptr, IDC_APPSTARTING);
310 static auto idcArrow = LoadCursorW(
nullptr, IDC_ARROW);
311 static auto idcHand = LoadCursorW(
nullptr, IDC_HAND);
312 static auto idcIBeam = LoadCursorW(
nullptr, IDC_IBEAM);
313 static auto idcNo = LoadCursorW(
nullptr, IDC_NO);
317 case mouse_cursor::None:
318 idc = idcAppStarting;
320 case mouse_cursor::Default:
323 case mouse_cursor::Button:
326 case mouse_cursor::TextEdit:
340 hi_axiom(loop::main().on_thread());
341 if (not PostMessageW(win32Window, WM_CLOSE, 0, 0)) {
342 hi_log_error(
"Could not send WM_CLOSE to window {}: {}", _title, get_last_error_message());
353 hi_axiom(loop::main().on_thread());
355 if (_size_state == state) {
359 if (_size_state == gui_window_size::normal) {
360 _restore_rectangle = rectangle;
361 }
else if (_size_state == gui_window_size::minimized) {
362 ShowWindow(win32Window, SW_RESTORE);
363 _size_state = gui_window_size::normal;
366 if (state == gui_window_size::normal) {
367 auto const left = round_cast<int>(_restore_rectangle.left());
368 auto const top = round_cast<int>(_restore_rectangle.top());
369 auto const width = round_cast<int>(_restore_rectangle.width());
370 auto const height = round_cast<int>(_restore_rectangle.height());
371 auto const inv_top = round_cast<int>(os_settings::primary_monitor_rectangle().height()) - top;
372 SetWindowPos(win32Window, HWND_TOP, left, inv_top, width, height, 0);
373 _size_state = gui_window_size::normal;
375 }
else if (state == gui_window_size::minimized) {
376 ShowWindow(win32Window, SW_MINIMIZE);
377 _size_state = gui_window_size::minimized;
379 }
else if (state == gui_window_size::maximized) {
380 auto const workspace = workspace_rectangle();
381 auto const max_size = _widget_constraints.maximum;
384 auto const width =
std::min(max_size.width(), workspace.width());
385 auto const height =
std::min(max_size.height(), workspace.height());
386 auto const left = std::clamp(rectangle.left(), workspace.left(), workspace.right() - width);
387 auto const top = std::clamp(rectangle.top(), workspace.bottom() + height, workspace.top());
388 auto const inv_top = os_settings::primary_monitor_rectangle().height() - top;
392 round_cast<int>(left),
393 round_cast<int>(inv_top),
394 round_cast<int>(width),
395 round_cast<int>(height),
397 _size_state = gui_window_size::maximized;
399 }
else if (state == gui_window_size::fullscreen) {
400 auto const fullscreen = fullscreen_rectangle();
401 auto const max_size = _widget_constraints.maximum;
402 if (fullscreen.width() > max_size.width() or fullscreen.height() > max_size.height()) {
407 auto const left = round_cast<int>(fullscreen.left());
408 auto const top = round_cast<int>(fullscreen.top());
409 auto const width = round_cast<int>(fullscreen.width());
410 auto const height = round_cast<int>(fullscreen.height());
411 auto const inv_top = round_cast<int>(os_settings::primary_monitor_rectangle().height()) - top;
412 SetWindowPos(win32Window, HWND_TOP, left, inv_top, width, height, 0);
413 _size_state = gui_window_size::fullscreen;
421 auto const monitor = MonitorFromWindow(win32Window, MONITOR_DEFAULTTOPRIMARY);
422 if (monitor == NULL) {
423 hi_log_error(
"Could not get monitor for the window.");
424 return {0, 0, 1920, 1080};
428 info.cbSize =
sizeof(MONITORINFO);
429 if (not GetMonitorInfo(monitor, &info)) {
430 hi_log_error(
"Could not get monitor info for the window.");
431 return {0, 0, 1920, 1080};
434 auto const left = narrow_cast<float>(info.rcWork.left);
435 auto const top = narrow_cast<float>(info.rcWork.top);
436 auto const right = narrow_cast<float>(info.rcWork.right);
437 auto const bottom = narrow_cast<float>(info.rcWork.bottom);
438 auto const width = right - left;
439 auto const height = bottom - top;
440 auto const inv_bottom = os_settings::primary_monitor_rectangle().height() - bottom;
441 return aarectangle{left, inv_bottom, width, height};
448 auto const monitor = MonitorFromWindow(win32Window, MONITOR_DEFAULTTOPRIMARY);
449 if (monitor == NULL) {
450 hi_log_error(
"Could not get monitor for the window.");
451 return {0, 0, 1920, 1080};
455 info.cbSize =
sizeof(MONITORINFO);
456 if (not GetMonitorInfo(monitor, &info)) {
457 hi_log_error(
"Could not get monitor info for the window.");
458 return {0, 0, 1920, 1080};
461 auto const left = narrow_cast<float>(info.rcMonitor.left);
462 auto const top = narrow_cast<float>(info.rcMonitor.top);
463 auto const right = narrow_cast<float>(info.rcMonitor.right);
464 auto const bottom = narrow_cast<float>(info.rcMonitor.bottom);
465 auto const width = right - left;
466 auto const height = bottom - top;
467 auto const inv_bottom = os_settings::primary_monitor_rectangle().height() - bottom;
468 return aarectangle{left, inv_bottom, width, height};
487 constexpr auto tan_half_degree = 0.00872686779075879f;
488 constexpr auto viewing_distance = 20.0f;
490 auto const ppd = 2 * viewing_distance * pixel_density.ppi * tan_half_degree;
492 if (ppd > pixels_per_inch(55.0f)) {
494 return hi::subpixel_orientation::unknown;
497 return os_settings::subpixel_orientation();
507 hi_axiom(loop::main().on_thread());
510 auto const left = rectangle.left();
511 auto const top = rectangle.top() - 30.0f;
514 auto const inv_top = os_settings::primary_monitor_rectangle().height() - top;
517 auto const system_menu = GetSystemMenu(win32Window,
false);
519 TrackPopupMenu(system_menu, TPM_RETURNCMD, round_cast<int>(left), round_cast<int>(inv_top), 0, win32Window, NULL);
521 SendMessage(win32Window, WM_SYSCOMMAND, narrow_cast<WPARAM>(cmd), LPARAM{0});
529 hi_axiom(loop::main().on_thread());
532 if (not GetWindowRect(win32Window, &original_rect)) {
533 hi_log_error(
"Could not get the window's rectangle on the screen.");
536 auto const new_width = round_cast<int>(new_extent.width());
537 auto const new_height = round_cast<int>(new_extent.height());
538 auto const new_x = os_settings::left_to_right() ? narrow_cast<int>(original_rect.left) :
539 narrow_cast<int>(original_rect.right - new_width);
540 auto const new_y = narrow_cast<int>(original_rect.top);
549 SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_DEFERERASE | SWP_NOCOPYBITS | SWP_FRAMECHANGED);
552 void update_mouse_target(widget_id new_target_id,
point2 position = {})
noexcept
554 hi_axiom(loop::main().on_thread());
556 if (_mouse_target_id != 0) {
557 if (new_target_id == _mouse_target_id) {
563 send_events_to_widget(_mouse_target_id,
std::vector{gui_event{gui_event_type::mouse_exit}});
566 if (new_target_id != 0) {
567 _mouse_target_id = new_target_id;
568 send_events_to_widget(new_target_id,
std::vector{gui_event::make_mouse_enter(position)});
570 _mouse_target_id = {};
582 hi_axiom(loop::main().on_thread());
584 auto new_target_widget = get_if(_widget.get(), new_target_id,
false);
588 auto new_target_parent_chain = new_target_widget ? new_target_widget->parent_chain() :
std::vector<widget_id>{};
593 if (new_target_widget ==
nullptr or not new_target_widget->accepts_keyboard_focus(group)) {
594 new_target_widget =
nullptr;
597 if (
auto const *
const keyboard_target_widget = get_if(_widget.get(), _keyboard_target_id,
false)) {
599 if (new_target_widget == keyboard_target_widget) {
604 send_events_to_widget(_keyboard_target_id,
std::vector{gui_event{gui_event_type::keyboard_exit}});
608 _widget->handle_event_recursive(gui_event_type::gui_cancel, new_target_parent_chain);
611 if (new_target_widget !=
nullptr) {
612 _keyboard_target_id = new_target_widget->id;
613 send_events_to_widget(_keyboard_target_id,
std::vector{gui_event{gui_event_type::keyboard_enter}});
615 _keyboard_target_id = {};
629 hi_axiom(loop::main().on_thread());
631 if (
auto tmp = _widget->find_next_widget(start_widget, group, direction); tmp != start_widget) {
632 update_keyboard_target(tmp, group);
634 }
else if (group == keyboard_focus_group::normal) {
637 tmp = _widget->find_next_widget({}, group, direction);
638 update_keyboard_target(tmp, group);
651 return update_keyboard_target(_keyboard_target_id, group, direction);
663 if (not OpenClipboard(win32Window)) {
665 hi_log_info(
"Could not open win32 clipboard '{}'", get_last_error_message());
669 auto const defer_CloseClipboard = defer([] {
674 while ((format = EnumClipboardFormats(format)) != 0) {
680 auto const cb_data = GetClipboardData(CF_UNICODETEXT);
681 if (cb_data ==
nullptr) {
682 hi_log_error(
"Could not get clipboard data: '{}'", get_last_error_message());
686 auto const *
const wstr_c =
static_cast<wchar_t const *
>(GlobalLock(cb_data));
687 if (wstr_c ==
nullptr) {
688 hi_log_error(
"Could not lock clipboard data: '{}'", get_last_error_message());
692 auto const defer_GlobalUnlock = defer([cb_data] {
693 if (not GlobalUnlock(cb_data) and GetLastError() != ERROR_SUCCESS) {
694 hi_log_error(
"Could not unlock clipboard data: '{}'", get_last_error_message());
698 auto r =
to_gstring(hi::to_string(std::wstring_view(wstr_c)));
699 hi_log_debug(
"get_text_from_clipboard '{}'", to_string(r));
708 if (GetLastError() != ERROR_SUCCESS) {
709 hi_log_error(
"Could not enumerator clipboard formats: '{}'", get_last_error_message());
722 if (not OpenClipboard(win32Window)) {
724 hi_log_info(
"Could not open win32 clipboard '{}'", get_last_error_message());
728 auto const defer_CloseClipboard = defer([] {
732 if (not EmptyClipboard()) {
733 hi_log_error(
"Could not empty win32 clipboard '{}'", get_last_error_message());
739 auto wtext_handle = GlobalAlloc(GMEM_MOVEABLE, (wtext.size() + 1) *
sizeof(
wchar_t));
740 if (wtext_handle ==
nullptr) {
741 hi_log_error(
"Could not allocate clipboard data '{}'", get_last_error_message());
745 auto const defer_GlobalFree([&wtext_handle] {
746 if (wtext_handle !=
nullptr) {
747 GlobalFree(wtext_handle);
752 auto wtext_c =
static_cast<wchar_t *
>(GlobalLock(wtext_handle));
753 if (wtext_c ==
nullptr) {
754 hi_log_error(
"Could not lock string data '{}'", get_last_error_message());
758 auto const defer_GlobalUnlock = defer([wtext_handle] {
759 if (not GlobalUnlock(wtext_handle) and GetLastError() != ERROR_SUCCESS) {
760 hi_log_error(
"Could not unlock string data '{}'", get_last_error_message());
764 std::memcpy(wtext_c, wtext.c_str(), (wtext.size() + 1) *
sizeof(
wchar_t));
767 if (SetClipboardData(CF_UNICODETEXT, wtext_handle) ==
nullptr) {
768 hi_log_error(
"Could not set clipboard data '{}'", get_last_error_message());
772 wtext_handle =
nullptr;
776 [[nodiscard]] translate2 window_to_screen() const noexcept
778 return translate2{rectangle.left(), rectangle.bottom()};
781 [[nodiscard]] translate2 screen_to_window() const noexcept
783 return ~window_to_screen();
796 using enum gui_event_type;
798 hi_axiom(loop::main().on_thread());
800 switch (event.type()) {
802 _redraw_rectangle.fetch_or(event.rectangle());
805 case window_relayout:
806 _relayout.store(
true, std::memory_order_relaxed);
809 case window_reconstrain:
810 _reconstrain.store(
true, std::memory_order_relaxed);
814 _resize.store(
true, std::memory_order_relaxed);
817 case window_minimize:
818 set_size_state(gui_window_size::minimized);
821 case window_maximize:
822 set_size_state(gui_window_size::maximized);
825 case window_normalize:
826 set_size_state(gui_window_size::normal);
833 case window_open_sysmenu:
837 case window_set_keyboard_target:
839 auto const& target =
event.keyboard_target();
840 if (target.widget_id == 0) {
841 update_keyboard_target(target.group, target.direction);
842 }
else if (target.direction == keyboard_focus_direction::here) {
843 update_keyboard_target(target.widget_id, target.group);
845 update_keyboard_target(target.widget_id, target.group, target.direction);
850 case window_set_clipboard:
851 put_text_on_clipboard(event.clipboard_data());
854 case mouse_exit_window:
855 update_mouse_target({});
862 event.mouse().hitbox = _widget->hitbox_test(event.mouse().position);
863 if (event == mouse_down or event == mouse_move) {
864 update_mouse_target(event.mouse().hitbox.widget_id, event.mouse().position);
866 if (event == mouse_down) {
867 update_keyboard_target(event.mouse().hitbox.widget_id, keyboard_focus_group::all);
876 if (event.type() == keyboard_down) {
877 for (
auto& e : translate_keyboard_event(event)) {
882 for (
auto& event_ : events) {
883 if (event_.type() == gui_event_type::text_edit_paste) {
886 if (
auto optional_text = get_text_from_clipboard()) {
887 event_.clipboard_data() = *optional_text;
893 auto const handled = send_events_to_widget(
894 events.front().variant() == gui_event_variant::mouse ? _mouse_target_id : _keyboard_target_id, events);
900 for (
auto const event_ : events) {
901 if (event_ == gui_cancel) {
902 update_keyboard_target({}, keyboard_focus_group::all);
910 constexpr static UINT_PTR move_and_resize_timer_id = 2;
913 inline static bool _first_window =
true;
914 inline static const wchar_t *win32WindowClassName =
nullptr;
915 inline static WNDCLASSW win32WindowClass = {};
916 inline static bool win32WindowClassIsRegistered =
false;
917 inline static bool firstWindowHasBeenOpened =
false;
927 box_constraints _widget_constraints = {};
936 gui_window_size _size_state = gui_window_size::normal;
940 aarectangle _restore_rectangle;
949 utc_nanoseconds last_forced_redraw = {};
955 widget_id _mouse_target_id;
960 widget_id _keyboard_target_id;
962 TRACKMOUSEEVENT track_mouse_leave_event_parameters;
963 bool tracking_mouse_leave_event =
false;
964 char32_t high_surrogate = 0;
965 gui_event mouse_button_event;
966 utc_nanoseconds multi_click_time_point;
967 point2 multi_click_position;
968 uint8_t multi_click_count;
970 bool keymenu_pressed =
false;
972 callback<void()> _setting_change_cbt;
974 callback<void(utc_nanoseconds)> _render_cbt;
986 if (target_id == 0) {
988 target_id = _widget->id;
991 auto target_widget = get_if(_widget.
get(), target_id,
false);
992 while (target_widget) {
994 for (
auto const& event : events) {
995 if (target_widget->handle_event(target_widget->layout().from_window * event)) {
1001 target_widget = target_widget->parent;
1007 void setOSWindowRectangleFromRECT(RECT new_rectangle)
noexcept
1009 hi_axiom(loop::main().on_thread());
1012 auto const inv_bottom = os_settings::primary_monitor_rectangle().height() - new_rectangle.bottom;
1014 auto const new_screen_rectangle = aarectangle{
1015 narrow_cast<float>(new_rectangle.left),
1016 narrow_cast<float>(inv_bottom),
1017 narrow_cast<float>(new_rectangle.right - new_rectangle.left),
1018 narrow_cast<float>(new_rectangle.bottom - new_rectangle.top)};
1020 if (
rectangle.size() != new_screen_rectangle.size()) {
1021 ++global_counter<
"gui_window:os-resize:relayout">;
1022 this->process_event({gui_event_type::window_relayout});
1028 [[nodiscard]] keyboard_state get_keyboard_state() noexcept
1030 auto r = keyboard_state::idle;
1032 if (GetKeyState(VK_CAPITAL) != 0) {
1033 r |= keyboard_state::caps_lock;
1035 if (GetKeyState(VK_NUMLOCK) != 0) {
1036 r |= keyboard_state::num_lock;
1038 if (GetKeyState(VK_SCROLL) != 0) {
1039 r |= keyboard_state::scroll_lock;
1049 static_assert(std::is_signed_v<
decltype(GetAsyncKeyState(VK_SHIFT))>);
1051 auto r = keyboard_modifiers::none;
1053 if (GetAsyncKeyState(VK_SHIFT) < 0) {
1054 r |= keyboard_modifiers::shift;
1056 if (GetAsyncKeyState(VK_CONTROL) < 0) {
1057 r |= keyboard_modifiers::control;
1059 if (GetAsyncKeyState(VK_MENU) < 0) {
1060 r |= keyboard_modifiers::alt;
1062 if (GetAsyncKeyState(VK_LWIN) < 0 or GetAsyncKeyState(VK_RWIN) < 0) {
1063 r |= keyboard_modifiers::super;
1069 [[nodiscard]]
char32_t handle_suragates(
char32_t c)
noexcept
1071 hi_axiom(loop::main().on_thread());
1073 if (c >= 0xd800 && c <= 0xdbff) {
1074 high_surrogate = ((c - 0xd800) << 10) + 0x10000;
1077 }
else if (c >= 0xdc00 && c <= 0xdfff) {
1078 c = high_surrogate ? high_surrogate | (c - 0xdc00) : 0xfffd;
1084 [[nodiscard]] gui_event create_mouse_event(
unsigned int uMsg, uint64_t wParam, int64_t lParam)
noexcept
1086 hi_axiom(loop::main().on_thread());
1088 auto r = gui_event{gui_event_type::mouse_move};
1089 r.keyboard_modifiers = get_keyboard_modifiers();
1090 r.keyboard_state = get_keyboard_state();
1092 auto const x = narrow_cast<float>(GET_X_LPARAM(lParam));
1093 auto const y = narrow_cast<float>(GET_Y_LPARAM(lParam));
1096 auto const inv_y =
rectangle.height() - y;
1100 r.mouse().position = point2{x, inv_y};
1101 r.mouse().wheel_delta = {};
1102 if (uMsg == WM_MOUSEWHEEL) {
1103 r.mouse().wheel_delta.y() = GET_WHEEL_DELTA_WPARAM(wParam) * 10.0f / WHEEL_DELTA;
1104 }
else if (uMsg == WM_MOUSEHWHEEL) {
1105 r.mouse().wheel_delta.x() = GET_WHEEL_DELTA_WPARAM(wParam) * 10.0f / WHEEL_DELTA;
1109 r.mouse().down.left_button = (GET_KEYSTATE_WPARAM(wParam) & MK_LBUTTON) > 0;
1110 r.mouse().down.middle_button = (GET_KEYSTATE_WPARAM(wParam) & MK_MBUTTON) > 0;
1111 r.mouse().down.right_button = (GET_KEYSTATE_WPARAM(wParam) & MK_RBUTTON) > 0;
1112 r.mouse().down.x1_button = (GET_KEYSTATE_WPARAM(wParam) & MK_XBUTTON1) > 0;
1113 r.mouse().down.x2_button = (GET_KEYSTATE_WPARAM(wParam) & MK_XBUTTON2) > 0;
1118 case WM_LBUTTONDOWN:
1119 case WM_LBUTTONDBLCLK:
1120 r.mouse().cause.left_button =
true;
1123 case WM_RBUTTONDOWN:
1124 case WM_RBUTTONDBLCLK:
1125 r.mouse().cause.right_button =
true;
1128 case WM_MBUTTONDOWN:
1129 case WM_MBUTTONDBLCLK:
1130 r.mouse().cause.middle_button =
true;
1133 case WM_XBUTTONDOWN:
1134 case WM_XBUTTONDBLCLK:
1135 r.mouse().cause.x1_button = (GET_XBUTTON_WPARAM(wParam) & XBUTTON1) > 0;
1136 r.mouse().cause.x2_button = (GET_XBUTTON_WPARAM(wParam) & XBUTTON2) > 0;
1139 if (mouse_button_event == gui_event_type::mouse_down) {
1140 r.mouse().cause = mouse_button_event.mouse().cause;
1144 case WM_MOUSEHWHEEL:
1151 auto const a_button_is_pressed = r.mouse().down.left_button or r.mouse().down.middle_button or r.mouse().down.right_button or
1152 r.mouse().down.x1_button or r.mouse().down.x2_button;
1159 r.set_type(gui_event_type::mouse_up);
1160 if (mouse_button_event) {
1161 r.mouse().down_position = mouse_button_event.mouse().down_position;
1163 r.mouse().click_count = 0;
1165 if (!a_button_is_pressed) {
1170 case WM_LBUTTONDBLCLK:
1171 case WM_MBUTTONDBLCLK:
1172 case WM_RBUTTONDBLCLK:
1173 case WM_XBUTTONDBLCLK:
1174 case WM_LBUTTONDOWN:
1175 case WM_MBUTTONDOWN:
1176 case WM_RBUTTONDOWN:
1177 case WM_XBUTTONDOWN:
1179 auto const within_double_click_time = r.time_point - multi_click_time_point < os_settings::double_click_interval();
1180 auto const double_click_distance =
1181 std::sqrt(narrow_cast<float>(squared_hypot(r.mouse().position - multi_click_position)));
1182 auto const within_double_click_distance = double_click_distance < os_settings::double_click_distance();
1184 multi_click_count = within_double_click_time and within_double_click_distance ? multi_click_count + 1 : 1;
1185 multi_click_time_point = r.time_point;
1186 multi_click_position = r.mouse().position;
1188 r.set_type(gui_event_type::mouse_down);
1189 r.mouse().down_position = r.mouse().position;
1190 r.mouse().click_count = multi_click_count;
1193 hi_assert_not_null(win32Window);
1194 SetCapture(win32Window);
1199 case WM_MOUSEHWHEEL:
1200 r.set_type(gui_event_type::mouse_wheel);
1206 r.set_type(a_button_is_pressed ? gui_event_type::mouse_drag :
gui_event_type::mouse_move);
1207 if (mouse_button_event) {
1208 r.mouse().down_position = mouse_button_event.mouse().down_position;
1209 r.mouse().click_count = mouse_button_event.mouse().click_count;
1215 r.set_type(gui_event_type::mouse_exit_window);
1216 if (mouse_button_event) {
1217 r.mouse().down_position = mouse_button_event.mouse().down_position;
1219 r.mouse().click_count = 0;
1222 tracking_mouse_leave_event =
false;
1226 current_mouse_cursor = mouse_cursor::None;
1235 if (not tracking_mouse_leave_event and uMsg != WM_MOUSELEAVE) {
1236 auto *track_mouse_leave_event_parameters_p = &track_mouse_leave_event_parameters;
1237 if (not TrackMouseEvent(track_mouse_leave_event_parameters_p)) {
1240 tracking_mouse_leave_event =
true;
1245 if (r == gui_event_type::mouse_down or r == gui_event_type::mouse_up or r == gui_event_type::mouse_exit_window) {
1246 mouse_button_event = r;
1252 void create_window(extent2 new_size)
1255 hi_assert(loop::main().on_thread());
1257 createWindowClass();
1259 auto u16title =
to_wstring(std::format(
"{}", _title));
1261 hi_log_info(
"Create window of size {} with title '{}'", new_size, _title);
1264 SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
1268 win32Window = CreateWindowExW(
1270 win32WindowClassName,
1272 WS_OVERLAPPEDWINDOW,
1276 round_cast<int>(new_size.width()),
1277 round_cast<int>(new_size.height()),
1281 reinterpret_cast<HINSTANCE
>(crt_application_instance),
1283 if (win32Window ==
nullptr) {
1289 MARGINS m{0, 0, 0, 1};
1290 DwmExtendFrameIntoClientArea(win32Window, &m);
1294 win32Window,
nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
1296 if (!firstWindowHasBeenOpened) {
1297 auto const win32_window_ = win32Window;
1298 switch (gui_window_size::normal) {
1299 case gui_window_size::normal:
1300 ShowWindow(win32_window_, SW_SHOWNORMAL);
1302 case gui_window_size::minimized:
1303 ShowWindow(win32_window_, SW_SHOWMINIMIZED);
1305 case gui_window_size::maximized:
1306 ShowWindow(win32_window_, SW_SHOWMAXIMIZED);
1311 firstWindowHasBeenOpened =
true;
1314 track_mouse_leave_event_parameters.cbSize =
sizeof(track_mouse_leave_event_parameters);
1315 track_mouse_leave_event_parameters.dwFlags = TME_LEAVE;
1316 track_mouse_leave_event_parameters.hwndTrack = win32Window;
1317 track_mouse_leave_event_parameters.dwHoverTime = HOVER_DEFAULT;
1319 ShowWindow(win32Window, SW_SHOW);
1321 auto ppi_ = GetDpiForWindow(win32Window);
1323 throw gui_error(
"Could not retrieve dpi for window.");
1325 pixel_density = {pixels_per_inch(ppi_), os_settings::device_type()};
1327 surface = make_unique_gfx_surface(crt_application_instance, win32Window);
1330 int windowProc(
unsigned int uMsg, uint64_t wParam, int64_t lParam)
noexcept
1332 using namespace std::chrono_literals;
1334 gui_event mouse_event;
1335 auto const current_time = std::chrono::utc_clock::now();
1348 auto const createstruct_ptr = std::launder(std::bit_cast<CREATESTRUCT *>(lParam));
1350 new_rectangle.left = createstruct_ptr->x;
1351 new_rectangle.top = createstruct_ptr->y;
1352 new_rectangle.right = createstruct_ptr->x + createstruct_ptr->cx;
1353 new_rectangle.bottom = createstruct_ptr->y + createstruct_ptr->cy;
1354 setOSWindowRectangleFromRECT(new_rectangle);
1363 auto const height = [
this]() {
1364 hi_axiom(loop::main().on_thread());
1369 BeginPaint(win32Window, &ps);
1371 auto const update_rectangle = aarectangle{
1372 narrow_cast<float>(ps.rcPaint.left),
1373 narrow_cast<float>(height - ps.rcPaint.bottom),
1374 narrow_cast<float>(ps.rcPaint.right - ps.rcPaint.left),
1375 narrow_cast<float>(ps.rcPaint.bottom - ps.rcPaint.top)};
1378 hi_axiom(loop::main().on_thread());
1379 this->process_event({gui_event_type::window_redraw, update_rectangle});
1382 EndPaint(win32Window, &ps);
1387 hi_axiom(loop::main().on_thread());
1388 this->process_event({gui_event_type::window_redraw, aarectangle{
rectangle.size()}});
1394 hi_axiom(loop::main().on_thread());
1396 case SIZE_MAXIMIZED:
1397 ShowWindow(win32Window, SW_RESTORE);
1398 set_size_state(gui_window_size::maximized);
1400 case SIZE_MINIMIZED:
1401 _size_state = gui_window_size::minimized;
1404 _size_state = gui_window_size::normal;
1412 if (last_forced_redraw + 16.7ms < current_time) {
1415 loop::main().resume_once();
1416 last_forced_redraw = current_time;
1422 auto const& rect_ptr = *std::launder(std::bit_cast<RECT *>(lParam));
1423 if (rect_ptr.right < rect_ptr.left or rect_ptr.bottom < rect_ptr.top) {
1425 "Invalid RECT received on WM_SIZING: left={}, right={}, bottom={}, top={}",
1432 setOSWindowRectangleFromRECT(rect_ptr);
1439 auto const& rect_ptr = *std::launder(std::bit_cast<RECT *>(lParam));
1440 if (rect_ptr.right < rect_ptr.left or rect_ptr.bottom < rect_ptr.top) {
1442 "Invalid RECT received on WM_MOVING: left={}, right={}, bottom={}, top={}",
1449 setOSWindowRectangleFromRECT(rect_ptr);
1454 case WM_WINDOWPOSCHANGED:
1456 auto const windowpos_ptr = std::launder(std::bit_cast<WINDOWPOS *>(lParam));
1458 new_rectangle.left = windowpos_ptr->x;
1459 new_rectangle.top = windowpos_ptr->y;
1460 new_rectangle.right = windowpos_ptr->x + windowpos_ptr->cx;
1461 new_rectangle.bottom = windowpos_ptr->y + windowpos_ptr->cy;
1462 setOSWindowRectangleFromRECT(new_rectangle);
1466 case WM_ENTERSIZEMOVE:
1467 hi_axiom(loop::main().on_thread());
1468 if (SetTimer(win32Window, move_and_resize_timer_id, 16, NULL) != move_and_resize_timer_id) {
1474 case WM_EXITSIZEMOVE:
1475 hi_axiom(loop::main().on_thread());
1476 if (not KillTimer(win32Window, move_and_resize_timer_id)) {
1482 _size_state = gui_window_size::normal;
1483 this->process_event({gui_event_type::window_redraw, aarectangle{
rectangle.size()}});
1487 hi_axiom(loop::main().on_thread());
1491 this->process_event({gui_event_type::window_activate});
1494 this->process_event({gui_event_type::window_deactivate});
1497 hi_log_error(
"Unknown WM_ACTIVE value.");
1499 ++global_counter<
"gui_window:WM_ACTIVATE:constrain">;
1500 this->process_event({gui_event_type::window_reconstrain});
1503 case WM_GETMINMAXINFO:
1505 hi_axiom(loop::main().on_thread());
1506 auto const minmaxinfo = std::launder(std::bit_cast<MINMAXINFO *>(lParam));
1507 minmaxinfo->ptMaxSize.x = round_cast<LONG>(_widget_constraints.maximum.width());
1508 minmaxinfo->ptMaxSize.y = round_cast<LONG>(_widget_constraints.maximum.height());
1509 minmaxinfo->ptMinTrackSize.x = round_cast<LONG>(_widget_constraints.minimum.width());
1510 minmaxinfo->ptMinTrackSize.y = round_cast<LONG>(_widget_constraints.minimum.height());
1511 minmaxinfo->ptMaxTrackSize.x = round_cast<LONG>(_widget_constraints.maximum.width());
1512 minmaxinfo->ptMaxTrackSize.y = round_cast<LONG>(_widget_constraints.maximum.height());
1517 if (
auto c = char_cast<char32_t>(wParam); c == UNICODE_NOCHAR) {
1521 }
else if (
auto const gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1523 process_event(gui_event::keyboard_grapheme(grapheme{c}));
1528 if (
auto c = handle_suragates(char_cast<char32_t>(wParam))) {
1529 if (
auto const gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1531 process_event(gui_event::keyboard_partial_grapheme(grapheme{c}));
1537 if (
auto c = handle_suragates(char_cast<char32_t>(wParam))) {
1538 if (
auto const gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1540 process_event(gui_event::keyboard_grapheme(grapheme{c}));
1546 if (wParam == SC_KEYMENU) {
1547 keymenu_pressed =
true;
1548 process_event(gui_event{gui_event_type::keyboard_down, keyboard_virtual_key::menu});
1556 auto const extended = (narrow_cast<uint32_t>(lParam) & 0x01000000) != 0;
1557 auto const key_code = narrow_cast<int>(wParam);
1558 auto const key_modifiers = get_keyboard_modifiers();
1559 auto virtual_key = to_keyboard_virtual_key(key_code, extended, key_modifiers);
1561 if (std::exchange(keymenu_pressed,
false) and uMsg == WM_KEYDOWN and virtual_key == keyboard_virtual_key::space) {
1563 virtual_key = keyboard_virtual_key::sysmenu;
1566 if (virtual_key != keyboard_virtual_key::nul) {
1567 auto const key_state = get_keyboard_state();
1568 auto const event_type = uMsg == WM_KEYDOWN ? gui_event_type::keyboard_down : gui_event_type::keyboard_up;
1569 process_event(gui_event{event_type, virtual_key, key_modifiers, key_state});
1574 case WM_LBUTTONDOWN:
1575 case WM_MBUTTONDOWN:
1576 case WM_RBUTTONDOWN:
1577 case WM_XBUTTONDOWN:
1582 case WM_LBUTTONDBLCLK:
1583 case WM_MBUTTONDBLCLK:
1584 case WM_RBUTTONDBLCLK:
1585 case WM_XBUTTONDBLCLK:
1587 case WM_MOUSEHWHEEL:
1590 keymenu_pressed =
false;
1591 process_event(create_mouse_event(uMsg, wParam, lParam));
1595 if (wParam == TRUE) {
1611 hi_axiom(loop::main().on_thread());
1613 auto const x = narrow_cast<float>(GET_X_LPARAM(lParam));
1614 auto const y = narrow_cast<float>(GET_Y_LPARAM(lParam));
1617 auto const inv_y = os_settings::primary_monitor_rectangle().height() - y;
1619 auto const hitbox_type = _widget->hitbox_test(screen_to_window() * point2{x, inv_y}).type;
1621 switch (hitbox_type) {
1622 case hitbox_type::bottom_resize_border:
1623 set_cursor(mouse_cursor::None);
1625 case hitbox_type::top_resize_border:
1626 set_cursor(mouse_cursor::None);
1628 case hitbox_type::left_resize_border:
1629 set_cursor(mouse_cursor::None);
1631 case hitbox_type::right_resize_border:
1632 set_cursor(mouse_cursor::None);
1634 case hitbox_type::bottom_left_resize_corner:
1635 set_cursor(mouse_cursor::None);
1636 return HTBOTTOMLEFT;
1637 case hitbox_type::bottom_right_resize_corner:
1638 set_cursor(mouse_cursor::None);
1639 return HTBOTTOMRIGHT;
1640 case hitbox_type::top_left_resize_corner:
1641 set_cursor(mouse_cursor::None);
1643 case hitbox_type::top_right_resize_corner:
1644 set_cursor(mouse_cursor::None);
1646 case hitbox_type::application_icon:
1647 set_cursor(mouse_cursor::None);
1649 case hitbox_type::move_area:
1650 set_cursor(mouse_cursor::None);
1652 case hitbox_type::text_edit:
1653 set_cursor(mouse_cursor::TextEdit);
1655 case hitbox_type::button:
1656 set_cursor(mouse_cursor::Button);
1658 case hitbox_type::scroll_bar:
1659 set_cursor(mouse_cursor::Default);
1661 case hitbox_type::_default:
1662 set_cursor(mouse_cursor::Default);
1664 case hitbox_type::outside:
1665 set_cursor(mouse_cursor::None);
1673 case WM_SETTINGCHANGE:
1674 hi_axiom(loop::main().on_thread());
1675 os_settings::gather();
1680 hi_axiom(loop::main().on_thread());
1682 pixel_density = {pixels_per_inch(LOWORD(wParam)), os_settings::device_type()};
1685 auto const new_rectangle = std::launder(
reinterpret_cast<RECT *
>(lParam));
1689 new_rectangle->left,
1691 new_rectangle->right - new_rectangle->left,
1692 new_rectangle->bottom - new_rectangle->top,
1693 SWP_NOZORDER | SWP_NOACTIVATE);
1694 ++global_counter<
"gui_window:WM_DPICHANGED:constrain">;
1695 this->process_event({gui_event_type::window_reconstrain});
1698 hi_log_info(
"DPI has changed to {} ppi", pixel_density.ppi.in(pixels_per_inch));
1713 static LRESULT CALLBACK _WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
noexcept
1715 if (uMsg == WM_CREATE && lParam) {
1716 auto const createData = std::launder(std::bit_cast<CREATESTRUCT *>(lParam));
1719 auto r = SetWindowLongPtrW(hwnd, GWLP_USERDATA, std::bit_cast<LONG_PTR>(createData->lpCreateParams));
1720 if (r != 0 || GetLastError() != 0) {
1728 auto window_userdata = GetWindowLongPtrW(hwnd, GWLP_USERDATA);
1729 if (window_userdata == 0) {
1730 return DefWindowProc(hwnd, uMsg, wParam, lParam);
1733 auto& window = *std::launder(std::bit_cast<gui_window *>(window_userdata));
1734 hi_axiom(loop::main().on_thread());
1738 if (uMsg == WM_CLOSE) {
1743 }
else if (uMsg == WM_DESTROY) {
1747 auto r = SetWindowLongPtrW(hwnd, GWLP_USERDATA, NULL);
1748 if (r == 0 || GetLastError() != 0) {
1753 window.win32Window =
nullptr;
1757 if (
auto result = window.windowProc(uMsg, wParam, lParam); result != -1) {
1760 return DefWindowProc(hwnd, uMsg, wParam, lParam);
1764 static void createWindowClass()
1766 if (!win32WindowClassIsRegistered) {
1768 win32WindowClassName = L
"HikoGUI Window Class";
1770 std::memset(&win32WindowClass, 0,
sizeof(WNDCLASSW));
1771 win32WindowClass.style = CS_DBLCLKS;
1772 win32WindowClass.lpfnWndProc = _WindowProc;
1774 win32WindowClass.lpszClassName = win32WindowClassName;
1775 win32WindowClass.hCursor =
nullptr;
1776 RegisterClassW(&win32WindowClass);
1778 win32WindowClassIsRegistered =
true;