27 HWND win32Window =
nullptr;
61 unit::pixel_density
pixel_density = {unit::pixels_per_inch(96.0f), device_type::desktop};
77 gui_window(gui_window
const&) =
delete;
78 gui_window& operator=(gui_window
const&) =
delete;
79 gui_window(gui_window&&) =
delete;
80 gui_window& operator=(gui_window&&) =
delete;
84 hi_assert_not_null(_widget);
88 hi_log_fatal(
"Could not start the os_settings subsystem.");
93 register_font_directories(font_dirs());
95 register_theme_directories(theme_dirs());
98 load_system_keyboard_bindings(URL{
"resource:win32.keybinds.json"});
100 hi_log_fatal(
"Could not load keyboard bindings. \"{}\"", e.
what());
103 SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
105 _first_window =
true;
113 _setting_change_cbt = os_settings::subscribe(
115 ++global_counter<
"gui_window:os_setting:constrain">;
121 _selected_theme_cbt = theme_book::global().selected_theme.subscribe(
123 ++global_counter<
"gui_window:selected_theme:constrain">;
128 _render_cbt =
loop::main().subscribe_render([
this](utc_nanoseconds display_time) {
129 this->
render(display_time);
134 auto const new_position =
point2{500.0f, 500.0f};
135 create_window(new_position);
137 apply_window_data(*_widget,
this,
pixel_density, get_selected_theme().attributes_from_theme_function());
142 _widget_constraints = _widget->update_constraints();
143 auto const new_size = _widget_constraints.preferred;
145 show_window(new_size);
151 if (win32Window !=
nullptr) {
152 DestroyWindow(win32Window);
153 hi_assert(win32Window ==
nullptr);
157 }
catch (std::exception
const& e) {
158 hi_log_fatal(
"Could not properly destruct gui_window. '{}'", e.
what());
166 hi_log_info(
"Window '{}' has been properly destructed.", _title);
168 }
catch (std::exception
const& e) {
169 hi_log_fatal(
"Could not properly destruct gui_window. '{}'", e.
what());
173 template<
typename W
idget>
174 [[nodiscard]] Widget& widget() const noexcept
176 return up_cast<Widget>(*_widget);
179 void set_title(label title)
noexcept
187 void render(utc_nanoseconds display_time_point)
189 if (surface->device() ==
nullptr) {
194 auto const t1 =
trace<
"window::render">();
197 hi_assert_not_null(surface);
198 hi_assert_not_null(_widget);
202 auto need_reconstrain = _reconstrain.exchange(
false, std::memory_order_relaxed);
206 need_reconstrain =
true;
209 if (need_reconstrain) {
210 auto const t2 =
trace<
"window::constrain">();
213 _widget_constraints = _widget->update_constraints();
225 if (_resize.exchange(
false, std::memory_order::relaxed)) {
227 auto const current_size =
rectangle.size();
228 auto const 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);
236 auto const current_size =
rectangle.size();
237 auto const 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);
244 if (
rectangle.size() < _widget_constraints.minimum or
rectangle.size() > _widget_constraints.maximum) {
255 auto need_relayout = _relayout.exchange(
false, std::memory_order_relaxed);
259 need_relayout =
true;
263 auto const t2 =
trace<
"window::layout">();
268 auto const 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});
281 if (
auto draw_context = surface->render_start(_redraw_rectangle)) {
282 _redraw_rectangle = aarectangle{};
283 draw_context.display_time_point = display_time_point;
284 draw_context.subpixel_orientation = subpixel_orientation();
285 draw_context.saturation = 1.0f;
288 auto const t2 =
trace<
"window::draw">();
289 _widget->draw(draw_context);
292 auto const t2 =
trace<
"window::submit">();
293 surface->render_finish(draw_context);
309 if (cursor == mouse_cursor::None) {
313 static auto idcAppStarting = LoadCursorW(
nullptr, IDC_APPSTARTING);
314 static auto idcArrow = LoadCursorW(
nullptr, IDC_ARROW);
315 static auto idcHand = LoadCursorW(
nullptr, IDC_HAND);
316 static auto idcIBeam = LoadCursorW(
nullptr, IDC_IBEAM);
317 static auto idcNo = LoadCursorW(
nullptr, IDC_NO);
321 case mouse_cursor::None:
322 idc = idcAppStarting;
324 case mouse_cursor::Default:
327 case mouse_cursor::Button:
330 case mouse_cursor::TextEdit:
345 if (not PostMessageW(win32Window, WM_CLOSE, 0, 0)) {
346 hi_log_error(
"Could not send WM_CLOSE to window {}: {}", _title, get_last_error_message());
359 if (_size_state == state) {
363 if (_size_state == gui_window_size::normal) {
365 }
else if (_size_state == gui_window_size::minimized) {
366 ShowWindow(win32Window, SW_RESTORE);
367 _size_state = gui_window_size::normal;
370 if (state == gui_window_size::normal) {
371 auto const left = round_cast<int>(_restore_rectangle.left());
372 auto const top = round_cast<int>(_restore_rectangle.top());
373 auto const width = round_cast<int>(_restore_rectangle.width());
374 auto const height = round_cast<int>(_restore_rectangle.height());
376 SetWindowPos(win32Window, HWND_TOP, left, inv_top, width, height, 0);
377 _size_state = gui_window_size::normal;
379 }
else if (state == gui_window_size::minimized) {
380 ShowWindow(win32Window, SW_MINIMIZE);
381 _size_state = gui_window_size::minimized;
383 }
else if (state == gui_window_size::maximized) {
385 auto const max_size = _widget_constraints.maximum;
388 auto const width =
std::min(max_size.width(), workspace.width());
389 auto const height =
std::min(max_size.height(), workspace.height());
390 auto const left = std::clamp(
rectangle.left(), workspace.left(), workspace.right() - width);
391 auto const top = std::clamp(
rectangle.top(), workspace.bottom() + height, workspace.top());
396 round_cast<int>(left),
397 round_cast<int>(inv_top),
398 round_cast<int>(width),
399 round_cast<int>(height),
401 _size_state = gui_window_size::maximized;
403 }
else if (state == gui_window_size::fullscreen) {
405 auto const max_size = _widget_constraints.maximum;
406 if (fullscreen.width() > max_size.width() or fullscreen.height() > max_size.height()) {
411 auto const left = round_cast<int>(fullscreen.left());
412 auto const top = round_cast<int>(fullscreen.top());
413 auto const width = round_cast<int>(fullscreen.width());
414 auto const height = round_cast<int>(fullscreen.height());
416 SetWindowPos(win32Window, HWND_TOP, left, inv_top, width, height, 0);
417 _size_state = gui_window_size::fullscreen;
425 auto const monitor = MonitorFromWindow(win32Window, MONITOR_DEFAULTTOPRIMARY);
426 if (monitor == NULL) {
427 hi_log_error(
"Could not get monitor for the window.");
428 return {0, 0, 1920, 1080};
432 info.cbSize =
sizeof(MONITORINFO);
433 if (not GetMonitorInfo(monitor, &info)) {
434 hi_log_error(
"Could not get monitor info for the window.");
435 return {0, 0, 1920, 1080};
438 auto const left = narrow_cast<float>(info.rcWork.left);
439 auto const top = narrow_cast<float>(info.rcWork.top);
440 auto const right = narrow_cast<float>(info.rcWork.right);
441 auto const bottom = narrow_cast<float>(info.rcWork.bottom);
442 auto const width = right - left;
443 auto const height = bottom - top;
445 return aarectangle{left, inv_bottom, width, height};
452 auto const monitor = MonitorFromWindow(win32Window, MONITOR_DEFAULTTOPRIMARY);
453 if (monitor == NULL) {
454 hi_log_error(
"Could not get monitor for the window.");
455 return {0, 0, 1920, 1080};
459 info.cbSize =
sizeof(MONITORINFO);
460 if (not GetMonitorInfo(monitor, &info)) {
461 hi_log_error(
"Could not get monitor info for the window.");
462 return {0, 0, 1920, 1080};
465 auto const left = narrow_cast<float>(info.rcMonitor.left);
466 auto const top = narrow_cast<float>(info.rcMonitor.top);
467 auto const right = narrow_cast<float>(info.rcMonitor.right);
468 auto const bottom = narrow_cast<float>(info.rcMonitor.bottom);
469 auto const width = right - left;
470 auto const height = bottom - top;
472 return aarectangle{left, inv_bottom, width, height};
491 constexpr auto tan_half_degree = 0.00872686779075879f;
492 constexpr auto viewing_distance = 20.0f;
494 auto const ppd = 2 * viewing_distance * pixel_density.ppi * tan_half_degree;
496 if (ppd > unit::pixels_per_inch(55.0f)) {
498 return hi::subpixel_orientation::unknown;
501 return os_settings::subpixel_orientation();
515 auto const top =
rectangle.top() - 30.0f;
521 auto const system_menu = GetSystemMenu(win32Window,
false);
523 TrackPopupMenu(system_menu, TPM_RETURNCMD, round_cast<int>(left), round_cast<int>(inv_top), 0, win32Window, NULL);
525 SendMessage(win32Window, WM_SYSCOMMAND, narrow_cast<WPARAM>(cmd), LPARAM{0});
536 if (not GetWindowRect(win32Window, &original_rect)) {
537 hi_log_error(
"Could not get the window's rectangle on the screen.");
540 auto const new_width = round_cast<int>(new_extent.width());
541 auto const new_height = round_cast<int>(new_extent.height());
543 narrow_cast<int>(original_rect.right - new_width);
544 auto const new_y = narrow_cast<int>(original_rect.top);
553 SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_DEFERERASE | SWP_NOCOPYBITS | SWP_FRAMECHANGED);
556 void update_mouse_target(widget_id new_target_id,
point2 position = {})
noexcept
558 hi_axiom(loop::main().on_thread());
560 if (_mouse_target_id != 0) {
561 if (new_target_id == _mouse_target_id) {
567 send_events_to_widget(_mouse_target_id, std::vector{gui_event{gui_event_type::mouse_exit}});
570 if (new_target_id != 0) {
571 _mouse_target_id = new_target_id;
572 send_events_to_widget(new_target_id, std::vector{gui_event::make_mouse_enter(position)});
574 _mouse_target_id = {};
588 auto new_target_widget = get_if(_widget.get(), new_target_id,
false);
592 auto new_target_parent_chain = new_target_widget ? new_target_widget->parent_chain() :
std::vector<widget_id>{};
597 if (new_target_widget ==
nullptr or not new_target_widget->accepts_keyboard_focus(group)) {
598 new_target_widget =
nullptr;
601 if (
auto const *
const keyboard_target_widget = get_if(_widget.get(), _keyboard_target_id,
false)) {
603 if (new_target_widget == keyboard_target_widget) {
608 send_events_to_widget(_keyboard_target_id,
std::vector{gui_event{gui_event_type::keyboard_exit}});
612 _widget->handle_event_recursive(gui_event_type::gui_cancel, new_target_parent_chain);
615 if (new_target_widget !=
nullptr) {
616 _keyboard_target_id = new_target_widget->id;
617 send_events_to_widget(_keyboard_target_id,
std::vector{gui_event{gui_event_type::keyboard_enter}});
619 _keyboard_target_id = {};
635 if (
auto tmp = _widget->find_next_widget(start_widget, group, direction); tmp != start_widget) {
641 tmp = _widget->find_next_widget({}, group, direction);
667 if (not OpenClipboard(win32Window)) {
669 hi_log_info(
"Could not open win32 clipboard '{}'", get_last_error_message());
673 auto const defer_CloseClipboard = defer([] {
678 while ((format = EnumClipboardFormats(format)) != 0) {
684 auto const cb_data = GetClipboardData(CF_UNICODETEXT);
685 if (cb_data ==
nullptr) {
686 hi_log_error(
"Could not get clipboard data: '{}'", get_last_error_message());
690 auto const *
const wstr_c =
static_cast<wchar_t const *
>(GlobalLock(cb_data));
691 if (wstr_c ==
nullptr) {
692 hi_log_error(
"Could not lock clipboard data: '{}'", get_last_error_message());
696 auto const defer_GlobalUnlock = defer([cb_data] {
697 if (not GlobalUnlock(cb_data) and GetLastError() != ERROR_SUCCESS) {
698 hi_log_error(
"Could not unlock clipboard data: '{}'", get_last_error_message());
702 auto r =
to_gstring(hi::to_string(std::wstring_view(wstr_c)));
703 hi_log_debug(
"get_text_from_clipboard '{}'", to_string(r));
712 if (GetLastError() != ERROR_SUCCESS) {
713 hi_log_error(
"Could not enumerator clipboard formats: '{}'", get_last_error_message());
726 if (not OpenClipboard(win32Window)) {
728 hi_log_info(
"Could not open win32 clipboard '{}'", get_last_error_message());
732 auto const defer_CloseClipboard = defer([] {
736 if (not EmptyClipboard()) {
737 hi_log_error(
"Could not empty win32 clipboard '{}'", get_last_error_message());
743 auto wtext_handle = GlobalAlloc(GMEM_MOVEABLE, (wtext.size() + 1) *
sizeof(
wchar_t));
744 if (wtext_handle ==
nullptr) {
745 hi_log_error(
"Could not allocate clipboard data '{}'", get_last_error_message());
749 auto const defer_GlobalFree([&wtext_handle] {
750 if (wtext_handle !=
nullptr) {
751 GlobalFree(wtext_handle);
756 auto wtext_c =
static_cast<wchar_t *
>(GlobalLock(wtext_handle));
757 if (wtext_c ==
nullptr) {
758 hi_log_error(
"Could not lock string data '{}'", get_last_error_message());
762 auto const defer_GlobalUnlock = defer([wtext_handle] {
763 if (not GlobalUnlock(wtext_handle) and GetLastError() != ERROR_SUCCESS) {
764 hi_log_error(
"Could not unlock string data '{}'", get_last_error_message());
768 std::memcpy(wtext_c, wtext.c_str(), (wtext.size() + 1) *
sizeof(
wchar_t));
771 if (SetClipboardData(CF_UNICODETEXT, wtext_handle) ==
nullptr) {
772 hi_log_error(
"Could not set clipboard data '{}'", get_last_error_message());
776 wtext_handle =
nullptr;
780 [[nodiscard]] translate2 window_to_screen() const noexcept
782 return translate2{rectangle.left(), rectangle.bottom()};
785 [[nodiscard]] translate2 screen_to_window() const noexcept
787 return ~window_to_screen();
800 using enum gui_event_type;
804 switch (event.type()) {
806 _redraw_rectangle.fetch_or(event.rectangle());
809 case window_relayout:
810 _relayout.store(
true, std::memory_order_relaxed);
813 case window_reconstrain:
814 _reconstrain.store(
true, std::memory_order_relaxed);
818 _resize.store(
true, std::memory_order_relaxed);
821 case window_minimize:
825 case window_maximize:
829 case window_normalize:
837 case window_open_sysmenu:
841 case window_set_keyboard_target:
843 auto const& target =
event.keyboard_target();
844 if (target.widget_id == 0) {
854 case window_set_clipboard:
858 case mouse_exit_window:
859 update_mouse_target({});
866 event.mouse().hitbox = _widget->hitbox_test(event.mouse().position);
867 if (event == mouse_down or event == mouse_move) {
868 update_mouse_target(event.mouse().hitbox.widget_id, event.mouse().position);
870 if (event == mouse_down) {
880 if (event.type() == keyboard_down) {
881 for (
auto& e : translate_keyboard_event(event)) {
886 for (
auto& event_ : events) {
887 if (event_.type() == gui_event_type::text_edit_paste) {
891 event_.clipboard_data() = *optional_text;
897 auto const handled = send_events_to_widget(
898 events.front().variant() == gui_event_variant::mouse ? _mouse_target_id : _keyboard_target_id, events);
904 for (
auto const event_ : events) {
905 if (event_ == gui_cancel) {
914 constexpr static UINT_PTR move_and_resize_timer_id = 2;
917 inline static bool _first_window =
true;
918 inline static const wchar_t *win32WindowClassName =
nullptr;
919 inline static WNDCLASSW win32WindowClass = {};
920 inline static bool win32WindowClassIsRegistered =
false;
921 inline static bool firstWindowHasBeenOpened =
false;
931 box_constraints _widget_constraints = {};
933 std::atomic<aarectangle> _redraw_rectangle = aarectangle{};
934 std::atomic<bool> _relayout =
false;
935 std::atomic<bool> _reconstrain =
false;
936 std::atomic<bool> _resize =
false;
940 gui_window_size _size_state = gui_window_size::normal;
944 aarectangle _restore_rectangle;
953 utc_nanoseconds last_forced_redraw = {};
959 widget_id _mouse_target_id;
964 widget_id _keyboard_target_id;
966 TRACKMOUSEEVENT track_mouse_leave_event_parameters;
967 bool tracking_mouse_leave_event =
false;
968 char32_t high_surrogate = 0;
969 gui_event mouse_button_event;
970 utc_nanoseconds multi_click_time_point;
971 point2 multi_click_position;
972 uint8_t multi_click_count;
974 bool keymenu_pressed =
false;
976 callback<void()> _setting_change_cbt;
977 callback<void(std::string)> _selected_theme_cbt;
978 callback<void(utc_nanoseconds)> _render_cbt;
988 bool send_events_to_widget(widget_id target_id, std::vector<gui_event>
const& events)
noexcept
990 if (target_id == 0) {
992 target_id = _widget->id;
995 auto target_widget = get_if(_widget.
get(), target_id,
false);
996 while (target_widget) {
998 for (
auto const& event : events) {
999 if (target_widget->handle_event(target_widget->layout().from_window * event)) {
1005 target_widget = target_widget->parent();
1011 void setOSWindowRectangleFromRECT(RECT new_rectangle)
noexcept
1013 hi_axiom(loop::main().on_thread());
1016 auto const inv_bottom = os_settings::primary_monitor_rectangle().height() - new_rectangle.bottom;
1018 auto const new_screen_rectangle = aarectangle{
1019 narrow_cast<float>(new_rectangle.left),
1020 narrow_cast<float>(inv_bottom),
1021 narrow_cast<float>(new_rectangle.right - new_rectangle.left),
1022 narrow_cast<float>(new_rectangle.bottom - new_rectangle.top)};
1024 if (
rectangle.size() != new_screen_rectangle.size()) {
1025 ++global_counter<
"gui_window:os-resize:relayout">;
1026 this->process_event({gui_event_type::window_relayout});
1032 [[nodiscard]] keyboard_state get_keyboard_state() noexcept
1034 auto r = keyboard_state::idle;
1036 if (GetKeyState(VK_CAPITAL) != 0) {
1037 r |= keyboard_state::caps_lock;
1039 if (GetKeyState(VK_NUMLOCK) != 0) {
1040 r |= keyboard_state::num_lock;
1042 if (GetKeyState(VK_SCROLL) != 0) {
1043 r |= keyboard_state::scroll_lock;
1053 static_assert(std::is_signed_v<
decltype(GetAsyncKeyState(VK_SHIFT))>);
1055 auto r = keyboard_modifiers::none;
1057 if (GetAsyncKeyState(VK_SHIFT) < 0) {
1058 r |= keyboard_modifiers::shift;
1060 if (GetAsyncKeyState(VK_CONTROL) < 0) {
1061 r |= keyboard_modifiers::control;
1063 if (GetAsyncKeyState(VK_MENU) < 0) {
1064 r |= keyboard_modifiers::alt;
1066 if (GetAsyncKeyState(VK_LWIN) < 0 or GetAsyncKeyState(VK_RWIN) < 0) {
1067 r |= keyboard_modifiers::super;
1073 [[nodiscard]]
char32_t handle_suragates(
char32_t c)
noexcept
1075 hi_axiom(loop::main().on_thread());
1077 if (c >= 0xd800 && c <= 0xdbff) {
1078 high_surrogate = ((c - 0xd800) << 10) + 0x10000;
1081 }
else if (c >= 0xdc00 && c <= 0xdfff) {
1082 c = high_surrogate ? high_surrogate | (c - 0xdc00) : 0xfffd;
1088 [[nodiscard]] gui_event create_mouse_event(
unsigned int uMsg, uint64_t wParam, int64_t lParam)
noexcept
1090 hi_axiom(loop::main().on_thread());
1092 auto r = gui_event{gui_event_type::mouse_move};
1093 r.keyboard_modifiers = get_keyboard_modifiers();
1094 r.keyboard_state = get_keyboard_state();
1096 auto const x = narrow_cast<float>(GET_X_LPARAM(lParam));
1097 auto const y = narrow_cast<float>(GET_Y_LPARAM(lParam));
1100 auto const inv_y =
rectangle.height() - y;
1104 r.mouse().position = point2{x, inv_y};
1105 r.mouse().wheel_delta = {};
1106 if (uMsg == WM_MOUSEWHEEL) {
1107 r.mouse().wheel_delta.y() = GET_WHEEL_DELTA_WPARAM(wParam) * 10.0f / WHEEL_DELTA;
1108 }
else if (uMsg == WM_MOUSEHWHEEL) {
1109 r.mouse().wheel_delta.x() = GET_WHEEL_DELTA_WPARAM(wParam) * 10.0f / WHEEL_DELTA;
1113 r.mouse().down.left_button = (GET_KEYSTATE_WPARAM(wParam) & MK_LBUTTON) > 0;
1114 r.mouse().down.middle_button = (GET_KEYSTATE_WPARAM(wParam) & MK_MBUTTON) > 0;
1115 r.mouse().down.right_button = (GET_KEYSTATE_WPARAM(wParam) & MK_RBUTTON) > 0;
1116 r.mouse().down.x1_button = (GET_KEYSTATE_WPARAM(wParam) & MK_XBUTTON1) > 0;
1117 r.mouse().down.x2_button = (GET_KEYSTATE_WPARAM(wParam) & MK_XBUTTON2) > 0;
1122 case WM_LBUTTONDOWN:
1123 case WM_LBUTTONDBLCLK:
1124 r.mouse().cause.left_button =
true;
1127 case WM_RBUTTONDOWN:
1128 case WM_RBUTTONDBLCLK:
1129 r.mouse().cause.right_button =
true;
1132 case WM_MBUTTONDOWN:
1133 case WM_MBUTTONDBLCLK:
1134 r.mouse().cause.middle_button =
true;
1137 case WM_XBUTTONDOWN:
1138 case WM_XBUTTONDBLCLK:
1139 r.mouse().cause.x1_button = (GET_XBUTTON_WPARAM(wParam) & XBUTTON1) > 0;
1140 r.mouse().cause.x2_button = (GET_XBUTTON_WPARAM(wParam) & XBUTTON2) > 0;
1143 if (mouse_button_event == gui_event_type::mouse_down) {
1144 r.mouse().cause = mouse_button_event.mouse().cause;
1148 case WM_MOUSEHWHEEL:
1155 auto const a_button_is_pressed = r.mouse().down.left_button or r.mouse().down.middle_button or r.mouse().down.right_button or
1156 r.mouse().down.x1_button or r.mouse().down.x2_button;
1163 r.set_type(gui_event_type::mouse_up);
1164 if (mouse_button_event) {
1165 r.mouse().down_position = mouse_button_event.mouse().down_position;
1167 r.mouse().click_count = 0;
1169 if (!a_button_is_pressed) {
1174 case WM_LBUTTONDBLCLK:
1175 case WM_MBUTTONDBLCLK:
1176 case WM_RBUTTONDBLCLK:
1177 case WM_XBUTTONDBLCLK:
1178 case WM_LBUTTONDOWN:
1179 case WM_MBUTTONDOWN:
1180 case WM_RBUTTONDOWN:
1181 case WM_XBUTTONDOWN:
1183 auto const within_double_click_time = r.time_point - multi_click_time_point < os_settings::double_click_interval();
1184 auto const double_click_distance =
1185 std::sqrt(narrow_cast<float>(squared_hypot(r.mouse().position - multi_click_position)));
1186 auto const within_double_click_distance = double_click_distance < os_settings::double_click_distance();
1188 multi_click_count = within_double_click_time and within_double_click_distance ? multi_click_count + 1 : 1;
1189 multi_click_time_point = r.time_point;
1190 multi_click_position = r.mouse().position;
1192 r.set_type(gui_event_type::mouse_down);
1193 r.mouse().down_position = r.mouse().position;
1194 r.mouse().click_count = multi_click_count;
1197 hi_assert_not_null(win32Window);
1198 SetCapture(win32Window);
1203 case WM_MOUSEHWHEEL:
1204 r.set_type(gui_event_type::mouse_wheel);
1210 r.set_type(a_button_is_pressed ? gui_event_type::mouse_drag : gui_event_type::mouse_move);
1211 if (mouse_button_event) {
1212 r.mouse().down_position = mouse_button_event.mouse().down_position;
1213 r.mouse().click_count = mouse_button_event.mouse().click_count;
1219 r.set_type(gui_event_type::mouse_exit_window);
1220 if (mouse_button_event) {
1221 r.mouse().down_position = mouse_button_event.mouse().down_position;
1223 r.mouse().click_count = 0;
1226 tracking_mouse_leave_event =
false;
1230 current_mouse_cursor = mouse_cursor::None;
1239 if (not tracking_mouse_leave_event and uMsg != WM_MOUSELEAVE) {
1240 auto *track_mouse_leave_event_parameters_p = &track_mouse_leave_event_parameters;
1241 if (not TrackMouseEvent(track_mouse_leave_event_parameters_p)) {
1244 tracking_mouse_leave_event =
true;
1249 if (r == gui_event_type::mouse_down or r == gui_event_type::mouse_up or r == gui_event_type::mouse_exit_window) {
1250 mouse_button_event = r;
1263 void create_window(point2 position)
1266 hi_assert(loop::main().on_thread());
1268 createWindowClass();
1270 auto u16title =
to_wstring(std::format(
"{}", _title));
1272 hi_log_info(
"Create window with title '{}'", _title);
1275 SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
1279 win32Window = CreateWindowExW(
1281 win32WindowClassName,
1283 WS_OVERLAPPEDWINDOW,
1285 round_cast<int>(position.x()),
1286 round_cast<int>(position.y()),
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 (not firstWindowHasBeenOpened) {
1307 auto const 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 auto ppi_ = GetDpiForWindow(win32Window);
1331 throw gui_error(
"Could not retrieve dpi for window.");
1333 pixel_density = {unit::pixels_per_inch(ppi_), os_settings::device_type()};
1334 surface = make_unique_gfx_surface(crt_application_instance, win32Window);
1341 void show_window(extent2 size)
noexcept
1343 hi_log_info(
"Show window with title '{}' with size {}", _title, size);
1345 win32Window,
nullptr, 0, 0, round_cast<int>(size.width()), round_cast<int>(size.width()), SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_DEFERERASE | SWP_NOCOPYBITS | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1350 int windowProc(
unsigned int uMsg, uint64_t wParam, int64_t lParam)
noexcept
1352 using namespace std::chrono_literals;
1354 gui_event mouse_event;
1355 auto const current_time = std::chrono::utc_clock::now();
1368 auto const createstruct_ptr = std::launder(std::bit_cast<CREATESTRUCT *>(lParam));
1370 new_rectangle.left = createstruct_ptr->x;
1371 new_rectangle.top = createstruct_ptr->y;
1372 new_rectangle.right = createstruct_ptr->x + createstruct_ptr->cx;
1373 new_rectangle.bottom = createstruct_ptr->y + createstruct_ptr->cy;
1374 setOSWindowRectangleFromRECT(new_rectangle);
1383 auto const height = [
this]() {
1384 hi_axiom(loop::main().on_thread());
1389 BeginPaint(win32Window, &ps);
1391 auto const update_rectangle = aarectangle{
1392 narrow_cast<float>(ps.rcPaint.left),
1393 narrow_cast<float>(height - ps.rcPaint.bottom),
1394 narrow_cast<float>(ps.rcPaint.right - ps.rcPaint.left),
1395 narrow_cast<float>(ps.rcPaint.bottom - ps.rcPaint.top)};
1398 hi_axiom(loop::main().on_thread());
1399 this->process_event({gui_event_type::window_redraw, update_rectangle});
1402 EndPaint(win32Window, &ps);
1407 hi_axiom(loop::main().on_thread());
1408 this->process_event({gui_event_type::window_redraw, aarectangle{
rectangle.size()}});
1414 hi_axiom(loop::main().on_thread());
1416 case SIZE_MAXIMIZED:
1417 ShowWindow(win32Window, SW_RESTORE);
1418 set_size_state(gui_window_size::maximized);
1420 case SIZE_MINIMIZED:
1421 _size_state = gui_window_size::minimized;
1424 _size_state = gui_window_size::normal;
1432 if (last_forced_redraw + 16.7ms < current_time) {
1435 loop::main().resume_once();
1436 last_forced_redraw = current_time;
1442 auto const& rect_ptr = *std::launder(std::bit_cast<RECT *>(lParam));
1443 if (rect_ptr.right < rect_ptr.left or rect_ptr.bottom < rect_ptr.top) {
1445 "Invalid RECT received on WM_SIZING: left={}, right={}, bottom={}, top={}",
1452 setOSWindowRectangleFromRECT(rect_ptr);
1459 auto const& rect_ptr = *std::launder(std::bit_cast<RECT *>(lParam));
1460 if (rect_ptr.right < rect_ptr.left or rect_ptr.bottom < rect_ptr.top) {
1462 "Invalid RECT received on WM_MOVING: left={}, right={}, bottom={}, top={}",
1469 setOSWindowRectangleFromRECT(rect_ptr);
1474 case WM_WINDOWPOSCHANGED:
1476 auto const windowpos_ptr = std::launder(std::bit_cast<WINDOWPOS *>(lParam));
1478 new_rectangle.left = windowpos_ptr->x;
1479 new_rectangle.top = windowpos_ptr->y;
1480 new_rectangle.right = windowpos_ptr->x + windowpos_ptr->cx;
1481 new_rectangle.bottom = windowpos_ptr->y + windowpos_ptr->cy;
1482 setOSWindowRectangleFromRECT(new_rectangle);
1486 case WM_ENTERSIZEMOVE:
1487 hi_axiom(loop::main().on_thread());
1488 if (SetTimer(win32Window, move_and_resize_timer_id, 16, NULL) != move_and_resize_timer_id) {
1494 case WM_EXITSIZEMOVE:
1495 hi_axiom(loop::main().on_thread());
1496 if (not KillTimer(win32Window, move_and_resize_timer_id)) {
1502 _size_state = gui_window_size::normal;
1503 this->process_event({gui_event_type::window_redraw, aarectangle{
rectangle.size()}});
1507 hi_axiom(loop::main().on_thread());
1511 this->process_event({gui_event_type::window_activate});
1514 this->process_event({gui_event_type::window_deactivate});
1517 hi_log_error(
"Unknown WM_ACTIVE value.");
1519 ++global_counter<
"gui_window:WM_ACTIVATE:constrain">;
1520 this->process_event({gui_event_type::window_reconstrain});
1523 case WM_GETMINMAXINFO:
1525 hi_axiom(loop::main().on_thread());
1526 auto const minmaxinfo = std::launder(std::bit_cast<MINMAXINFO *>(lParam));
1527 minmaxinfo->ptMaxSize.x = round_cast<LONG>(_widget_constraints.maximum.width());
1528 minmaxinfo->ptMaxSize.y = round_cast<LONG>(_widget_constraints.maximum.height());
1529 minmaxinfo->ptMinTrackSize.x = round_cast<LONG>(_widget_constraints.minimum.width());
1530 minmaxinfo->ptMinTrackSize.y = round_cast<LONG>(_widget_constraints.minimum.height());
1531 minmaxinfo->ptMaxTrackSize.x = round_cast<LONG>(_widget_constraints.maximum.width());
1532 minmaxinfo->ptMaxTrackSize.y = round_cast<LONG>(_widget_constraints.maximum.height());
1537 if (
auto c = char_cast<char32_t>(wParam); c == UNICODE_NOCHAR) {
1541 }
else if (
auto const gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1543 process_event(gui_event::keyboard_grapheme(grapheme{c}));
1548 if (
auto c = handle_suragates(char_cast<char32_t>(wParam))) {
1549 if (
auto const gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1551 process_event(gui_event::keyboard_partial_grapheme(grapheme{c}));
1557 if (
auto c = handle_suragates(char_cast<char32_t>(wParam))) {
1558 if (
auto const gc = ucd_get_general_category(c); not is_C(gc) and not is_M(gc)) {
1560 process_event(gui_event::keyboard_grapheme(grapheme{c}));
1566 if (wParam == SC_KEYMENU) {
1567 keymenu_pressed =
true;
1568 process_event(gui_event{gui_event_type::keyboard_down, keyboard_virtual_key::menu});
1576 auto const extended = (narrow_cast<uint32_t>(lParam) & 0x01000000) != 0;
1577 auto const key_code = narrow_cast<int>(wParam);
1578 auto const key_modifiers = get_keyboard_modifiers();
1579 auto virtual_key = to_keyboard_virtual_key(key_code, extended, key_modifiers);
1581 if (std::exchange(keymenu_pressed,
false) and uMsg == WM_KEYDOWN and virtual_key == keyboard_virtual_key::space) {
1583 virtual_key = keyboard_virtual_key::sysmenu;
1586 if (virtual_key != keyboard_virtual_key::nul) {
1587 auto const key_state = get_keyboard_state();
1588 auto const event_type = uMsg == WM_KEYDOWN ? gui_event_type::keyboard_down : gui_event_type::keyboard_up;
1589 process_event(gui_event{event_type, virtual_key, key_modifiers, key_state});
1594 case WM_LBUTTONDOWN:
1595 case WM_MBUTTONDOWN:
1596 case WM_RBUTTONDOWN:
1597 case WM_XBUTTONDOWN:
1602 case WM_LBUTTONDBLCLK:
1603 case WM_MBUTTONDBLCLK:
1604 case WM_RBUTTONDBLCLK:
1605 case WM_XBUTTONDBLCLK:
1607 case WM_MOUSEHWHEEL:
1610 keymenu_pressed =
false;
1611 process_event(create_mouse_event(uMsg, wParam, lParam));
1615 if (wParam == TRUE) {
1631 hi_axiom(loop::main().on_thread());
1633 auto const x = narrow_cast<float>(GET_X_LPARAM(lParam));
1634 auto const y = narrow_cast<float>(GET_Y_LPARAM(lParam));
1637 auto const inv_y = os_settings::primary_monitor_rectangle().height() - y;
1639 auto const hitbox_type = _widget->hitbox_test(screen_to_window() * point2{x, inv_y}).type;
1641 switch (hitbox_type) {
1642 case hitbox_type::bottom_resize_border:
1643 set_cursor(mouse_cursor::None);
1645 case hitbox_type::top_resize_border:
1646 set_cursor(mouse_cursor::None);
1648 case hitbox_type::left_resize_border:
1649 set_cursor(mouse_cursor::None);
1651 case hitbox_type::right_resize_border:
1652 set_cursor(mouse_cursor::None);
1654 case hitbox_type::bottom_left_resize_corner:
1655 set_cursor(mouse_cursor::None);
1656 return HTBOTTOMLEFT;
1657 case hitbox_type::bottom_right_resize_corner:
1658 set_cursor(mouse_cursor::None);
1659 return HTBOTTOMRIGHT;
1660 case hitbox_type::top_left_resize_corner:
1661 set_cursor(mouse_cursor::None);
1663 case hitbox_type::top_right_resize_corner:
1664 set_cursor(mouse_cursor::None);
1666 case hitbox_type::application_icon:
1667 set_cursor(mouse_cursor::None);
1669 case hitbox_type::move_area:
1670 set_cursor(mouse_cursor::None);
1672 case hitbox_type::text_edit:
1673 set_cursor(mouse_cursor::TextEdit);
1675 case hitbox_type::button:
1676 set_cursor(mouse_cursor::Button);
1678 case hitbox_type::scroll_bar:
1679 set_cursor(mouse_cursor::Default);
1681 case hitbox_type::_default:
1682 set_cursor(mouse_cursor::Default);
1684 case hitbox_type::outside:
1685 set_cursor(mouse_cursor::None);
1693 case WM_SETTINGCHANGE:
1694 hi_axiom(loop::main().on_thread());
1695 os_settings::gather();
1700 hi_axiom(loop::main().on_thread());
1702 pixel_density = {unit::pixels_per_inch(LOWORD(wParam)), os_settings::device_type()};
1705 auto const new_rectangle = std::launder(
reinterpret_cast<RECT *
>(lParam));
1709 new_rectangle->left,
1711 new_rectangle->right - new_rectangle->left,
1712 new_rectangle->bottom - new_rectangle->top,
1713 SWP_NOZORDER | SWP_NOACTIVATE);
1714 ++global_counter<
"gui_window:WM_DPICHANGED:constrain">;
1715 this->process_event({gui_event_type::window_reconstrain});
1718 hi_log_info(
"DPI has changed to {} ppi",
pixel_density.ppi.in(unit::pixels_per_inch));
1720 hi_assert_not_null(_widget);
1721 apply_window_data(*_widget,
this, pixel_density, get_selected_theme().attributes_from_theme_function());
1736 static LRESULT CALLBACK _WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
noexcept
1738 if (uMsg == WM_CREATE && lParam) {
1739 auto const createData = std::launder(std::bit_cast<CREATESTRUCT *>(lParam));
1742 auto r = SetWindowLongPtrW(hwnd, GWLP_USERDATA, std::bit_cast<LONG_PTR>(createData->lpCreateParams));
1743 if (r != 0 || GetLastError() != 0) {
1751 auto window_userdata = GetWindowLongPtrW(hwnd, GWLP_USERDATA);
1752 if (window_userdata == 0) {
1753 return DefWindowProc(hwnd, uMsg, wParam, lParam);
1756 auto& window = *std::launder(std::bit_cast<gui_window *>(window_userdata));
1757 hi_axiom(loop::main().on_thread());
1761 if (uMsg == WM_CLOSE) {
1766 }
else if (uMsg == WM_DESTROY) {
1770 auto r = SetWindowLongPtrW(hwnd, GWLP_USERDATA, NULL);
1771 if (r == 0 || GetLastError() != 0) {
1776 window.win32Window =
nullptr;
1780 if (
auto result = window.windowProc(uMsg, wParam, lParam); result != -1) {
1783 return DefWindowProc(hwnd, uMsg, wParam, lParam);
1787 static void createWindowClass()
1789 if (!win32WindowClassIsRegistered) {
1791 win32WindowClassName = L
"HikoGUI Window Class";
1793 std::memset(&win32WindowClass, 0,
sizeof(WNDCLASSW));
1794 win32WindowClass.style = CS_DBLCLKS;
1795 win32WindowClass.lpfnWndProc = _WindowProc;
1797 win32WindowClass.lpszClassName = win32WindowClassName;
1798 win32WindowClass.hCursor =
nullptr;
1799 RegisterClassW(&win32WindowClass);
1801 win32WindowClassIsRegistered =
true;