feat: Add window class

This commit is contained in:
2026-10-17 16:25:09 +02:00
parent 67c685a158
commit 89a168f76d
9 changed files with 513 additions and 36 deletions
+1
View File
@@ -34,6 +34,7 @@ target_compile_definitions(destrum_assets
set(SRC_FILES set(SRC_FILES
"src/App.cpp" "src/App.cpp"
"src/Event.cpp" "src/Event.cpp"
"src/Window.cpp"
"src/Components/Animator.cpp" "src/Components/Animator.cpp"
"src/Components/CameraComponent.cpp" "src/Components/CameraComponent.cpp"
+4 -1
View File
@@ -8,6 +8,7 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <destrum/Window.h>
#include <destrum/Graphics/GfxDevice.h> #include <destrum/Graphics/GfxDevice.h>
#include <destrum/Graphics/Renderer.h> #include <destrum/Graphics/Renderer.h>
#include <destrum/Graphics/Pipelines/ImguiPass.h> #include <destrum/Graphics/Pipelines/ImguiPass.h>
@@ -25,6 +26,8 @@ public:
std::string windowTitle; std::string windowTitle;
std::filesystem::path exeDir; std::filesystem::path exeDir;
bool showDebugUi{true}; bool showDebugUi{true};
WindowMode windowMode{WindowMode::Windowed};
bool startMaximized{false};
}; };
App(); App();
@@ -46,7 +49,7 @@ public:
virtual void customWindowsMenu() {} virtual void customWindowsMenu() {}
protected: protected:
SDL_Window* window{nullptr}; Window window;
AppParams m_params{}; AppParams m_params{};
GfxDevice gfxDevice; GfxDevice gfxDevice;
+98
View File
@@ -0,0 +1,98 @@
#ifndef DESTRUM_WINDOW_H
#define DESTRUM_WINDOW_H
#include <cstdint>
#include <string>
#include <string_view>
#include <glm/vec2.hpp>
#include <SDL2/SDL.h>
enum class WindowMode {
Windowed,
Fullscreen,
BorderlessFullscreen,
};
struct WindowDesc {
std::string title{"Destrum"};
glm::ivec2 size{1280, 720};
glm::ivec2 minimumSize{320, 180};
WindowMode mode{WindowMode::Windowed};
bool resizable{true};
bool maximized{false};
};
class Window final {
public:
Window() = default;
~Window();
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
Window(Window&&) = delete;
Window& operator=(Window&&) = delete;
void Create(const WindowDesc& desc);
void Destroy();
void ProcessEvent(const SDL_Event& event);
[[nodiscard]] bool ConsumeResized();
[[nodiscard]] bool ConsumeCloseRequested();
[[nodiscard]] bool IsCreated() const { return m_Handle != nullptr; }
[[nodiscard]] SDL_Window* GetHandle() const { return m_Handle; }
[[nodiscard]] std::uint32_t GetId() const;
void SetTitle(std::string_view title);
[[nodiscard]] std::string GetTitle() const;
void SetSize(const glm::ivec2& size);
[[nodiscard]] glm::ivec2 GetSize() const;
[[nodiscard]] glm::ivec2 GetDrawableSize() const;
[[nodiscard]] float GetAspectRatio() const;
void SetMinimumSize(const glm::ivec2& size);
void SetMaximumSize(const glm::ivec2& size);
void SetPosition(const glm::ivec2& position);
[[nodiscard]] glm::ivec2 GetPosition() const;
void Center();
void Maximize();
void Minimize();
void Restore();
void ToggleMaximized();
[[nodiscard]] bool IsMaximized() const;
[[nodiscard]] bool IsMinimized() const;
void SetMode(WindowMode mode);
[[nodiscard]] WindowMode GetMode() const;
void ToggleFullscreen();
[[nodiscard]] bool IsFullscreen() const { return GetMode() != WindowMode::Windowed; }
void SetResizable(bool resizable);
[[nodiscard]] bool IsResizable() const;
void SetBordered(bool bordered);
[[nodiscard]] bool IsBordered() const;
void SetAlwaysOnTop(bool alwaysOnTop);
void Show();
void Hide();
void Focus();
[[nodiscard]] bool IsVisible() const;
[[nodiscard]] bool HasFocus() const;
[[nodiscard]] int GetDisplayIndex() const;
[[nodiscard]] glm::ivec2 GetDisplaySize() const;
private:
[[nodiscard]] std::uint32_t getFlags() const;
[[nodiscard]] SDL_Window* requireHandle() const;
SDL_Window* m_Handle{nullptr};
bool m_Resized{false};
bool m_CloseRequested{false};
};
#endif // DESTRUM_WINDOW_H
+18 -30
View File
@@ -1,7 +1,6 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <exception> #include <exception>
#include <SDL_vulkan.h>
#include <thread> #include <thread>
#include <stdexcept> #include <stdexcept>
#include <destrum/App.h> #include <destrum/App.h>
@@ -52,23 +51,15 @@ void App::init(const AppParams& params)
TracySetProgramName(params.appName.c_str()); TracySetProgramName(params.appName.c_str());
AssetFS::GetInstance().Init(params.exeDir); AssetFS::GetInstance().Init(params.exeDir);
window = SDL_CreateWindow( window.Create({
params.windowTitle.c_str(), .title = params.windowTitle,
SDL_WINDOWPOS_UNDEFINED, .size = params.windowSize,
SDL_WINDOWPOS_UNDEFINED, .mode = params.windowMode,
params.windowSize.x, .maximized = params.startMaximized,
params.windowSize.y, });
SDL_WINDOW_VULKAN);
if (!window) { gfxDevice.init(window.GetHandle(), params.appName, false);
spdlog::error("Failed to create window. SDL Error: {}", SDL_GetError()); imguiPass.init(window.GetHandle(), gfxDevice);
throw std::runtime_error(
"Failed to create window: " + std::string{SDL_GetError()});
}
SDL_SetWindowResizable(window, SDL_TRUE);
gfxDevice.init(window, params.appName, false);
imguiPass.init(window, gfxDevice);
InputManager::GetInstance().Init(); InputManager::GetInstance().Init();
Time::GetInstance().Update(); Time::GetInstance().Update();
@@ -144,16 +135,16 @@ void App::run()
break; break;
} }
if (event.type == SDL_WINDOWEVENT) window.ProcessEvent(event);
if (window.ConsumeResized())
{ {
switch (event.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
resizePending = true; resizePending = true;
lastResizeTime = std::chrono::steady_clock::now(); lastResizeTime = std::chrono::steady_clock::now();
break;
} }
if (window.ConsumeCloseRequested())
{
isRunning = false;
break;
} }
const bool keyboardEvent = const bool keyboardEvent =
@@ -290,9 +281,9 @@ if (!isRunning) break;
continue; continue;
} }
int w = 0; const glm::ivec2 drawableSize = window.GetDrawableSize();
int h = 0; const int w = drawableSize.x;
SDL_Vulkan_GetDrawableSize(window, &w, &h); const int h = drawableSize.y;
if (w == 0 || h == 0) if (w == 0 || h == 0)
{ {
@@ -362,10 +353,7 @@ void App::cleanup()
attempt([this] { resources.cleanup(gfxDevice); }); attempt([this] { resources.cleanup(gfxDevice); });
attempt([this] { gfxDevice.cleanup(); }); attempt([this] { gfxDevice.cleanup(); });
if (window) { window.Destroy();
SDL_DestroyWindow(window);
window = nullptr;
}
AssetFS::GetInstance().Reset(); AssetFS::GetInstance().Reset();
customInitStarted = false; customInitStarted = false;
+310
View File
@@ -0,0 +1,310 @@
#include <destrum/Window.h>
#include <algorithm>
#include <stdexcept>
#include <SDL_vulkan.h>
#include <spdlog/spdlog.h>
namespace {
std::uint32_t modeFlags(WindowMode mode)
{
switch (mode) {
case WindowMode::Fullscreen:
return SDL_WINDOW_FULLSCREEN;
case WindowMode::BorderlessFullscreen:
return SDL_WINDOW_FULLSCREEN_DESKTOP;
case WindowMode::Windowed:
break;
}
return 0;
}
}
Window::~Window()
{
Destroy();
}
void Window::Create(const WindowDesc& desc)
{
if (m_Handle) {
throw std::logic_error("Window::Create called on a window that already exists");
}
std::uint32_t flags = SDL_WINDOW_VULKAN | modeFlags(desc.mode);
if (desc.resizable) {
flags |= SDL_WINDOW_RESIZABLE;
}
if (desc.maximized) {
flags |= SDL_WINDOW_MAXIMIZED;
}
m_Handle = SDL_CreateWindow(
desc.title.c_str(),
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
desc.size.x,
desc.size.y,
flags);
if (!m_Handle) {
throw std::runtime_error("Failed to create window: " + std::string{SDL_GetError()});
}
SetMinimumSize(desc.minimumSize);
m_Resized = false;
m_CloseRequested = false;
}
void Window::Destroy()
{
if (m_Handle) {
SDL_DestroyWindow(m_Handle);
m_Handle = nullptr;
}
}
void Window::ProcessEvent(const SDL_Event& event)
{
if (!m_Handle || event.type != SDL_WINDOWEVENT || event.window.windowID != GetId()) {
return;
}
switch (event.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_MAXIMIZED:
case SDL_WINDOWEVENT_RESTORED:
m_Resized = true;
break;
case SDL_WINDOWEVENT_CLOSE:
m_CloseRequested = true;
break;
default:
break;
}
}
bool Window::ConsumeResized()
{
const bool resized = m_Resized;
m_Resized = false;
return resized;
}
bool Window::ConsumeCloseRequested()
{
const bool closeRequested = m_CloseRequested;
m_CloseRequested = false;
return closeRequested;
}
std::uint32_t Window::GetId() const
{
return SDL_GetWindowID(requireHandle());
}
void Window::SetTitle(std::string_view title)
{
SDL_SetWindowTitle(requireHandle(), std::string{title}.c_str());
}
std::string Window::GetTitle() const
{
return SDL_GetWindowTitle(requireHandle());
}
void Window::SetSize(const glm::ivec2& size)
{
SDL_SetWindowSize(requireHandle(), size.x, size.y);
}
glm::ivec2 Window::GetSize() const
{
glm::ivec2 size{0};
SDL_GetWindowSize(requireHandle(), &size.x, &size.y);
return size;
}
glm::ivec2 Window::GetDrawableSize() const
{
glm::ivec2 size{0};
SDL_Vulkan_GetDrawableSize(requireHandle(), &size.x, &size.y);
return size;
}
float Window::GetAspectRatio() const
{
const glm::ivec2 size = GetDrawableSize();
return size.y > 0 ? static_cast<float>(size.x) / static_cast<float>(size.y) : 1.0f;
}
void Window::SetMinimumSize(const glm::ivec2& size)
{
SDL_SetWindowMinimumSize(requireHandle(), size.x, size.y);
}
void Window::SetMaximumSize(const glm::ivec2& size)
{
SDL_SetWindowMaximumSize(requireHandle(), size.x, size.y);
}
void Window::SetPosition(const glm::ivec2& position)
{
SDL_SetWindowPosition(requireHandle(), position.x, position.y);
}
glm::ivec2 Window::GetPosition() const
{
glm::ivec2 position{0};
SDL_GetWindowPosition(requireHandle(), &position.x, &position.y);
return position;
}
void Window::Center()
{
const int display = GetDisplayIndex();
SDL_SetWindowPosition(
requireHandle(),
static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(display)),
static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(display)));
}
void Window::Maximize()
{
SDL_MaximizeWindow(requireHandle());
}
void Window::Minimize()
{
SDL_MinimizeWindow(requireHandle());
}
void Window::Restore()
{
SDL_RestoreWindow(requireHandle());
}
void Window::ToggleMaximized()
{
if (IsMaximized()) {
Restore();
} else {
Maximize();
}
}
bool Window::IsMaximized() const
{
return (getFlags() & SDL_WINDOW_MAXIMIZED) != 0;
}
bool Window::IsMinimized() const
{
return (getFlags() & SDL_WINDOW_MINIMIZED) != 0;
}
void Window::SetMode(WindowMode mode)
{
if (SDL_SetWindowFullscreen(requireHandle(), modeFlags(mode)) != 0) {
spdlog::warn("Failed to change the window mode: {}", SDL_GetError());
return;
}
m_Resized = true;
}
WindowMode Window::GetMode() const
{
const std::uint32_t flags = getFlags();
if ((flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
return WindowMode::BorderlessFullscreen;
}
if ((flags & SDL_WINDOW_FULLSCREEN) != 0) {
return WindowMode::Fullscreen;
}
return WindowMode::Windowed;
}
void Window::ToggleFullscreen()
{
SetMode(IsFullscreen() ? WindowMode::Windowed : WindowMode::BorderlessFullscreen);
}
void Window::SetResizable(bool resizable)
{
SDL_SetWindowResizable(requireHandle(), resizable ? SDL_TRUE : SDL_FALSE);
}
bool Window::IsResizable() const
{
return (getFlags() & SDL_WINDOW_RESIZABLE) != 0;
}
void Window::SetBordered(bool bordered)
{
SDL_SetWindowBordered(requireHandle(), bordered ? SDL_TRUE : SDL_FALSE);
}
bool Window::IsBordered() const
{
return (getFlags() & SDL_WINDOW_BORDERLESS) == 0;
}
void Window::SetAlwaysOnTop(bool alwaysOnTop)
{
SDL_SetWindowAlwaysOnTop(requireHandle(), alwaysOnTop ? SDL_TRUE : SDL_FALSE);
}
void Window::Show()
{
SDL_ShowWindow(requireHandle());
}
void Window::Hide()
{
SDL_HideWindow(requireHandle());
}
void Window::Focus()
{
SDL_RaiseWindow(requireHandle());
}
bool Window::IsVisible() const
{
return (getFlags() & SDL_WINDOW_SHOWN) != 0 && (getFlags() & SDL_WINDOW_HIDDEN) == 0;
}
bool Window::HasFocus() const
{
return (getFlags() & SDL_WINDOW_INPUT_FOCUS) != 0;
}
int Window::GetDisplayIndex() const
{
return std::max(SDL_GetWindowDisplayIndex(requireHandle()), 0);
}
glm::ivec2 Window::GetDisplaySize() const
{
SDL_DisplayMode displayMode{};
if (SDL_GetDesktopDisplayMode(GetDisplayIndex(), &displayMode) != 0) {
return GetSize();
}
return {displayMode.w, displayMode.h};
}
std::uint32_t Window::getFlags() const
{
return m_Handle ? SDL_GetWindowFlags(m_Handle) : 0;
}
SDL_Window* Window::requireHandle() const
{
if (!m_Handle) {
throw std::logic_error("Window used before Create or after Destroy");
}
return m_Handle;
}
+1
View File
@@ -37,6 +37,7 @@ private:
// LightkeeperDebugUi.cpp // LightkeeperDebugUi.cpp
void updateDebugUi(); void updateDebugUi();
void drawDebugMenuBar(); void drawDebugMenuBar();
void drawWindowMenu();
void drawTestWindow(); void drawTestWindow();
void spawnBall(); void spawnBall();
void saveCurrentScene(); void saveCurrentScene();
+5
View File
@@ -68,6 +68,11 @@ void LightKeeper::customUpdate(float dt)
{ {
camera.Update(dt); camera.Update(dt);
} }
if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_F11))
{
window.ToggleFullscreen();
}
SceneManager::GetInstance().Update(dt); SceneManager::GetInstance().Update(dt);
SceneManager::GetInstance().LateUpdate(dt); SceneManager::GetInstance().LateUpdate(dt);
+72
View File
@@ -158,9 +158,81 @@ void LightKeeper::drawDebugMenuBar()
ImGui::EndMenu(); ImGui::EndMenu();
} }
drawWindowMenu();
ImGui::EndMainMenuBar(); ImGui::EndMainMenuBar();
} }
void LightKeeper::drawWindowMenu()
{
if (!ImGui::BeginMenu("Window")) {
return;
}
const WindowMode mode = window.GetMode();
const bool fullscreen = window.IsFullscreen();
const bool maximized = window.IsMaximized();
if (ImGui::MenuItem("Fullscreen", "F11", fullscreen)) {
window.ToggleFullscreen();
}
if (ImGui::BeginMenu("Mode")) {
if (ImGui::MenuItem("Windowed", nullptr, mode == WindowMode::Windowed)) {
window.SetMode(WindowMode::Windowed);
}
if (ImGui::MenuItem("Borderless Fullscreen", nullptr, mode == WindowMode::BorderlessFullscreen)) {
window.SetMode(WindowMode::BorderlessFullscreen);
}
if (ImGui::MenuItem("Exclusive Fullscreen", nullptr, mode == WindowMode::Fullscreen)) {
window.SetMode(WindowMode::Fullscreen);
}
ImGui::EndMenu();
}
ImGui::Separator();
if (ImGui::MenuItem(maximized ? "Restore" : "Maximize", nullptr, false, !fullscreen)) {
window.ToggleMaximized();
}
if (ImGui::MenuItem("Minimize", nullptr, false, !fullscreen)) {
window.Minimize();
}
if (ImGui::MenuItem("Center", nullptr, false, !fullscreen && !maximized)) {
window.Center();
}
if (ImGui::BeginMenu("Size", !fullscreen)) {
const glm::ivec2 sizes[] = {glm::ivec2{1280, 720}, glm::ivec2{1600, 900}, glm::ivec2{1920, 1080}};
for (const glm::ivec2& size : sizes) {
const std::string label = std::to_string(size.x) + " x " + std::to_string(size.y);
if (ImGui::MenuItem(label.c_str(), nullptr, window.GetSize() == size)) {
if (maximized) {
window.Restore();
}
window.SetSize(size);
window.Center();
}
}
ImGui::EndMenu();
}
ImGui::Separator();
if (bool resizable = window.IsResizable(); ImGui::MenuItem("Resizable", nullptr, &resizable, !fullscreen)) {
window.SetResizable(resizable);
}
if (bool bordered = window.IsBordered(); ImGui::MenuItem("Bordered", nullptr, &bordered, !fullscreen)) {
window.SetBordered(bordered);
}
ImGui::Separator();
const glm::ivec2 drawableSize = window.GetDrawableSize();
ImGui::TextDisabled("Drawable: %d x %d", drawableSize.x, drawableSize.y);
ImGui::EndMenu();
}
void LightKeeper::saveCurrentScene() void LightKeeper::saveCurrentScene()
{ {
try { try {
@@ -43,8 +43,7 @@ void InteractableButton::Interact(GameObject& interactor)
void InteractableButton::ApplyColor() void InteractableButton::ApplyColor()
{ {
const auto* meshRenderer = GetGameObject()->GetComponent<MeshRendererComponent>(); const auto* meshRenderer = GetGameObject()->GetComponent<MeshRendererComponent>();
if (meshRenderer == nullptr || meshRenderer->GetMaterialID() == NULL_MATERIAL_ID || if (meshRenderer == nullptr || meshRenderer->GetMaterialID() == NULL_MATERIAL_ID || !GameState::GetInstance().HasRenderer()) {
!GameState::GetInstance().HasRenderer()) {
return; return;
} }