feat(surface/linux): replace glfw with xlib

This commit is contained in:
light7734 2025-09-18 19:15:29 +03:30
parent 320cc66267
commit 21e9933a42
Signed by: light7734
GPG key ID: 8C30176798F1A6BA
9 changed files with 554 additions and 452 deletions

View file

@ -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
)

View file

@ -1,143 +1,42 @@
#define GLFW_EXPOSE_NATIVE_X11
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <ecs/components.hpp>
#include <surface/components.hpp>
#include <surface/events/mouse.hpp>
#include <surface/requests/surface.hpp>
#include <surface/system.hpp>
//
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>
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<class... Ts>
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<std::vector<SurfaceComponent::EventCallback> *>(
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<uint32_t>(width), static_cast<uint32_t>(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<float>(xpos), static_cast<float>(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<float>(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<ecs::Registry> 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 &registry, entt::entity entity)
{
try
{
auto &surface = registry.get<SurfaceComponent>(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<int>(surface.get_resolution().x),
static_cast<int>(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 &registry, entt::entity entity)
void System::on_surface_update(entt::registry &registry, entt::entity entity)
{
auto &surface = registry.get<SurfaceComponent>(entity);
glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks);
}
void System::on_surface_destroy(entt::registry &registry, entt::entity entity)
{
auto &surface = registry.get<SurfaceComponent>(entity);
if (surface.m_glfw_handle)
const auto &[display, window, _] = registry.get<SurfaceComponent>(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<KeyPressedEvent>(
static_cast<uint32_t>(XLookupKeysym(&event.xkey, 0))
);
break;
}
case KeyRelease:
{
queue.emplace_back<KeyReleasedEvent>(
static_cast<uint32_t>(XLookupKeysym(&event.xkey, 0))
);
break;
}
case ButtonPress:
{
queue.emplace_back<ButtonPressedEvent>(static_cast<int>(event.xbutton.button));
break;
}
case ButtonRelease:
{
queue.emplace_back<ButtonReleasedEvent>(static_cast<int>(event.xbutton.button));
break;
}
case FocusIn:
{
queue.emplace_back<GainFocusEvent>({});
break;
}
case FocusOut:
{
queue.emplace_back<LostFocusEvent>({});
break;
}
case ClientMessage:
{
if (event.xclient.data.l[0] == wm_delete_message)
{
queue.emplace_back<ClosedEvent>({});
}
break;
}
case MotionNotify:
{
queue.emplace_back<MouseMovedEvent>(MouseMovedEvent {
static_cast<float>(event.xmotion.x),
static_cast<float>(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>(ResizedEvent {
static_cast<uint32_t>(new_width),
static_cast<uint32_t>(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>(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<SurfaceComponent>();
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<int>(x), static_cast<int>(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<SurfaceComponent>().each([](SurfaceComponent &surface) {
glfwSwapBuffers(surface.m_glfw_handle);
m_registry->view<SurfaceComponent>().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<SurfaceComponent>();
surface.m_resolution = new_size;
glfwSetWindowSize(
surface.m_glfw_handle,
static_cast<int>(new_size.x),
static_cast<int>(new_size.y)
);
}
void System::set_v_sync(ecs::Entity surface_entity, bool vsync)
{
auto &surface = surface_entity.get_component<SurfaceComponent>();
surface.m_vsync = vsync;
glfwSwapInterval(vsync);
}
void System::set_visibility(ecs::Entity surface_entity, bool visible)
{
auto &surface = surface_entity.get_component<SurfaceComponent>();
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<SurfaceComponent>();
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();

View file

@ -1,11 +1,13 @@
#include <ecs/entity.hpp>
#include <ecs/scene.hpp>
#include <surface/components.hpp>
#include <surface/system.hpp>
#include <test/fuzz.hpp>
#include <test/test.hpp>
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 &registry)
@ -55,6 +72,14 @@ void remove_surface_component(ecs::Registry &registry)
}
}
void push_request(ecs::Registry &registry)
{
}
void push_event(ecs::Registry &registry)
{
}
void check_invariants()
{
}
@ -67,9 +92,14 @@ test::FuzzHarness harness = [](const uint8_t *data, size_t size) {
while (auto action = provider.consume<uint8_t>())
{
switch (static_cast<Action>(action.value()))
if (*action > std::to_underlying(FuzzAction::count))
{
case Action::create_entity:
*action = *action % std::to_underlying(FuzzAction::count);
}
switch (static_cast<FuzzAction>(action.value()))
{
case FuzzAction::create_entity:
{
const auto length = std::min(provider.consume<uint32_t>().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<SurfaceComponent>();
if (!view->empty())
{
view.each([&](auto entity, SurfaceComponent &surface) {
provider.consume<uint8_t>().value_or(0);
});
registry->get_entt_registry().remove<SurfaceComponent>(*view.begin());
}
break;
}
case FuzzAction::push_request:
{
break;
}
case FuzzAction::tick_system:
{
system.tick();
break;

View file

@ -1,3 +1,6 @@
#include <ecs/entity.hpp>
#include <surface/components.hpp>
#include <surface/requests/surface.hpp>
#include <surface/system.hpp>
#include <test/test.hpp>
@ -18,6 +21,13 @@ constexpr auto height = 600u;
constexpr auto vsync = true;
constexpr auto visible = false;
template<class... Ts>
struct overloads: Ts...
{
using Ts::operator()...;
};
class Fixture
{
public:
@ -41,7 +51,11 @@ public:
void check_values(const SurfaceComponent &component)
{
expect_ne(std::get<SurfaceComponent::X11NativeHandle>(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<SurfaceComponent>().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);
}
};
};

View file

@ -1,143 +1,12 @@
#define GLFW_EXPOSE_NATIVE_X11
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <surface/system.hpp>
#include <utility>
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<ecs::Registry> registry, Ref<app::EventMediator> 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<std::vector<SurfaceComponent::EventCallback> *>(
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<uint32_t>(width), static_cast<uint32_t>(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<float>(xpos), static_cast<float>(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<float>(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<ecs::Registry> 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 &registry, entt::entity entity)
try
{
auto &surface = registry.get<SurfaceComponent>(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<int>(surface.get_resolution().x),
static_cast<int>(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 &registry, entt::entity entity)
void System::on_surface_update(entt::registry &registry, entt::entity entity)
{
auto &surface = registry.get<SurfaceComponent>(entity);
glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks);
}
void System::on_surface_destroy(entt::registry &registry, entt::entity entity)
{
auto &surface = registry.get<SurfaceComponent>(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<SurfaceComponent>();
surface.m_title = new_title;
glfwSetWindowTitle(surface.m_glfw_handle, surface.m_title.c_str());
}
auto System::tick() -> bool
{
m_registry->view<SurfaceComponent>().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<SurfaceComponent>();
surface.m_resolution = new_size;
glfwSetWindowSize(
surface.m_glfw_handle,
static_cast<int>(new_size.x),
static_cast<int>(new_size.y)
);
}
void System::set_v_sync(ecs::Entity surface_entity, bool vsync)
{
auto &surface = surface_entity.get_component<SurfaceComponent>();
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<SurfaceComponent>();
surface.m_event_callbacks.emplace_back(std::move(callback));
}
void System::ensure_component_sanity(const SurfaceComponent &component)
{
auto [width, height] = component.get_resolution();

View file

@ -4,16 +4,14 @@
#include <surface/events/keyboard.hpp>
#include <surface/events/mouse.hpp>
#include <surface/events/surface.hpp>
#include <surface/requests/surface.hpp>
#include <variant>
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<bool(const Event &)>;
using Request = std::variant<
ModifyTitleRequest,
ModifyResolutionRequest,
ModifyPositionRequest,
ModifyVisibilityRequest>;
using WindowsNativeHandle = void *;
using X11NativeHandle = unsigned long;
using NativeHandle = std::variant<WindowsNativeHandle, X11NativeHandle>;
#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<Event> &
{
return m_event_queue;
}
[[nodiscard]] auto peek_requests() const -> const std::vector<Request> &
{
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<Event> m_event_queue;
std::vector<EventCallback> m_event_callbacks;
std::vector<Request> m_requests;
};
} // namespace lt::surface

View file

@ -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

View file

@ -0,0 +1,27 @@
#pragma once
#include <math/vec2.hpp>
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

View file

@ -1,9 +1,7 @@
#pragma once
#include <app/system.hpp>
#include <ecs/entity.hpp>
#include <ecs/scene.hpp>
#include <surface/components.hpp>
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 &registry, entt::entity entity);
@ -49,10 +33,35 @@ private:
void on_surface_destroy(entt::registry &registry, 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<ecs::Registry> m_registry;
};
} // namespace lt::surface