Files
lunar/src/Application.cpp
2026-01-10 17:25:09 +02:00

873 lines
21 KiB
C++

#include "Application.h"
#include <algorithm>
#include <cerrno>
#include <cmath>
#include <cstdint>
#include <fcntl.h>
#include <iostream>
#include <numbers>
#include <optional>
#include <print>
#include <stdexcept>
#include <unistd.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_timer.h>
#include <SDL3/SDL_video.h>
#include <imgui_impl_sdl3.h>
#include <imgui_impl_vulkan.h>
#include <libinput.h>
#include <libudev.h>
#include <linux/input-event-codes.h>
#include <smath.hpp>
#include "Util.h"
#include "VulkanRenderer.h"
#if defined(TRACY_ENABLE)
# include <tracy/Tracy.hpp>
#endif
namespace {
int open_restricted(char const *path, int flags, void * /*user_data*/)
{
int fd { open(path, flags | O_CLOEXEC) };
return fd < 0 ? -errno : fd;
}
void close_restricted(int fd, void * /*user_data*/) { close(fd); }
libinput_interface const g_libinput_interface {
.open_restricted = open_restricted,
.close_restricted = close_restricted,
};
auto linux_key_to_imgui(uint32_t keycode) -> std::optional<ImGuiKey>
{
switch (keycode) {
case KEY_ESC:
return ImGuiKey_Escape;
case KEY_TAB:
return ImGuiKey_Tab;
case KEY_ENTER:
return ImGuiKey_Enter;
case KEY_BACKSPACE:
return ImGuiKey_Backspace;
case KEY_SPACE:
return ImGuiKey_Space;
case KEY_LEFTSHIFT:
return ImGuiKey_LeftShift;
case KEY_RIGHTSHIFT:
return ImGuiKey_RightShift;
case KEY_LEFTCTRL:
return ImGuiKey_LeftCtrl;
case KEY_RIGHTCTRL:
return ImGuiKey_RightCtrl;
case KEY_LEFTALT:
return ImGuiKey_LeftAlt;
case KEY_RIGHTALT:
return ImGuiKey_RightAlt;
case KEY_LEFTMETA:
return ImGuiKey_LeftSuper;
case KEY_RIGHTMETA:
return ImGuiKey_RightSuper;
case KEY_CAPSLOCK:
return ImGuiKey_CapsLock;
case KEY_NUMLOCK:
return ImGuiKey_NumLock;
case KEY_SCROLLLOCK:
return ImGuiKey_ScrollLock;
case KEY_UP:
return ImGuiKey_UpArrow;
case KEY_DOWN:
return ImGuiKey_DownArrow;
case KEY_LEFT:
return ImGuiKey_LeftArrow;
case KEY_RIGHT:
return ImGuiKey_RightArrow;
case KEY_HOME:
return ImGuiKey_Home;
case KEY_END:
return ImGuiKey_End;
case KEY_PAGEUP:
return ImGuiKey_PageUp;
case KEY_PAGEDOWN:
return ImGuiKey_PageDown;
case KEY_INSERT:
return ImGuiKey_Insert;
case KEY_DELETE:
return ImGuiKey_Delete;
case KEY_F1:
return ImGuiKey_F1;
case KEY_F2:
return ImGuiKey_F2;
case KEY_F3:
return ImGuiKey_F3;
case KEY_F4:
return ImGuiKey_F4;
case KEY_F5:
return ImGuiKey_F5;
case KEY_F6:
return ImGuiKey_F6;
case KEY_F7:
return ImGuiKey_F7;
case KEY_F8:
return ImGuiKey_F8;
case KEY_F9:
return ImGuiKey_F9;
case KEY_F10:
return ImGuiKey_F10;
case KEY_F11:
return ImGuiKey_F11;
case KEY_F12:
return ImGuiKey_F12;
case KEY_KP0:
return ImGuiKey_Keypad0;
case KEY_KP1:
return ImGuiKey_Keypad1;
case KEY_KP2:
return ImGuiKey_Keypad2;
case KEY_KP3:
return ImGuiKey_Keypad3;
case KEY_KP4:
return ImGuiKey_Keypad4;
case KEY_KP5:
return ImGuiKey_Keypad5;
case KEY_KP6:
return ImGuiKey_Keypad6;
case KEY_KP7:
return ImGuiKey_Keypad7;
case KEY_KP8:
return ImGuiKey_Keypad8;
case KEY_KP9:
return ImGuiKey_Keypad9;
case KEY_KPDOT:
return ImGuiKey_KeypadDecimal;
case KEY_KPENTER:
return ImGuiKey_KeypadEnter;
case KEY_KPPLUS:
return ImGuiKey_KeypadAdd;
case KEY_KPMINUS:
return ImGuiKey_KeypadSubtract;
case KEY_KPASTERISK:
return ImGuiKey_KeypadMultiply;
case KEY_KPSLASH:
return ImGuiKey_KeypadDivide;
case KEY_KPEQUAL:
return ImGuiKey_KeypadEqual;
case KEY_0:
return ImGuiKey_0;
case KEY_1:
return ImGuiKey_1;
case KEY_2:
return ImGuiKey_2;
case KEY_3:
return ImGuiKey_3;
case KEY_4:
return ImGuiKey_4;
case KEY_5:
return ImGuiKey_5;
case KEY_6:
return ImGuiKey_6;
case KEY_7:
return ImGuiKey_7;
case KEY_8:
return ImGuiKey_8;
case KEY_9:
return ImGuiKey_9;
case KEY_A:
return ImGuiKey_A;
case KEY_B:
return ImGuiKey_B;
case KEY_C:
return ImGuiKey_C;
case KEY_D:
return ImGuiKey_D;
case KEY_E:
return ImGuiKey_E;
case KEY_F:
return ImGuiKey_F;
case KEY_G:
return ImGuiKey_G;
case KEY_H:
return ImGuiKey_H;
case KEY_I:
return ImGuiKey_I;
case KEY_J:
return ImGuiKey_J;
case KEY_K:
return ImGuiKey_K;
case KEY_L:
return ImGuiKey_L;
case KEY_M:
return ImGuiKey_M;
case KEY_N:
return ImGuiKey_N;
case KEY_O:
return ImGuiKey_O;
case KEY_P:
return ImGuiKey_P;
case KEY_Q:
return ImGuiKey_Q;
case KEY_R:
return ImGuiKey_R;
case KEY_S:
return ImGuiKey_S;
case KEY_T:
return ImGuiKey_T;
case KEY_U:
return ImGuiKey_U;
case KEY_V:
return ImGuiKey_V;
case KEY_W:
return ImGuiKey_W;
case KEY_X:
return ImGuiKey_X;
case KEY_Y:
return ImGuiKey_Y;
case KEY_Z:
return ImGuiKey_Z;
default:
return std::nullopt;
}
}
auto linux_key_to_char(uint32_t keycode, bool shift) -> std::optional<char32_t>
{
switch (keycode) {
case KEY_0:
return shift ? U')' : U'0';
case KEY_1:
return shift ? U'!' : U'1';
case KEY_2:
return shift ? U'@' : U'2';
case KEY_3:
return shift ? U'#' : U'3';
case KEY_4:
return shift ? U'$' : U'4';
case KEY_5:
return shift ? U'%' : U'5';
case KEY_6:
return shift ? U'^' : U'6';
case KEY_7:
return shift ? U'&' : U'7';
case KEY_8:
return shift ? U'*' : U'8';
case KEY_9:
return shift ? U'(' : U'9';
case KEY_KP0:
return U'0';
case KEY_KP1:
return U'1';
case KEY_KP2:
return U'2';
case KEY_KP3:
return U'3';
case KEY_KP4:
return U'4';
case KEY_KP5:
return U'5';
case KEY_KP6:
return U'6';
case KEY_KP7:
return U'7';
case KEY_KP8:
return U'8';
case KEY_KP9:
return U'9';
case KEY_Q:
return shift ? U'Q' : U'q';
case KEY_W:
return shift ? U'W' : U'w';
case KEY_E:
return shift ? U'E' : U'e';
case KEY_R:
return shift ? U'R' : U'r';
case KEY_T:
return shift ? U'T' : U't';
case KEY_Y:
return shift ? U'Y' : U'y';
case KEY_U:
return shift ? U'U' : U'u';
case KEY_I:
return shift ? U'I' : U'i';
case KEY_O:
return shift ? U'O' : U'o';
case KEY_P:
return shift ? U'P' : U'p';
case KEY_A:
return shift ? U'A' : U'a';
case KEY_S:
return shift ? U'S' : U's';
case KEY_D:
return shift ? U'D' : U'd';
case KEY_F:
return shift ? U'F' : U'f';
case KEY_G:
return shift ? U'G' : U'g';
case KEY_H:
return shift ? U'H' : U'h';
case KEY_J:
return shift ? U'J' : U'j';
case KEY_K:
return shift ? U'K' : U'k';
case KEY_L:
return shift ? U'L' : U'l';
case KEY_Z:
return shift ? U'Z' : U'z';
case KEY_X:
return shift ? U'X' : U'x';
case KEY_C:
return shift ? U'C' : U'c';
case KEY_V:
return shift ? U'V' : U'v';
case KEY_B:
return shift ? U'B' : U'b';
case KEY_N:
return shift ? U'N' : U'n';
case KEY_M:
return shift ? U'M' : U'm';
case KEY_SPACE:
return U' ';
case KEY_MINUS:
return shift ? U'_' : U'-';
case KEY_EQUAL:
return shift ? U'+' : U'=';
case KEY_LEFTBRACE:
return shift ? U'{' : U'[';
case KEY_RIGHTBRACE:
return shift ? U'}' : U']';
case KEY_BACKSLASH:
return shift ? U'|' : U'\\';
case KEY_SEMICOLON:
return shift ? U':' : U';';
case KEY_APOSTROPHE:
return shift ? U'"' : U'\'';
case KEY_GRAVE:
return shift ? U'~' : U'`';
case KEY_COMMA:
return shift ? U'<' : U',';
case KEY_DOT:
return shift ? U'>' : U'.';
case KEY_SLASH:
return shift ? U'?' : U'/';
case KEY_KPDOT:
return U'.';
case KEY_KPPLUS:
return U'+';
case KEY_KPMINUS:
return U'-';
case KEY_KPASTERISK:
return U'*';
case KEY_KPSLASH:
return U'/';
case KEY_KPEQUAL:
return U'=';
default:
return std::nullopt;
}
}
} // namespace
namespace Lunar {
Application::Application()
{
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::println(std::cerr, "Failed to initialize SDL.");
throw std::runtime_error("App init fail");
}
m_window = SDL_CreateWindow(
"Lunar", 1280, 720, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
if (!m_window) {
m_logger.err("Failed to create SDL window");
throw std::runtime_error("App init fail");
}
m_renderer = std::make_unique<VulkanRenderer>(m_window, m_logger);
init_input();
mouse_captured(true);
m_logger.info("App init done!");
m_renderer->set_antialiasing(VulkanRenderer::AntiAliasingKind::MSAA_4X);
m_cursor = PolarCoordinate::from_vec3(m_camera.target - m_camera.position);
}
Application::~Application()
{
m_renderer.reset();
shutdown_input();
SDL_DestroyWindow(m_window);
SDL_Quit();
m_logger.info("App destroy done!");
}
auto Application::run() -> void
{
SDL_Event e;
ImGuiIO &io = ImGui::GetIO();
io.IniFilename = nullptr;
uint64_t last { 0 };
float fps { 0.0f };
while (m_running) {
#if defined(TRACY_ENABLE)
ZoneScopedN("Frame");
#endif
uint64_t now { SDL_GetTicks() };
uint64_t dt { now - last };
float dt_seconds { static_cast<float>(dt) / 1000.0f };
last = now;
if (dt > 0)
fps = 1000.0f / (float)dt;
process_libinput_events();
while (SDL_PollEvent(&e)) {
bool forward_to_imgui { false };
if (e.type == SDL_EVENT_QUIT) {
m_running = false;
} else if (e.type == SDL_EVENT_WINDOW_RESIZED) {
int width {}, height {};
SDL_GetWindowSize(m_window, &width, &height);
m_renderer->resize(static_cast<uint32_t>(width),
static_cast<uint32_t>(height));
clamp_mouse_to_window(width, height);
forward_to_imgui = true;
} else if (e.type == SDL_EVENT_MOUSE_MOTION) {
m_mouse_x = e.motion.x;
m_mouse_y = e.motion.y;
m_mouse_dx = e.motion.xrel;
m_mouse_dy = e.motion.yrel;
forward_to_imgui = true;
} else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN
|| e.type == SDL_EVENT_MOUSE_BUTTON_UP) {
m_mouse_x = e.button.x;
m_mouse_y = e.button.y;
forward_to_imgui = true;
} else if (e.type == SDL_EVENT_MOUSE_WHEEL) {
m_mouse_x = e.wheel.mouse_x;
m_mouse_y = e.wheel.mouse_y;
forward_to_imgui = true;
}
if (forward_to_imgui)
ImGui_ImplSDL3_ProcessEvent(&e);
}
auto const target_offset { m_camera.target - m_camera.position };
auto const target_distance { target_offset.magnitude() };
auto const target_polar { PolarCoordinate::from_vec3(target_offset) };
// Keep cursor angles in sync with externally-updated targets so view
// aligns to the target direction. Preserve radius when the target sits
// on top of the camera to avoid collapsing it.
if (target_distance > 0.0f) {
m_cursor.r = target_distance;
m_cursor.theta = target_polar.theta;
m_cursor.phi = target_polar.phi;
}
bool rotated_this_frame { false };
if (mouse_captured()) {
constexpr float phi_epsilon { smath::deg(5.0f) };
m_cursor.theta
+= static_cast<float>(m_mouse_dx) * m_mouse_sensitivity;
m_cursor.phi
+= static_cast<float>(m_mouse_dy) * m_mouse_sensitivity;
m_cursor.phi = std::clamp(m_cursor.phi, phi_epsilon,
std::numbers::pi_v<float> - phi_epsilon);
rotated_this_frame = (m_mouse_dx != 0.0 || m_mouse_dy != 0.0);
}
auto look_dir { m_cursor.to_vec3().normalized_safe() };
if (!rotated_this_frame && target_distance > 0.0f) {
look_dir = target_offset.normalized_safe();
}
if (look_dir.magnitude() == 0.0f)
look_dir = smath::Vec3 { 0.0f, 0.0f, -1.0f };
smath::Vec3 const world_up { 0.0f, 1.0f, 0.0f };
auto right { look_dir.cross(world_up).normalized_safe() };
if (right.magnitude() == 0.0f)
right = smath::Vec3 { 1.0f, 0.0f, 0.0f };
auto camera_up { right.cross(look_dir).normalized_safe() };
if (camera_up.magnitude() == 0.0f)
camera_up = world_up;
smath::Vec3 move_dir {};
if (is_key_pressed(KEY_W))
move_dir += look_dir;
if (is_key_pressed(KEY_S))
move_dir -= look_dir;
if (is_key_pressed(KEY_D))
move_dir += right;
if (is_key_pressed(KEY_A))
move_dir -= right;
if (is_key_pressed(KEY_SPACE))
move_dir += world_up;
if (is_key_pressed(KEY_LEFTSHIFT))
move_dir -= world_up;
if (move_dir.magnitude() > 0.0f) {
constexpr float move_speed { 10.0f };
move_dir = move_dir.normalized_safe();
m_camera.position += move_dir * (move_speed * dt_seconds);
}
if (!m_show_imgui) {
m_camera.up = camera_up;
auto const distance = target_distance > 0.0f
? target_distance
: std::max(1.0f, m_cursor.r);
m_camera.target = m_camera.position + look_dir * distance;
}
m_mouse_dx = 0.0;
m_mouse_dy = 0.0;
ImGui_ImplSDL3_NewFrame();
ImGui_ImplVulkan_NewFrame();
ImGui::NewFrame();
ImGui::SetNextWindowSize({ 300, 100 });
ImGui::SetNextWindowPos({ 0, 0 });
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4 { 0, 0, 0, 0.5f });
bool debug_open { ImGui::Begin("Debug Info", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize) };
if (debug_open) {
ImGui::Text("%s", std::format("FPS: {:.2f}", fps).c_str());
ImGui::Text("%s",
std::format("Cam pos: ({:.2f}, {:.2f}, {:.2f})",
m_camera.position.x(), m_camera.position.y(),
m_camera.position.z())
.c_str());
ImGui::Text("%s",
std::format("Cam tgt: ({:.2f}, {:.2f}, {:.2f})",
m_camera.target.x(), m_camera.target.y(),
m_camera.target.z())
.c_str());
ImGui::Text("%s",
std::format("Cam up: ({:.2f}, {:.2f}, {:.2f})",
m_camera.up.x(), m_camera.up.y(), m_camera.up.z())
.c_str());
ImGui::Text("%s",
std::format("Cursor r/theta/phi: {:.2f}, {:.2f}, {:.2f}",
m_cursor.r, m_cursor.theta, m_cursor.phi)
.c_str());
}
ImGui::End();
ImGui::PopStyleColor();
if (m_show_imgui) {
ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once);
ImGui::ShowDemoWindow();
ImGui::SetNextWindowSize({ 300, -1 }, ImGuiCond_Once);
bool fun_menu_open { ImGui::Begin("Fun menu") };
if (fun_menu_open) {
static std::array<char const *, 4> const aa_items {
"None",
"MSAA 2X",
"MSAA 4X",
"MSAA 8X",
};
int selected_item {
static_cast<int>(m_renderer->antialiasing()),
};
if (ImGui::Combo("Antialiasing", &selected_item,
aa_items.data(), aa_items.size())) {
m_renderer->set_antialiasing(
static_cast<VulkanRenderer::AntiAliasingKind>(
selected_item));
}
if (ImGui::CollapsingHeader("Camera",
ImGuiTreeNodeFlags_Framed
| ImGuiTreeNodeFlags_SpanAvailWidth
| ImGuiTreeNodeFlags_DefaultOpen)) {
auto const camera_offset { m_camera.target
- m_camera.position };
auto const camera_distance { camera_offset.magnitude() };
auto const camera_direction {
camera_offset.normalized_safe()
};
ImGui::SliderFloat("Mouse sensitivity",
&m_mouse_sensitivity, 0.0001f, 0.01f, "%.4f");
constexpr float position_step { 0.05f };
constexpr float target_step { 0.05f };
bool position_changed { ImGui::DragFloat3(
"Pos", m_camera.position.data(), position_step) };
bool target_changed { ImGui::DragFloat3(
"Target", m_camera.target.data(), target_step) };
ImGui::DragFloat3("Up", m_camera.up.data());
if (position_changed && !target_changed) {
auto offset { m_cursor.to_vec3() };
auto const preserve_distance { camera_distance > 0.0f
? camera_distance
: offset.magnitude() };
if (offset.magnitude() == 0.0f
&& camera_direction.magnitude() > 0.0f) {
offset = camera_direction * preserve_distance;
}
if (offset.magnitude() > 0.0f) {
m_camera.target = m_camera.position + offset;
}
}
if (target_changed && !position_changed) {
auto const new_offset { m_camera.target
- m_camera.position };
auto new_direction { new_offset.normalized_safe() };
auto const preserve_distance { camera_distance > 0.0f
? camera_distance
: new_offset.magnitude() };
if (new_direction.magnitude() == 0.0f) {
new_direction = camera_direction;
}
if (new_direction.magnitude() > 0.0f
&& preserve_distance > 0.0f) {
m_camera.position = m_camera.target
- new_direction * preserve_distance;
}
}
}
}
ImGui::End();
}
ImGui::Render();
m_renderer->render([&](VulkanRenderer::GL &gl) {
#if defined(TRACY_ENABLE)
ZoneScopedN("Render");
#endif
auto view { smath::matrix_look_at(
m_camera.position, m_camera.target, m_camera.up) };
auto const draw_extent = m_renderer->draw_extent();
auto const aspect = draw_extent.height == 0
? 1.0f
: static_cast<float>(draw_extent.width)
/ static_cast<float>(draw_extent.height);
auto projection { smath::matrix_perspective(
m_camera.fovy, aspect, 0.1f, 10000.0f) };
projection[1][1] *= -1;
auto view_projection { projection * view };
gl.set_transform(view_projection);
gl.set_texture();
auto const &meshes { m_renderer->test_meshes() };
if (meshes.size() > 2 && !meshes[2]->surfaces.empty()) {
auto const &surface = meshes[2]->surfaces[0];
gl.draw_mesh(meshes[2]->mesh_buffers,
view_projection
* smath::translate(smath::Vec3 { 0.0f, 0.0f, -5.0f }),
surface.count, surface.start_index);
}
gl.push_transform();
gl.set_transform(view_projection
* smath::translate(smath::Vec3 { 0.0f, 0.0f, 5.0f })
* smath::Quaternion<float>::from_axis_angle(
smath::Vec3 { 0.0f, 1.0f, 0.0f }, smath::deg(180))
.as_matrix());
gl.set_texture(&m_renderer->white_texture());
gl.begin(VulkanRenderer::GL::GeometryKind::Quads);
gl.color(smath::Vec3 { 0.0f, 0.0f, 0.0f });
gl.uv(smath::Vec2 { 1.0f, 1.0f });
gl.vert(smath::Vec3 { 0.5f, -0.5f, 0.0f });
gl.color(smath::Vec3 { 0.5f, 0.5f, 0.5f });
gl.uv(smath::Vec2 { 1.0f, 0.0f });
gl.vert(smath::Vec3 { 0.5f, 0.5f, 0.0f });
gl.color(smath::Vec3 { 1.0f, 0.0f, 0.0f });
gl.uv(smath::Vec2 { 0.0f, 1.0f });
gl.vert(smath::Vec3 { -0.5f, -0.5f, 0.0f });
gl.color(smath::Vec3 { 0.0f, 1.0f, 0.0f });
gl.uv(smath::Vec2 { 0.0f, 0.0f });
gl.vert(smath::Vec3 { -0.5f, 0.5f, 0.0f });
gl.end();
gl.draw_rectangle({ -0.5f, 0.5f }, { 0.5f, 0.5f });
gl.draw_rectangle(
{ 0, 0.5f }, { 0.5f, 0.5f }, { Colors::TEAL, 1.0f });
gl.set_transform(view_projection);
gl.draw_sphere(m_camera.target, 0.01f);
});
#if defined(TRACY_ENABLE)
FrameMark;
#endif
}
}
auto Application::init_input() -> void
{
m_udev = udev_new();
if (!m_udev) {
m_logger.err("Failed to create udev context");
throw std::runtime_error("App init fail");
}
m_libinput
= libinput_udev_create_context(&g_libinput_interface, this, m_udev);
if (!m_libinput) {
m_logger.err("Failed to create libinput context");
shutdown_input();
throw std::runtime_error("App init fail");
}
if (libinput_udev_assign_seat(m_libinput, "seat0") != 0) {
m_logger.err("Failed to assign libinput seat");
shutdown_input();
throw std::runtime_error("App init fail");
}
int width {}, height {};
SDL_GetWindowSize(m_window, &width, &height);
float mouse_x {}, mouse_y {};
SDL_GetMouseState(&mouse_x, &mouse_y);
m_mouse_x = mouse_x;
m_mouse_y = mouse_y;
ImGui::GetIO().AddMousePosEvent(
static_cast<float>(m_mouse_x), static_cast<float>(m_mouse_y));
}
auto Application::shutdown_input() -> void
{
if (m_libinput) {
libinput_unref(m_libinput);
m_libinput = nullptr;
}
if (m_udev) {
udev_unref(m_udev);
m_udev = nullptr;
}
}
auto Application::process_libinput_events() -> void
{
if (!m_libinput)
return;
if (int const rc { libinput_dispatch(m_libinput) }; rc != 0) {
m_logger.err("libinput_dispatch failed ({})", rc);
return;
}
for (libinput_event *event { libinput_get_event(m_libinput) };
event != nullptr; event = libinput_get_event(m_libinput)) {
switch (libinput_event_get_type(event)) {
case LIBINPUT_EVENT_KEYBOARD_KEY:
handle_keyboard_event(libinput_event_get_keyboard_event(event));
break;
default:
break;
}
libinput_event_destroy(event);
}
}
auto Application::handle_keyboard_event(libinput_event_keyboard *event) -> void
{
uint32_t const key { libinput_event_keyboard_get_key(event) };
auto const state { libinput_event_keyboard_get_key_state(event) };
bool const pressed { state == LIBINPUT_KEY_STATE_PRESSED };
if (key == KEY_LEFTCTRL || key == KEY_RIGHTCTRL) {
if (pressed) {
++m_ctrl_pressed_count;
} else if (m_ctrl_pressed_count > 0) {
--m_ctrl_pressed_count;
}
}
if (pressed && key == KEY_F11 && m_ctrl_pressed_count > 0) {
bool const new_show_imgui { !m_show_imgui };
m_show_imgui = new_show_imgui;
mouse_captured(!new_show_imgui);
}
if (auto imgui_key { linux_key_to_imgui(key) }) {
if (m_show_imgui)
ImGui::GetIO().AddKeyEvent(*imgui_key, pressed);
}
if (m_show_imgui && pressed) {
bool const shift_pressed { is_key_pressed(KEY_LEFTSHIFT)
|| is_key_pressed(KEY_RIGHTSHIFT)
|| (key == KEY_LEFTSHIFT && pressed)
|| (key == KEY_RIGHTSHIFT && pressed) };
if (auto ch { linux_key_to_char(key, shift_pressed) })
ImGui::GetIO().AddInputCharacter(*ch);
}
if (key < m_key_state.size())
m_key_state[key] = pressed;
}
auto Application::clamp_mouse_to_window(int width, int height) -> void
{
double const max_x { std::max(0.0, static_cast<double>(width - 1)) };
double const max_y { std::max(0.0, static_cast<double>(height - 1)) };
m_mouse_x = std::clamp(m_mouse_x, 0.0, max_x);
m_mouse_y = std::clamp(m_mouse_y, 0.0, max_y);
ImGui::GetIO().AddMousePosEvent(
static_cast<float>(m_mouse_x), static_cast<float>(m_mouse_y));
}
auto Application::mouse_captured(bool new_state) -> void
{
if (!SDL_SetWindowRelativeMouseMode(m_window, new_state)) {
m_logger.err("Failed to capture mouse");
return;
}
m_mouse_captured = new_state && !m_show_imgui;
}
auto Application::is_key_pressed(uint32_t key) const -> bool
{
if (key >= m_key_state.size())
return false;
return m_key_state[key];
}
} // namespace Lunar