Support default editor panel layout and locked panels #5

Closed
opened 2026-09-19 02:05:09 -07:00 by burkkyy · 0 comments
Owner

Summary

The editor has docking enabled but no defined layout. On first launch every panel is a free-floating window, and the user has to arrange them by hand. There is also no way to mark a panel as fixed (unmovable, undockable, unclosable), which editors usually want for things like a toolbar or status bar.

Background

  • Docking is enabled in engine/src/imgui_renderer.cpp, which calls DockSpaceOverViewport(...) each frame. Nothing is assigned to the dockspace, so it starts empty.
  • ImGui persists layout to imgui.ini once the user rearranges windows, but nothing seeds the initial layout.
  • Panel (engine/include/sophia/ui/panel.hpp) only has visible. It has no way to express window flags or lock behaviour.
  • Each panel currently calls ImGui::Begin("...") and End() itself, so the base class can't apply per-panel window settings.
  • The editor panels (Scene Hierarchy, Inspector, Asset Browser) live in editor/panels/ and are registered in Editor::onCreate() in editor/main.cpp.

Proposal

1. Panel owns Begin/End, configured via Panel::Config

  • Add a Panel::Config struct, passed to the Panel constructor and used with designated initializers, like Application::Config. Example: Panel{{.name = "Toolbar", .locked = true, .size = {300, 0}}}.
  • Fields: name, locked, closable, and size (initial size). More can be added later.
  • PanelRegistry::update calls a non-virtual Panel::draw(), which does Begin(name, &open, flags), calls the virtual onUpdate, then End(). Subclasses drop their own Begin/End.
  • The base class translates Config into ImGui window flags in one place, and handles the close button.
  • Runtime-mutable state stays as setters (setVisible). If something needs to change dynamically, such as locking during play mode, add a single setLocked alongside the config rather than a full set of setters.

2. Locked panels

  • A locked panel uses NoMove | NoResize | NoDocking | NoCollapse (and optionally NoTitleBar) and is drawn outside the dockspace, for example a fixed top toolbar.
  • Decide whether locked panels can be hidden via setVisible(), or must always be shown.

3. Default layout

  • Build an initial dock layout using the DockBuilder API (imgui_internal.h): DockBuilderSplitNode() to carve the dockspace into regions, then DockBuilderDockWindow(name, nodeId) to assign each panel.
  • Example target layout: Scene Hierarchy on the left, Inspector on the right, Asset Browser and Console along the bottom, viewport in the centre.
  • Apply it only when no saved layout exists (no imgui.ini, or the dockspace node has no children). After that, normal ini persistence takes over.
  • Add a "Reset Layout" action that clears the dock nodes and re-applies the default.
  • The application, not the engine renderer, owns the layout definition, since Editor and Testbed want different layouts. ImGuiRenderer::beginFrame() should expose a hook for it.

Suggested order

  1. Move Begin/End into Panel::draw() and add Panel::Config. Migrate the existing panels (Console, Frame Stats, PanelRegistry, ECS Inspector, and the three editor panels).
  2. Implement locked and closable.
  3. Implement the default layout and "Reset Layout".

Acceptance criteria

  • No subclass calls ImGui::Begin/End directly.
  • A panel marked locked cannot be moved, resized, docked, collapsed or closed.
  • Deleting imgui.ini and launching the editor produces the default docked layout.
  • After the user rearranges panels, the arrangement persists across restarts and the default is not re-applied.
  • "Reset Layout" restores the default.
  • Existing debug panels still work and can be docked into the default layout.

Risks / open questions

  • DockBuilder lives in imgui_internal.h and upstream marks it as subject to change. It is still the standard way to seed a layout, but we should isolate its usage in one place.
  • Panels are matched to dock nodes by window title string, so renaming a panel silently breaks the default layout. Consider logging when a name in the layout doesn't match a registered panel.
  • Moving Begin/End into the base class touches every existing panel, so it should land as its own change before the layout work.
## Summary The editor has docking enabled but no defined layout. On first launch every panel is a free-floating window, and the user has to arrange them by hand. There is also no way to mark a panel as fixed (unmovable, undockable, unclosable), which editors usually want for things like a toolbar or status bar. ## Background - Docking is enabled in `engine/src/imgui_renderer.cpp`, which calls `DockSpaceOverViewport(...)` each frame. Nothing is assigned to the dockspace, so it starts empty. - ImGui persists layout to `imgui.ini` once the user rearranges windows, but nothing seeds the initial layout. - `Panel` (`engine/include/sophia/ui/panel.hpp`) only has `visible`. It has no way to express window flags or lock behaviour. - Each panel currently calls `ImGui::Begin("...")` and `End()` itself, so the base class can't apply per-panel window settings. - The editor panels (Scene Hierarchy, Inspector, Asset Browser) live in `editor/panels/` and are registered in `Editor::onCreate()` in `editor/main.cpp`. ## Proposal ### 1. Panel owns `Begin`/`End`, configured via `Panel::Config` - Add a `Panel::Config` struct, passed to the `Panel` constructor and used with designated initializers, like `Application::Config`. Example: `Panel{{.name = "Toolbar", .locked = true, .size = {300, 0}}}`. - Fields: `name`, `locked`, `closable`, and `size` (initial size). More can be added later. - `PanelRegistry::update` calls a non-virtual `Panel::draw()`, which does `Begin(name, &open, flags)`, calls the virtual `onUpdate`, then `End()`. Subclasses drop their own `Begin`/`End`. - The base class translates `Config` into ImGui window flags in one place, and handles the close button. - Runtime-mutable state stays as setters (`setVisible`). If something needs to change dynamically, such as locking during play mode, add a single `setLocked` alongside the config rather than a full set of setters. ### 2. Locked panels - A locked panel uses `NoMove | NoResize | NoDocking | NoCollapse` (and optionally `NoTitleBar`) and is drawn outside the dockspace, for example a fixed top toolbar. - Decide whether locked panels can be hidden via `setVisible()`, or must always be shown. ### 3. Default layout - Build an initial dock layout using the `DockBuilder` API (`imgui_internal.h`): `DockBuilderSplitNode()` to carve the dockspace into regions, then `DockBuilderDockWindow(name, nodeId)` to assign each panel. - Example target layout: Scene Hierarchy on the left, Inspector on the right, Asset Browser and Console along the bottom, viewport in the centre. - Apply it only when no saved layout exists (no `imgui.ini`, or the dockspace node has no children). After that, normal ini persistence takes over. - Add a "Reset Layout" action that clears the dock nodes and re-applies the default. - The application, not the engine renderer, owns the layout definition, since Editor and Testbed want different layouts. `ImGuiRenderer::beginFrame()` should expose a hook for it. ## Suggested order 1. Move `Begin`/`End` into `Panel::draw()` and add `Panel::Config`. Migrate the existing panels (Console, Frame Stats, PanelRegistry, ECS Inspector, and the three editor panels). 2. Implement `locked` and `closable`. 3. Implement the default layout and "Reset Layout". ## Acceptance criteria - No subclass calls `ImGui::Begin`/`End` directly. - A panel marked `locked` cannot be moved, resized, docked, collapsed or closed. - Deleting `imgui.ini` and launching the editor produces the default docked layout. - After the user rearranges panels, the arrangement persists across restarts and the default is not re-applied. - "Reset Layout" restores the default. - Existing debug panels still work and can be docked into the default layout. ## Risks / open questions - `DockBuilder` lives in `imgui_internal.h` and upstream marks it as subject to change. It is still the standard way to seed a layout, but we should isolate its usage in one place. - Panels are matched to dock nodes by window title string, so renaming a panel silently breaks the default layout. Consider logging when a name in the layout doesn't match a registered panel. - Moving `Begin`/`End` into the base class touches every existing panel, so it should land as its own change before the layout work.
burkkyy added this to the sophia project 2026-09-19 19:39:55 -07:00
burkkyy moved this to In Progress in sophia on 2026-09-19 19:40:11 -07:00
burkkyy moved this to Done in sophia on 2026-09-19 22:45:20 -07:00
Sign in to join this conversation.