From 21e9933a427a5ca8dc363f3c9486b70676bdd3ed Mon Sep 17 00:00:00 2001 From: light7734 Date: Thu, 18 Sep 2025 19:15:29 +0330 Subject: [PATCH] feat(surface/linux): replace glfw with xlib --- modules/surface/CMakeLists.txt | 4 +- modules/surface/private/linux/system.cpp | 493 ++++++++++++-------- modules/surface/private/system.fuzz.cpp | 63 ++- modules/surface/private/system.test.cpp | 91 +++- modules/surface/private/windows/system.cpp | 189 +------- modules/surface/public/components.hpp | 74 ++- modules/surface/public/events/keyboard.hpp | 16 +- modules/surface/public/requests/surface.hpp | 27 ++ modules/surface/public/system.hpp | 49 +- 9 files changed, 554 insertions(+), 452 deletions(-) create mode 100644 modules/surface/public/requests/surface.hpp diff --git a/modules/surface/CMakeLists.txt b/modules/surface/CMakeLists.txt index 7af9a4d..ba78f7c 100644 --- a/modules/surface/CMakeLists.txt +++ b/modules/surface/CMakeLists.txt @@ -1,14 +1,16 @@ if (NOT WIN32) add_library_module(surface linux/system.cpp) + target_link_libraries(surface PRIVATE X11) + else(WIN32) add_library_module(surface windows/system.cpp) + endif() target_link_libraries(surface PUBLIC ecs app PRIVATE - glfw logger lt_debug ) diff --git a/modules/surface/private/linux/system.cpp b/modules/surface/private/linux/system.cpp index 171b22d..c1ab936 100644 --- a/modules/surface/private/linux/system.cpp +++ b/modules/surface/private/linux/system.cpp @@ -1,143 +1,42 @@ -#define GLFW_EXPOSE_NATIVE_X11 -#include -#include +#include +#include +#include +#include #include +// +#include +#include +#include +#include + namespace lt::surface { -// This class is to ensure glfwInit/glfwTerminate is called only once and exactly when needed during -// entire application runtime -class GlfwSingleton +template +struct overloads: Ts... { -public: - [[nodiscard]] static auto get() -> GlfwSingleton & - { - static auto instance = GlfwSingleton {}; - return instance; - } - - GlfwSingleton(GlfwSingleton &&) = delete; - - GlfwSingleton(const GlfwSingleton &) = delete; - - auto operator=(GlfwSingleton &&) -> GlfwSingleton & = delete; - - auto operator=(const GlfwSingleton &) -> GlfwSingleton & = delete; - -private: - GlfwSingleton() - { - log_inf("Initializing glfw..."); - ensure(glfwInit(), "Failed to initialize 'glfw'"); - log_inf("...Finished"); - } - - ~GlfwSingleton() - { - log_inf("Terminating glfw..."); - glfwTerminate(); - log_inf("...Finished"); - } + using Ts::operator()...; }; -void glfw_error_callbac(int32_t code, const char *description) -{ - log_err("GLFW ERROR: {} -> {}", code, description); -} +void ensure_component_sanity(const SurfaceComponent &component); -void handle_event(GLFWwindow *window, const SurfaceComponent::Event &event) -{ - auto &callbacks = *static_cast *>( - glfwGetWindowUserPointer(window) - ); - - for (auto &callback : callbacks) - { - if (callback(event)) - { - return; - } - } -} - -void bind_glfw_events(GLFWwindow *handle) -{ - glfwSetWindowPosCallback(handle, [](GLFWwindow *window, int xpos, int ypos) { - handle_event(window, MovedEvent { xpos, ypos }); - }); - - glfwSetWindowSizeCallback(handle, [](GLFWwindow *window, int width, int height) { - handle_event( - window, - ResizedEvent { static_cast(width), static_cast(height) } - ); - }); - - glfwSetWindowCloseCallback(handle, [](GLFWwindow *window) { - handle_event(window, ClosedEvent {}); - }); - - glfwSetWindowFocusCallback(handle, [](GLFWwindow *window, int focus) { - if (focus == GLFW_TRUE) - { - handle_event(window, GainFocusEvent {}); - } - else - { - handle_event(window, LostFocusEvent {}); - } - }); - - glfwSetCursorPosCallback(handle, [](GLFWwindow *window, double xpos, double ypos) { - handle_event( - window, - MouseMovedEvent { static_cast(xpos), static_cast(ypos) } - ); - }); - - glfwSetMouseButtonCallback( - handle, - [](GLFWwindow *window, int button, int action, int /*mods*/) { - if (action == GLFW_PRESS) - { - handle_event(window, ButtonPressedEvent { button }); - } - else if (action == GLFW_RELEASE) - { - handle_event(window, ButtonReleasedEvent { button }); - } - } - ); - - glfwSetScrollCallback(handle, [](GLFWwindow *window, double /*xoffset*/, double yoffset) { - handle_event(window, WheelScrolledEvent { static_cast(yoffset) }); - }); - - glfwSetKeyCallback( - handle, - [](GLFWwindow *window, int key, int /*scancode*/, int action, int /*mods*/) { - if (action == GLFW_PRESS) - { - handle_event(window, KeyPressedEvent { key }); - } - else if (action == GLFW_RELEASE) - { - handle_event(window, KeyReleasedEvent { key }); - } - } - ); - - glfwSetCharCallback(handle, [](GLFWwindow *window, unsigned int character) { - handle_event(window, KeySetCharEvent { character }); - }); -} +constexpr auto all_events_mask = KeyPressMask | // + KeyReleaseMask | // + ButtonPressMask | // + ButtonReleaseMask | // + EnterWindowMask | // + LeaveWindowMask | // + PointerMotionMask | // + KeymapStateMask | // + ExposureMask | // + VisibilityChangeMask | // + StructureNotifyMask | // + FocusChangeMask | // + ColormapChangeMask | // + OwnerGrabButtonMask; System::System(Ref registry): m_registry(std::move(registry)) { - glfwSetErrorCallback(&glfw_error_callbac); - - // will call `glfwInit()` only the first time - auto &glfw_instance = GlfwSingleton::get(); ensure(m_registry, "Failed to initialize surface system: null registry"); ensure( @@ -178,29 +77,94 @@ System::~System() }); } +void System::on_register() +{ +} + +void System::on_unregister() +{ +} + void System::on_surface_construct(entt::registry ®istry, entt::entity entity) { try { auto &surface = registry.get(entity); + const auto &resolution = surface.get_resolution(); + const auto &position = surface.get_position(); ensure_component_sanity(surface); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + // TODO(Light): refactor "environment" into standalone module + // NOLINTNEXTLINE(concurrency-mt-unsafe) + auto *display_env = std::getenv("DISPLAY"); + ensure(display_env != nullptr, "DISPLAY env var not found!"); - surface.m_glfw_handle = glfwCreateWindow( - static_cast(surface.get_resolution().x), - static_cast(surface.get_resolution().y), - surface.get_title().begin(), - nullptr, - nullptr + auto *display = XOpenDisplay(display_env); + auto root_window = XDefaultRootWindow(display); + + auto border_width = 0; + auto depth = int32_t { CopyFromParent }; + auto window_class = CopyFromParent; + auto *visual = (Visual *)CopyFromParent; + + auto attribute_value_mask = CWBackPixel | CWEventMask; + auto attributes = XSetWindowAttributes { + .background_pixel = 0xffafe9af, + .event_mask = all_events_mask, + }; + + typedef struct Hints + { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; + } Hints; + + auto main_window = XCreateWindow( + display, + root_window, + position.x, + position.y, + resolution.x, + resolution.y, + border_width, + depth, + window_class, + visual, + attribute_value_mask, + &attributes ); - ensure(surface.m_glfw_handle, "Failed to create 'GLFWwindow'"); + surface.m_native_data.display = display; + surface.m_native_data.window = main_window; - glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks); - surface.m_native_handle = glfwGetX11Window(surface.m_glfw_handle); - bind_glfw_events(surface.m_glfw_handle); + surface.m_native_data.wm_delete_message = XInternAtom(display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(display, main_window, &surface.m_native_data.wm_delete_message, 1); + + // code to remove decoration + long hints[5] = { 2, 0, 0, 0, 0 }; + Atom motif_hints = XInternAtom(display, "_MOTIF_WM_HINTS", False); + + XChangeProperty( + display, + surface.m_native_data.window, + motif_hints, + motif_hints, + 32, + PropModeReplace, + (unsigned char *)&hints, + 5 + ); + + XMapWindow(display, main_window); + XStoreName(display, main_window, surface.m_title.c_str()); + XFlush(display); + + if (!surface.is_visible()) + { + XUnmapWindow(display, main_window); + } } catch (...) { @@ -212,82 +176,213 @@ void System::on_surface_construct(entt::registry ®istry, entt::entity entity) void System::on_surface_update(entt::registry ®istry, entt::entity entity) { auto &surface = registry.get(entity); - glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks); } void System::on_surface_destroy(entt::registry ®istry, entt::entity entity) { - auto &surface = registry.get(entity); - - if (surface.m_glfw_handle) + const auto &[display, window, _] = registry.get(entity).get_native_data(); + if (!display) { - glfwDestroyWindow(surface.m_glfw_handle); + log_wrn("Surface component destroyed with null display"); + return; + } + + XDestroyWindow(display, window); +} + +void System::handle_events(SurfaceComponent &surface) +{ + auto &queue = surface.m_event_queue; + queue.clear(); + + auto event = XEvent {}; + auto &[display, window, wm_delete_message] = surface.m_native_data; + + XFlush(display); + while (XEventsQueued(display, QueuedAlready) != 0) + { + XNextEvent(surface.m_native_data.display, &event); + + switch (event.type) + { + case KeyPress: + { + queue.emplace_back( + static_cast(XLookupKeysym(&event.xkey, 0)) + ); + break; + } + case KeyRelease: + { + queue.emplace_back( + static_cast(XLookupKeysym(&event.xkey, 0)) + ); + break; + } + case ButtonPress: + { + queue.emplace_back(static_cast(event.xbutton.button)); + break; + } + case ButtonRelease: + { + queue.emplace_back(static_cast(event.xbutton.button)); + break; + } + case FocusIn: + { + queue.emplace_back({}); + break; + } + case FocusOut: + { + queue.emplace_back({}); + break; + } + case ClientMessage: + { + if (event.xclient.data.l[0] == wm_delete_message) + { + queue.emplace_back({}); + } + + break; + } + case MotionNotify: + { + queue.emplace_back(MouseMovedEvent { + static_cast(event.xmotion.x), + static_cast(event.xmotion.y), + }); + break; + } + case ConfigureNotify: + { + const auto [prev_width, prev_height] = surface.get_resolution(); + const auto new_width = event.xconfigure.width; + const auto new_height = event.xconfigure.height; + if (prev_width != new_width || prev_height != new_height) + { + surface.m_resolution.x = new_width; + surface.m_resolution.y = new_height; + queue.emplace_back(ResizedEvent { + static_cast(new_width), + static_cast(new_height), + }); + } + + const auto [prev_x, prev_y] = surface.get_position(); + const auto new_x = event.xconfigure.x; + const auto new_y = event.xconfigure.y; + if (prev_x != new_x || prev_y != new_y) + { + surface.m_position.x = new_x; + surface.m_position.y = new_y; + queue.emplace_back(MovedEvent { + new_x, + new_y, + }); + } + break; + } + + case Expose: break; + case GraphicsExpose: break; + case NoExpose: break; + case CirculateRequest: break; + case ConfigureRequest: break; + case MapRequest: break; + case ResizeRequest: break; + case CirculateNotify: break; + case CreateNotify: break; + case DestroyNotify: break; + case GravityNotify: break; + case MapNotify: break; + case MappingNotify: break; + case ReparentNotify: break; + case UnmapNotify: break; + case VisibilityNotify: break; + case ColormapNotify: break; + case PropertyNotify: break; + case SelectionClear: break; + case SelectionNotify: break; + case SelectionRequest: break; + default: log_inf("Unknown X Event"); + } } } -void System::set_title(ecs::Entity entity, std::string_view new_title) +void System::handle_requests(SurfaceComponent &surface) { - auto &surface = entity.get_component(); + const auto visitor = overloads { + [&](const ModifyTitleRequest &request) { modify_title(surface, request); }, + [&](const ModifyResolutionRequest &request) { modify_resolution(surface, request); }, + [&](const ModifyPositionRequest &request) { modify_position(surface, request); }, + [&](const ModifyVisibilityRequest &request) { modify_visiblity(surface, request); }, + [&](const auto &) { log_err("Unknown surface request"); }, + }; - surface.m_title = new_title; - glfwSetWindowTitle(surface.m_glfw_handle, surface.m_title.c_str()); + for (const auto &request : surface.peek_requests()) + { + std::visit(visitor, request); + } + + surface.m_requests.clear(); +} + +void System::modify_title(SurfaceComponent &surface, const ModifyTitleRequest &request) +{ + surface.m_title = request.title; + + const auto &[display, window, _] = surface.get_native_data(); + XStoreName(display, window, request.title.c_str()); +} + +void System::modify_resolution(SurfaceComponent &surface, const ModifyResolutionRequest &request) +{ + surface.m_resolution = request.resolution; + + const auto &[display, window, _] = surface.get_native_data(); + const auto &[width, height] = request.resolution; + XResizeWindow(display, window, width, height); +} + +void System::modify_position(SurfaceComponent &surface, const ModifyPositionRequest &request) +{ + surface.m_position = request.position; + + const auto &[display, window, _] = surface.get_native_data(); + const auto &[x, y] = request.position; + XMoveWindow(display, window, static_cast(x), static_cast(y)); +} + +void System::modify_visiblity(SurfaceComponent &surface, const ModifyVisibilityRequest &request) +{ + const auto &[display, window, _] = surface.get_native_data(); + surface.m_visible = request.visible; + + if (request.visible) + { + XMapWindow(display, window); + } + else + { + XUnmapWindow(display, window); + } } auto System::tick() -> bool { - m_registry->view().each([](SurfaceComponent &surface) { - glfwSwapBuffers(surface.m_glfw_handle); + m_registry->view().each([this](SurfaceComponent &surface) { + handle_requests(surface); + + handle_events(surface); }); - glfwPollEvents(); return false; } -void System::set_size(ecs::Entity surface_entity, const math::uvec2 &new_size) -{ - auto &surface = surface_entity.get_component(); - surface.m_resolution = new_size; - - glfwSetWindowSize( - surface.m_glfw_handle, - static_cast(new_size.x), - static_cast(new_size.y) - ); -} - -void System::set_v_sync(ecs::Entity surface_entity, bool vsync) -{ - auto &surface = surface_entity.get_component(); - surface.m_vsync = vsync; - - glfwSwapInterval(vsync); -} - -void System::set_visibility(ecs::Entity surface_entity, bool visible) -{ - auto &surface = surface_entity.get_component(); - surface.m_visible = visible; - - if (visible) - { - glfwShowWindow(surface.m_glfw_handle); - } - else - { - glfwHideWindow(surface.m_glfw_handle); - } -} - -void System::add_event_listener( - ecs::Entity surface_entity, - SurfaceComponent::EventCallback callback -) -{ - auto &surface = surface_entity.get_component(); - surface.m_event_callbacks.emplace_back(std::move(callback)); -} - -void System::ensure_component_sanity(const SurfaceComponent &component) +void ensure_component_sanity(const SurfaceComponent &component) { auto [width, height] = component.get_resolution(); diff --git a/modules/surface/private/system.fuzz.cpp b/modules/surface/private/system.fuzz.cpp index 718148d..ad1197a 100644 --- a/modules/surface/private/system.fuzz.cpp +++ b/modules/surface/private/system.fuzz.cpp @@ -1,11 +1,13 @@ +#include #include +#include #include #include #include namespace lt::surface { -enum class Action : uint8_t +enum class FuzzAction : uint8_t { create_entity, @@ -13,7 +15,22 @@ enum class Action : uint8_t destroy_surface_component, - tick, + push_request, + + push_event, + + tick_system, + + count, +}; + +enum class EventType : uint8_t +{ + Closed, + Moved, + Resized, + LostFocus, + GainFocus, }; void create_surface_component(test::FuzzDataProvider &provider, ecs::Registry ®istry) @@ -55,6 +72,14 @@ void remove_surface_component(ecs::Registry ®istry) } } +void push_request(ecs::Registry ®istry) +{ +} + +void push_event(ecs::Registry ®istry) +{ +} + void check_invariants() { } @@ -67,9 +92,14 @@ test::FuzzHarness harness = [](const uint8_t *data, size_t size) { while (auto action = provider.consume()) { - switch (static_cast(action.value())) + if (*action > std::to_underlying(FuzzAction::count)) { - case Action::create_entity: + *action = *action % std::to_underlying(FuzzAction::count); + } + + switch (static_cast(action.value())) + { + case FuzzAction::create_entity: { const auto length = std::min(provider.consume().value_or(16), 255u); const auto tag = provider.consume_string(length).value_or(""); @@ -77,17 +107,36 @@ test::FuzzHarness harness = [](const uint8_t *data, size_t size) { break; } - case Action::create_surface_component: + case FuzzAction::create_surface_component: { create_surface_component(provider, *registry); break; } - case Action::destroy_surface_component: + case FuzzAction::destroy_surface_component: { remove_surface_component(*registry); break; } - case Action::tick: + case FuzzAction::push_event: + { + const auto view = registry->get_entt_registry().view(); + + if (!view->empty()) + { + view.each([&](auto entity, SurfaceComponent &surface) { + provider.consume().value_or(0); + }); + + registry->get_entt_registry().remove(*view.begin()); + } + + break; + } + case FuzzAction::push_request: + { + break; + } + case FuzzAction::tick_system: { system.tick(); break; diff --git a/modules/surface/private/system.test.cpp b/modules/surface/private/system.test.cpp index 8531f9f..c5bce3b 100644 --- a/modules/surface/private/system.test.cpp +++ b/modules/surface/private/system.test.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #include #include @@ -18,6 +21,13 @@ constexpr auto height = 600u; constexpr auto vsync = true; constexpr auto visible = false; +template +struct overloads: Ts... +{ + using Ts::operator()...; +}; + + class Fixture { public: @@ -41,7 +51,11 @@ public: void check_values(const SurfaceComponent &component) { - expect_ne(std::get(component.get_native_handle()), 0); +#ifdef LIGHT_PLATFORM_LINUX + expect_not_nullptr(component.get_native_data().display); + expect_ne(component.get_native_data().window, 0); +#endif + expect_eq(component.get_resolution().x, width); expect_eq(component.get_resolution().y, height); expect_eq(component.get_title(), title); @@ -61,9 +75,7 @@ Suite raii = [] { Case { "many won't freeze/throw" } = [] { auto fixture = Fixture {}; - - /* range is small since glfw init/terminate is slow. */ - for (auto idx : std::views::iota(0, 100)) + for (auto idx : std::views::iota(0, 250)) { ignore = System { fixture.registry() }; } @@ -74,7 +86,7 @@ Suite raii = [] { auto fixture = Fixture {}; fixture.add_surface_component(); - expect_throw([&] { ignore = System { { fixture.registry() } }; }); + expect_throw([&] { ignore = System { fixture.registry() }; }); }; Case { "post construct has correct state" } = [] { @@ -107,8 +119,8 @@ Suite system_events = [] { Case { "on_unregister won't throw" } = [] { auto fixture = Fixture {}; auto system = System { fixture.registry() }; - system.on_register(); + system.on_register(); system.on_unregister(); expect_eq(fixture.registry()->view().size(), 0); }; @@ -185,14 +197,73 @@ Suite tick = [] { fixture.add_surface_component(); system.tick(); }; +}; - Case { "ticking on chaotic registry won't throw" } = [] { +Suite tick_handles_events = [] { + Case { "ticking clears previous tick's events" } = [] { + auto fixture = Fixture {}; + auto system = System { fixture.registry() }; + auto &surface = fixture.add_surface_component(); + + // flush window-creation events + system.tick(); + expect_eq(surface.peek_events().size(), 0); + + surface.push_event(surface::MovedEvent({}, {})); + expect_eq(surface.peek_events().size(), 1); + + surface.push_event(surface::ButtonPressedEvent({})); + expect_eq(surface.peek_events().size(), 2); + + system.tick(); + expect_eq(surface.peek_events().size(), 0); }; }; -Suite property_setters = [] { +Suite tick_handles_requests = [] { + Case { "ticking clears requests" } = [] { + auto fixture = Fixture {}; + auto system = System { fixture.registry() }; + auto &surface = fixture.add_surface_component(); -}; + constexpr auto title = "ABC"; + constexpr auto position = math::ivec2 { 50, 50 }; + constexpr auto resolution = math::uvec2 { 50, 50 }; -Suite listeners = [] { + expect_eq(surface.peek_requests().size(), 0); + + surface.push_request(surface::ModifyVisibilityRequest(true)); + expect_eq(surface.peek_requests().size(), 1); + system.tick(); + expect_eq(surface.peek_requests().size(), 0); + + surface.push_request(surface::ModifyTitleRequest(title)); + expect_eq(surface.peek_requests().size(), 1); + + surface.push_request(surface::ModifyResolutionRequest(resolution)); + surface.push_request(surface::ModifyPositionRequest(position)); + expect_eq(surface.peek_requests().size(), 1 + 2); + + surface.push_request(surface::ModifyVisibilityRequest(false)); + surface.push_request(surface::ModifyVisibilityRequest(true)); + surface.push_request(surface::ModifyVisibilityRequest(false)); + expect_eq(surface.peek_requests().size(), 1 + 2 + 3); + + system.tick(); + 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); + + log_dbg("EVENT COUNT: {}", surface.peek_events().size()); + for (const auto &event : surface.peek_events()) + { + const auto visitor = overloads { + [&](auto event) { log_dbg("event: {}", event.to_string()); }, + }; + + std::visit(visitor, event); + } + }; }; diff --git a/modules/surface/private/windows/system.cpp b/modules/surface/private/windows/system.cpp index 171b22d..053f8aa 100644 --- a/modules/surface/private/windows/system.cpp +++ b/modules/surface/private/windows/system.cpp @@ -1,143 +1,12 @@ -#define GLFW_EXPOSE_NATIVE_X11 -#include -#include #include +#include namespace lt::surface { -// This class is to ensure glfwInit/glfwTerminate is called only once and exactly when needed during -// entire application runtime -class GlfwSingleton +System::System(Ref registry, Ref event_mediator) + : m_registry(std::move(registry)) + , m_event_mediator(std::move(event_mediator)) { -public: - [[nodiscard]] static auto get() -> GlfwSingleton & - { - static auto instance = GlfwSingleton {}; - return instance; - } - - GlfwSingleton(GlfwSingleton &&) = delete; - - GlfwSingleton(const GlfwSingleton &) = delete; - - auto operator=(GlfwSingleton &&) -> GlfwSingleton & = delete; - - auto operator=(const GlfwSingleton &) -> GlfwSingleton & = delete; - -private: - GlfwSingleton() - { - log_inf("Initializing glfw..."); - ensure(glfwInit(), "Failed to initialize 'glfw'"); - log_inf("...Finished"); - } - - ~GlfwSingleton() - { - log_inf("Terminating glfw..."); - glfwTerminate(); - log_inf("...Finished"); - } -}; - -void glfw_error_callbac(int32_t code, const char *description) -{ - log_err("GLFW ERROR: {} -> {}", code, description); -} - -void handle_event(GLFWwindow *window, const SurfaceComponent::Event &event) -{ - auto &callbacks = *static_cast *>( - glfwGetWindowUserPointer(window) - ); - - for (auto &callback : callbacks) - { - if (callback(event)) - { - return; - } - } -} - -void bind_glfw_events(GLFWwindow *handle) -{ - glfwSetWindowPosCallback(handle, [](GLFWwindow *window, int xpos, int ypos) { - handle_event(window, MovedEvent { xpos, ypos }); - }); - - glfwSetWindowSizeCallback(handle, [](GLFWwindow *window, int width, int height) { - handle_event( - window, - ResizedEvent { static_cast(width), static_cast(height) } - ); - }); - - glfwSetWindowCloseCallback(handle, [](GLFWwindow *window) { - handle_event(window, ClosedEvent {}); - }); - - glfwSetWindowFocusCallback(handle, [](GLFWwindow *window, int focus) { - if (focus == GLFW_TRUE) - { - handle_event(window, GainFocusEvent {}); - } - else - { - handle_event(window, LostFocusEvent {}); - } - }); - - glfwSetCursorPosCallback(handle, [](GLFWwindow *window, double xpos, double ypos) { - handle_event( - window, - MouseMovedEvent { static_cast(xpos), static_cast(ypos) } - ); - }); - - glfwSetMouseButtonCallback( - handle, - [](GLFWwindow *window, int button, int action, int /*mods*/) { - if (action == GLFW_PRESS) - { - handle_event(window, ButtonPressedEvent { button }); - } - else if (action == GLFW_RELEASE) - { - handle_event(window, ButtonReleasedEvent { button }); - } - } - ); - - glfwSetScrollCallback(handle, [](GLFWwindow *window, double /*xoffset*/, double yoffset) { - handle_event(window, WheelScrolledEvent { static_cast(yoffset) }); - }); - - glfwSetKeyCallback( - handle, - [](GLFWwindow *window, int key, int /*scancode*/, int action, int /*mods*/) { - if (action == GLFW_PRESS) - { - handle_event(window, KeyPressedEvent { key }); - } - else if (action == GLFW_RELEASE) - { - handle_event(window, KeyReleasedEvent { key }); - } - } - ); - - glfwSetCharCallback(handle, [](GLFWwindow *window, unsigned int character) { - handle_event(window, KeySetCharEvent { character }); - }); -} - -System::System(Ref registry): m_registry(std::move(registry)) -{ - glfwSetErrorCallback(&glfw_error_callbac); - - // will call `glfwInit()` only the first time - auto &glfw_instance = GlfwSingleton::get(); ensure(m_registry, "Failed to initialize surface system: null registry"); ensure( @@ -183,24 +52,9 @@ void System::on_surface_construct(entt::registry ®istry, entt::entity entity) try { auto &surface = registry.get(entity); + const auto &resolution = surface.get_resolution(); + const auto &position = surface.get_position(); ensure_component_sanity(surface); - - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - surface.m_glfw_handle = glfwCreateWindow( - static_cast(surface.get_resolution().x), - static_cast(surface.get_resolution().y), - surface.get_title().begin(), - nullptr, - nullptr - ); - ensure(surface.m_glfw_handle, "Failed to create 'GLFWwindow'"); - - glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks); - surface.m_native_handle = glfwGetX11Window(surface.m_glfw_handle); - bind_glfw_events(surface.m_glfw_handle); } catch (...) { @@ -212,17 +66,11 @@ void System::on_surface_construct(entt::registry ®istry, entt::entity entity) void System::on_surface_update(entt::registry ®istry, entt::entity entity) { auto &surface = registry.get(entity); - glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks); } void System::on_surface_destroy(entt::registry ®istry, entt::entity entity) { auto &surface = registry.get(entity); - - if (surface.m_glfw_handle) - { - glfwDestroyWindow(surface.m_glfw_handle); - } } void System::set_title(ecs::Entity entity, std::string_view new_title) @@ -230,16 +78,10 @@ void System::set_title(ecs::Entity entity, std::string_view new_title) auto &surface = entity.get_component(); surface.m_title = new_title; - glfwSetWindowTitle(surface.m_glfw_handle, surface.m_title.c_str()); } auto System::tick() -> bool { - m_registry->view().each([](SurfaceComponent &surface) { - glfwSwapBuffers(surface.m_glfw_handle); - }); - - glfwPollEvents(); return false; } @@ -247,20 +89,12 @@ void System::set_size(ecs::Entity surface_entity, const math::uvec2 &new_size) { auto &surface = surface_entity.get_component(); surface.m_resolution = new_size; - - glfwSetWindowSize( - surface.m_glfw_handle, - static_cast(new_size.x), - static_cast(new_size.y) - ); } void System::set_v_sync(ecs::Entity surface_entity, bool vsync) { auto &surface = surface_entity.get_component(); surface.m_vsync = vsync; - - glfwSwapInterval(vsync); } void System::set_visibility(ecs::Entity surface_entity, bool visible) @@ -270,23 +104,12 @@ void System::set_visibility(ecs::Entity surface_entity, bool visible) if (visible) { - glfwShowWindow(surface.m_glfw_handle); } else { - glfwHideWindow(surface.m_glfw_handle); } } -void System::add_event_listener( - ecs::Entity surface_entity, - SurfaceComponent::EventCallback callback -) -{ - auto &surface = surface_entity.get_component(); - surface.m_event_callbacks.emplace_back(std::move(callback)); -} - void System::ensure_component_sanity(const SurfaceComponent &component) { auto [width, height] = component.get_resolution(); diff --git a/modules/surface/public/components.hpp b/modules/surface/public/components.hpp index ad1ac38..7f0e29b 100644 --- a/modules/surface/public/components.hpp +++ b/modules/surface/public/components.hpp @@ -4,16 +4,14 @@ #include #include #include +#include #include -struct GLFWwindow; +typedef struct _XDisplay Display; namespace lt::surface { -/** Represents a platform's surface (eg. a Window). - * - * @note Read-only component, should only be modified through a system. - */ +/** Represents a platform's surface (eg. a Window). */ class SurfaceComponent { public: @@ -29,23 +27,27 @@ public: // keyboard events KeyPressedEvent, - KeyRepeatEvent, KeyReleasedEvent, - KeySetCharEvent, // mouse events MouseMovedEvent, - WheelScrolledEvent, ButtonPressedEvent, ButtonReleasedEvent>; - using EventCallback = std::function; + using Request = std::variant< + ModifyTitleRequest, + ModifyResolutionRequest, + ModifyPositionRequest, + ModifyVisibilityRequest>; - using WindowsNativeHandle = void *; - - using X11NativeHandle = unsigned long; - - using NativeHandle = std::variant; +#ifdef LIGHT_PLATFORM_LINUX + struct NativeData + { + Display *display; + uint32_t window; + unsigned long wm_delete_message; + }; +#endif static constexpr auto max_dimension = 4096; @@ -67,6 +69,7 @@ public: , m_resolution(info.resolution) , m_vsync(info.vsync) , m_visible(info.visible) + , m_native_data({}) { } @@ -80,6 +83,11 @@ public: return m_resolution; } + [[nodiscard]] auto get_position() const -> const math::ivec2 & + { + return m_position; + } + [[nodiscard]] auto is_vsync() const -> bool { return m_vsync; @@ -90,30 +98,48 @@ public: return m_visible; } - [[nodiscard]] auto get_native_handle() const -> NativeHandle + [[nodiscard]] auto get_native_data() const -> const NativeData & { - return m_native_handle; + return m_native_data; + } + + [[nodiscard]] auto peek_events() const -> const std::vector & + { + return m_event_queue; + } + + [[nodiscard]] auto peek_requests() const -> const std::vector & + { + return m_requests; + }; + + void push_request(const Request &request) + { + m_requests.emplace_back(request); + } + + /** @note: Only the surface system and tests should push events */ + void push_event(const Event &event) + { + m_event_queue.emplace_back(event); } private: - [[nodiscard]] auto get_glfw_handle() const -> GLFWwindow * - { - return m_glfw_handle; - } - std::string m_title; math::uvec2 m_resolution; + math::ivec2 m_position; + bool m_vsync; bool m_visible; - NativeHandle m_native_handle; + NativeData m_native_data; - GLFWwindow *m_glfw_handle {}; + std::vector m_event_queue; - std::vector m_event_callbacks; + std::vector m_requests; }; } // namespace lt::surface diff --git a/modules/surface/public/events/keyboard.hpp b/modules/surface/public/events/keyboard.hpp index 556196f..527d4b8 100644 --- a/modules/surface/public/events/keyboard.hpp +++ b/modules/surface/public/events/keyboard.hpp @@ -7,11 +7,11 @@ namespace lt::surface { class KeyPressedEvent { public: - KeyPressedEvent(int32_t key): m_key(key) + KeyPressedEvent(uint32_t key): m_key(key) { } - [[nodiscard]] auto get_key() const -> int32_t + [[nodiscard]] auto get_key() const -> uint32_t { return m_key; } @@ -22,7 +22,7 @@ public: } private: - int32_t m_key; + uint32_t m_key; }; class KeyRepeatEvent @@ -32,7 +32,7 @@ public: { } - [[nodiscard]] auto get_key() const -> int32_t + [[nodiscard]] auto get_key() const -> uint32_t { return m_key; } @@ -43,17 +43,17 @@ public: } private: - int32_t m_key; + uint32_t m_key; }; class KeyReleasedEvent { public: - KeyReleasedEvent(int key): m_key(key) + KeyReleasedEvent(uint32_t key): m_key(key) { } - [[nodiscard]] auto get_key() const -> int32_t + [[nodiscard]] auto get_key() const -> uint32_t { return m_key; } @@ -64,7 +64,7 @@ public: } private: - int32_t m_key; + uint32_t m_key; }; class KeySetCharEvent diff --git a/modules/surface/public/requests/surface.hpp b/modules/surface/public/requests/surface.hpp new file mode 100644 index 0000000..0812055 --- /dev/null +++ b/modules/surface/public/requests/surface.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace lt::surface { + +struct ModifyTitleRequest +{ + std::string title; +}; + +struct ModifyResolutionRequest +{ + math::uvec2 resolution; +}; + +struct ModifyPositionRequest +{ + math::ivec2 position; +}; + +struct ModifyVisibilityRequest +{ + bool visible; +}; + +}; // namespace lt::surface diff --git a/modules/surface/public/system.hpp b/modules/surface/public/system.hpp index 06f7dcf..2280c25 100644 --- a/modules/surface/public/system.hpp +++ b/modules/surface/public/system.hpp @@ -1,9 +1,7 @@ #pragma once #include -#include #include -#include namespace lt::surface { @@ -22,26 +20,12 @@ public: auto operator=(const System &) -> System & = delete; - void on_register() override - { - } + void on_register() override; - void on_unregister() override - { - } + void on_unregister() override; auto tick() -> bool override; - static void set_title(ecs::Entity surface_entity, std::string_view new_title); - - void set_size(ecs::Entity surface_entity, const math::uvec2 &new_size); - - void set_v_sync(ecs::Entity surface_entity, bool vsync); - - void set_visibility(ecs::Entity surface_entity, bool visible); - - void add_event_listener(ecs::Entity surface_entity, SurfaceComponent::EventCallback callback); - private: void on_surface_construct(entt::registry ®istry, entt::entity entity); @@ -49,10 +33,35 @@ private: void on_surface_destroy(entt::registry ®istry, entt::entity entity); - void ensure_component_sanity(const SurfaceComponent &component); + void handle_requests(struct SurfaceComponent &surface); + + void handle_events(struct SurfaceComponent &surface); + + void modify_title(struct SurfaceComponent &surface, const struct ModifyTitleRequest &request); + + void modify_resolution( + struct SurfaceComponent &surface, + const struct ModifyResolutionRequest &request + ); + + void modify_position( + struct SurfaceComponent &surface, + const struct ModifyPositionRequest &request + ); + + void modify_visiblity( + struct SurfaceComponent &surface, + const struct ModifyVisibilityRequest &request + ); + + void modify_position(ecs::Entity surface_entity, const math::ivec2 &new_size); + + void modify_position(ecs::Entity surface_entity, const math::uvec2 &new_size); + + void set_visibility(ecs::Entity surface_entity, bool visible); + Ref m_registry; }; - } // namespace lt::surface