25 HWND win32Window =
nullptr;
47 mouse_cursor current_mouse_cursor = mouse_cursor::None;
53 bool resizing =
false;
86 _widget(
std::move(widget)), track_mouse_leave_event_parameters()
89 if (not os_settings::start_subsystem()) {
90 hi_log_fatal(
"Could not start the os_settings subsystem.");
95 register_font_directories(get_paths(path_location::font_dirs));
97 register_theme_directories(get_paths(path_location::theme_dirs));
100 load_system_keyboard_bindings(URL{
"resource:win32.keybinds.json"});
102 hi_log_fatal(
"Could not load keyboard bindings. \"{}\"", e.
what());
105 _first_window =
true;
108 SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
110 _widget->set_window(
this);
115 _widget_constraints = _widget->update_constraints();
116 hilet new_size = _widget_constraints.preferred;
119 update_keyboard_target({});
123 _setting_change_cbt = os_settings::subscribe(
125 ++global_counter<
"gui_window:os_setting:constrain">;
126 this->process_event({gui_event_type::window_reconstrain});
128 callback_flags::main);
131 _selected_theme_cbt = theme_book::global().selected_theme.subscribe(
133 ++global_counter<
"gui_window:selected_theme:constrain">;
134 this->process_event({gui_event_type::window_reconstrain});
136 callback_flags::main);
138 _render_cbt = loop::main().subscribe_render([
this](utc_nanoseconds display_time) {
139 this->render(display_time);
144 create_window(new_size);
150 if (win32Window !=
nullptr) {
151 DestroyWindow(win32Window);
152 hi_assert(win32Window ==
nullptr);
157 hi_log_fatal(
"Could not properly destruct gui_window. '{}'", e.
what());
165 hi_log_info(
"Window '{}' has been properly destructed.", _title);
168 hi_log_fatal(
"Could not properly destruct gui_window. '{}'", e.
what());
172 template<
typename W
idget>
173 [[nodiscard]] Widget& widget() const noexcept
175 return up_cast<Widget>(*_widget);
178 void set_title(label title)
noexcept
186 void render(utc_nanoseconds display_time_point)
188 if (surface->device() ==
nullptr) {
193 hilet t1 =
trace<
"window::render">();
195 hi_axiom(loop::main().on_thread());
196 hi_assert_not_null(surface);
197 hi_assert_not_null(_widget);
201 auto need_reconstrain = _reconstrain.exchange(
false, std::memory_order_relaxed);
205 need_reconstrain =
true;
208 if (need_reconstrain) {
209 hilet t2 =
trace<
"window::constrain">();
213 _widget_constraints = _widget->update_constraints();
225 if (_resize.exchange(
false, std::memory_order::relaxed)) {
227 hilet current_size = rectangle.size();
228 hilet new_size = _widget_constraints.preferred;
229 if (new_size != current_size) {
230 hi_log_info(
"A new preferred window size {} was requested by one of the widget.", new_size);
231 set_window_size(new_size);
236 hilet current_size = rectangle.size();
237 hilet new_size = clamp(current_size, _widget_constraints.minimum, _widget_constraints.maximum);
238 if (new_size != current_size and size_state() != gui_window_size::minimized) {
239 hi_log_info(
"The current window size {} must grow or shrink to {} to fit the widgets.", current_size, new_size);
240 set_window_size(new_size);
244 if (rectangle.size() < _widget_constraints.minimum or rectangle.size() > _widget_constraints.maximum) {
252 surface->update(rectangle.size());
255 auto need_relayout = _relayout.exchange(
false, std::memory_order_relaxed);
259 need_relayout =
true;
262 if (need_reconstrain or need_relayout or widget_size != rectangle.size()) {
263 hilet t2 =
trace<
"window::layout">();
264 widget_size = rectangle.size();
268 hilet widget_layout_size = max(_widget_constraints.minimum, widget_size);
269 _widget->set_layout(widget_layout{widget_layout_size, _size_state,
subpixel_orientation(), display_time_point});
272 _redraw_rectangle = aarectangle{widget_size};
277 _redraw_rectangle = aarectangle{widget_size};
281 if (
auto draw_context = surface->render_start(_redraw_rectangle)) {
282 _redraw_rectangle = aarectangle{};
283 draw_context.display_time_point = display_time_point;
285 draw_context.active = active;
287 if (_animated_active.update(active ? 1.0f : 0.0f, display_time_point)) {
288 this->process_event({gui_event_type::window_redraw, aarectangle{rectangle.size()}});
290 draw_context.saturation = _animated_active.current_value();
293 hilet t2 =
trace<
"window::draw">();
294 _widget->draw(draw_context);
297 hilet t2 =
trace<
"window::submit">();
298 surface->render_finish(draw_context);
307 hi_axiom(loop::main().on_thread());
309 if (current_mouse_cursor == cursor) {
312 current_mouse_cursor = cursor;
314 if (cursor == mouse_cursor::None) {
318 static auto idcAppStarting = LoadCursorW(
nullptr, IDC_APPSTARTING);
319 static auto idcArrow = LoadCursorW(
nullptr, IDC_ARROW);
320 static auto idcHand = LoadCursorW(
nullptr, IDC_HAND);
321 static auto idcIBeam = LoadCursorW(
nullptr, IDC_IBEAM);
322 static auto idcNo = LoadCursorW(
nullptr, IDC_NO);
326 case mouse_cursor::None:
327 idc = idcAppStarting;
329 case mouse_cursor::Default:
332 case mouse_cursor::Button:
335 case mouse_cursor::TextEdit:
349 hi_axiom(loop::main().on_thread());
350 if (not PostMessageW(win32Window, WM_CLOSE, 0, 0)) {
351 hi_log_error(
"Could not send WM_CLOSE to window {}: {}", _title, get_last_error_message());
362 hi_axiom(loop::main().on_thread());
364 if (_size_state == state) {
368 if (_size_state == gui_window_size::normal) {
369 _restore_rectangle = rectangle;
370 }
else if (_size_state == gui_window_size::minimized) {
371 ShowWindow(win32Window, SW_RESTORE);
372 _size_state = gui_window_size::normal;
375 if (state == gui_window_size::normal) {
376 hilet left = round_cast<int>(_restore_rectangle.left());
377 hilet top = round_cast<int>(_restore_rectangle.top());
378 hilet width = round_cast<int>(_restore_rectangle.width());
379 hilet height = round_cast<int>(_restore_rectangle.height());
380 hilet inv_top = round_cast<int>(os_settings::primary_monitor_rectangle().height()) - top;
381 SetWindowPos(win32Window, HWND_TOP, left, inv_top, width, height, 0);
382 _size_state = gui_window_size::normal;
384 }
else if (state == gui_window_size::minimized) {
385 ShowWindow(win32Window, SW_MINIMIZE);
386 _size_state = gui_window_size::minimized;
388 }
else if (state == gui_window_size::maximized) {
389 hilet workspace = workspace_rectangle();
390 hilet max_size = _widget_constraints.maximum;
393 hilet width =
std::min(max_size.width(), workspace.width());
394 hilet height =
std::min(max_size.height(), workspace.height());
395 hilet left = std::clamp(rectangle.left(), workspace.left(), workspace.right() - width);
396 hilet top = std::clamp(rectangle.top(), workspace.bottom() + height, workspace.top());
397 hilet inv_top = os_settings::primary_monitor_rectangle().height() - top;
401 round_cast<int>(left),
402 round_cast<int>(inv_top),
403 round_cast<int>(width),
404 round_cast<int>(height),
406 _size_state = gui_window_size::maximized;
408 }
else if (state == gui_window_size::fullscreen) {
409 hilet fullscreen = fullscreen_rectangle();
410 hilet max_size = _widget_constraints.maximum;
411 if (fullscreen.width() > max_size.width() or fullscreen.height() > max_size.height()) {
416 hilet left = round_cast<int>(fullscreen.left());
417 hilet top = round_cast<int>(fullscreen.top());
418 hilet width = round_cast<int>(fullscreen.width());
419 hilet height = round_cast<int>(fullscreen.height());
420 hilet inv_top = round_cast<int>(os_settings::primary_monitor_rectangle().height()) - top;
421 SetWindowPos(win32Window, HWND_TOP, left, inv_top, width, height, 0);
422 _size_state = gui_window_size::fullscreen;
430 hilet monitor = MonitorFromWindow(win32Window, MONITOR_DEFAULTTOPRIMARY);
431 if (monitor == NULL) {
432 hi_log_error(
"Could not get monitor for the window.");
433 return {0, 0, 1920, 1080};
437 info.cbSize =
sizeof(MONITORINFO);
438 if (not GetMonitorInfo(monitor, &info)) {
439 hi_log_error(
"Could not get monitor info for the window.");
440 return {0, 0, 1920, 1080};
443 hilet left = narrow_cast<float>(info.rcWork.left);
444 hilet top = narrow_cast<float>(info.rcWork.top);
445 hilet right = narrow_cast<float>(info.rcWork.right);
446 hilet bottom = narrow_cast<float>(info.rcWork.bottom);
447 hilet width = right - left;
448 hilet height = bottom - top;
449 hilet inv_bottom = os_settings::primary_monitor_rectangle().height() - bottom;
450 return aarectangle{left, inv_bottom, width, height};
457 hilet monitor = MonitorFromWindow(win32Window, MONITOR_DEFAULTTOPRIMARY);
458 if (monitor == NULL) {
459 hi_log_error(
"Could not get monitor for the window.");
460 return {0, 0, 1920, 1080};
464 info.cbSize =
sizeof(MONITORINFO);
465 if (not GetMonitorInfo(monitor, &info)) {
466 hi_log_error(
"Could not get monitor info for the window.");
467 return {0, 0, 1920, 1080};
470 hilet left = narrow_cast<float>(info.rcMonitor.left);
471 hilet top = narrow_cast<float>(info.rcMonitor.top);
472 hilet right = narrow_cast<float>(info.rcMonitor.right);
473 hilet bottom = narrow_cast<float>(info.rcMonitor.bottom);
474 hilet width = right - left;
475 hilet height = bottom - top;
476 hilet inv_bottom = os_settings::primary_monitor_rectangle().height() - bottom;
477 return aarectangle{left, inv_bottom, width, height};
496 constexpr auto tan_half_degree = 0.00872686779075879f;
497 constexpr auto viewing_distance = 20.0f;
499 hilet ppd = 2 * viewing_distance * dpi * tan_half_degree;
503 return hi::subpixel_orientation::unknown;
506 return os_settings::subpixel_orientation();
516 hi_axiom(loop::main().on_thread());
519 hilet left = rectangle.left();
520 hilet top = rectangle.top() - 30.0f;
523 hilet inv_top = os_settings::primary_monitor_rectangle().height() - top;
526 hilet system_menu = GetSystemMenu(win32Window,
false);
528 TrackPopupMenu(system_menu, TPM_RETURNCMD, round_cast<int>(left), round_cast<int>(inv_top), 0, win32Window, NULL);
530 SendMessage(win32Window, WM_SYSCOMMAND, narrow_cast<WPARAM>(cmd), LPARAM{0});
538 hi_axiom(loop::main().on_thread());
541 if (not GetWindowRect(win32Window, &original_rect)) {
542 hi_log_error(
"Could not get the window's rectangle on the screen.");
545 hilet new_width = round_cast<int>(new_extent.width());
546 hilet new_height = round_cast<int>(new_extent.height());
547 hilet new_x = os_settings::left_to_right() ? narrow_cast<int>(original_rect.left) :
548 narrow_cast<int>(original_rect.right - new_width);
549 hilet new_y = narrow_cast<int>(original_rect.top);
558 SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_DEFERERASE | SWP_NOCOPYBITS | SWP_FRAMECHANGED);
561void update_mouse_target(widget_id new_target_id,
point2 position = {})
noexcept
563 hi_axiom(loop::main().on_thread());
565 if (_mouse_target_id) {
566 if (new_target_id == _mouse_target_id) {
572 send_events_to_widget(_mouse_target_id,
std::vector{gui_event{gui_event_type::mouse_exit}});
576 _mouse_target_id = new_target_id;
577 send_events_to_widget(new_target_id,
std::vector{gui_event::make_mouse_enter(position)});
579 _mouse_target_id = std::nullopt;
591 hi_axiom(loop::main().on_thread());
593 auto new_target_widget = get_if(_widget.get(), new_target_id,
false);
597 auto new_target_parent_chain = new_target_widget ? new_target_widget->parent_chain() :
std::vector<widget_id>{};
602 if (new_target_widget ==
nullptr or not new_target_widget->accepts_keyboard_focus(group)) {
603 new_target_widget =
nullptr;
606 if (
auto const *
const keyboard_target_widget = get_if(_widget.get(), _keyboard_target_id,
false)) {
608 if (new_target_widget == keyboard_target_widget) {
613 send_events_to_widget(_keyboard_target_id,
std::vector{gui_event{gui_event_type::keyboard_exit}});
617 _widget->handle_event_recursive(gui_event_type::gui_cancel, new_target_parent_chain);
620 if (new_target_widget !=
nullptr) {
621 _keyboard_target_id = new_target_widget->id;
622 send_events_to_widget(_keyboard_target_id,
std::vector{gui_event{gui_event_type::keyboard_enter}});
624 _keyboard_target_id = std::nullopt;
638 hi_axiom(loop::main().on_thread());
640 auto tmp = _widget->find_next_widget(start_widget, group, direction);
641 if (tmp == start_widget) {
643 tmp = _widget->find_next_widget({}, group, direction);
645 update_keyboard_target(tmp, group);
657 return update_keyboard_target(_keyboard_target_id, group, direction);
669 if (not OpenClipboard(win32Window)) {
671 hi_log_info(
"Could not open win32 clipboard '{}'", get_last_error_message());
675 hilet defer_CloseClipboard = defer([] {
680 while ((format = EnumClipboardFormats(format)) != 0) {
686 hilet cb_data = GetClipboardData(CF_UNICODETEXT);
687 if (cb_data ==
nullptr) {
688 hi_log_error(
"Could not get clipboard data: '{}'", get_last_error_message());
692 auto const *
const wstr_c =
static_cast<wchar_t const *
>(GlobalLock(cb_data));
693 if (wstr_c ==
nullptr) {
694 hi_log_error(
"Could not lock clipboard data: '{}'", get_last_error_message());
698 hilet defer_GlobalUnlock = defer([cb_data] {
699 if (not GlobalUnlock(cb_data) and GetLastError() != ERROR_SUCCESS) {
700 hi_log_error(
"Could not unlock clipboard data: '{}'", get_last_error_message());
704 auto r =
to_gstring(hi::to_string(std::wstring_view(wstr_c)));
705 hi_log_debug(
"get_text_from_clipboard '{}'", to_string(r));
714 if (GetLastError() != ERROR_SUCCESS) {
715 hi_log_error(
"Could not enumerator clipboard formats: '{}'", get_last_error_message());
728 if (not OpenClipboard(win32Window)) {
730 hi_log_info(
"Could not open win32 clipboard '{}'", get_last_error_message());
734 hilet defer_CloseClipboard = defer([] {
738 if (not EmptyClipboard()) {
739 hi_log_error(
"Could not empty win32 clipboard '{}'", get_last_error_message());
745 auto wtext_handle = GlobalAlloc(GMEM_MOVEABLE, (wtext.size() + 1) *
sizeof(
wchar_t));
746 if (wtext_handle ==
nullptr) {
747 hi_log_error(
"Could not allocate clipboard data '{}'", get_last_error_message());
751 hilet defer_GlobalFree([&wtext_handle] {
752 if (wtext_handle !=
nullptr) {
753 GlobalFree(wtext_handle);
758 auto wtext_c =
static_cast<wchar_t *
>(GlobalLock(wtext_handle));
759 if (wtext_c ==
nullptr) {
760 hi_log_error(
"Could not lock string data '{}'", get_last_error_message());
764 hilet defer_GlobalUnlock = defer([wtext_handle] {
765 if (not GlobalUnlock(wtext_handle) and GetLastError() != ERROR_SUCCESS) {
766 hi_log_error(
"Could not unlock string data '{}'", get_last_error_message());
770 std::memcpy(wtext_c, wtext.c_str(), (wtext.size() + 1) *
sizeof(
wchar_t));
773 if (SetClipboardData(CF_UNICODETEXT, wtext_handle) ==
nullptr) {
774 hi_log_error(
"Could not set clipboard data '{}'", get_last_error_message());
778 wtext_handle =
nullptr;
782[[nodiscard]] translate2 window_to_screen() const noexcept
784 return translate2{rectangle.left(), rectangle.bottom()};
787 [[nodiscard]] translate2 screen_to_window() const noexcept
789 return ~window_to_screen();
802 using enum gui_event_type;
804 hi_axiom(loop::main().on_thread());
808 switch (event.type()) {
810 _redraw_rectangle.fetch_or(event.rectangle());
813 case window_relayout:
814 _relayout.store(
true, std::memory_order_relaxed);
817 case window_reconstrain:
818 _reconstrain.store(
true, std::memory_order_relaxed);
822 _resize.store(
true, std::memory_order_relaxed);
825 case window_minimize:
826 set_size_state(gui_window_size::minimized);
829 case window_maximize:
830 set_size_state(gui_window_size::maximized);
833 case window_normalize:
834 set_size_state(gui_window_size::normal);
841 case window_open_sysmenu:
845 case window_set_keyboard_target:
847 hilet& target =
event.keyboard_target();
848 if (target.widget_id ==
nullptr) {
849 update_keyboard_target(target.group, target.direction);
850 }
else if (target.direction == keyboard_focus_direction::here) {
851 update_keyboard_target(target.widget_id, target.group);
853 update_keyboard_target(target.widget_id, target.group, target.direction);
858 case window_set_clipboard:
859 put_text_on_clipboard(event.clipboard_data());
862 case mouse_exit_window:
863 update_mouse_target({});
869 hilet
hitbox = _widget->hitbox_test(event.mouse().position);
870 update_mouse_target(
hitbox.widget_id, event.mouse().position);
872 if (event == mouse_down) {
873 update_keyboard_target(
hitbox.widget_id, keyboard_focus_group::all);
879 for (
auto& e : translate_keyboard_event(event)) {
887 for (
auto& event_ : events) {
888 if (event_.type() == gui_event_type::text_edit_paste) {
891 if (
auto optional_text = get_text_from_clipboard()) {
892 event_.clipboard_data() = *optional_text;
897 hilet handled = [&] {
898 hilet target_id =
event.variant() == gui_event_variant::mouse ? _mouse_target_id : _keyboard_target_id;
899 return send_events_to_widget(target_id, events);
906 for (hilet event_ : events) {
907 if (event_ == gui_cancel) {
908 update_keyboard_target({}, keyboard_focus_group::all);
916 constexpr static UINT_PTR move_and_resize_timer_id = 2;
919 inline static bool _first_window =
true;
920 inline static const wchar_t *win32WindowClassName =
nullptr;
921 inline static WNDCLASSW win32WindowClass = {};
922 inline static bool win32WindowClassIsRegistered =
false;
923 inline static bool firstWindowHasBeenOpened =
false;
933 box_constraints _widget_constraints = {};
942 gui_window_size _size_state = gui_window_size::normal;
946 aarectangle _restore_rectangle;
955 utc_nanoseconds last_forced_redraw = {};
959 animator<float> _animated_active = _animation_duration;
965 widget_id _mouse_target_id;
970 widget_id _keyboard_target_id;
972 notifier<>::callback_token _setting_change_cbt;
973 observer<std::string>::callback_token _selected_theme_cbt;
974 loop::render_callback_token _render_cbt;
976 TRACKMOUSEEVENT track_mouse_leave_event_parameters;
977 bool tracking_mouse_leave_event =
false;
978 char32_t high_surrogate = 0;
979 gui_event mouse_button_event;
980 utc_nanoseconds multi_click_time_point;
981 point2 multi_click_position;
982 uint8_t multi_click_count;
984 bool keymenu_pressed =
false;
998 target_id = _widget->id;
1001 auto target_widget = get_if(_widget.
get(), target_id,
false);
1002 while (target_widget) {
1004 for (hilet& event : events) {
1005 if (target_widget->handle_event(target_widget->layout().from_window * event)) {
1011 target_widget = target_widget->parent;
1017 void setOSWindowRectangleFromRECT(RECT new_rectangle)
noexcept
1019 hi_axiom(loop::main().on_thread());
1022 hilet inv_bottom = os_settings::primary_monitor_rectangle().height() - new_rectangle.bottom;
1024 hilet new_screen_rectangle = aarectangle{
1025 narrow_cast<float>(new_rectangle.left),
1026 narrow_cast<float>(inv_bottom),
1027 narrow_cast<float>(new_rectangle.right - new_rectangle.left),
1028 narrow_cast<float>(new_rectangle.bottom - new_rectangle.top)};
1030 if (
rectangle.size() != new_screen_rectangle.size()) {
1031 ++global_counter<
"gui_window:os-resize:relayout">;
1032 this->process_event({gui_event_type::window_relayout});
1038 [[nodiscard]] keyboard_state get_keyboard_state() noexcept
1040 auto r = keyboard_state::idle;
1042 if (GetKeyState(VK_CAPITAL) != 0) {
1043 r |= keyboard_state::caps_lock;
1045 if (GetKeyState(VK_NUMLOCK) != 0) {
1046 r |= keyboard_state::num_lock;
1048 if (GetKeyState(VK_SCROLL) != 0) {
1049 r |= keyboard_state::scroll_lock;
1059 static_assert(std::is_signed_v<
decltype(GetAsyncKeyState(VK_SHIFT))>);
1061 auto r = keyboard_modifiers::none;
1063 if (GetAsyncKeyState(VK_SHIFT) < 0) {
1064 r |= keyboard_modifiers::shift;
1066 if (GetAsyncKeyState(VK_CONTROL) < 0) {
1067 r |= keyboard_modifiers::control;
1069 if (GetAsyncKeyState(VK_MENU) < 0) {
1070 r |= keyboard_modifiers::alt;
1072 if (GetAsyncKeyState(VK_LWIN) < 0 or GetAsyncKeyState(VK_RWIN) < 0) {
1073 r |= keyboard_modifiers::super;
1079 [[nodiscard]]
char32_t handle_suragates(
char32_t c)
noexcept
1081 hi_axiom(loop::main().on_thread());
1083 if (c >= 0xd800 && c <= 0xdbff) {
1084 high_surrogate = ((c - 0xd800) << 10) + 0x10000;
1087 }
else if (c >= 0xdc00 && c <= 0xdfff) {
1088 c = high_surrogate ? high_surrogate | (c - 0xdc00) : 0xfffd;
1094 [[nodiscard]] gui_event create_mouse_event(
unsigned int uMsg, uint64_t wParam, int64_t lParam)
noexcept
1096 hi_axiom(loop::main().on_thread());
1098 auto r = gui_event{gui_event_type::mouse_move};
1099 r.keyboard_modifiers = get_keyboard_modifiers();
1100 r.keyboard_state = get_keyboard_state();
1102 hilet x = narrow_cast<float>(GET_X_LPARAM(lParam));
1103 hilet y = narrow_cast<float>(GET_Y_LPARAM(lParam));
1110 r.mouse().position = point2{x, inv_y};
1111 r.mouse().wheel_delta = {};
1112 if (uMsg == WM_MOUSEWHEEL) {
1113 r.mouse().wheel_delta.y() = GET_WHEEL_DELTA_WPARAM(wParam) * 10.0f / WHEEL_DELTA;
1114 }
else if (uMsg == WM_MOUSEHWHEEL) {
1115 r.mouse().wheel_delta.x() = GET_WHEEL_DELTA_WPARAM(wParam) * 10.0f / WHEEL_DELTA;
1119 r.mouse().down.left_button = (GET_KEYSTATE_WPARAM(wParam) & MK_LBUTTON) > 0;
1120 r.mouse().down.middle_button = (GET_KEYSTATE_WPARAM(wParam) & MK_MBUTTON) > 0;
1121 r.mouse().down.right_button = (GET_KEYSTATE_WPARAM(wParam) & MK_RBUTTON) > 0;
1122 r.mouse().down.x1_button = (GET_KEYSTATE_WPARAM(wParam) & MK_XBUTTON1) > 0;
1123 r.mouse().down.x2_button = (GET_KEYSTATE_WPARAM(wParam) & MK_XBUTTON2) > 0;
1128 case WM_LBUTTONDOWN:
1129 case WM_LBUTTONDBLCLK:
1130 r.mouse().cause.left_button =
true;
1133 case WM_RBUTTONDOWN:
1134 case WM_RBUTTONDBLCLK:
1135 r.mouse().cause.right_button =
true;
1138 case WM_MBUTTONDOWN:
1139 case WM_MBUTTONDBLCLK:
1140 r.mouse().cause.middle_button =
true;
1143 case WM_XBUTTONDOWN:
1144 case WM_XBUTTONDBLCLK:
1145 r.mouse().cause.x1_button = (GET_XBUTTON_WPARAM(wParam) & XBUTTON1) > 0;
1146 r.mouse().cause.x2_button = (GET_XBUTTON_WPARAM(wParam) & XBUTTON2) > 0;
1149 if (mouse_button_event == gui_event_type::mouse_down) {
1150 r.mouse().cause = mouse_button_event.mouse().cause;
1154 case WM_MOUSEHWHEEL:
1161 hilet a_button_is_pressed = r.mouse().down.left_button or r.mouse().down.middle_button or r.mouse().down.right_button or
1162 r.mouse().down.x1_button or r.mouse().down.x2_button;
1169 r.set_type(gui_event_type::mouse_up);
1170 if (mouse_button_event) {
1171 r.mouse().down_position = mouse_button_event.mouse().down_position;
1173 r.mouse().click_count = 0;
1175 if (!a_button_is_pressed) {
1180 case WM_LBUTTONDBLCLK:
1181 case WM_MBUTTONDBLCLK:
1182 case WM_RBUTTONDBLCLK:
1183 case WM_XBUTTONDBLCLK:
1184 case WM_LBUTTONDOWN:
1185 case WM_MBUTTONDOWN:
1186 case WM_RBUTTONDOWN:
1187 case WM_XBUTTONDOWN:
1189 hilet within_double_click_time = r.time_point - multi_click_time_point < os_settings::double_click_interval();
1190 hilet double_click_distance =
1191 std::sqrt(narrow_cast<float>(squared_hypot(r.mouse().position - multi_click_position)));
1192 hilet within_double_click_distance = double_click_distance < os_settings::double_click_distance();
1194 multi_click_count = within_double_click_time and within_double_click_distance ? multi_click_count + 1 : 1;
1195 multi_click_time_point = r.time_point;
1196 multi_click_position = r.mouse().position;
1198 r.set_type(gui_event_type::mouse_down);
1199 r.mouse().down_position = r.mouse().position;
1200 r.mouse().click_count = multi_click_count;
1203 hi_assert_not_null(win32Window);
1204 SetCapture(win32Window);
1209 case WM_MOUSEHWHEEL:
1210 r.set_type(gui_event_type::mouse_wheel);
1216 r.set_type(a_button_is_pressed ? gui_event_type::mouse_drag :
gui_event_type::mouse_move);
1217 if (mouse_button_event) {
1218 r.mouse().down_position = mouse_button_event.mouse().down_position;
1219 r.mouse().click_count = mouse_button_event.mouse().click_count;
1225 r.set_type(gui_event_type::mouse_exit_window);
1226 if (mouse_button_event) {
1227 r.mouse().down_position = mouse_button_event.mouse().down_position;
1229 r.mouse().click_count = 0;
1232 tracking_mouse_leave_event =
false;
1236 current_mouse_cursor = mouse_cursor::None;
1245 if (not tracking_mouse_leave_event and uMsg != WM_MOUSELEAVE) {
1246 auto *track_mouse_leave_event_parameters_p = &track_mouse_leave_event_parameters;
1247 if (not TrackMouseEvent(track_mouse_leave_event_parameters_p)) {
1250 tracking_mouse_leave_event =
true;
1255 if (r == gui_event_type::mouse_down or r == gui_event_type::mouse_up or r == gui_event_type::mouse_exit_window) {
1256 mouse_button_event = r;
1262void create_window(extent2 new_size)
1265 hi_assert(loop::main().on_thread());
1267 createWindowClass();
1269 auto u16title =
to_wstring(std::format(
"{}", _title));
1271 hi_log_info(
"Create window of size {} with title '{}'", new_size, _title);
1274 SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
1278 win32Window = CreateWindowExW(
1280 win32WindowClassName,
1282 WS_OVERLAPPEDWINDOW,
1286 round_cast<int>(new_size.width()),
1287 round_cast<int>(new_size.height()),
1291 reinterpret_cast<HINSTANCE
>(crt_application_instance),
1293 if (win32Window ==
nullptr) {
1299 MARGINS m{0, 0, 0, 1};
1300 DwmExtendFrameIntoClientArea(win32Window, &m);
1304 win32Window,
nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
1306 if (!firstWindowHasBeenOpened) {
1307 hilet win32_window_ = win32Window;
1308 switch (gui_window_size::normal) {
1309 case gui_window_size::normal:
1310 ShowWindow(win32_window_, SW_SHOWNORMAL);
1312 case gui_window_size::minimized:
1313 ShowWindow(win32_window_, SW_SHOWMINIMIZED);
1315 case gui_window_size::maximized:
1316 ShowWindow(win32_window_, SW_SHOWMAXIMIZED);
1321 firstWindowHasBeenOpened =
true;
1324 track_mouse_leave_event_parameters.cbSize =
sizeof(track_mouse_leave_event_parameters);
1325 track_mouse_leave_event_parameters.dwFlags = TME_LEAVE;
1326 track_mouse_leave_event_parameters.hwndTrack = win32Window;
1327 track_mouse_leave_event_parameters.dwHoverTime = HOVER_DEFAULT;
1329 ShowWindow(win32Window, SW_SHOW);
1331 auto _dpi = GetDpiForWindow(win32Window);
1333 throw gui_error(
"Could not retrieve dpi for window.");
1335 dpi = narrow_cast<float>(_dpi);
1337 surface = make_unique_gfx_surface(crt_application_instance, win32Window);
1340 int windowProc(
unsigned int uMsg, uint64_t wParam, int64_t lParam)
noexcept
1342 using namespace std::chrono_literals;
1344 gui_event mouse_event;
1345 hilet current_time = std::chrono::utc_clock::now();
1358 hilet createstruct_ptr = std::launder(std::bit_cast<CREATESTRUCT *>(lParam));
1360 new_rectangle.left = createstruct_ptr->x;
1361 new_rectangle.top = createstruct_ptr->y;
1362 new_rectangle.right = createstruct_ptr->x + createstruct_ptr->cx;
1363 new_rectangle.bottom = createstruct_ptr->y + createstruct_ptr->cy;
1364 setOSWindowRectangleFromRECT(new_rectangle);
1373 hilet height = [
this]() {
1374 hi_axiom(loop::main().on_thread());
1379 BeginPaint(win32Window, &ps);
1381 hilet update_rectangle = aarectangle{
1382 narrow_cast<float>(ps.rcPaint.left),
1383 narrow_cast<float>(height - ps.rcPaint.bottom),
1384 narrow_cast<float>(ps.rcPaint.right - ps.rcPaint.left),
1385 narrow_cast<float>(ps.rcPaint.bottom - ps.rcPaint.top)};
1388 hi_axiom(loop::main().on_thread());
1389 this->process_event({gui_event_type::window_redraw, update_rectangle});
1392 EndPaint(win32Window, &ps);
1397 hi_axiom(loop::main().on_thread());
1398 this->process_event({gui_event_type::window_redraw, aarectangle{
rectangle.size()}});
1404 hi_axiom(loop::main().on_thread());
1406 case SIZE_MAXIMIZED:
1407 ShowWindow(win32Window, SW_RESTORE);
1408 set_size_state(gui_window_size::maximized);
1410 case SIZE_MINIMIZED:
1411 _size_state = gui_window_size::minimized;
1414 _size_state = gui_window_size::normal;
1422 if (last_forced_redraw + 16.7ms < current_time) {
1425 loop::main().resume_once();
1426 last_forced_redraw = current_time;
1432 hilet& rect_ptr = *std::launder(std::bit_cast<RECT *>(lParam));
1433 if (rect_ptr.right < rect_ptr.left or rect_ptr.bottom < rect_ptr.top) {
1435 "Invalid RECT received on WM_SIZING: left={}, right={}, bottom={}, top={}",
1442 setOSWindowRectangleFromRECT(rect_ptr);
1449 hilet& rect_ptr = *std::launder(std::bit_cast<RECT *>(lParam));
1450 if (rect_ptr.right < rect_ptr.left or rect_ptr.bottom < rect_ptr.top) {
1452 "Invalid RECT received on WM_MOVING: left={}, right={}, bottom={}, top={}",
1459 setOSWindowRectangleFromRECT(rect_ptr);
1464 case WM_WINDOWPOSCHANGED:
1466 hilet windowpos_ptr = std::launder(std::bit_cast<WINDOWPOS *>(lParam));
1468 new_rectangle.left = windowpos_ptr->x;
1469 new_rectangle.top = windowpos_ptr->y;
1470 new_rectangle.right = windowpos_ptr->x + windowpos_ptr->cx;
1471 new_rectangle.bottom = windowpos_ptr->y + windowpos_ptr->cy;
1472 setOSWindowRectangleFromRECT(new_rectangle);
1476 case WM_ENTERSIZEMOVE:
1477 hi_axiom(loop::main().on_thread());
1478 if (SetTimer(win32Window, move_and_resize_timer_id, 16, NULL) != move_and_resize_timer_id) {
1484 case WM_EXITSIZEMOVE:
1485 hi_axiom(loop::main().on_thread());
1486 if (not KillTimer(win32Window, move_and_resize_timer_id)) {
1492 _size_state = gui_window_size::normal;
1493 this->process_event({gui_event_type::window_redraw, aarectangle{
rectangle.size()}});
1497 hi_axiom(loop::main().on_thread());
1507 hi_log_error(
"Unknown WM_ACTIVE value.");
1509 ++global_counter<
"gui_window:WM_ACTIVATE:constrain">;
1510 this->process_event({gui_event_type::window_reconstrain});
1513 case WM_GETMINMAXINFO:
1515 hi_axiom(loop::main().on_thread());
1516 hilet minmaxinfo = std::launder(std::bit_cast<MINMAXINFO *>(lParam));
1517 minmaxinfo->ptMaxSize.x = round_cast<LONG>(_widget_constraints.maximum.width());
1518 minmaxinfo->ptMaxSize.y = round_cast<LONG>(_widget_constraints.maximum.height());
1519 minmaxinfo->ptMinTrackSize.x = round_cast<LONG>(_widget_constraints.minimum.width());
1520 minmaxinfo->ptMinTrackSize.y = round_cast<LONG>(_widget_constraints.minimum.height());
1521 minmaxinfo->ptMaxTrackSize.x = round_cast<LONG>(_widget_constraints.maximum.width());
1522 minmaxinfo->ptMaxTrackSize.y = round_cast<LONG>(_widget_constraints.maximum.height());
1527 if (
auto c = char_cast<char32_t>(wParam); c == UNICODE_NOCHAR) {
1531 }
else if (hilet gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1533 process_event(gui_event::keyboard_grapheme(grapheme{c}));
1538 if (
auto c = handle_suragates(char_cast<char32_t>(wParam))) {
1539 if (hilet gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1541 process_event(gui_event::keyboard_partial_grapheme(grapheme{c}));
1547 if (
auto c = handle_suragates(char_cast<char32_t>(wParam))) {
1548 if (hilet gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1550 process_event(gui_event::keyboard_grapheme(grapheme{c}));
1556 if (wParam == SC_KEYMENU) {
1557 keymenu_pressed =
true;
1558 process_event(gui_event{gui_event_type::keyboard_down, keyboard_virtual_key::menu});
1566 hilet extended = (narrow_cast<uint32_t>(lParam) & 0x01000000) != 0;
1567 hilet key_code = narrow_cast<int>(wParam);
1568 hilet key_modifiers = get_keyboard_modifiers();
1569 auto virtual_key = to_keyboard_virtual_key(key_code, extended, key_modifiers);
1571 if (std::exchange(keymenu_pressed,
false) and uMsg == WM_KEYDOWN and virtual_key == keyboard_virtual_key::space) {
1573 virtual_key = keyboard_virtual_key::sysmenu;
1576 if (virtual_key != keyboard_virtual_key::nul) {
1577 hilet key_state = get_keyboard_state();
1578 hilet event_type = uMsg == WM_KEYDOWN ? gui_event_type::keyboard_down : gui_event_type::keyboard_up;
1579 process_event(gui_event{event_type, virtual_key, key_modifiers, key_state});
1584 case WM_LBUTTONDOWN:
1585 case WM_MBUTTONDOWN:
1586 case WM_RBUTTONDOWN:
1587 case WM_XBUTTONDOWN:
1592 case WM_LBUTTONDBLCLK:
1593 case WM_MBUTTONDBLCLK:
1594 case WM_RBUTTONDBLCLK:
1595 case WM_XBUTTONDBLCLK:
1597 case WM_MOUSEHWHEEL:
1600 keymenu_pressed =
false;
1601 process_event(create_mouse_event(uMsg, wParam, lParam));
1605 if (wParam == TRUE) {
1621 hi_axiom(loop::main().on_thread());
1623 hilet x = narrow_cast<float>(GET_X_LPARAM(lParam));
1624 hilet y = narrow_cast<float>(GET_Y_LPARAM(lParam));
1627 hilet inv_y = os_settings::primary_monitor_rectangle().height() - y;
1629 hilet hitbox_type = _widget->hitbox_test(screen_to_window() * point2{x, inv_y}).type;
1631 switch (hitbox_type) {
1632 case hitbox_type::bottom_resize_border:
1633 set_cursor(mouse_cursor::None);
1635 case hitbox_type::top_resize_border:
1636 set_cursor(mouse_cursor::None);
1638 case hitbox_type::left_resize_border:
1639 set_cursor(mouse_cursor::None);
1641 case hitbox_type::right_resize_border:
1642 set_cursor(mouse_cursor::None);
1644 case hitbox_type::bottom_left_resize_corner:
1645 set_cursor(mouse_cursor::None);
1646 return HTBOTTOMLEFT;
1647 case hitbox_type::bottom_right_resize_corner:
1648 set_cursor(mouse_cursor::None);
1649 return HTBOTTOMRIGHT;
1650 case hitbox_type::top_left_resize_corner:
1651 set_cursor(mouse_cursor::None);
1653 case hitbox_type::top_right_resize_corner:
1654 set_cursor(mouse_cursor::None);
1656 case hitbox_type::application_icon:
1657 set_cursor(mouse_cursor::None);
1659 case hitbox_type::move_area:
1660 set_cursor(mouse_cursor::None);
1662 case hitbox_type::text_edit:
1663 set_cursor(mouse_cursor::TextEdit);
1665 case hitbox_type::button:
1666 set_cursor(mouse_cursor::Button);
1668 case hitbox_type::scroll_bar:
1669 set_cursor(mouse_cursor::Default);
1671 case hitbox_type::_default:
1672 set_cursor(mouse_cursor::Default);
1674 case hitbox_type::outside:
1675 set_cursor(mouse_cursor::None);
1683 case WM_SETTINGCHANGE:
1684 hi_axiom(loop::main().on_thread());
1685 os_settings::gather();
1690 hi_axiom(loop::main().on_thread());
1692 dpi = narrow_cast<float>(LOWORD(wParam));
1695 hilet new_rectangle = std::launder(
reinterpret_cast<RECT *
>(lParam));
1699 new_rectangle->left,
1701 new_rectangle->right - new_rectangle->left,
1702 new_rectangle->bottom - new_rectangle->top,
1703 SWP_NOZORDER | SWP_NOACTIVATE);
1704 ++global_counter<
"gui_window:WM_DPICHANGED:constrain">;
1705 this->process_event({gui_event_type::window_reconstrain});
1707 hi_log_info(
"DPI has changed to {}", dpi);
1722 static LRESULT CALLBACK _WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
noexcept
1724 if (uMsg == WM_CREATE && lParam) {
1725 hilet createData = std::launder(std::bit_cast<CREATESTRUCT *>(lParam));
1728 auto r = SetWindowLongPtrW(hwnd, GWLP_USERDATA, std::bit_cast<LONG_PTR>(createData->lpCreateParams));
1729 if (r != 0 || GetLastError() != 0) {
1737 auto window_userdata = GetWindowLongPtrW(hwnd, GWLP_USERDATA);
1738 if (window_userdata == 0) {
1739 return DefWindowProc(hwnd, uMsg, wParam, lParam);
1742 auto& window = *std::launder(std::bit_cast<gui_window *>(window_userdata));
1743 hi_axiom(loop::main().on_thread());
1747 if (uMsg == WM_CLOSE) {
1752 }
else if (uMsg == WM_DESTROY) {
1756 auto r = SetWindowLongPtrW(hwnd, GWLP_USERDATA, NULL);
1757 if (r == 0 || GetLastError() != 0) {
1762 window.win32Window =
nullptr;
1766 if (
auto result = window.windowProc(uMsg, wParam, lParam); result != -1) {
1769 return DefWindowProc(hwnd, uMsg, wParam, lParam);
1773 static void createWindowClass()
1775 if (!win32WindowClassIsRegistered) {
1777 win32WindowClassName = L
"HikoGUI Window Class";
1779 std::memset(&win32WindowClass, 0,
sizeof(WNDCLASSW));
1780 win32WindowClass.style = CS_DBLCLKS;
1781 win32WindowClass.lpfnWndProc = _WindowProc;
1783 win32WindowClass.lpszClassName = win32WindowClassName;
1784 win32WindowClass.hCursor =
nullptr;
1785 RegisterClassW(&win32WindowClass);
1787 win32WindowClassIsRegistered =
true;