25 HWND win32Window =
nullptr;
47 mouse_cursor current_mouse_cursor = mouse_cursor::None;
53 bool resizing =
false;
88 if (not os_settings::start_subsystem()) {
89 hi_log_fatal(
"Could not start the os_settings subsystem.");
94 register_font_directories(font_dirs());
96 register_theme_directories(theme_dirs());
99 load_system_keyboard_bindings(URL{
"resource:win32.keybinds.json"});
101 hi_log_fatal(
"Could not load keyboard bindings. \"{}\"", e.
what());
104 _first_window =
true;
107 SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
109 _widget->set_window(
this);
114 _widget_constraints = _widget->update_constraints();
115 hilet new_size = _widget_constraints.preferred;
118 update_keyboard_target({});
122 _setting_change_cbt = os_settings::subscribe(
124 ++global_counter<
"gui_window:os_setting:constrain">;
125 this->process_event({gui_event_type::window_reconstrain});
127 callback_flags::main);
130 _selected_theme_cbt = theme_book::global().selected_theme.subscribe(
132 ++global_counter<
"gui_window:selected_theme:constrain">;
133 this->process_event({gui_event_type::window_reconstrain});
135 callback_flags::main);
137 _render_cbt = loop::main().subscribe_render([
this](utc_nanoseconds display_time) {
138 this->render(display_time);
143 create_window(new_size);
149 if (win32Window !=
nullptr) {
150 DestroyWindow(win32Window);
151 hi_assert(win32Window ==
nullptr);
156 hi_log_fatal(
"Could not properly destruct gui_window. '{}'", e.
what());
164 hi_log_info(
"Window '{}' has been properly destructed.", _title);
167 hi_log_fatal(
"Could not properly destruct gui_window. '{}'", e.
what());
171 template<
typename W
idget>
172 [[nodiscard]] Widget& widget() const noexcept
174 return up_cast<Widget>(*_widget);
177 void set_title(label title)
noexcept
185 void render(utc_nanoseconds display_time_point)
187 if (surface->device() ==
nullptr) {
192 hilet t1 =
trace<
"window::render">();
194 hi_axiom(loop::main().on_thread());
195 hi_assert_not_null(surface);
196 hi_assert_not_null(_widget);
200 auto need_reconstrain = _reconstrain.exchange(
false, std::memory_order_relaxed);
204 need_reconstrain =
true;
207 if (need_reconstrain) {
208 hilet t2 =
trace<
"window::constrain">();
212 _widget_constraints = _widget->update_constraints();
224 if (_resize.exchange(
false, std::memory_order::relaxed)) {
226 hilet current_size = rectangle.size();
227 hilet new_size = _widget_constraints.preferred;
228 if (new_size != current_size) {
229 hi_log_info(
"A new preferred window size {} was requested by one of the widget.", new_size);
230 set_window_size(new_size);
235 hilet current_size = rectangle.size();
236 hilet new_size = clamp(current_size, _widget_constraints.minimum, _widget_constraints.maximum);
237 if (new_size != current_size and size_state() != gui_window_size::minimized) {
238 hi_log_info(
"The current window size {} must grow or shrink to {} to fit the widgets.", current_size, new_size);
239 set_window_size(new_size);
243 if (rectangle.size() < _widget_constraints.minimum or rectangle.size() > _widget_constraints.maximum) {
251 surface->update(rectangle.size());
254 auto need_relayout = _relayout.exchange(
false, std::memory_order_relaxed);
258 need_relayout =
true;
261 if (need_reconstrain or need_relayout or widget_size != rectangle.size()) {
262 hilet t2 =
trace<
"window::layout">();
263 widget_size = rectangle.size();
267 hilet widget_layout_size = max(_widget_constraints.minimum, widget_size);
268 _widget->set_layout(widget_layout{widget_layout_size, _size_state,
subpixel_orientation(), display_time_point});
271 _redraw_rectangle = aarectangle{widget_size};
276 _redraw_rectangle = aarectangle{widget_size};
280 if (
auto draw_context = surface->render_start(_redraw_rectangle)) {
281 _redraw_rectangle = aarectangle{};
282 draw_context.display_time_point = display_time_point;
284 draw_context.active = active;
286 if (_animated_active.update(active ? 1.0f : 0.0f, display_time_point)) {
287 this->process_event({gui_event_type::window_redraw, aarectangle{rectangle.size()}});
289 draw_context.saturation = _animated_active.current_value();
292 hilet t2 =
trace<
"window::draw">();
293 _widget->draw(draw_context);
296 hilet t2 =
trace<
"window::submit">();
297 surface->render_finish(draw_context);
306 hi_axiom(loop::main().on_thread());
308 if (current_mouse_cursor == cursor) {
311 current_mouse_cursor = cursor;
313 if (cursor == mouse_cursor::None) {
317 static auto idcAppStarting = LoadCursorW(
nullptr, IDC_APPSTARTING);
318 static auto idcArrow = LoadCursorW(
nullptr, IDC_ARROW);
319 static auto idcHand = LoadCursorW(
nullptr, IDC_HAND);
320 static auto idcIBeam = LoadCursorW(
nullptr, IDC_IBEAM);
321 static auto idcNo = LoadCursorW(
nullptr, IDC_NO);
325 case mouse_cursor::None:
326 idc = idcAppStarting;
328 case mouse_cursor::Default:
331 case mouse_cursor::Button:
334 case mouse_cursor::TextEdit:
348 hi_axiom(loop::main().on_thread());
349 if (not PostMessageW(win32Window, WM_CLOSE, 0, 0)) {
350 hi_log_error(
"Could not send WM_CLOSE to window {}: {}", _title, get_last_error_message());
361 hi_axiom(loop::main().on_thread());
363 if (_size_state == state) {
367 if (_size_state == gui_window_size::normal) {
368 _restore_rectangle = rectangle;
369 }
else if (_size_state == gui_window_size::minimized) {
370 ShowWindow(win32Window, SW_RESTORE);
371 _size_state = gui_window_size::normal;
374 if (state == gui_window_size::normal) {
375 hilet left = round_cast<int>(_restore_rectangle.left());
376 hilet top = round_cast<int>(_restore_rectangle.top());
377 hilet width = round_cast<int>(_restore_rectangle.width());
378 hilet height = round_cast<int>(_restore_rectangle.height());
379 hilet inv_top = round_cast<int>(os_settings::primary_monitor_rectangle().height()) - top;
380 SetWindowPos(win32Window, HWND_TOP, left, inv_top, width, height, 0);
381 _size_state = gui_window_size::normal;
383 }
else if (state == gui_window_size::minimized) {
384 ShowWindow(win32Window, SW_MINIMIZE);
385 _size_state = gui_window_size::minimized;
387 }
else if (state == gui_window_size::maximized) {
388 hilet workspace = workspace_rectangle();
389 hilet max_size = _widget_constraints.maximum;
392 hilet width =
std::min(max_size.width(), workspace.width());
393 hilet height =
std::min(max_size.height(), workspace.height());
394 hilet left = std::clamp(rectangle.left(), workspace.left(), workspace.right() - width);
395 hilet top = std::clamp(rectangle.top(), workspace.bottom() + height, workspace.top());
396 hilet inv_top = os_settings::primary_monitor_rectangle().height() - top;
400 round_cast<int>(left),
401 round_cast<int>(inv_top),
402 round_cast<int>(width),
403 round_cast<int>(height),
405 _size_state = gui_window_size::maximized;
407 }
else if (state == gui_window_size::fullscreen) {
408 hilet fullscreen = fullscreen_rectangle();
409 hilet max_size = _widget_constraints.maximum;
410 if (fullscreen.width() > max_size.width() or fullscreen.height() > max_size.height()) {
415 hilet left = round_cast<int>(fullscreen.left());
416 hilet top = round_cast<int>(fullscreen.top());
417 hilet width = round_cast<int>(fullscreen.width());
418 hilet height = round_cast<int>(fullscreen.height());
419 hilet inv_top = round_cast<int>(os_settings::primary_monitor_rectangle().height()) - top;
420 SetWindowPos(win32Window, HWND_TOP, left, inv_top, width, height, 0);
421 _size_state = gui_window_size::fullscreen;
429 hilet monitor = MonitorFromWindow(win32Window, MONITOR_DEFAULTTOPRIMARY);
430 if (monitor == NULL) {
431 hi_log_error(
"Could not get monitor for the window.");
432 return {0, 0, 1920, 1080};
436 info.cbSize =
sizeof(MONITORINFO);
437 if (not GetMonitorInfo(monitor, &info)) {
438 hi_log_error(
"Could not get monitor info for the window.");
439 return {0, 0, 1920, 1080};
442 hilet left = narrow_cast<float>(info.rcWork.left);
443 hilet top = narrow_cast<float>(info.rcWork.top);
444 hilet right = narrow_cast<float>(info.rcWork.right);
445 hilet bottom = narrow_cast<float>(info.rcWork.bottom);
446 hilet width = right - left;
447 hilet height = bottom - top;
448 hilet inv_bottom = os_settings::primary_monitor_rectangle().height() - bottom;
449 return aarectangle{left, inv_bottom, width, height};
456 hilet monitor = MonitorFromWindow(win32Window, MONITOR_DEFAULTTOPRIMARY);
457 if (monitor == NULL) {
458 hi_log_error(
"Could not get monitor for the window.");
459 return {0, 0, 1920, 1080};
463 info.cbSize =
sizeof(MONITORINFO);
464 if (not GetMonitorInfo(monitor, &info)) {
465 hi_log_error(
"Could not get monitor info for the window.");
466 return {0, 0, 1920, 1080};
469 hilet left = narrow_cast<float>(info.rcMonitor.left);
470 hilet top = narrow_cast<float>(info.rcMonitor.top);
471 hilet right = narrow_cast<float>(info.rcMonitor.right);
472 hilet bottom = narrow_cast<float>(info.rcMonitor.bottom);
473 hilet width = right - left;
474 hilet height = bottom - top;
475 hilet inv_bottom = os_settings::primary_monitor_rectangle().height() - bottom;
476 return aarectangle{left, inv_bottom, width, height};
495 constexpr auto tan_half_degree = 0.00872686779075879f;
496 constexpr auto viewing_distance = 20.0f;
498 hilet ppd = 2 * viewing_distance * dpi * tan_half_degree;
502 return hi::subpixel_orientation::unknown;
505 return os_settings::subpixel_orientation();
515 hi_axiom(loop::main().on_thread());
518 hilet left = rectangle.left();
519 hilet top = rectangle.top() - 30.0f;
522 hilet inv_top = os_settings::primary_monitor_rectangle().height() - top;
525 hilet system_menu = GetSystemMenu(win32Window,
false);
527 TrackPopupMenu(system_menu, TPM_RETURNCMD, round_cast<int>(left), round_cast<int>(inv_top), 0, win32Window, NULL);
529 SendMessage(win32Window, WM_SYSCOMMAND, narrow_cast<WPARAM>(cmd), LPARAM{0});
537 hi_axiom(loop::main().on_thread());
540 if (not GetWindowRect(win32Window, &original_rect)) {
541 hi_log_error(
"Could not get the window's rectangle on the screen.");
544 hilet new_width = round_cast<int>(new_extent.width());
545 hilet new_height = round_cast<int>(new_extent.height());
546 hilet new_x = os_settings::left_to_right() ? narrow_cast<int>(original_rect.left) :
547 narrow_cast<int>(original_rect.right - new_width);
548 hilet new_y = narrow_cast<int>(original_rect.top);
557 SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_DEFERERASE | SWP_NOCOPYBITS | SWP_FRAMECHANGED);
560 void update_mouse_target(widget_id new_target_id,
point2 position = {})
noexcept
562 hi_axiom(loop::main().on_thread());
564 if (_mouse_target_id) {
565 if (new_target_id == _mouse_target_id) {
571 send_events_to_widget(_mouse_target_id,
std::vector{gui_event{gui_event_type::mouse_exit}});
575 _mouse_target_id = new_target_id;
576 send_events_to_widget(new_target_id,
std::vector{gui_event::make_mouse_enter(position)});
578 _mouse_target_id = std::nullopt;
590 hi_axiom(loop::main().on_thread());
592 auto new_target_widget = get_if(_widget.get(), new_target_id,
false);
596 auto new_target_parent_chain = new_target_widget ? new_target_widget->parent_chain() :
std::vector<widget_id>{};
601 if (new_target_widget ==
nullptr or not new_target_widget->accepts_keyboard_focus(group)) {
602 new_target_widget =
nullptr;
605 if (
auto const *
const keyboard_target_widget = get_if(_widget.get(), _keyboard_target_id,
false)) {
607 if (new_target_widget == keyboard_target_widget) {
612 send_events_to_widget(_keyboard_target_id,
std::vector{gui_event{gui_event_type::keyboard_exit}});
616 _widget->handle_event_recursive(gui_event_type::gui_cancel, new_target_parent_chain);
619 if (new_target_widget !=
nullptr) {
620 _keyboard_target_id = new_target_widget->id;
621 send_events_to_widget(_keyboard_target_id,
std::vector{gui_event{gui_event_type::keyboard_enter}});
623 _keyboard_target_id = std::nullopt;
637 hi_axiom(loop::main().on_thread());
639 auto tmp = _widget->find_next_widget(start_widget, group, direction);
640 if (tmp == start_widget) {
642 tmp = _widget->find_next_widget({}, group, direction);
644 update_keyboard_target(tmp, group);
656 return update_keyboard_target(_keyboard_target_id, group, direction);
668 if (not OpenClipboard(win32Window)) {
670 hi_log_info(
"Could not open win32 clipboard '{}'", get_last_error_message());
674 hilet defer_CloseClipboard = defer([] {
679 while ((format = EnumClipboardFormats(format)) != 0) {
685 hilet cb_data = GetClipboardData(CF_UNICODETEXT);
686 if (cb_data ==
nullptr) {
687 hi_log_error(
"Could not get clipboard data: '{}'", get_last_error_message());
691 auto const *
const wstr_c =
static_cast<wchar_t const *
>(GlobalLock(cb_data));
692 if (wstr_c ==
nullptr) {
693 hi_log_error(
"Could not lock clipboard data: '{}'", get_last_error_message());
697 hilet defer_GlobalUnlock = defer([cb_data] {
698 if (not GlobalUnlock(cb_data) and GetLastError() != ERROR_SUCCESS) {
699 hi_log_error(
"Could not unlock clipboard data: '{}'", get_last_error_message());
703 auto r =
to_gstring(hi::to_string(std::wstring_view(wstr_c)));
704 hi_log_debug(
"get_text_from_clipboard '{}'", to_string(r));
713 if (GetLastError() != ERROR_SUCCESS) {
714 hi_log_error(
"Could not enumerator clipboard formats: '{}'", get_last_error_message());
727 if (not OpenClipboard(win32Window)) {
729 hi_log_info(
"Could not open win32 clipboard '{}'", get_last_error_message());
733 hilet defer_CloseClipboard = defer([] {
737 if (not EmptyClipboard()) {
738 hi_log_error(
"Could not empty win32 clipboard '{}'", get_last_error_message());
744 auto wtext_handle = GlobalAlloc(GMEM_MOVEABLE, (wtext.size() + 1) *
sizeof(
wchar_t));
745 if (wtext_handle ==
nullptr) {
746 hi_log_error(
"Could not allocate clipboard data '{}'", get_last_error_message());
750 hilet defer_GlobalFree([&wtext_handle] {
751 if (wtext_handle !=
nullptr) {
752 GlobalFree(wtext_handle);
757 auto wtext_c =
static_cast<wchar_t *
>(GlobalLock(wtext_handle));
758 if (wtext_c ==
nullptr) {
759 hi_log_error(
"Could not lock string data '{}'", get_last_error_message());
763 hilet defer_GlobalUnlock = defer([wtext_handle] {
764 if (not GlobalUnlock(wtext_handle) and GetLastError() != ERROR_SUCCESS) {
765 hi_log_error(
"Could not unlock string data '{}'", get_last_error_message());
769 std::memcpy(wtext_c, wtext.c_str(), (wtext.size() + 1) *
sizeof(
wchar_t));
772 if (SetClipboardData(CF_UNICODETEXT, wtext_handle) ==
nullptr) {
773 hi_log_error(
"Could not set clipboard data '{}'", get_last_error_message());
777 wtext_handle =
nullptr;
781 [[nodiscard]] translate2 window_to_screen() const noexcept
783 return translate2{rectangle.left(), rectangle.bottom()};
786 [[nodiscard]] translate2 screen_to_window() const noexcept
788 return ~window_to_screen();
801 using enum gui_event_type;
803 hi_axiom(loop::main().on_thread());
807 switch (event.type()) {
809 _redraw_rectangle.fetch_or(event.rectangle());
812 case window_relayout:
813 _relayout.store(
true, std::memory_order_relaxed);
816 case window_reconstrain:
817 _reconstrain.store(
true, std::memory_order_relaxed);
821 _resize.store(
true, std::memory_order_relaxed);
824 case window_minimize:
825 set_size_state(gui_window_size::minimized);
828 case window_maximize:
829 set_size_state(gui_window_size::maximized);
832 case window_normalize:
833 set_size_state(gui_window_size::normal);
840 case window_open_sysmenu:
844 case window_set_keyboard_target:
846 hilet& target =
event.keyboard_target();
847 if (target.widget_id ==
nullptr) {
848 update_keyboard_target(target.group, target.direction);
849 }
else if (target.direction == keyboard_focus_direction::here) {
850 update_keyboard_target(target.widget_id, target.group);
852 update_keyboard_target(target.widget_id, target.group, target.direction);
857 case window_set_clipboard:
858 put_text_on_clipboard(event.clipboard_data());
861 case mouse_exit_window:
862 update_mouse_target({});
868 hilet
hitbox = _widget->hitbox_test(event.mouse().position);
869 update_mouse_target(
hitbox.widget_id, event.mouse().position);
871 if (event == mouse_down) {
872 update_keyboard_target(
hitbox.widget_id, keyboard_focus_group::all);
878 for (
auto& e : translate_keyboard_event(event)) {
886 for (
auto& event_ : events) {
887 if (event_.type() == gui_event_type::text_edit_paste) {
890 if (
auto optional_text = get_text_from_clipboard()) {
891 event_.clipboard_data() = *optional_text;
896 hilet handled = [&] {
897 hilet target_id =
event.variant() == gui_event_variant::mouse ? _mouse_target_id : _keyboard_target_id;
898 return send_events_to_widget(target_id, events);
905 for (hilet event_ : events) {
906 if (event_ == gui_cancel) {
907 update_keyboard_target({}, keyboard_focus_group::all);
915 constexpr static UINT_PTR move_and_resize_timer_id = 2;
918 inline static bool _first_window =
true;
919 inline static const wchar_t *win32WindowClassName =
nullptr;
920 inline static WNDCLASSW win32WindowClass = {};
921 inline static bool win32WindowClassIsRegistered =
false;
922 inline static bool firstWindowHasBeenOpened =
false;
932 box_constraints _widget_constraints = {};
941 gui_window_size _size_state = gui_window_size::normal;
945 aarectangle _restore_rectangle;
954 utc_nanoseconds last_forced_redraw = {};
958 animator<float> _animated_active = _animation_duration;
964 widget_id _mouse_target_id;
969 widget_id _keyboard_target_id;
971 notifier<>::callback_token _setting_change_cbt;
972 observer<std::string>::callback_token _selected_theme_cbt;
973 loop::render_callback_token _render_cbt;
975 TRACKMOUSEEVENT track_mouse_leave_event_parameters;
976 bool tracking_mouse_leave_event =
false;
977 char32_t high_surrogate = 0;
978 gui_event mouse_button_event;
979 utc_nanoseconds multi_click_time_point;
980 point2 multi_click_position;
981 uint8_t multi_click_count;
983 bool keymenu_pressed =
false;
997 target_id = _widget->id;
1000 auto target_widget = get_if(_widget.
get(), target_id,
false);
1001 while (target_widget) {
1003 for (hilet& event : events) {
1004 if (target_widget->handle_event(target_widget->layout().from_window * event)) {
1010 target_widget = target_widget->parent;
1016 void setOSWindowRectangleFromRECT(RECT new_rectangle)
noexcept
1018 hi_axiom(loop::main().on_thread());
1021 hilet inv_bottom = os_settings::primary_monitor_rectangle().height() - new_rectangle.bottom;
1023 hilet new_screen_rectangle = aarectangle{
1024 narrow_cast<float>(new_rectangle.left),
1025 narrow_cast<float>(inv_bottom),
1026 narrow_cast<float>(new_rectangle.right - new_rectangle.left),
1027 narrow_cast<float>(new_rectangle.bottom - new_rectangle.top)};
1029 if (
rectangle.size() != new_screen_rectangle.size()) {
1030 ++global_counter<
"gui_window:os-resize:relayout">;
1031 this->process_event({gui_event_type::window_relayout});
1037 [[nodiscard]] keyboard_state get_keyboard_state() noexcept
1039 auto r = keyboard_state::idle;
1041 if (GetKeyState(VK_CAPITAL) != 0) {
1042 r |= keyboard_state::caps_lock;
1044 if (GetKeyState(VK_NUMLOCK) != 0) {
1045 r |= keyboard_state::num_lock;
1047 if (GetKeyState(VK_SCROLL) != 0) {
1048 r |= keyboard_state::scroll_lock;
1058 static_assert(std::is_signed_v<
decltype(GetAsyncKeyState(VK_SHIFT))>);
1060 auto r = keyboard_modifiers::none;
1062 if (GetAsyncKeyState(VK_SHIFT) < 0) {
1063 r |= keyboard_modifiers::shift;
1065 if (GetAsyncKeyState(VK_CONTROL) < 0) {
1066 r |= keyboard_modifiers::control;
1068 if (GetAsyncKeyState(VK_MENU) < 0) {
1069 r |= keyboard_modifiers::alt;
1071 if (GetAsyncKeyState(VK_LWIN) < 0 or GetAsyncKeyState(VK_RWIN) < 0) {
1072 r |= keyboard_modifiers::super;
1078 [[nodiscard]]
char32_t handle_suragates(
char32_t c)
noexcept
1080 hi_axiom(loop::main().on_thread());
1082 if (c >= 0xd800 && c <= 0xdbff) {
1083 high_surrogate = ((c - 0xd800) << 10) + 0x10000;
1086 }
else if (c >= 0xdc00 && c <= 0xdfff) {
1087 c = high_surrogate ? high_surrogate | (c - 0xdc00) : 0xfffd;
1093 [[nodiscard]] gui_event create_mouse_event(
unsigned int uMsg, uint64_t wParam, int64_t lParam)
noexcept
1095 hi_axiom(loop::main().on_thread());
1097 auto r = gui_event{gui_event_type::mouse_move};
1098 r.keyboard_modifiers = get_keyboard_modifiers();
1099 r.keyboard_state = get_keyboard_state();
1101 hilet x = narrow_cast<float>(GET_X_LPARAM(lParam));
1102 hilet y = narrow_cast<float>(GET_Y_LPARAM(lParam));
1109 r.mouse().position = point2{x, inv_y};
1110 r.mouse().wheel_delta = {};
1111 if (uMsg == WM_MOUSEWHEEL) {
1112 r.mouse().wheel_delta.y() = GET_WHEEL_DELTA_WPARAM(wParam) * 10.0f / WHEEL_DELTA;
1113 }
else if (uMsg == WM_MOUSEHWHEEL) {
1114 r.mouse().wheel_delta.x() = GET_WHEEL_DELTA_WPARAM(wParam) * 10.0f / WHEEL_DELTA;
1118 r.mouse().down.left_button = (GET_KEYSTATE_WPARAM(wParam) & MK_LBUTTON) > 0;
1119 r.mouse().down.middle_button = (GET_KEYSTATE_WPARAM(wParam) & MK_MBUTTON) > 0;
1120 r.mouse().down.right_button = (GET_KEYSTATE_WPARAM(wParam) & MK_RBUTTON) > 0;
1121 r.mouse().down.x1_button = (GET_KEYSTATE_WPARAM(wParam) & MK_XBUTTON1) > 0;
1122 r.mouse().down.x2_button = (GET_KEYSTATE_WPARAM(wParam) & MK_XBUTTON2) > 0;
1127 case WM_LBUTTONDOWN:
1128 case WM_LBUTTONDBLCLK:
1129 r.mouse().cause.left_button =
true;
1132 case WM_RBUTTONDOWN:
1133 case WM_RBUTTONDBLCLK:
1134 r.mouse().cause.right_button =
true;
1137 case WM_MBUTTONDOWN:
1138 case WM_MBUTTONDBLCLK:
1139 r.mouse().cause.middle_button =
true;
1142 case WM_XBUTTONDOWN:
1143 case WM_XBUTTONDBLCLK:
1144 r.mouse().cause.x1_button = (GET_XBUTTON_WPARAM(wParam) & XBUTTON1) > 0;
1145 r.mouse().cause.x2_button = (GET_XBUTTON_WPARAM(wParam) & XBUTTON2) > 0;
1148 if (mouse_button_event == gui_event_type::mouse_down) {
1149 r.mouse().cause = mouse_button_event.mouse().cause;
1153 case WM_MOUSEHWHEEL:
1160 hilet a_button_is_pressed = r.mouse().down.left_button or r.mouse().down.middle_button or r.mouse().down.right_button or
1161 r.mouse().down.x1_button or r.mouse().down.x2_button;
1168 r.set_type(gui_event_type::mouse_up);
1169 if (mouse_button_event) {
1170 r.mouse().down_position = mouse_button_event.mouse().down_position;
1172 r.mouse().click_count = 0;
1174 if (!a_button_is_pressed) {
1179 case WM_LBUTTONDBLCLK:
1180 case WM_MBUTTONDBLCLK:
1181 case WM_RBUTTONDBLCLK:
1182 case WM_XBUTTONDBLCLK:
1183 case WM_LBUTTONDOWN:
1184 case WM_MBUTTONDOWN:
1185 case WM_RBUTTONDOWN:
1186 case WM_XBUTTONDOWN:
1188 hilet within_double_click_time = r.time_point - multi_click_time_point < os_settings::double_click_interval();
1189 hilet double_click_distance =
1190 std::sqrt(narrow_cast<float>(squared_hypot(r.mouse().position - multi_click_position)));
1191 hilet within_double_click_distance = double_click_distance < os_settings::double_click_distance();
1193 multi_click_count = within_double_click_time and within_double_click_distance ? multi_click_count + 1 : 1;
1194 multi_click_time_point = r.time_point;
1195 multi_click_position = r.mouse().position;
1197 r.set_type(gui_event_type::mouse_down);
1198 r.mouse().down_position = r.mouse().position;
1199 r.mouse().click_count = multi_click_count;
1202 hi_assert_not_null(win32Window);
1203 SetCapture(win32Window);
1208 case WM_MOUSEHWHEEL:
1209 r.set_type(gui_event_type::mouse_wheel);
1215 r.set_type(a_button_is_pressed ? gui_event_type::mouse_drag :
gui_event_type::mouse_move);
1216 if (mouse_button_event) {
1217 r.mouse().down_position = mouse_button_event.mouse().down_position;
1218 r.mouse().click_count = mouse_button_event.mouse().click_count;
1224 r.set_type(gui_event_type::mouse_exit_window);
1225 if (mouse_button_event) {
1226 r.mouse().down_position = mouse_button_event.mouse().down_position;
1228 r.mouse().click_count = 0;
1231 tracking_mouse_leave_event =
false;
1235 current_mouse_cursor = mouse_cursor::None;
1244 if (not tracking_mouse_leave_event and uMsg != WM_MOUSELEAVE) {
1245 auto *track_mouse_leave_event_parameters_p = &track_mouse_leave_event_parameters;
1246 if (not TrackMouseEvent(track_mouse_leave_event_parameters_p)) {
1249 tracking_mouse_leave_event =
true;
1254 if (r == gui_event_type::mouse_down or r == gui_event_type::mouse_up or r == gui_event_type::mouse_exit_window) {
1255 mouse_button_event = r;
1261 void create_window(extent2 new_size)
1264 hi_assert(loop::main().on_thread());
1266 createWindowClass();
1268 auto u16title =
to_wstring(std::format(
"{}", _title));
1270 hi_log_info(
"Create window of size {} with title '{}'", new_size, _title);
1273 SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
1277 win32Window = CreateWindowExW(
1279 win32WindowClassName,
1281 WS_OVERLAPPEDWINDOW,
1285 round_cast<int>(new_size.width()),
1286 round_cast<int>(new_size.height()),
1290 reinterpret_cast<HINSTANCE
>(crt_application_instance),
1292 if (win32Window ==
nullptr) {
1298 MARGINS m{0, 0, 0, 1};
1299 DwmExtendFrameIntoClientArea(win32Window, &m);
1303 win32Window,
nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
1305 if (!firstWindowHasBeenOpened) {
1306 hilet win32_window_ = win32Window;
1307 switch (gui_window_size::normal) {
1308 case gui_window_size::normal:
1309 ShowWindow(win32_window_, SW_SHOWNORMAL);
1311 case gui_window_size::minimized:
1312 ShowWindow(win32_window_, SW_SHOWMINIMIZED);
1314 case gui_window_size::maximized:
1315 ShowWindow(win32_window_, SW_SHOWMAXIMIZED);
1320 firstWindowHasBeenOpened =
true;
1323 track_mouse_leave_event_parameters.cbSize =
sizeof(track_mouse_leave_event_parameters);
1324 track_mouse_leave_event_parameters.dwFlags = TME_LEAVE;
1325 track_mouse_leave_event_parameters.hwndTrack = win32Window;
1326 track_mouse_leave_event_parameters.dwHoverTime = HOVER_DEFAULT;
1328 ShowWindow(win32Window, SW_SHOW);
1330 auto _dpi = GetDpiForWindow(win32Window);
1332 throw gui_error(
"Could not retrieve dpi for window.");
1334 dpi = narrow_cast<float>(_dpi);
1336 surface = make_unique_gfx_surface(crt_application_instance, win32Window);
1339 int windowProc(
unsigned int uMsg, uint64_t wParam, int64_t lParam)
noexcept
1341 using namespace std::chrono_literals;
1343 gui_event mouse_event;
1344 hilet current_time = std::chrono::utc_clock::now();
1357 hilet createstruct_ptr = std::launder(std::bit_cast<CREATESTRUCT *>(lParam));
1359 new_rectangle.left = createstruct_ptr->x;
1360 new_rectangle.top = createstruct_ptr->y;
1361 new_rectangle.right = createstruct_ptr->x + createstruct_ptr->cx;
1362 new_rectangle.bottom = createstruct_ptr->y + createstruct_ptr->cy;
1363 setOSWindowRectangleFromRECT(new_rectangle);
1372 hilet height = [
this]() {
1373 hi_axiom(loop::main().on_thread());
1378 BeginPaint(win32Window, &ps);
1380 hilet update_rectangle = aarectangle{
1381 narrow_cast<float>(ps.rcPaint.left),
1382 narrow_cast<float>(height - ps.rcPaint.bottom),
1383 narrow_cast<float>(ps.rcPaint.right - ps.rcPaint.left),
1384 narrow_cast<float>(ps.rcPaint.bottom - ps.rcPaint.top)};
1387 hi_axiom(loop::main().on_thread());
1388 this->process_event({gui_event_type::window_redraw, update_rectangle});
1391 EndPaint(win32Window, &ps);
1396 hi_axiom(loop::main().on_thread());
1397 this->process_event({gui_event_type::window_redraw, aarectangle{
rectangle.size()}});
1403 hi_axiom(loop::main().on_thread());
1405 case SIZE_MAXIMIZED:
1406 ShowWindow(win32Window, SW_RESTORE);
1407 set_size_state(gui_window_size::maximized);
1409 case SIZE_MINIMIZED:
1410 _size_state = gui_window_size::minimized;
1413 _size_state = gui_window_size::normal;
1421 if (last_forced_redraw + 16.7ms < current_time) {
1424 loop::main().resume_once();
1425 last_forced_redraw = current_time;
1431 hilet& rect_ptr = *std::launder(std::bit_cast<RECT *>(lParam));
1432 if (rect_ptr.right < rect_ptr.left or rect_ptr.bottom < rect_ptr.top) {
1434 "Invalid RECT received on WM_SIZING: left={}, right={}, bottom={}, top={}",
1441 setOSWindowRectangleFromRECT(rect_ptr);
1448 hilet& rect_ptr = *std::launder(std::bit_cast<RECT *>(lParam));
1449 if (rect_ptr.right < rect_ptr.left or rect_ptr.bottom < rect_ptr.top) {
1451 "Invalid RECT received on WM_MOVING: left={}, right={}, bottom={}, top={}",
1458 setOSWindowRectangleFromRECT(rect_ptr);
1463 case WM_WINDOWPOSCHANGED:
1465 hilet windowpos_ptr = std::launder(std::bit_cast<WINDOWPOS *>(lParam));
1467 new_rectangle.left = windowpos_ptr->x;
1468 new_rectangle.top = windowpos_ptr->y;
1469 new_rectangle.right = windowpos_ptr->x + windowpos_ptr->cx;
1470 new_rectangle.bottom = windowpos_ptr->y + windowpos_ptr->cy;
1471 setOSWindowRectangleFromRECT(new_rectangle);
1475 case WM_ENTERSIZEMOVE:
1476 hi_axiom(loop::main().on_thread());
1477 if (SetTimer(win32Window, move_and_resize_timer_id, 16, NULL) != move_and_resize_timer_id) {
1483 case WM_EXITSIZEMOVE:
1484 hi_axiom(loop::main().on_thread());
1485 if (not KillTimer(win32Window, move_and_resize_timer_id)) {
1491 _size_state = gui_window_size::normal;
1492 this->process_event({gui_event_type::window_redraw, aarectangle{
rectangle.size()}});
1496 hi_axiom(loop::main().on_thread());
1506 hi_log_error(
"Unknown WM_ACTIVE value.");
1508 ++global_counter<
"gui_window:WM_ACTIVATE:constrain">;
1509 this->process_event({gui_event_type::window_reconstrain});
1512 case WM_GETMINMAXINFO:
1514 hi_axiom(loop::main().on_thread());
1515 hilet minmaxinfo = std::launder(std::bit_cast<MINMAXINFO *>(lParam));
1516 minmaxinfo->ptMaxSize.x = round_cast<LONG>(_widget_constraints.maximum.width());
1517 minmaxinfo->ptMaxSize.y = round_cast<LONG>(_widget_constraints.maximum.height());
1518 minmaxinfo->ptMinTrackSize.x = round_cast<LONG>(_widget_constraints.minimum.width());
1519 minmaxinfo->ptMinTrackSize.y = round_cast<LONG>(_widget_constraints.minimum.height());
1520 minmaxinfo->ptMaxTrackSize.x = round_cast<LONG>(_widget_constraints.maximum.width());
1521 minmaxinfo->ptMaxTrackSize.y = round_cast<LONG>(_widget_constraints.maximum.height());
1526 if (
auto c = char_cast<char32_t>(wParam); c == UNICODE_NOCHAR) {
1530 }
else if (hilet gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1532 process_event(gui_event::keyboard_grapheme(grapheme{c}));
1537 if (
auto c = handle_suragates(char_cast<char32_t>(wParam))) {
1538 if (hilet gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1540 process_event(gui_event::keyboard_partial_grapheme(grapheme{c}));
1546 if (
auto c = handle_suragates(char_cast<char32_t>(wParam))) {
1547 if (hilet gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1549 process_event(gui_event::keyboard_grapheme(grapheme{c}));
1555 if (wParam == SC_KEYMENU) {
1556 keymenu_pressed =
true;
1557 process_event(gui_event{gui_event_type::keyboard_down, keyboard_virtual_key::menu});
1565 hilet extended = (narrow_cast<uint32_t>(lParam) & 0x01000000) != 0;
1566 hilet key_code = narrow_cast<int>(wParam);
1567 hilet key_modifiers = get_keyboard_modifiers();
1568 auto virtual_key = to_keyboard_virtual_key(key_code, extended, key_modifiers);
1570 if (std::exchange(keymenu_pressed,
false) and uMsg == WM_KEYDOWN and virtual_key == keyboard_virtual_key::space) {
1572 virtual_key = keyboard_virtual_key::sysmenu;
1575 if (virtual_key != keyboard_virtual_key::nul) {
1576 hilet key_state = get_keyboard_state();
1577 hilet event_type = uMsg == WM_KEYDOWN ? gui_event_type::keyboard_down : gui_event_type::keyboard_up;
1578 process_event(gui_event{event_type, virtual_key, key_modifiers, key_state});
1583 case WM_LBUTTONDOWN:
1584 case WM_MBUTTONDOWN:
1585 case WM_RBUTTONDOWN:
1586 case WM_XBUTTONDOWN:
1591 case WM_LBUTTONDBLCLK:
1592 case WM_MBUTTONDBLCLK:
1593 case WM_RBUTTONDBLCLK:
1594 case WM_XBUTTONDBLCLK:
1596 case WM_MOUSEHWHEEL:
1599 keymenu_pressed =
false;
1600 process_event(create_mouse_event(uMsg, wParam, lParam));
1604 if (wParam == TRUE) {
1620 hi_axiom(loop::main().on_thread());
1622 hilet x = narrow_cast<float>(GET_X_LPARAM(lParam));
1623 hilet y = narrow_cast<float>(GET_Y_LPARAM(lParam));
1626 hilet inv_y = os_settings::primary_monitor_rectangle().height() - y;
1628 hilet hitbox_type = _widget->hitbox_test(screen_to_window() * point2{x, inv_y}).type;
1630 switch (hitbox_type) {
1631 case hitbox_type::bottom_resize_border:
1632 set_cursor(mouse_cursor::None);
1634 case hitbox_type::top_resize_border:
1635 set_cursor(mouse_cursor::None);
1637 case hitbox_type::left_resize_border:
1638 set_cursor(mouse_cursor::None);
1640 case hitbox_type::right_resize_border:
1641 set_cursor(mouse_cursor::None);
1643 case hitbox_type::bottom_left_resize_corner:
1644 set_cursor(mouse_cursor::None);
1645 return HTBOTTOMLEFT;
1646 case hitbox_type::bottom_right_resize_corner:
1647 set_cursor(mouse_cursor::None);
1648 return HTBOTTOMRIGHT;
1649 case hitbox_type::top_left_resize_corner:
1650 set_cursor(mouse_cursor::None);
1652 case hitbox_type::top_right_resize_corner:
1653 set_cursor(mouse_cursor::None);
1655 case hitbox_type::application_icon:
1656 set_cursor(mouse_cursor::None);
1658 case hitbox_type::move_area:
1659 set_cursor(mouse_cursor::None);
1661 case hitbox_type::text_edit:
1662 set_cursor(mouse_cursor::TextEdit);
1664 case hitbox_type::button:
1665 set_cursor(mouse_cursor::Button);
1667 case hitbox_type::scroll_bar:
1668 set_cursor(mouse_cursor::Default);
1670 case hitbox_type::_default:
1671 set_cursor(mouse_cursor::Default);
1673 case hitbox_type::outside:
1674 set_cursor(mouse_cursor::None);
1682 case WM_SETTINGCHANGE:
1683 hi_axiom(loop::main().on_thread());
1684 os_settings::gather();
1689 hi_axiom(loop::main().on_thread());
1691 dpi = narrow_cast<float>(LOWORD(wParam));
1694 hilet new_rectangle = std::launder(
reinterpret_cast<RECT *
>(lParam));
1698 new_rectangle->left,
1700 new_rectangle->right - new_rectangle->left,
1701 new_rectangle->bottom - new_rectangle->top,
1702 SWP_NOZORDER | SWP_NOACTIVATE);
1703 ++global_counter<
"gui_window:WM_DPICHANGED:constrain">;
1704 this->process_event({gui_event_type::window_reconstrain});
1706 hi_log_info(
"DPI has changed to {}", dpi);
1721 static LRESULT CALLBACK _WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
noexcept
1723 if (uMsg == WM_CREATE && lParam) {
1724 hilet createData = std::launder(std::bit_cast<CREATESTRUCT *>(lParam));
1727 auto r = SetWindowLongPtrW(hwnd, GWLP_USERDATA, std::bit_cast<LONG_PTR>(createData->lpCreateParams));
1728 if (r != 0 || GetLastError() != 0) {
1736 auto window_userdata = GetWindowLongPtrW(hwnd, GWLP_USERDATA);
1737 if (window_userdata == 0) {
1738 return DefWindowProc(hwnd, uMsg, wParam, lParam);
1741 auto& window = *std::launder(std::bit_cast<gui_window *>(window_userdata));
1742 hi_axiom(loop::main().on_thread());
1746 if (uMsg == WM_CLOSE) {
1751 }
else if (uMsg == WM_DESTROY) {
1755 auto r = SetWindowLongPtrW(hwnd, GWLP_USERDATA, NULL);
1756 if (r == 0 || GetLastError() != 0) {
1761 window.win32Window =
nullptr;
1765 if (
auto result = window.windowProc(uMsg, wParam, lParam); result != -1) {
1768 return DefWindowProc(hwnd, uMsg, wParam, lParam);
1772 static void createWindowClass()
1774 if (!win32WindowClassIsRegistered) {
1776 win32WindowClassName = L
"HikoGUI Window Class";
1778 std::memset(&win32WindowClass, 0,
sizeof(WNDCLASSW));
1779 win32WindowClass.style = CS_DBLCLKS;
1780 win32WindowClass.lpfnWndProc = _WindowProc;
1782 win32WindowClass.lpszClassName = win32WindowClassName;
1783 win32WindowClass.hCursor =
nullptr;
1784 RegisterClassW(&win32WindowClass);
1786 win32WindowClassIsRegistered =
true;