From da6bda7ca7270d7c6c85323d525a9683f6b7e284 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sat, 19 Sep 2026 19:41:39 -0700 Subject: [PATCH 1/7] Supporting setting panel layout --- editor/main.cpp | 8 +++ editor/panels/asset_browser_panel.cpp | 4 -- editor/panels/asset_browser_panel.hpp | 2 +- editor/panels/inspector_panel.cpp | 4 -- editor/panels/inspector_panel.hpp | 2 +- editor/panels/scene_hierarchy_panel.cpp | 2 - editor/panels/scene_hierarchy_panel.hpp | 2 +- editor/panels/viewport_panel.cpp | 45 +++++++++++++ editor/panels/viewport_panel.hpp | 17 +++++ engine/include/sophia/ui/dock_layout.hpp | 23 +++++++ engine/include/sophia/ui/panel.hpp | 28 ++++++-- engine/include/sophia/ui/panel_registry.hpp | 7 ++ engine/src/application.cpp | 9 +++ engine/src/imgui_renderer.cpp | 67 ++++++++++++++++++- engine/src/imgui_renderer.hpp | 16 +++++ engine/src/ui/panel.cpp | 35 ++++++++++ engine/src/ui/panel_registry.cpp | 2 +- engine/src/ui/panels/console_panel.cpp | 4 -- engine/src/ui/panels/console_panel.hpp | 2 +- engine/src/ui/panels/ecs_inspector_panel.cpp | 4 -- engine/src/ui/panels/ecs_inspector_panel.hpp | 2 +- engine/src/ui/panels/frame_stats_panel.hpp | 4 +- engine/src/ui/panels/panel_registry_panel.cpp | 3 - engine/src/ui/panels/panel_registry_panel.hpp | 2 +- 24 files changed, 257 insertions(+), 37 deletions(-) create mode 100644 editor/panels/viewport_panel.cpp create mode 100644 editor/panels/viewport_panel.hpp create mode 100644 engine/include/sophia/ui/dock_layout.hpp create mode 100644 engine/src/ui/panel.cpp diff --git a/editor/main.cpp b/editor/main.cpp index 58d939c..bb66b1f 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -4,6 +4,7 @@ #include "panels/asset_browser_panel.hpp" #include "panels/inspector_panel.hpp" #include "panels/scene_hierarchy_panel.hpp" +#include "panels/viewport_panel.hpp" class Editor : public sophia::Application { public: @@ -17,6 +18,13 @@ class Editor : public sophia::Application { panels().emplace(); panels().emplace(); panels().emplace(); + panels().emplace(); + + panels().setDefaultLayout( + {.left = {"Scene Hierarchy"}, + .right = {"Inspector", "Frame Stats", "PanelRegistry"}, + .bottom = {"Asset Browser", "Console"}, + .center = {"Viewport"}}); } void onUpdate(const sophia::FrameInfo& frameInfo) override {} diff --git a/editor/panels/asset_browser_panel.cpp b/editor/panels/asset_browser_panel.cpp index 5371e4f..426a462 100644 --- a/editor/panels/asset_browser_panel.cpp +++ b/editor/panels/asset_browser_panel.cpp @@ -3,8 +3,6 @@ #include void AssetBrowserPanel::onUpdate(const sophia::FrameInfo&) { - ImGui::Begin("Asset Browser"); - ImGui::BeginChild("Folders", ImVec2(150, 0), true); for (int i = 0; i < static_cast(folders.size()); i++) { if (ImGui::Selectable(folders[static_cast(i)].name.c_str(), @@ -22,6 +20,4 @@ void AssetBrowserPanel::onUpdate(const sophia::FrameInfo&) { ImGui::Selectable(file.c_str()); } ImGui::EndChild(); - - ImGui::End(); } diff --git a/editor/panels/asset_browser_panel.hpp b/editor/panels/asset_browser_panel.hpp index fc9577f..23993ab 100644 --- a/editor/panels/asset_browser_panel.hpp +++ b/editor/panels/asset_browser_panel.hpp @@ -7,7 +7,7 @@ class AssetBrowserPanel : public sophia::Panel { public: - AssetBrowserPanel() : Panel{"Asset Browser"} {} + AssetBrowserPanel() : Panel{{.name = "Asset Browser"}} {} void onUpdate(const sophia::FrameInfo& frameInfo) override; diff --git a/editor/panels/inspector_panel.cpp b/editor/panels/inspector_panel.cpp index 0910b10..3fe5f83 100644 --- a/editor/panels/inspector_panel.cpp +++ b/editor/panels/inspector_panel.cpp @@ -5,8 +5,6 @@ #include void InspectorPanel::onUpdate(const sophia::FrameInfo&) { - ImGui::Begin("Inspector"); - char nameBuf[128]; std::snprintf(nameBuf, sizeof(nameBuf), "%s", name.c_str()); if (ImGui::InputText("Name", nameBuf, sizeof(nameBuf))) { @@ -31,6 +29,4 @@ void InspectorPanel::onUpdate(const sophia::FrameInfo&) { if (ImGui::CollapsingHeader("Script", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Text("%s", scriptName.c_str()); } - - ImGui::End(); } diff --git a/editor/panels/inspector_panel.hpp b/editor/panels/inspector_panel.hpp index a316315..3a080c1 100644 --- a/editor/panels/inspector_panel.hpp +++ b/editor/panels/inspector_panel.hpp @@ -7,7 +7,7 @@ class InspectorPanel : public sophia::Panel { public: - InspectorPanel() : Panel{"Inspector"} {} + InspectorPanel() : Panel{{.name = "Inspector"}} {} void onUpdate(const sophia::FrameInfo& frameInfo) override; diff --git a/editor/panels/scene_hierarchy_panel.cpp b/editor/panels/scene_hierarchy_panel.cpp index 14d663c..08cf3c4 100644 --- a/editor/panels/scene_hierarchy_panel.cpp +++ b/editor/panels/scene_hierarchy_panel.cpp @@ -26,9 +26,7 @@ void SceneHierarchyPanel::drawNode(const Node& node) { } void SceneHierarchyPanel::onUpdate(const sophia::FrameInfo&) { - ImGui::Begin("Scene Hierarchy"); for (const Node& node : nodes) { drawNode(node); } - ImGui::End(); } diff --git a/editor/panels/scene_hierarchy_panel.hpp b/editor/panels/scene_hierarchy_panel.hpp index 78d70c1..b4be4b4 100644 --- a/editor/panels/scene_hierarchy_panel.hpp +++ b/editor/panels/scene_hierarchy_panel.hpp @@ -7,7 +7,7 @@ class SceneHierarchyPanel : public sophia::Panel { public: - SceneHierarchyPanel() : Panel{"Scene Hierarchy"} {} + SceneHierarchyPanel() : Panel{{.name = "Scene Hierarchy"}} {} void onUpdate(const sophia::FrameInfo& frameInfo) override; diff --git a/editor/panels/viewport_panel.cpp b/editor/panels/viewport_panel.cpp new file mode 100644 index 0000000..ef14bed --- /dev/null +++ b/editor/panels/viewport_panel.cpp @@ -0,0 +1,45 @@ +#include "viewport_panel.hpp" + +#include + +void ViewportPanel::onUpdate(const sophia::FrameInfo&) { + const struct { + const char* label; + GizmoMode mode; + } modes[] = {{"Move", GizmoMode::Move}, + {"Rotate", GizmoMode::Rotate}, + {"Scale", GizmoMode::Scale}}; + + for (const auto& entry : modes) { + if (ImGui::RadioButton(entry.label, gizmoMode == entry.mode)) { + gizmoMode = entry.mode; + } + ImGui::SameLine(); + } + ImGui::Checkbox("Grid", &showGrid); + + ImGui::Separator(); + + const ImVec2 origin = ImGui::GetCursorScreenPos(); + const ImVec2 size = ImGui::GetContentRegionAvail(); + const ImVec2 end(origin.x + size.x, origin.y + size.y); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(origin, end, IM_COL32(24, 26, 32, 255)); + + if (showGrid) { + constexpr float GRID_STEP = 40.0f; + const ImU32 gridColor = IM_COL32(255, 255, 255, 18); + for (float x = GRID_STEP; x < size.x; x += GRID_STEP) { + drawList->AddLine(ImVec2(origin.x + x, origin.y), + ImVec2(origin.x + x, end.y), gridColor); + } + for (float y = GRID_STEP; y < size.y; y += GRID_STEP) { + drawList->AddLine(ImVec2(origin.x, origin.y + y), + ImVec2(end.x, origin.y + y), gridColor); + } + } + + ImGui::SetCursorScreenPos(ImVec2(origin.x + 8.0f, origin.y + 8.0f)); + ImGui::TextDisabled("%.0f x %.0f", size.x, size.y); +} diff --git a/editor/panels/viewport_panel.hpp b/editor/panels/viewport_panel.hpp new file mode 100644 index 0000000..b68205b --- /dev/null +++ b/editor/panels/viewport_panel.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +class ViewportPanel : public sophia::Panel { + public: + ViewportPanel() : Panel{{.name = "Viewport"}} {} + + void onUpdate(const sophia::FrameInfo& frameInfo) override; + + private: + enum class GizmoMode { Move, Rotate, Scale }; + + GizmoMode gizmoMode = GizmoMode::Move; + bool showGrid = true; +}; diff --git a/engine/include/sophia/ui/dock_layout.hpp b/engine/include/sophia/ui/dock_layout.hpp new file mode 100644 index 0000000..fce3fc7 --- /dev/null +++ b/engine/include/sophia/ui/dock_layout.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace sophia { + +struct DockLayout { + float leftWidth = 0.2f; + float rightWidth = 0.25f; + float bottomHeight = 0.25f; + + std::vector left; + std::vector right; + std::vector bottom; + std::vector center; + + bool empty() const { + return left.empty() && right.empty() && bottom.empty() && center.empty(); + } +}; + +} // namespace sophia diff --git a/engine/include/sophia/ui/panel.hpp b/engine/include/sophia/ui/panel.hpp index ef81a54..c46b8b5 100644 --- a/engine/include/sophia/ui/panel.hpp +++ b/engine/include/sophia/ui/panel.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -8,28 +10,46 @@ namespace sophia { class Panel { public: + struct Config { + std::string name; + + // Locked panels can't be moved, resized, docked, collapsed or closed. + bool locked = false; + bool closable = true; + + // A zero component lets ImGui size the window. Applied on first use, or + // every frame when locked. + glm::vec2 size{0.0f, 0.0f}; + std::optional position; + }; + Panel(const Panel&) = delete; Panel& operator=(const Panel&) = delete; Panel(Panel&&) noexcept = default; Panel& operator=(Panel&&) noexcept = default; - explicit Panel(std::string name = {}) : name{std::move(name)} {} + explicit Panel(Config config) : config{std::move(config)} {} virtual ~Panel() = default; - virtual void onUpdate(const FrameInfo& frameInfo) = 0; + // Owns the ImGui window: Begin, onUpdate, End. + void draw(const FrameInfo& frameInfo); - const std::string& getName() const { return name; } + const std::string& getName() const { return config.name; } + bool isLocked() const { return config.locked; } bool isVisible() const { return this->visible; } void setVisible(bool visible) { this->visible = visible; } void toggleVisible() { this->visible = !this->visible; } protected: + // Draw the panel's contents; the window is already open. + virtual void onUpdate(const FrameInfo& frameInfo) = 0; + bool visible = true; private: - std::string name; + Config config; }; } // namespace sophia diff --git a/engine/include/sophia/ui/panel_registry.hpp b/engine/include/sophia/ui/panel_registry.hpp index 9c425ed..09b3b87 100644 --- a/engine/include/sophia/ui/panel_registry.hpp +++ b/engine/include/sophia/ui/panel_registry.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -94,6 +95,11 @@ class PanelRegistry { void update(const FrameInfo& frameInfo); + void setDefaultLayout(DockLayout layout) { + defaultLayout = std::move(layout); + } + const DockLayout& getDefaultLayout() const { return defaultLayout; } + private: struct Slot { std::unique_ptr panel; @@ -102,6 +108,7 @@ class PanelRegistry { std::vector slots; std::vector freeList; + DockLayout defaultLayout; }; } // namespace sophia diff --git a/engine/src/application.cpp b/engine/src/application.cpp index 1123bcc..b838e7b 100644 --- a/engine/src/application.cpp +++ b/engine/src/application.cpp @@ -57,7 +57,16 @@ class Application::Impl { ImGuiRenderer imguiRenderer{window, device, renderContext.getImageFormat(), renderContext.getImageCount()}; + this->application.consoleCommandRegistry.registerCommand( + "reset_layout", "Restore the default panel layout", + [&imguiRenderer](const std::vector&) { + imguiRenderer.resetLayout(); + return std::string{"Layout reset"}; + }); + this->application.onCreate(); + imguiRenderer.setDefaultLayout( + this->application.panelRegistry.getDefaultLayout()); auto startTime = std::chrono::high_resolution_clock::now(); auto currentTime = startTime; diff --git a/engine/src/imgui_renderer.cpp b/engine/src/imgui_renderer.cpp index f0e2f04..fd32ded 100644 --- a/engine/src/imgui_renderer.cpp +++ b/engine/src/imgui_renderer.cpp @@ -3,9 +3,21 @@ #include #include #include +#include // DockBuilder API namespace sophia { +namespace { +constexpr ImGuiDockNodeFlags DOCKSPACE_FLAGS = + ImGuiDockNodeFlags_PassthruCentralNode; + +void dockAll(const std::vector& windows, ImGuiID nodeId) { + for (const std::string& name : windows) { + ImGui::DockBuilderDockWindow(name.c_str(), nodeId); + } +} +} // namespace + ImGuiRenderer::ImGuiRenderer(Window& window, Device& device, vk::Format colorAttachmentFormat, u32 minImageCount) { @@ -65,8 +77,59 @@ void ImGuiRenderer::beginFrame() { ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), - ImGuiDockNodeFlags_PassthruCentralNode); + ImGuiID dockspaceId = ImGui::DockSpaceOverViewport( + 0, ImGui::GetMainViewport(), DOCKSPACE_FLAGS); + applyLayoutIfNeeded(dockspaceId, DOCKSPACE_FLAGS); +} + +void ImGuiRenderer::setDefaultLayout(DockLayout layout) { + defaultLayout = std::move(layout); + seedIfUnsaved = true; +} + +void ImGuiRenderer::resetLayout() { forceReset = true; } + +void ImGuiRenderer::applyLayoutIfNeeded(ImGuiID dockspaceId, + ImGuiDockNodeFlags flags) { + if (defaultLayout.empty()) { + return; + } + + bool apply = forceReset; + if (seedIfUnsaved) { + // A layout restored from imgui.ini shows up as a split root node. + ImGuiDockNode* root = ImGui::DockBuilderGetNode(dockspaceId); + apply = apply || root == nullptr || !root->IsSplitNode(); + seedIfUnsaved = false; + } + if (!apply) { + return; + } + forceReset = false; + + const ImVec2 size = ImGui::GetMainViewport()->Size; + + ImGui::DockBuilderRemoveNode(dockspaceId); + ImGui::DockBuilderAddNode(dockspaceId, flags | ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(dockspaceId, size); + + ImGuiID center = dockspaceId; + const ImGuiID left = ImGui::DockBuilderSplitNode( + center, ImGuiDir_Left, defaultLayout.leftWidth, nullptr, ¢er); + // The right split is taken from what's left after the left split. + const float rightRatio = + defaultLayout.rightWidth / (1.0f - defaultLayout.leftWidth); + const ImGuiID right = ImGui::DockBuilderSplitNode( + center, ImGuiDir_Right, rightRatio, nullptr, ¢er); + const ImGuiID bottom = ImGui::DockBuilderSplitNode( + center, ImGuiDir_Down, defaultLayout.bottomHeight, nullptr, ¢er); + + dockAll(defaultLayout.left, left); + dockAll(defaultLayout.right, right); + dockAll(defaultLayout.bottom, bottom); + dockAll(defaultLayout.center, center); + + ImGui::DockBuilderFinish(dockspaceId); } void ImGuiRenderer::endFrame(vk::CommandBuffer commandBuffer) { diff --git a/engine/src/imgui_renderer.hpp b/engine/src/imgui_renderer.hpp index 0016730..a852a7d 100644 --- a/engine/src/imgui_renderer.hpp +++ b/engine/src/imgui_renderer.hpp @@ -1,6 +1,9 @@ #pragma once +#include + #include +#include #include "device.hpp" #include "window.hpp" @@ -18,6 +21,19 @@ class ImGuiRenderer { void beginFrame(); void endFrame(vk::CommandBuffer commandBuffer); + + // Seeds the layout on the next frame, but only if no saved layout exists. + void setDefaultLayout(DockLayout layout); + + // Discards the current layout and re-applies the default on the next frame. + void resetLayout(); + + private: + void applyLayoutIfNeeded(ImGuiID dockspaceId, ImGuiDockNodeFlags flags); + + DockLayout defaultLayout; + bool seedIfUnsaved = false; + bool forceReset = false; }; } // namespace sophia diff --git a/engine/src/ui/panel.cpp b/engine/src/ui/panel.cpp new file mode 100644 index 0000000..56242cd --- /dev/null +++ b/engine/src/ui/panel.cpp @@ -0,0 +1,35 @@ +#include + +#include + +namespace sophia { + +void Panel::draw(const FrameInfo& frameInfo) { + ImGuiWindowFlags flags = ImGuiWindowFlags_None; + if (config.locked) { + flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoCollapse; + } + + const ImGuiCond cond = + config.locked ? ImGuiCond_Always : ImGuiCond_FirstUseEver; + if (config.size.x > 0.0f || config.size.y > 0.0f) { + ImGui::SetNextWindowSize(ImVec2(config.size.x, config.size.y), cond); + } + if (config.position) { + ImGui::SetNextWindowPos(ImVec2(config.position->x, config.position->y), + cond); + } + + bool open = true; + const bool showCloseButton = config.closable && !config.locked; + ImGui::Begin(config.name.c_str(), showCloseButton ? &open : nullptr, flags); + onUpdate(frameInfo); + ImGui::End(); + + if (!open) { + visible = false; + } +} + +} // namespace sophia diff --git a/engine/src/ui/panel_registry.cpp b/engine/src/ui/panel_registry.cpp index 92b58d6..5b34ea9 100644 --- a/engine/src/ui/panel_registry.cpp +++ b/engine/src/ui/panel_registry.cpp @@ -80,7 +80,7 @@ u32 PanelRegistry::visibleCount() const { void PanelRegistry::update(const FrameInfo& frameInfo) { for (auto& slot : slots) { if (slot.panel && slot.panel->isVisible()) { - slot.panel->onUpdate(frameInfo); + slot.panel->draw(frameInfo); } } } diff --git a/engine/src/ui/panels/console_panel.cpp b/engine/src/ui/panels/console_panel.cpp index 4e9f192..dc9568c 100644 --- a/engine/src/ui/panels/console_panel.cpp +++ b/engine/src/ui/panels/console_panel.cpp @@ -111,8 +111,6 @@ int ConsolePanel::textEditCallback(ImGuiInputTextCallbackData* data) { } void ConsolePanel::onUpdate(const FrameInfo&) { - ImGui::Begin("Console"); - if (ImGui::SmallButton("Clear")) { clear(); } @@ -161,8 +159,6 @@ void ConsolePanel::onUpdate(const FrameInfo&) { if (reclaimFocus) { ImGui::SetKeyboardFocusHere(-1); } - - ImGui::End(); } } // namespace sophia diff --git a/engine/src/ui/panels/console_panel.hpp b/engine/src/ui/panels/console_panel.hpp index c35ef5f..a183d48 100644 --- a/engine/src/ui/panels/console_panel.hpp +++ b/engine/src/ui/panels/console_panel.hpp @@ -13,7 +13,7 @@ namespace sophia { class ConsolePanel : public Panel { public: explicit ConsolePanel(ConsoleCommandRegistry& consoleCommandRegistry) - : Panel{"Console"}, consoleCommandRegistry{consoleCommandRegistry} { + : Panel{{.name = "Console"}}, consoleCommandRegistry{consoleCommandRegistry} { log("Console ready. Type 'help' for a list of commands."); } diff --git a/engine/src/ui/panels/ecs_inspector_panel.cpp b/engine/src/ui/panels/ecs_inspector_panel.cpp index 49e504e..eb9384f 100644 --- a/engine/src/ui/panels/ecs_inspector_panel.cpp +++ b/engine/src/ui/panels/ecs_inspector_panel.cpp @@ -7,8 +7,6 @@ namespace sophia { void EcsInspectorPanel::onUpdate(const FrameInfo&) { - ImGui::Begin("ECS Inspector"); - entityScratch.clear(); for (entt::entity entity : registry.storage()) { entityScratch.push_back(entity); @@ -50,8 +48,6 @@ void EcsInspectorPanel::onUpdate(const FrameInfo&) { } else { ImGui::TextDisabled("No entity selected"); } - - ImGui::End(); } } // namespace sophia diff --git a/engine/src/ui/panels/ecs_inspector_panel.hpp b/engine/src/ui/panels/ecs_inspector_panel.hpp index 1666ff5..2a99231 100644 --- a/engine/src/ui/panels/ecs_inspector_panel.hpp +++ b/engine/src/ui/panels/ecs_inspector_panel.hpp @@ -10,7 +10,7 @@ namespace sophia { class EcsInspectorPanel : public Panel { public: explicit EcsInspectorPanel(entt::registry& registry) - : Panel{"ECS Inspector"}, registry{registry} {} + : Panel{{.name = "ECS Inspector"}}, registry{registry} {} void onUpdate(const FrameInfo& frameInfo) override; diff --git a/engine/src/ui/panels/frame_stats_panel.hpp b/engine/src/ui/panels/frame_stats_panel.hpp index 0d7917f..7923779 100644 --- a/engine/src/ui/panels/frame_stats_panel.hpp +++ b/engine/src/ui/panels/frame_stats_panel.hpp @@ -9,7 +9,7 @@ namespace sophia { class FrameStatsPanel : public Panel { public: - FrameStatsPanel() : Panel{"Frame Stats"} {} + FrameStatsPanel() : Panel{{.name = "Frame Stats"}} {} void onUpdate(const FrameInfo& frameInfo) override { frameCount++; @@ -25,10 +25,8 @@ class FrameStatsPanel : public Panel { nextRefreshTime = frameInfo.elapsedTime + REFRESH_INTERVAL; } - ImGui::Begin("Frame Stats"); ImGui::Text("FPS: %.1f", displayedFps); ImGui::Text("Frame time: %.3f ms", displayedFrameTimeMs); - ImGui::End(); } private: diff --git a/engine/src/ui/panels/panel_registry_panel.cpp b/engine/src/ui/panels/panel_registry_panel.cpp index c481b5a..6746408 100644 --- a/engine/src/ui/panels/panel_registry_panel.cpp +++ b/engine/src/ui/panels/panel_registry_panel.cpp @@ -5,7 +5,6 @@ namespace sophia { void PanelRegistryPanel::onUpdate(const FrameInfo&) { - ImGui::Begin("PanelRegistry"); ImGui::Text("%u panels (%u visible)", registry.size(), registry.visibleCount()); ImGui::Separator(); @@ -21,8 +20,6 @@ void PanelRegistryPanel::onUpdate(const FrameInfo&) { panel->setVisible(visible); } } - - ImGui::End(); } } // namespace sophia diff --git a/engine/src/ui/panels/panel_registry_panel.hpp b/engine/src/ui/panels/panel_registry_panel.hpp index 29e16e2..cebefae 100644 --- a/engine/src/ui/panels/panel_registry_panel.hpp +++ b/engine/src/ui/panels/panel_registry_panel.hpp @@ -9,7 +9,7 @@ namespace sophia { class PanelRegistryPanel : public Panel { public: explicit PanelRegistryPanel(PanelRegistry& registry) - : Panel{"PanelRegistry"}, registry{registry} {} + : Panel{{.name = "PanelRegistry"}}, registry{registry} {} void onUpdate(const FrameInfo& frameInfo) override; From f814ad969f923de2184e0c5ca8418f54f35743d6 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sat, 19 Sep 2026 21:13:41 -0700 Subject: [PATCH 2/7] making config editable --- editor/panels/scene_hierarchy_panel.hpp | 2 +- engine/include/sophia/ui/panel.hpp | 20 +++++++++--- engine/src/ui/panel.cpp | 41 ++++++++++++++++++++----- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/editor/panels/scene_hierarchy_panel.hpp b/editor/panels/scene_hierarchy_panel.hpp index b4be4b4..2833b32 100644 --- a/editor/panels/scene_hierarchy_panel.hpp +++ b/editor/panels/scene_hierarchy_panel.hpp @@ -7,7 +7,7 @@ class SceneHierarchyPanel : public sophia::Panel { public: - SceneHierarchyPanel() : Panel{{.name = "Scene Hierarchy"}} {} + SceneHierarchyPanel() : Panel{{.name = "Scene Hierarchy", .locked = true}} {} void onUpdate(const sophia::FrameInfo& frameInfo) override; diff --git a/engine/include/sophia/ui/panel.hpp b/engine/include/sophia/ui/panel.hpp index c46b8b5..9be5282 100644 --- a/engine/include/sophia/ui/panel.hpp +++ b/engine/include/sophia/ui/panel.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -11,9 +13,11 @@ namespace sophia { class Panel { public: struct Config { - std::string name; + const std::string name; + + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_None; + ImGuiDockNodeFlags dockNodeFlags = ImGuiDockNodeFlags_None; - // Locked panels can't be moved, resized, docked, collapsed or closed. bool locked = false; bool closable = true; @@ -21,20 +25,27 @@ class Panel { // every frame when locked. glm::vec2 size{0.0f, 0.0f}; std::optional position; + + ImGuiWindowFlags getWindowFlags() const; + ImGuiDockNodeFlags getDockNodeFlags() const; + bool hasCloseButton() const; + ImGuiCond getPlacementCond() const; }; Panel(const Panel&) = delete; Panel& operator=(const Panel&) = delete; Panel(Panel&&) noexcept = default; - Panel& operator=(Panel&&) noexcept = default; + Panel& operator=(Panel&&) = delete; explicit Panel(Config config) : config{std::move(config)} {} virtual ~Panel() = default; - // Owns the ImGui window: Begin, onUpdate, End. void draw(const FrameInfo& frameInfo); + Config& getConfig() { return config; } + const Config& getConfig() const { return config; } + const std::string& getName() const { return config.name; } bool isLocked() const { return config.locked; } @@ -43,7 +54,6 @@ class Panel { void toggleVisible() { this->visible = !this->visible; } protected: - // Draw the panel's contents; the window is already open. virtual void onUpdate(const FrameInfo& frameInfo) = 0; bool visible = true; diff --git a/engine/src/ui/panel.cpp b/engine/src/ui/panel.cpp index 56242cd..0fc6ebc 100644 --- a/engine/src/ui/panel.cpp +++ b/engine/src/ui/panel.cpp @@ -1,18 +1,43 @@ #include +#include #include namespace sophia { -void Panel::draw(const FrameInfo& frameInfo) { - ImGuiWindowFlags flags = ImGuiWindowFlags_None; - if (config.locked) { +ImGuiWindowFlags Panel::Config::getWindowFlags() const { + ImGuiWindowFlags flags = windowFlags; + if (locked) { flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoCollapse; + ImGuiWindowFlags_NoCollapse; } + return flags; +} - const ImGuiCond cond = - config.locked ? ImGuiCond_Always : ImGuiCond_FirstUseEver; +ImGuiDockNodeFlags Panel::Config::getDockNodeFlags() const { + ImGuiDockNodeFlags flags = dockNodeFlags; + if (locked) { + flags |= static_cast(ImGuiDockNodeFlags_NoUndocking) | + static_cast(ImGuiDockNodeFlags_NoResize) | + static_cast(ImGuiDockNodeFlags_NoCloseButton) | + static_cast(ImGuiDockNodeFlags_NoDockingOverMe) | + static_cast(ImGuiDockNodeFlags_NoDockingSplit); + } + return flags; +} + +bool Panel::Config::hasCloseButton() const { return closable && !locked; } + +ImGuiCond Panel::Config::getPlacementCond() const { + return locked ? ImGuiCond_Always : ImGuiCond_FirstUseEver; +} + +void Panel::draw(const FrameInfo& frameInfo) { + ImGuiWindowClass windowClass; + windowClass.DockNodeFlagsOverrideSet = config.getDockNodeFlags(); + ImGui::SetNextWindowClass(&windowClass); + + const ImGuiCond cond = config.getPlacementCond(); if (config.size.x > 0.0f || config.size.y > 0.0f) { ImGui::SetNextWindowSize(ImVec2(config.size.x, config.size.y), cond); } @@ -22,8 +47,8 @@ void Panel::draw(const FrameInfo& frameInfo) { } bool open = true; - const bool showCloseButton = config.closable && !config.locked; - ImGui::Begin(config.name.c_str(), showCloseButton ? &open : nullptr, flags); + ImGui::Begin(config.name.c_str(), config.hasCloseButton() ? &open : nullptr, + config.getWindowFlags()); onUpdate(frameInfo); ImGui::End(); From 7837acdf9287a13f95ff67ac987e959e1f233359 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sat, 19 Sep 2026 22:36:24 -0700 Subject: [PATCH 3/7] removing comments --- engine/src/imgui_renderer.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/engine/src/imgui_renderer.hpp b/engine/src/imgui_renderer.hpp index a852a7d..34765cb 100644 --- a/engine/src/imgui_renderer.hpp +++ b/engine/src/imgui_renderer.hpp @@ -22,10 +22,7 @@ class ImGuiRenderer { void beginFrame(); void endFrame(vk::CommandBuffer commandBuffer); - // Seeds the layout on the next frame, but only if no saved layout exists. void setDefaultLayout(DockLayout layout); - - // Discards the current layout and re-applies the default on the next frame. void resetLayout(); private: From 7d9633fb33b2f2e9dccb4427ad32a3e714771264 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sat, 19 Sep 2026 22:36:50 -0700 Subject: [PATCH 4/7] Formatting --- engine/src/ui/panels/console_panel.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/src/ui/panels/console_panel.hpp b/engine/src/ui/panels/console_panel.hpp index a183d48..9325dc3 100644 --- a/engine/src/ui/panels/console_panel.hpp +++ b/engine/src/ui/panels/console_panel.hpp @@ -13,7 +13,8 @@ namespace sophia { class ConsolePanel : public Panel { public: explicit ConsolePanel(ConsoleCommandRegistry& consoleCommandRegistry) - : Panel{{.name = "Console"}}, consoleCommandRegistry{consoleCommandRegistry} { + : Panel{{.name = "Console"}}, + consoleCommandRegistry{consoleCommandRegistry} { log("Console ready. Type 'help' for a list of commands."); } From 0e26ccae98534ca80733aa6fd67b369e6295cdb4 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sat, 19 Sep 2026 22:38:47 -0700 Subject: [PATCH 5/7] Removed comment --- engine/include/sophia/ui/panel.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/include/sophia/ui/panel.hpp b/engine/include/sophia/ui/panel.hpp index 9be5282..078104e 100644 --- a/engine/include/sophia/ui/panel.hpp +++ b/engine/include/sophia/ui/panel.hpp @@ -21,8 +21,6 @@ class Panel { bool locked = false; bool closable = true; - // A zero component lets ImGui size the window. Applied on first use, or - // every frame when locked. glm::vec2 size{0.0f, 0.0f}; std::optional position; From 98b3d4a552eb471668f0f6e2d1fde804c9c29206 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sat, 19 Sep 2026 22:40:26 -0700 Subject: [PATCH 6/7] Formatting --- engine/include/sophia/event.hpp | 9 +++++---- engine/include/sophia/util/logger.hpp | 3 +-- engine/src/window.hpp | 12 ++++-------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/engine/include/sophia/event.hpp b/engine/include/sophia/event.hpp index 0ac3e37..213fa54 100644 --- a/engine/include/sophia/event.hpp +++ b/engine/include/sophia/event.hpp @@ -2,12 +2,11 @@ #include #include -#include -#include - #include #include #include +#include +#include #define BIT(x) (1 << (x)) @@ -116,7 +115,9 @@ class EventSystem { static_assert(std::is_base_of::value, "E must derive from Event"); EventType eventType = GET_EVENT_TYPE(E); - if (!this->eventDelegates.count(eventType)) { return; } + if (!this->eventDelegates.count(eventType)) { + return; + } try { EventDelegate& delegate = diff --git a/engine/include/sophia/util/logger.hpp b/engine/include/sophia/util/logger.hpp index cc3c17f..46d4bf1 100644 --- a/engine/include/sophia/util/logger.hpp +++ b/engine/include/sophia/util/logger.hpp @@ -12,9 +12,8 @@ #pragma once #include -#include - #include +#include namespace sophia::log { diff --git a/engine/src/window.hpp b/engine/src/window.hpp index ae0b950..672215d 100644 --- a/engine/src/window.hpp +++ b/engine/src/window.hpp @@ -3,11 +3,10 @@ #define GLFW_INCLUDE_VULKAN #include -#include -#include - #include #include +#include +#include namespace sophia { @@ -35,11 +34,8 @@ class Window { private: static void resizeCallback(GLFWwindow* window, int width, int height); - static void keyEventCallback(GLFWwindow* window, - int key, - int scancode, - int action, - int mods); + static void keyEventCallback(GLFWwindow* window, int key, int scancode, + int action, int mods); void initialize(); int width, height; From 625ed87538a13caa3641c27ef65ed1e5de3b613e Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sat, 19 Sep 2026 22:43:30 -0700 Subject: [PATCH 7/7] Removing comment --- engine/src/imgui_renderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/imgui_renderer.cpp b/engine/src/imgui_renderer.cpp index fd32ded..538c29c 100644 --- a/engine/src/imgui_renderer.cpp +++ b/engine/src/imgui_renderer.cpp @@ -3,7 +3,7 @@ #include #include #include -#include // DockBuilder API +#include namespace sophia {