Supporting setting panel layout
This commit is contained in:
@@ -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<std::string>&) {
|
||||
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;
|
||||
|
||||
@@ -3,9 +3,21 @@
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_vulkan.h>
|
||||
#include <imgui_internal.h> // DockBuilder API
|
||||
|
||||
namespace sophia {
|
||||
|
||||
namespace {
|
||||
constexpr ImGuiDockNodeFlags DOCKSPACE_FLAGS =
|
||||
ImGuiDockNodeFlags_PassthruCentralNode;
|
||||
|
||||
void dockAll(const std::vector<std::string>& 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) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <sophia/types.hpp>
|
||||
#include <sophia/ui/dock_layout.hpp>
|
||||
|
||||
#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
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#include <imgui.h>
|
||||
|
||||
#include <sophia/ui/panel.hpp>
|
||||
|
||||
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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
namespace sophia {
|
||||
|
||||
void EcsInspectorPanel::onUpdate(const FrameInfo&) {
|
||||
ImGui::Begin("ECS Inspector");
|
||||
|
||||
entityScratch.clear();
|
||||
for (entt::entity entity : registry.storage<entt::entity>()) {
|
||||
entityScratch.push_back(entity);
|
||||
@@ -50,8 +48,6 @@ void EcsInspectorPanel::onUpdate(const FrameInfo&) {
|
||||
} else {
|
||||
ImGui::TextDisabled("No entity selected");
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user