From 9cfacb02cd39eecaccaf5204deb0f57467a24560 Mon Sep 17 00:00:00 2001 From: light7734 Date: Wed, 11 Feb 2026 11:39:08 +0330 Subject: [PATCH] feat(surface): windows api --- modules/CMakeLists.txt | 5 + modules/input/system.cppm | 29 +-- modules/input/system.test.cpp | 6 +- modules/input_codes/input_codes.cppm | 29 ++- modules/renderer/vk/renderer.cppm | 28 ++- modules/sandbox/sandbox.cpp | 103 +++++++-- modules/surface/events.cppm | 12 ++ modules/surface/system.cppm | 311 +++++++++++++-------------- modules/surface/system.test.cpp | 175 ++++++++++++--- modules/test/test.test.cpp | 1 - tools/ci/amd64/msvc/unit_tests.ps1 | 4 +- 11 files changed, 457 insertions(+), 246 deletions(-) diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 6ab5d51..a56c156 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -128,6 +128,11 @@ if(WIN32) memory input_codes PRIVATE_DEPENDENCIES + user32 + gdi32 + kernel32 + dwmapi + Shcore logger time TESTS diff --git a/modules/input/system.cppm b/modules/input/system.cppm index 8a176ba..f4fc052 100644 --- a/modules/input/system.cppm +++ b/modules/input/system.cppm @@ -36,18 +36,12 @@ private: void on_key_release(const lt::surface::KeyReleasedEvent &event); - void on_pointer_move(const lt::surface::MouseMovedEvent &event); - - void on_button_press(const lt::surface::ButtonPressedEvent &event); - - void on_button_release(const lt::surface::ButtonReleasedEvent &event); + void on_pointer(const lt::surface::PointerEvent &event); memory::Ref m_registry; std::array m_keys {}; - std::array m_buttons {}; - math::vec2 m_pointer_position; app::TickResult m_last_tick_result {}; @@ -131,9 +125,7 @@ void System::handle_event(const surface::SurfaceComponent::Event &event) [this](const surface::LostFocusEvent &) { on_surface_lost_focus(); }, [this](const surface::KeyPressedEvent &event) { on_key_press(event); }, [this](const surface::KeyReleasedEvent &event) { on_key_release(event); }, - [this](const surface::MouseMovedEvent &event) { on_pointer_move(event); }, - [this](const surface::ButtonPressedEvent &event) { on_button_press(event); }, - [this](const surface::ButtonReleasedEvent &event) { on_button_release(event); }, + [this](const surface::PointerEvent &event) { on_pointer(event); }, [this](auto) {}, }; @@ -146,11 +138,6 @@ void System::on_surface_lost_focus() { key = false; } - - for (auto &button : m_buttons) - { - button = false; - } } void System::on_key_press(const lt::surface::KeyPressedEvent &event) @@ -183,19 +170,9 @@ void System::on_key_release(const lt::surface::KeyReleasedEvent &event) m_keys[std::to_underlying(event.get_key())] = false; } -void System::on_pointer_move(const lt::surface::MouseMovedEvent &event) +void System::on_pointer(const lt::surface::PointerEvent &event) { m_pointer_position = event.get_position(); } -void System::on_button_press(const lt::surface::ButtonPressedEvent &event) -{ - m_buttons[std::to_underlying(event.get_button())] = true; -} - -void System::on_button_release(const lt::surface::ButtonReleasedEvent &event) -{ - m_buttons[std::to_underlying(event.get_button())] = false; -} - } // namespace lt::input diff --git a/modules/input/system.test.cpp b/modules/input/system.test.cpp index 07df74b..4bd0520 100644 --- a/modules/input/system.test.cpp +++ b/modules/input/system.test.cpp @@ -147,7 +147,7 @@ Suite tick = "tick"_suite = [] { auto action_key = input.add_action( { .name { "test" }, - .trigger = { .mapped_keycode = Key::a }, + .trigger = { .mapped_keycode = lt::Key::a }, } ); @@ -156,7 +156,7 @@ Suite tick = "tick"_suite = [] { system.tick(tick_info()); expect_eq(input.get_action(action_key).state, inactive); - surface.push_event(lt::surface::KeyPressedEvent(Key::a)); + surface.push_event(lt::surface::KeyPressedEvent(lt::Key::a)); system.tick(tick_info()); expect_eq(input.get_action(action_key).state, triggered); @@ -168,7 +168,7 @@ Suite tick = "tick"_suite = [] { system.tick(tick_info()); expect_eq(input.get_action(action_key).state, active); - surface.push_event(lt::surface::KeyReleasedEvent(Key::a)); + surface.push_event(lt::surface::KeyReleasedEvent(lt::Key::a)); system.tick(tick_info()); expect_eq(input.get_action(action_key).state, inactive); }; diff --git a/modules/input_codes/input_codes.cppm b/modules/input_codes/input_codes.cppm index 4dafa66..71af2cc 100644 --- a/modules/input_codes/input_codes.cppm +++ b/modules/input_codes/input_codes.cppm @@ -11,7 +11,10 @@ export module input.codes; import preliminary; -export enum class Key: u16 { +export namespace lt { + +enum class Key : u16 +{ none = 0, left_button, @@ -27,6 +30,12 @@ export enum class Key: u16 { x_button_1, x_button_2, + // Mouse-wheel movement is treated like a key, deal with it. + wheel_down, + wheel_up, + + escape, + escp = escape, backspace, tab, capslock, @@ -167,7 +176,7 @@ export enum class Key: u16 { unknown, }; -export [[nodiscard]] constexpr auto to_string(Key key) -> std::string +[[nodiscard]] constexpr auto to_string(Key key) -> std::string { using enum Key; switch (key) @@ -181,6 +190,10 @@ export [[nodiscard]] constexpr auto to_string(Key key) -> std::string case x_button_1: return "x_button_1"; case x_button_2: return "x_button_2"; + case wheel_down: return "wheel_down"; + case wheel_up: return "wheel_up"; + + case escape: return "escape"; case backspace: return "backspace"; case tab: return "tab"; case capslock: return "capslock"; @@ -290,3 +303,15 @@ export [[nodiscard]] constexpr auto to_string(Key key) -> std::string return ""; } + +} // namespace lt + +template<> +struct std::formatter: std::formatter +{ + template + auto format(lt::Key key, FormatContext &ctx) const + { + return std::formatter::format(lt::to_string(key), ctx); + } +}; diff --git a/modules/renderer/vk/renderer.cppm b/modules/renderer/vk/renderer.cppm index 50fc152..4fa424d 100644 --- a/modules/renderer/vk/renderer.cppm +++ b/modules/renderer/vk/renderer.cppm @@ -1,6 +1,7 @@ export module renderer.vk.renderer; import preliminary; +import time; import logger; import assets.shader; import renderer.vk.api_wrapper; @@ -290,22 +291,27 @@ void Renderer::record_cmd(vk::CommandBuffer &cmd, u32 image_idx) } ); + static lt::time::Timer timer; + using Attachment = vk::CommandBuffer::RenderingInfo::AttachmentInfo; - cmd.begin_rendering( - { + cmd.begin_rendering({ .area_offset = {0u, 0u,}, .area_extent = m_resolution, .color_attachments = std::vector { - Attachment{ - .view= &m_swapchain->get_image_view(image_idx), - .layout = vk::Image::Layout::color_attachment_optimal, - .load_operation = Attachment::LoadOperation::clear, - .store_operation = Attachment::StoreOperation::store, - .color_clear_values = {.5f, .5f, .5f, 1.f} + Attachment{ + .view= &m_swapchain->get_image_view(image_idx), + .layout = vk::Image::Layout::color_attachment_optimal, + .load_operation = Attachment::LoadOperation::clear, + .store_operation = Attachment::StoreOperation::store, + .color_clear_values = { + static_cast(std::sin(timer.elapsed_time().count())), + static_cast(std::cos(timer.elapsed_time().count() * 2.0)), + static_cast(std::sin(timer.elapsed_time().count() / 2.0)), + 1.f + } + } } - } - } - ); + }); // cmd.bind_pipeline(m_pass->get_pipeline(), vk::Pipeline::BindPoint::graphics); // cmd.set_viewport( // { diff --git a/modules/sandbox/sandbox.cpp b/modules/sandbox/sandbox.cpp index 3e5eb69..c99398c 100644 --- a/modules/sandbox/sandbox.cpp +++ b/modules/sandbox/sandbox.cpp @@ -1,6 +1,8 @@ import preliminary; import time; import logger; +import renderer.system; +import renderer.frontend; import surface.system; import surface.events; import surface.requests; @@ -15,37 +17,114 @@ constexpr auto title = "TestWindow"; constexpr auto width = 800u; constexpr auto height = 600u; constexpr auto vsync = true; -constexpr auto visible = false; +constexpr auto visible = true; + +template +struct overloads: Ts... +{ + using Ts::operator()...; +}; + +void renderer_debug_callback( + lt::renderer::IDebugger::MessageSeverity message_severity, + lt::renderer::IDebugger::MessageType message_type, + lt::renderer::IDebugger::MessageData data, + std::any &user_data +) +{ + ignore = message_severity; + ignore = message_type; + ignore = user_data; + + lt::log::trace("< Renderer > ==> {}", std::string { data.message }); +} auto main() -> i32 try { - for (auto idx = 0; idx < 100; ++idx) - { - std::println("{}: \033[1;{}m| color |\033[0m", idx, idx); - } auto registry = lt::memory::create_ref(); - auto system = lt::surface::System { registry }; + auto surface_system = lt::surface::System { registry }; auto entity = registry->create_entity(); - system.create_surface_component( + lt::log::trace("Creating surface component"); + surface_system.create_surface_component( entity, lt::surface::SurfaceComponent::CreateInfo { .title = title, + .position = { 500, 500 }, + .resolution = { width, height }, .vsync = vsync, .visible = visible, } ); - auto timer = lt::time::Timer {}; - lt::log::trace("Ticking for 3 seconds..."); - while (timer.elapsed_time() < std::chrono::seconds { 30 }) + auto &window = registry->get(entity); + + const auto config = lt::renderer::System::Configuration { + .target_api = lt::renderer::Api::vulkan, + .max_frames_in_flight = 3u, + }; + + const auto debug_callback_info = lt::renderer::IDebugger::CreateInfo { + .severities = lt::renderer::IDebugger::MessageSeverity::all, + .types = lt::renderer::IDebugger::MessageType::all, + .callback = &renderer_debug_callback, + .user_data = nullptr, + }; + + const auto surface_entity = lt::ecs::Entity { registry, entity }; + + auto renderer_system = lt::renderer::System { lt::renderer::System::CreateInfo { + config, + registry, + surface_entity, + debug_callback_info, + } }; + + registry.add_ + + auto should_close = false; + const auto visitor = overloads { + [&](const lt::surface::ClosedEvent &) { + lt::log::info("Closing due to: Window X button pressed"); + should_close = true; + }, + [&](const lt::surface::MovedEvent &event) { + lt::log::info("Moved: {}", event.get_position()); + }, + [&](const lt::surface::ResizedEvent &event) { + lt::log::info("Resized: {}", event.get_size()); + }, + [&](const lt::surface::LostFocusEvent &) { lt::log::info("Lost focus"); }, + [&](const lt::surface::GainFocusEvent &) { lt::log::info("Gain focus"); }, + [&](const lt::surface::KeyPressedEvent &event) { + if (event.get_key() == lt::Key::escape) + { + lt::log::info("Closing due to: Escape key pressed"); + should_close = true; + } + lt::log::info("Key pressed: {}", event.get_key()); + }, + [&](const lt::surface::KeyReleasedEvent &event) { + lt::log::info("Key released: {}", event.get_key()); + }, + [&](const lt::surface::PointerEvent &event) { + lt::log::info("Pointer: {}", event.get_position()); + } + }; + + while (!should_close) { - system.tick({}); + surface_system.tick({}); + renderer_system.tick({}); + + for (const auto &event : window.peek_events()) + { + std::visit(visitor, event); + } } - lt::log::trace("Three seconds passed, quitting..."); } catch (const std::exception &exp) { diff --git a/modules/surface/events.cppm b/modules/surface/events.cppm index 591c36b..731316f 100644 --- a/modules/surface/events.cppm +++ b/modules/surface/events.cppm @@ -72,6 +72,10 @@ private: class PointerEvent { public: + PointerEvent(math::vec2 position): m_position(position) + { + } + PointerEvent(f32 x, f32 y): m_position(x, y) { } @@ -135,6 +139,10 @@ public: class MovedEvent { public: + MovedEvent(math::vec2_i32 position): m_position(position) + { + } + MovedEvent(i32 x, i32 y): m_position(x, y) { } @@ -156,6 +164,10 @@ private: class ResizedEvent { public: + ResizedEvent(math::vec2_u32 size): m_size(size) + { + } + ResizedEvent(u32 width, u32 height): m_size(width, height) { } diff --git a/modules/surface/system.cppm b/modules/surface/system.cppm index 59d60a2..cc7cf47 100644 --- a/modules/surface/system.cppm +++ b/modules/surface/system.cppm @@ -6,6 +6,8 @@ module; #include #include #include + #include // For DWM functions + #include #if defined(UNICODE) || defined(_UNICODE) #error "Unicode is not turned off" @@ -165,11 +167,7 @@ private: void modify_position(SurfaceComponent &surface, const ModifyPositionRequest &request); - void modify_visiblity(SurfaceComponent &surface, const ModifyVisibilityRequest &request); - - void modify_position(ecs::EntityId surface_entity, const math::vec2_i32 &new_size); - - void modify_position(ecs::EntityId surface_entity, const math::vec2_u32 &new_size); + void modify_visibility(SurfaceComponent &surface, const ModifyVisibilityRequest &request); void set_visibility(ecs::EntityId surface_entity, bool visible); @@ -636,31 +634,26 @@ try auto &surface = m_registry->add(entity, info); ensure_component_sanity(surface); - const auto style = WS_OVERLAPPEDWINDOW; - const auto ex_style = ::DWORD {}; - const auto dpi = get_dpi_from_point(info.position); + // auto style = WS_THICKFRAME // Required for a standard resizeable window + // | WS_SYSMENU // Support snapping via Win + ← / Win + → + // | WS_MAXIMIZEBOX // Support maximizing via mouse dragging to the top of the + // screen | WS_MINIMIZEBOX // Support minimizing by clicking on the taskbar icon + // ; - auto client_area_rectangle = ::RECT { - .left = info.position.x, - .top = info.position.y, - .right = static_cast<::LONG>(info.position.x + info.resolution.x), - .bottom = static_cast<::LONG>(info.position.y + info.resolution.y), - }; - ensure( - ::AdjustWindowRectExForDpi(&client_area_rectangle, style, false, ex_style, dpi), - "Failed ::AdjustWindowRectExForDpi: {}", - get_error_message() - ); + // if (info.visible) + // { + // style |= WS_VISIBLE; + // } auto hwnd = ::CreateWindowEx( 0, constants::class_name, info.title.data(), - WS_OVERLAPPEDWINDOW, - client_area_rectangle.left, - client_area_rectangle.top, - client_area_rectangle.right - client_area_rectangle.left, - client_area_rectangle.bottom - client_area_rectangle.top, + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + info.position.x, + info.position.y, + info.resolution.x, + info.resolution.y, nullptr, nullptr, ::GetModuleHandle(nullptr), @@ -669,10 +662,35 @@ try std::bit_cast(&surface) ); ensure(hwnd, "Failed ::CreateWindowEx: {}", get_error_message()); - surface.m_native_data.window = hwnd; - ::ShowWindow(surface.m_native_data.window, SW_SHOW); + auto margins = MARGINS { 0, 0, 1, 0 }; // Extend 1px on right for shadow rendering + DwmExtendFrameIntoClientArea(hwnd, &margins); + + BOOL useDarkMode = TRUE; + DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &useDarkMode, sizeof(useDarkMode)); + + ShowWindow(hwnd, SW_SHOW); + SetWindowPos( + hwnd, + NULL, + 0, + 0, + 0, + 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE + ); + UpdateWindow(hwnd); + surface.m_visible = info.visible; + + if (info.visible) + { + ShowWindow(hwnd, SW_SHOW); + } + else + { + ShowWindow(hwnd, SW_HIDE); + } } catch (const std::exception &exp) { @@ -714,7 +732,7 @@ void System::handle_requests(SurfaceComponent &surface) [&](const ModifyResolutionRequest &request) { modify_resolution(surface, request); }, [&](const ModifyPositionRequest &request) { modify_position(surface, request); }, [&](const ModifyVisibilityRequest &request) { - modify_visiblity(surface, request); + modify_visibility(surface, request); } }; @@ -728,126 +746,41 @@ void System::handle_requests(SurfaceComponent &surface) void System::modify_title(SurfaceComponent &surface, const ModifyTitleRequest &request) { - // WIP(Light): - ignore = surface; - ignore = request; - + auto hwnd = surface.m_native_data.window; surface.m_title = request.title; - // const auto &[display, window, _] = surface.get_native_data(); - // XStoreName(display, window, request.title.c_str()); + ::SetWindowTextA(hwnd, request.title.c_str()); } void System::modify_resolution(SurfaceComponent &surface, const ModifyResolutionRequest &request) { - // WIP(Light): - ignore = surface; - ignore = request; - - // surface.m_resolution = request.resolution; - - // auto &[display, window, _] = surface.m_native_data; - // const auto &[width, height] = request.resolution; - // // XResizeWindow(display, window, width, height); - // - // // get baseline serial number for X requests generated from XResizeWindow - // auto serial = NextRequest(display); - // - // // request a new window size from the X server - // XResizeWindow( - // display, - // window, - // static_cast(width), - // static_cast(height) - // ); - // - // // flush output queue and wait for X server to processes the request - // XSync(display, False); - // // The documentation for XResizeWindow includes this important note: - // // - // // If the override-redirect flag of the window is False and some - // // other client has selected SubstructureRedirectMask on the parent, - // // the X server generates a ConfigureRequest event, and no further - // // processing is performed. - // // - // // What this means, essentially, is that if this window is a top-level - // // window, then it's the window manager (the "other client") that is - // // responsible for changing this window's size. So when we call - // // XResizeWindow() on a top-level window, then instead of resizing - // // the window immediately, the X server informs the window manager, - // // and then the window manager sets our new size (usually it will be - // // the size we asked for). We receive a ConfigureNotify event when - // // our new size has been set. - // constexpr auto lifespan = std::chrono::milliseconds { 10 }; - // auto timer = time::Timer {}; - // auto event = XEvent {}; - // while (!XCheckIfEvent( - // display, - // &event, - // XEventTypeEquals, - // reinterpret_cast(&window) // NOLINT - // ) - // || event.xconfigure.serial < serial) - // { - // std::this_thread::sleep_for(std::chrono::microseconds { 100 }); - // if (timer.elapsed_time() > lifespan) - // { - // log::error("Timed out waiting for XResizeWindow's event"); - // return; - // } - // } - // // We don't need to update the component's state and handle the event in this funcion. - // // Since handle_requests is called before handle_events. - // // So we just put the event back into the queue and move on. - // XPutBackEvent(display, &event); - // XSync(display, False); - // XFlush(display); + ::SetWindowPos( + surface.m_native_data.window, + {}, + surface.get_position().x, + surface.get_position().y, + request.resolution.x, + request.resolution.y, + SWP_NOZORDER | SWP_NOACTIVATE + ); } void System::modify_position(SurfaceComponent &surface, const ModifyPositionRequest &request) { - auto hwnd = surface.m_native_data.window; - - const auto style = ::GetWindowLong(hwnd, GWL_STYLE); - const auto ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE); - const auto dpi = get_dpi_from_point(request.position); - - auto rectangle = ::RECT { - .left = {}, - .top = {}, - .right = static_cast<::LONG>(surface.get_resolution().x), - .bottom = static_cast<::LONG>(surface.get_resolution().y), - }; - AdjustWindowRectExForDpi(&rectangle, style, false, ex_style, dpi); - - SetWindowPos( + ::SetWindowPos( surface.m_native_data.window, {}, - request.position.x + rectangle.left, - request.position.y + rectangle.top, + request.position.x, + request.position.y, surface.get_resolution().x, surface.get_resolution().y, SWP_NOZORDER | SWP_NOACTIVATE ); } -void System::modify_visiblity(SurfaceComponent &surface, const ModifyVisibilityRequest &request) +void System::modify_visibility(SurfaceComponent &surface, const ModifyVisibilityRequest &request) { - // WIP(Light): Use ignored local-variables - ignore = surface; - ignore = request; - - // const auto &[display, window, _] = surface.get_native_data(); - // surface.m_visible = request.visible; - - // if (request.visible) - // { - // XMapWindow(display, window); - // } - // else - // { - // XUnmapWindow(display, window); - // } + ::ShowWindow(surface.m_native_data.window, request.visible ? SW_SHOW : SW_HIDE); } void System::tick(app::TickInfo tick) @@ -1120,6 +1053,74 @@ void System::tick(app::TickInfo tick) switch (uMsg) { + case WM_NCCALCSIZE: + { + if (wParam == TRUE) + { + // Get border thicknesses + // int borderPadding = GetSystemMetrics(SM_CXPADDEDBORDER); + // int borderLR = GetSystemMetrics(SM_CXFRAME) + borderPadding; + // int borderTB = GetSystemMetrics(SM_CYFRAME) + borderPadding; + // + // NCCALCSIZE_PARAMS *params = (NCCALCSIZE_PARAMS *)lParam; + // RECT *rect = ¶ms->rgrc[0]; + + // Adjust client rect: Subtract borders from left/right/bottom + // rect->left += borderLR; + // rect->right -= borderLR; + // rect->bottom -= borderTB; + + // For maximized windows, also subtract top border + // if (IsZoomed(hwnd)) + // { + // rect->top += borderTB; + // } + + // Return 0 to indicate we handled it (prevents default title bar) + return 0; + } + break; + } + + case WM_NCHITTEST: + { + // Get mouse position in client coordinates + POINT pt; + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + ScreenToClient(hwnd, &pt); + + RECT clientRect; + GetClientRect(hwnd, &clientRect); + + // Define a drag area (e.g., top 30px of client area acts as "caption" for moving) + const int dragHeight = 30; // Adjust as needed; set to 0 if no dragging desired + if (pt.y < dragHeight && pt.y >= 0) + { + // Check corners for diagonal resize + const int resizeBorder = 8; // Pixels for corner hit detection + if (pt.x < resizeBorder) + return HTTOPLEFT; + if (pt.x > clientRect.right - resizeBorder) + return HTTOPRIGHT; + // Middle top: vertical resize + return HTTOP; + } + + // Fallback to default handling for side/bottom borders and other areas + return DefWindowProc(hwnd, uMsg, wParam, lParam); + } + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + RECT rect; + GetClientRect(hwnd, &rect); + FillRect(hdc, &rect, (HBRUSH)(COLOR_WINDOW + 5)); + EndPaint(hwnd, &ps); + return 0; + } case WM_NCCREATE: [[unlikely]] { @@ -1130,42 +1131,36 @@ void System::tick(app::TickInfo tick) return !!surface_ptr; } - case WM_SETFOCUS: get_userdata().m_event_queue.emplace_back(GainFocusEvent {}); break; + case WM_SETTEXT: + [[unlikely]] + { + get_userdata().m_title = std::string { std::bit_cast(lParam) }; + break; + } - case WM_KILLFOCUS: get_userdata().m_event_queue.emplace_back(LostFocusEvent {}); break; + case WM_SHOWWINDOW: get_userdata().m_visible = static_cast(wParam); return 0; + + case WM_SETFOCUS: get_userdata().m_event_queue.emplace_back(GainFocusEvent {}); return 0; + + case WM_KILLFOCUS: get_userdata().m_event_queue.emplace_back(LostFocusEvent {}); return 0; case WM_SIZE: { - const auto width = LOWORD(lParam); - const auto height = HIWORD(lParam); - - lt::log::test("Received WM_SIZE: {}, {}", width, height); auto &surface = get_userdata(); - surface.m_resolution = math::vec2_u32 { width, height }; - surface.m_event_queue.emplace_back(ResizedEvent { width, height }); + + + surface.m_resolution = math::vec2_u32 { LOWORD(lParam), HIWORD(lParam) }; + surface.m_event_queue.emplace_back(surface.m_resolution); return 0; } case WM_MOVE: { - auto client_top_left = ::POINT {}; - ensure( - ::ClientToScreen(hwnd, &client_top_left), - "Failed ::ClientToScreen (hwnd: {:x})", - std::bit_cast(hwnd) - ); - auto &surface = get_userdata(); - surface.m_position = { - client_top_left.x, - client_top_left.y, - }; - - surface.m_event_queue.emplace_back( - MovedEvent { static_cast(client_top_left.x), static_cast(client_top_left.y) } - ); + surface.m_position = math::vec2_i32 { LOWORD(lParam), HIWORD(lParam) }; + surface.m_event_queue.emplace_back(MovedEvent { surface.m_position }); return 0; } @@ -1222,7 +1217,7 @@ void System::tick(app::TickInfo tick) const auto key = static_cast( std::to_underlying(Key::x_button_1) + GET_XBUTTON_WPARAM(wParam) - 1 ); - get_userdata().m_event_queue.emplace_back(key); + get_userdata().m_event_queue.emplace_back(key); break; } case WM_XBUTTONUP: @@ -1230,7 +1225,7 @@ void System::tick(app::TickInfo tick) const auto key = static_cast( std::to_underlying(Key::x_button_1) + GET_XBUTTON_WPARAM(wParam) - 1 ); - get_userdata().m_event_queue.emplace_back(key); + get_userdata().m_event_queue.emplace_back(key); break; } case WM_KEYDOWN: @@ -1255,11 +1250,11 @@ void System::tick(app::TickInfo tick) return 0; } - case WM_DESTROY: - { - PostQuitMessage(0); - return 0; - } + // case WM_DESTROY: + // { + // PostQuitMessage(0); + // return 0; + // } } return DefWindowProcA(hwnd, uMsg, wParam, lParam); diff --git a/modules/surface/system.test.cpp b/modules/surface/system.test.cpp index 50efa56..be2bd7e 100644 --- a/modules/surface/system.test.cpp +++ b/modules/surface/system.test.cpp @@ -20,6 +20,7 @@ import memory.reference; import math.vec2; import app.system; +using ::lt::Key; using ::lt::surface::SurfaceComponent; using ::lt::surface::System; @@ -32,21 +33,6 @@ using ::lt::surface::System; }; } - -// void simulate_key_down(lt::surface::SurfaceComponent &surface, lt::Key key) -// { -// #if defined(LIGHT_PLATFORM_LINUX) -// #elif defined(LIGHT_PLATFORM_WINDOWS) -// #endif -// } -// -// void simulate_key_up(lt::Key key) -// { -// #if defined(LIGHT_PLATFORM_LINUX) -// #elif defined(LIGHT_PLATFORM_WINDOWS) -// #endif -// } - constexpr auto title = "TestWindow"; constexpr auto width = 800u; constexpr auto height = 600u; @@ -164,9 +150,19 @@ Suite system_events = "system_events"_suite = [] { Suite registry_events = "registry_events"_suite = [] { Case { "on_construct initializes component" } = [] { auto fixture = Fixture {}; + auto system = System { fixture.registry() }; + system.tick({}); + system.tick({}); const auto &component = fixture.create_component(); + system.tick({}); + system.tick({}); + system.tick({}); expect_eq(fixture.registry()->view().get_size(), 1); + + system.tick({}); + system.tick({}); + system.tick({}); fixture.check_values(*component); }; @@ -256,8 +252,9 @@ Suite tick = "ticking"_suite = [] { auto system = System { fixture.registry() }; auto &surface = **fixture.create_component(); - constexpr auto position = lt::math::vec2_i32 { 50, 50 }; - constexpr auto resolution = lt::math::vec2_u32 { width, height }; + const auto new_title = std::string { title } + std::string { "_" }; + constexpr auto new_position = lt::math::vec2_i32 { position_x + 50, position_y + 50 }; + constexpr auto new_resolution = lt::math::vec2_u32 { width + 50, height + 50 }; expect_eq(surface.peek_requests().size(), 0); @@ -266,11 +263,11 @@ Suite tick = "ticking"_suite = [] { system.tick(tick_info()); expect_eq(surface.peek_requests().size(), 0); - surface.push_request(lt::surface::ModifyTitleRequest(title)); + surface.push_request(lt::surface::ModifyTitleRequest(new_title)); expect_eq(surface.peek_requests().size(), 1); - surface.push_request(lt::surface::ModifyResolutionRequest(resolution)); - surface.push_request(lt::surface::ModifyPositionRequest(position)); + surface.push_request(lt::surface::ModifyResolutionRequest(new_resolution)); + surface.push_request(lt::surface::ModifyPositionRequest(new_position)); expect_eq(surface.peek_requests().size(), 1 + 2); surface.push_request(lt::surface::ModifyVisibilityRequest(false)); @@ -280,10 +277,51 @@ Suite tick = "ticking"_suite = [] { system.tick(tick_info()); expect_eq(surface.peek_requests().size(), 0); + }; +}; - expect_eq(surface.get_title(), title); - expect_eq(surface.get_position(), position); - expect_eq(surface.get_resolution(), resolution); +Suite requests = "requests"_suite = [] { + using ::lt::surface::ModifyTitleRequest; + using ::lt::surface::ModifyResolutionRequest; + using ::lt::surface::ModifyPositionRequest; + using ::lt::surface::ModifyVisibilityRequest; + + auto fixture = Fixture {}; + auto system = System { fixture.registry() }; + auto &surface = **fixture.create_component(); + + Case { "ModifyTitleRequest" } = [&] { + const auto new_title = std::string { title } + std::string { "_" }; + surface.push_request({ ModifyTitleRequest { new_title } }); + + system.tick({}); + expect_eq(surface.get_title(), new_title); + }; + + Case { "ModifyResolutionRequest" } = [&] { + constexpr auto new_resolution = lt::math::vec2_u32 { width + 50, height + 50 }; + surface.push_request({ ModifyResolutionRequest { new_resolution } }); + + system.tick({}); + expect_eq(surface.get_resolution(), new_resolution); + }; + + Case { "ModifyPositionRequest" } = [&] { + constexpr auto new_position = lt::math::vec2_i32 { position_x + 50, position_y + 50 }; + surface.push_request({ ModifyPositionRequest { new_position } }); + + system.tick({}); + expect_eq(surface.get_position(), new_position); + }; + + Case { "ModifyVisibilityRequest" } = [&] { + surface.push_request({ ModifyVisibilityRequest { .visible = false } }); + system.tick({}); + expect_eq(surface.is_visible(), false); + + surface.push_request({ ModifyVisibilityRequest { .visible = true } }); + system.tick({}); + expect_eq(surface.is_visible(), true); }; }; @@ -302,7 +340,8 @@ Suite windows_window_proc = "windows_window_proc"_suite = [] { ::SendMessage(hwnd, WM_SETFOCUS, {}, {}); expect_eq(events.size(), 1u); - ignore = std::get(events.front()); + auto event = std::get(events.front()); + ::lt::log::trace("{}", event.to_string()); // make sure it's not optimized away? }; system.tick({}); @@ -311,7 +350,8 @@ Suite windows_window_proc = "windows_window_proc"_suite = [] { ::SendMessage(hwnd, WM_KILLFOCUS, {}, {}); expect_eq(events.size(), 1u); - ignore = std::get(events.front()); + auto event = std::get(events.front()); + ::lt::log::trace("{}", event.to_string()); // make sure it's not optimized away? }; system.tick({}); @@ -350,60 +390,133 @@ Suite windows_window_proc = "windows_window_proc"_suite = [] { system.tick({}); Case { "WM_MOUSEWHEEL" } = [&] { + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_MOUSEWHEEL, MAKEWPARAM(0, WHEEL_DELTA), {}); + ::SendMessage(hwnd, WM_MOUSEWHEEL, MAKEWPARAM(0, -WHEEL_DELTA), {}); + + // Mouse wheel is treated like key presses, + // but since there is no "release" action for it... + // Every movement causes two key press events together: + // Press + Release of wheel_up/down. + expect_eq(events.size(), 4u); + expect_eq(std::get(events[0]).get_key(), Key::wheel_up); + expect_eq(std::get(events[1]).get_key(), Key::wheel_up); + expect_eq(std::get(events[2]).get_key(), Key::wheel_down); + expect_eq(std::get(events[3]).get_key(), Key::wheel_down); }; system.tick({}); Case { "WM_LBUTTONDOWN" } = [&] { + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_LBUTTONDOWN, {}, {}); + expect_eq(events.size(), 1u); + + // Mouse buttons are treated like key presses. + expect_eq(std::get(events[0]).get_key(), Key::left_button); }; system.tick({}); Case { "WM_LBUTTONUP" } = [&] { + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_LBUTTONUP, {}, {}); + expect_eq(events.size(), 1u); + + // Mouse buttons are treated like key presses. + expect_eq(std::get(events[0]).get_key(), Key::left_button); }; system.tick({}); Case { "WM_RBUTTONDOWN" } = [&] { + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_RBUTTONDOWN, {}, {}); + expect_eq(events.size(), 1u); + + // Mouse buttons are treated like key presses. + expect_eq(std::get(events[0]).get_key(), Key::right_button); }; system.tick({}); Case { "WM_RBUTTONUP" } = [&] { + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_RBUTTONUP, {}, {}); + expect_eq(events.size(), 1u); + + // Mouse buttons are treated like key presses. + expect_eq(std::get(events[0]).get_key(), Key::right_button); }; system.tick({}); Case { "WM_MBUTTONDOWN" } = [&] { + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_MBUTTONDOWN, {}, {}); + expect_eq(events.size(), 1u); + + // Mouse buttons are treated like key presses. + expect_eq(std::get(events[0]).get_key(), Key::middle_button); }; system.tick({}); Case { "WM_MBUTTONUP" } = [&] { + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_MBUTTONUP, {}, {}); + expect_eq(events.size(), 1u); + + // Mouse buttons are treated like key presses. + expect_eq(std::get(events[0]).get_key(), Key::middle_button); }; + system.tick({}); Case { "WM_XBUTTONDOWN" } = [&] { + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_XBUTTONDOWN, MAKEWPARAM(0, XBUTTON1), {}); + ::SendMessage(hwnd, WM_XBUTTONDOWN, MAKEWPARAM(0, XBUTTON2), {}); + expect_eq(events.size(), 2u); + + // Mouse buttons are treated like key presses. + expect_eq(std::get(events[0]).get_key(), Key::x_button_1); + expect_eq(std::get(events[1]).get_key(), Key::x_button_2); }; system.tick({}); Case { "WM_XBUTTONUP" } = [&] { + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_XBUTTONUP, MAKEWPARAM(0, XBUTTON1), {}); + ::SendMessage(hwnd, WM_XBUTTONUP, MAKEWPARAM(0, XBUTTON2), {}); + expect_eq(events.size(), 2u); + + // Mouse buttons are treated like key presses. + expect_eq(std::get(events[0]).get_key(), Key::x_button_1); + expect_eq(std::get(events[1]).get_key(), Key::x_button_2); }; system.tick({}); Case { "WM_KEYDOWN" } = [&] { expect_eq(events.size(), 0u); - ::SendMessage(hwnd, WM_KEYDOWN, System::to_native_key(lt::Key::escape), {}); + ::SendMessage(hwnd, WM_KEYDOWN, System::to_native_key(Key::escape), {}); expect_eq(events.size(), 1u); - const auto &event = std::get(events.front()); - expect_eq(event.get_key(), lt::Key::escape); + expect_eq(std::get(events[0]).get_key(), Key::escape); }; system.tick({}); Case { "WM_KEYUP" } = [&] { + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_KEYUP, System::to_native_key(Key::escape), {}); + expect_eq(events.size(), 1u); + + expect_eq(std::get(events[0]).get_key(), Key::escape); }; system.tick({}); Case { "WM_CLOSE" } = [&] { - }; + expect_eq(events.size(), 0u); + ::SendMessage(hwnd, WM_CLOSE, {}, {}); + expect_eq(events.size(), 1u); - system.tick({}); - Case { "WM_DESTROY" } = [&] { + // would throw if type is incorrect + auto event = std::get(events[0]); + ::lt::log::trace("{}", event.to_string()); // make sure it's not optimized away? }; }; diff --git a/modules/test/test.test.cpp b/modules/test/test.test.cpp index 6c95038..ec65b5f 100644 --- a/modules/test/test.test.cpp +++ b/modules/test/test.test.cpp @@ -8,7 +8,6 @@ Suite expects = "expects"_suite = []() { }; Case { "this emptiness machine" } = [] { - expect_le(9, 6); }; Case { "expect_unreachable" } = [] { diff --git a/tools/ci/amd64/msvc/unit_tests.ps1 b/tools/ci/amd64/msvc/unit_tests.ps1 index 9dd82a3..2706c89 100644 --- a/tools/ci/amd64/msvc/unit_tests.ps1 +++ b/tools/ci/amd64/msvc/unit_tests.ps1 @@ -19,11 +19,11 @@ cmake ` -B build ` -G Ninja ` -D ENABLE_UNIT_TESTS=ON ` - -D CMAKE_BUILD_TYPE=Debug ` + -D CMAKE_BUILD_TYPE=Release ` -D CMAKE_EXPORT_COMPILE_COMMANDS=True ` -D CMAKE_CXX_FLAGS="/std:c++latest /EHsc /Zi /Oy- /WX /W4" -cmake --build ./build +cmake --build ./build || $(exit $LASTEXITCODE) $tests = Get-ChildItem -Path "./build" -Recurse -File | Where-Object { $_.Name -like "*_tests.exe"