feat(surface/linux): replace glfw with xlib
This commit is contained in:
parent
320cc66267
commit
21e9933a42
9 changed files with 554 additions and 452 deletions
|
@ -1,14 +1,16 @@
|
||||||
if (NOT WIN32)
|
if (NOT WIN32)
|
||||||
add_library_module(surface linux/system.cpp)
|
add_library_module(surface linux/system.cpp)
|
||||||
|
target_link_libraries(surface PRIVATE X11)
|
||||||
|
|
||||||
else(WIN32)
|
else(WIN32)
|
||||||
add_library_module(surface windows/system.cpp)
|
add_library_module(surface windows/system.cpp)
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(surface PUBLIC
|
target_link_libraries(surface PUBLIC
|
||||||
ecs
|
ecs
|
||||||
app
|
app
|
||||||
PRIVATE
|
PRIVATE
|
||||||
glfw
|
|
||||||
logger
|
logger
|
||||||
lt_debug
|
lt_debug
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,143 +1,42 @@
|
||||||
#define GLFW_EXPOSE_NATIVE_X11
|
#include <ecs/components.hpp>
|
||||||
#include <GLFW/glfw3.h>
|
#include <surface/components.hpp>
|
||||||
#include <GLFW/glfw3native.h>
|
#include <surface/events/mouse.hpp>
|
||||||
|
#include <surface/requests/surface.hpp>
|
||||||
#include <surface/system.hpp>
|
#include <surface/system.hpp>
|
||||||
|
|
||||||
|
//
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <X11/keysym.h>
|
||||||
|
#include <X11/keysymdef.h>
|
||||||
|
|
||||||
namespace lt::surface {
|
namespace lt::surface {
|
||||||
|
|
||||||
// This class is to ensure glfwInit/glfwTerminate is called only once and exactly when needed during
|
template<class... Ts>
|
||||||
// entire application runtime
|
struct overloads: Ts...
|
||||||
class GlfwSingleton
|
|
||||||
{
|
{
|
||||||
public:
|
using Ts::operator()...;
|
||||||
[[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)
|
void ensure_component_sanity(const SurfaceComponent &component);
|
||||||
{
|
|
||||||
log_err("GLFW ERROR: {} -> {}", code, description);
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_event(GLFWwindow *window, const SurfaceComponent::Event &event)
|
constexpr auto all_events_mask = KeyPressMask | //
|
||||||
{
|
KeyReleaseMask | //
|
||||||
auto &callbacks = *static_cast<std::vector<SurfaceComponent::EventCallback> *>(
|
ButtonPressMask | //
|
||||||
glfwGetWindowUserPointer(window)
|
ButtonReleaseMask | //
|
||||||
);
|
EnterWindowMask | //
|
||||||
|
LeaveWindowMask | //
|
||||||
for (auto &callback : callbacks)
|
PointerMotionMask | //
|
||||||
{
|
KeymapStateMask | //
|
||||||
if (callback(event))
|
ExposureMask | //
|
||||||
{
|
VisibilityChangeMask | //
|
||||||
return;
|
StructureNotifyMask | //
|
||||||
}
|
FocusChangeMask | //
|
||||||
}
|
ColormapChangeMask | //
|
||||||
}
|
OwnerGrabButtonMask;
|
||||||
|
|
||||||
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))
|
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(m_registry, "Failed to initialize surface system: null registry");
|
||||||
|
|
||||||
ensure(
|
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)
|
void System::on_surface_construct(entt::registry ®istry, entt::entity entity)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto &surface = registry.get<SurfaceComponent>(entity);
|
auto &surface = registry.get<SurfaceComponent>(entity);
|
||||||
|
const auto &resolution = surface.get_resolution();
|
||||||
|
const auto &position = surface.get_position();
|
||||||
ensure_component_sanity(surface);
|
ensure_component_sanity(surface);
|
||||||
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
// TODO(Light): refactor "environment" into standalone module
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
|
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
auto *display_env = std::getenv("DISPLAY");
|
||||||
|
ensure(display_env != nullptr, "DISPLAY env var not found!");
|
||||||
|
|
||||||
surface.m_glfw_handle = glfwCreateWindow(
|
auto *display = XOpenDisplay(display_env);
|
||||||
static_cast<int>(surface.get_resolution().x),
|
auto root_window = XDefaultRootWindow(display);
|
||||||
static_cast<int>(surface.get_resolution().y),
|
|
||||||
surface.get_title().begin(),
|
auto border_width = 0;
|
||||||
nullptr,
|
auto depth = int32_t { CopyFromParent };
|
||||||
nullptr
|
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_data.wm_delete_message = XInternAtom(display, "WM_DELETE_WINDOW", False);
|
||||||
surface.m_native_handle = glfwGetX11Window(surface.m_glfw_handle);
|
XSetWMProtocols(display, main_window, &surface.m_native_data.wm_delete_message, 1);
|
||||||
bind_glfw_events(surface.m_glfw_handle);
|
|
||||||
|
// 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 (...)
|
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)
|
void System::on_surface_update(entt::registry ®istry, entt::entity entity)
|
||||||
{
|
{
|
||||||
auto &surface = registry.get<SurfaceComponent>(entity);
|
auto &surface = registry.get<SurfaceComponent>(entity);
|
||||||
glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::on_surface_destroy(entt::registry ®istry, entt::entity entity)
|
void System::on_surface_destroy(entt::registry ®istry, entt::entity entity)
|
||||||
{
|
{
|
||||||
auto &surface = registry.get<SurfaceComponent>(entity);
|
const auto &[display, window, _] = registry.get<SurfaceComponent>(entity).get_native_data();
|
||||||
|
if (!display)
|
||||||
if (surface.m_glfw_handle)
|
|
||||||
{
|
{
|
||||||
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;
|
for (const auto &request : surface.peek_requests())
|
||||||
glfwSetWindowTitle(surface.m_glfw_handle, surface.m_title.c_str());
|
{
|
||||||
|
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
|
auto System::tick() -> bool
|
||||||
{
|
{
|
||||||
m_registry->view<SurfaceComponent>().each([](SurfaceComponent &surface) {
|
m_registry->view<SurfaceComponent>().each([this](SurfaceComponent &surface) {
|
||||||
glfwSwapBuffers(surface.m_glfw_handle);
|
handle_requests(surface);
|
||||||
|
|
||||||
|
handle_events(surface);
|
||||||
});
|
});
|
||||||
|
|
||||||
glfwPollEvents();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::set_size(ecs::Entity surface_entity, const math::uvec2 &new_size)
|
void ensure_component_sanity(const SurfaceComponent &component)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
auto [width, height] = component.get_resolution();
|
auto [width, height] = component.get_resolution();
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
#include <ecs/entity.hpp>
|
||||||
#include <ecs/scene.hpp>
|
#include <ecs/scene.hpp>
|
||||||
|
#include <surface/components.hpp>
|
||||||
#include <surface/system.hpp>
|
#include <surface/system.hpp>
|
||||||
#include <test/fuzz.hpp>
|
#include <test/fuzz.hpp>
|
||||||
#include <test/test.hpp>
|
#include <test/test.hpp>
|
||||||
|
|
||||||
namespace lt::surface {
|
namespace lt::surface {
|
||||||
|
|
||||||
enum class Action : uint8_t
|
enum class FuzzAction : uint8_t
|
||||||
{
|
{
|
||||||
create_entity,
|
create_entity,
|
||||||
|
|
||||||
|
@ -13,7 +15,22 @@ enum class Action : uint8_t
|
||||||
|
|
||||||
destroy_surface_component,
|
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)
|
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()
|
void check_invariants()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -67,9 +92,14 @@ test::FuzzHarness harness = [](const uint8_t *data, size_t size) {
|
||||||
|
|
||||||
while (auto action = provider.consume<uint8_t>())
|
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 length = std::min(provider.consume<uint32_t>().value_or(16), 255u);
|
||||||
const auto tag = provider.consume_string(length).value_or("");
|
const auto tag = provider.consume_string(length).value_or("");
|
||||||
|
@ -77,17 +107,36 @@ test::FuzzHarness harness = [](const uint8_t *data, size_t size) {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Action::create_surface_component:
|
case FuzzAction::create_surface_component:
|
||||||
{
|
{
|
||||||
create_surface_component(provider, *registry);
|
create_surface_component(provider, *registry);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Action::destroy_surface_component:
|
case FuzzAction::destroy_surface_component:
|
||||||
{
|
{
|
||||||
remove_surface_component(*registry);
|
remove_surface_component(*registry);
|
||||||
break;
|
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();
|
system.tick();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#include <ecs/entity.hpp>
|
||||||
|
#include <surface/components.hpp>
|
||||||
|
#include <surface/requests/surface.hpp>
|
||||||
#include <surface/system.hpp>
|
#include <surface/system.hpp>
|
||||||
#include <test/test.hpp>
|
#include <test/test.hpp>
|
||||||
|
|
||||||
|
@ -18,6 +21,13 @@ constexpr auto height = 600u;
|
||||||
constexpr auto vsync = true;
|
constexpr auto vsync = true;
|
||||||
constexpr auto visible = false;
|
constexpr auto visible = false;
|
||||||
|
|
||||||
|
template<class... Ts>
|
||||||
|
struct overloads: Ts...
|
||||||
|
{
|
||||||
|
using Ts::operator()...;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class Fixture
|
class Fixture
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -41,7 +51,11 @@ public:
|
||||||
|
|
||||||
void check_values(const SurfaceComponent &component)
|
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().x, width);
|
||||||
expect_eq(component.get_resolution().y, height);
|
expect_eq(component.get_resolution().y, height);
|
||||||
expect_eq(component.get_title(), title);
|
expect_eq(component.get_title(), title);
|
||||||
|
@ -61,9 +75,7 @@ Suite raii = [] {
|
||||||
|
|
||||||
Case { "many won't freeze/throw" } = [] {
|
Case { "many won't freeze/throw" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
|
for (auto idx : std::views::iota(0, 250))
|
||||||
/* range is small since glfw init/terminate is slow. */
|
|
||||||
for (auto idx : std::views::iota(0, 100))
|
|
||||||
{
|
{
|
||||||
ignore = System { fixture.registry() };
|
ignore = System { fixture.registry() };
|
||||||
}
|
}
|
||||||
|
@ -74,7 +86,7 @@ Suite raii = [] {
|
||||||
|
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
fixture.add_surface_component();
|
fixture.add_surface_component();
|
||||||
expect_throw([&] { ignore = System { { fixture.registry() } }; });
|
expect_throw([&] { ignore = System { fixture.registry() }; });
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "post construct has correct state" } = [] {
|
Case { "post construct has correct state" } = [] {
|
||||||
|
@ -107,8 +119,8 @@ Suite system_events = [] {
|
||||||
Case { "on_unregister won't throw" } = [] {
|
Case { "on_unregister won't throw" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
system.on_register();
|
|
||||||
|
|
||||||
|
system.on_register();
|
||||||
system.on_unregister();
|
system.on_unregister();
|
||||||
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
|
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
|
||||||
};
|
};
|
||||||
|
@ -185,14 +197,73 @@ Suite tick = [] {
|
||||||
fixture.add_surface_component();
|
fixture.add_surface_component();
|
||||||
system.tick();
|
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 };
|
||||||
|
|
||||||
|
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()); },
|
||||||
};
|
};
|
||||||
|
|
||||||
Suite listeners = [] {
|
std::visit(visitor, event);
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,143 +1,12 @@
|
||||||
#define GLFW_EXPOSE_NATIVE_X11
|
|
||||||
#include <GLFW/glfw3.h>
|
|
||||||
#include <GLFW/glfw3native.h>
|
|
||||||
#include <surface/system.hpp>
|
#include <surface/system.hpp>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace lt::surface {
|
namespace lt::surface {
|
||||||
|
|
||||||
// This class is to ensure glfwInit/glfwTerminate is called only once and exactly when needed during
|
System::System(Ref<ecs::Registry> registry, Ref<app::EventMediator> event_mediator)
|
||||||
// entire application runtime
|
: m_registry(std::move(registry))
|
||||||
class GlfwSingleton
|
, 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(m_registry, "Failed to initialize surface system: null registry");
|
||||||
|
|
||||||
ensure(
|
ensure(
|
||||||
|
@ -183,24 +52,9 @@ void System::on_surface_construct(entt::registry ®istry, entt::entity entity)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto &surface = registry.get<SurfaceComponent>(entity);
|
auto &surface = registry.get<SurfaceComponent>(entity);
|
||||||
|
const auto &resolution = surface.get_resolution();
|
||||||
|
const auto &position = surface.get_position();
|
||||||
ensure_component_sanity(surface);
|
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 (...)
|
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)
|
void System::on_surface_update(entt::registry ®istry, entt::entity entity)
|
||||||
{
|
{
|
||||||
auto &surface = registry.get<SurfaceComponent>(entity);
|
auto &surface = registry.get<SurfaceComponent>(entity);
|
||||||
glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::on_surface_destroy(entt::registry ®istry, entt::entity entity)
|
void System::on_surface_destroy(entt::registry ®istry, entt::entity entity)
|
||||||
{
|
{
|
||||||
auto &surface = registry.get<SurfaceComponent>(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)
|
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>();
|
auto &surface = entity.get_component<SurfaceComponent>();
|
||||||
|
|
||||||
surface.m_title = new_title;
|
surface.m_title = new_title;
|
||||||
glfwSetWindowTitle(surface.m_glfw_handle, surface.m_title.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::tick() -> bool
|
auto System::tick() -> bool
|
||||||
{
|
{
|
||||||
m_registry->view<SurfaceComponent>().each([](SurfaceComponent &surface) {
|
|
||||||
glfwSwapBuffers(surface.m_glfw_handle);
|
|
||||||
});
|
|
||||||
|
|
||||||
glfwPollEvents();
|
|
||||||
return false;
|
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>();
|
auto &surface = surface_entity.get_component<SurfaceComponent>();
|
||||||
surface.m_resolution = new_size;
|
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)
|
void System::set_v_sync(ecs::Entity surface_entity, bool vsync)
|
||||||
{
|
{
|
||||||
auto &surface = surface_entity.get_component<SurfaceComponent>();
|
auto &surface = surface_entity.get_component<SurfaceComponent>();
|
||||||
surface.m_vsync = vsync;
|
surface.m_vsync = vsync;
|
||||||
|
|
||||||
glfwSwapInterval(vsync);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::set_visibility(ecs::Entity surface_entity, bool visible)
|
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)
|
if (visible)
|
||||||
{
|
{
|
||||||
glfwShowWindow(surface.m_glfw_handle);
|
|
||||||
}
|
}
|
||||||
else
|
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 System::ensure_component_sanity(const SurfaceComponent &component)
|
||||||
{
|
{
|
||||||
auto [width, height] = component.get_resolution();
|
auto [width, height] = component.get_resolution();
|
||||||
|
|
|
@ -4,16 +4,14 @@
|
||||||
#include <surface/events/keyboard.hpp>
|
#include <surface/events/keyboard.hpp>
|
||||||
#include <surface/events/mouse.hpp>
|
#include <surface/events/mouse.hpp>
|
||||||
#include <surface/events/surface.hpp>
|
#include <surface/events/surface.hpp>
|
||||||
|
#include <surface/requests/surface.hpp>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
struct GLFWwindow;
|
typedef struct _XDisplay Display;
|
||||||
|
|
||||||
namespace lt::surface {
|
namespace lt::surface {
|
||||||
|
|
||||||
/** Represents a platform's surface (eg. a Window).
|
/** Represents a platform's surface (eg. a Window). */
|
||||||
*
|
|
||||||
* @note Read-only component, should only be modified through a system.
|
|
||||||
*/
|
|
||||||
class SurfaceComponent
|
class SurfaceComponent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -29,23 +27,27 @@ public:
|
||||||
|
|
||||||
// keyboard events
|
// keyboard events
|
||||||
KeyPressedEvent,
|
KeyPressedEvent,
|
||||||
KeyRepeatEvent,
|
|
||||||
KeyReleasedEvent,
|
KeyReleasedEvent,
|
||||||
KeySetCharEvent,
|
|
||||||
|
|
||||||
// mouse events
|
// mouse events
|
||||||
MouseMovedEvent,
|
MouseMovedEvent,
|
||||||
WheelScrolledEvent,
|
|
||||||
ButtonPressedEvent,
|
ButtonPressedEvent,
|
||||||
ButtonReleasedEvent>;
|
ButtonReleasedEvent>;
|
||||||
|
|
||||||
using EventCallback = std::function<bool(const Event &)>;
|
using Request = std::variant<
|
||||||
|
ModifyTitleRequest,
|
||||||
|
ModifyResolutionRequest,
|
||||||
|
ModifyPositionRequest,
|
||||||
|
ModifyVisibilityRequest>;
|
||||||
|
|
||||||
using WindowsNativeHandle = void *;
|
#ifdef LIGHT_PLATFORM_LINUX
|
||||||
|
struct NativeData
|
||||||
using X11NativeHandle = unsigned long;
|
{
|
||||||
|
Display *display;
|
||||||
using NativeHandle = std::variant<WindowsNativeHandle, X11NativeHandle>;
|
uint32_t window;
|
||||||
|
unsigned long wm_delete_message;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
static constexpr auto max_dimension = 4096;
|
static constexpr auto max_dimension = 4096;
|
||||||
|
|
||||||
|
@ -67,6 +69,7 @@ public:
|
||||||
, m_resolution(info.resolution)
|
, m_resolution(info.resolution)
|
||||||
, m_vsync(info.vsync)
|
, m_vsync(info.vsync)
|
||||||
, m_visible(info.visible)
|
, m_visible(info.visible)
|
||||||
|
, m_native_data({})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +83,11 @@ public:
|
||||||
return m_resolution;
|
return m_resolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_position() const -> const math::ivec2 &
|
||||||
|
{
|
||||||
|
return m_position;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto is_vsync() const -> bool
|
[[nodiscard]] auto is_vsync() const -> bool
|
||||||
{
|
{
|
||||||
return m_vsync;
|
return m_vsync;
|
||||||
|
@ -90,30 +98,48 @@ public:
|
||||||
return m_visible;
|
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:
|
private:
|
||||||
[[nodiscard]] auto get_glfw_handle() const -> GLFWwindow *
|
|
||||||
{
|
|
||||||
return m_glfw_handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string m_title;
|
std::string m_title;
|
||||||
|
|
||||||
math::uvec2 m_resolution;
|
math::uvec2 m_resolution;
|
||||||
|
|
||||||
|
math::ivec2 m_position;
|
||||||
|
|
||||||
bool m_vsync;
|
bool m_vsync;
|
||||||
|
|
||||||
bool m_visible;
|
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
|
} // namespace lt::surface
|
||||||
|
|
|
@ -7,11 +7,11 @@ namespace lt::surface {
|
||||||
class KeyPressedEvent
|
class KeyPressedEvent
|
||||||
{
|
{
|
||||||
public:
|
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;
|
return m_key;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int32_t m_key;
|
uint32_t m_key;
|
||||||
};
|
};
|
||||||
|
|
||||||
class KeyRepeatEvent
|
class KeyRepeatEvent
|
||||||
|
@ -32,7 +32,7 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto get_key() const -> int32_t
|
[[nodiscard]] auto get_key() const -> uint32_t
|
||||||
{
|
{
|
||||||
return m_key;
|
return m_key;
|
||||||
}
|
}
|
||||||
|
@ -43,17 +43,17 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int32_t m_key;
|
uint32_t m_key;
|
||||||
};
|
};
|
||||||
|
|
||||||
class KeyReleasedEvent
|
class KeyReleasedEvent
|
||||||
{
|
{
|
||||||
public:
|
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;
|
return m_key;
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int32_t m_key;
|
uint32_t m_key;
|
||||||
};
|
};
|
||||||
|
|
||||||
class KeySetCharEvent
|
class KeySetCharEvent
|
||||||
|
|
27
modules/surface/public/requests/surface.hpp
Normal file
27
modules/surface/public/requests/surface.hpp
Normal 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
|
|
@ -1,9 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <app/system.hpp>
|
#include <app/system.hpp>
|
||||||
#include <ecs/entity.hpp>
|
|
||||||
#include <ecs/scene.hpp>
|
#include <ecs/scene.hpp>
|
||||||
#include <surface/components.hpp>
|
|
||||||
|
|
||||||
namespace lt::surface {
|
namespace lt::surface {
|
||||||
|
|
||||||
|
@ -22,26 +20,12 @@ public:
|
||||||
|
|
||||||
auto operator=(const System &) -> System & = delete;
|
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;
|
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:
|
private:
|
||||||
void on_surface_construct(entt::registry ®istry, entt::entity entity);
|
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 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<ecs::Registry> m_registry;
|
Ref<ecs::Registry> m_registry;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace lt::surface
|
} // namespace lt::surface
|
||||||
|
|
Loading…
Add table
Reference in a new issue