| name | editor-panel-creation |
| description | Step-by-step workflow for creating new editor panels including interface design, DI registration, EditorLayer integration, and menu bar setup. Focuses on panel architecture and lifecycle, not UI component APIs. |
Editor Panel Creation
Overview
This skill provides comprehensive guidance for creating new ImGui-based editor panels, ensuring consistency with the engine's dependency injection architecture, UI styling standards, and editor integration patterns.
CRITICAL REQUIREMENT: All editor panels MUST use the UI infrastructure (Drawers, Elements, FieldEditors) instead of manual ImGui code. This ensures consistency, maintainability, and productivity across the entire editor.
UI Infrastructure Reference
The editor provides comprehensive UI infrastructure:
- UI Drawers: ButtonDrawer, ModalDrawer, TableDrawer, TreeDrawer, TextDrawer, LayoutDrawer, DragDropDrawer
- UI Elements: TextureDropTarget, AudioDropTarget, ComponentSelector, EntityContextMenu, PrefabManager
- Field Editors: IFieldEditor (non-generic, primarily for script inspector - rarely used in panels)
- Constants: EditorUIConstants for all sizing, spacing, and colors
When to Use
Invoke this skill when:
- Adding a new editor panel or tool window
- Creating asset browsers or managers
- Building debugging or profiling panels
- Implementing workflow tools for artists/designers
- Questions about editor panel architecture and lifecycle
- Integrating panels with the editor layer system
- Questions about DI registration and menu integration
Panel Creation Workflow
Follow these 6 steps to create a new panel. Use the Testing Checklist to verify completeness.
Step 1: Define Panel Interface
Location: Editor/Panels/
Pattern: All panels use interface-based design for testability and DI
Interface Template:
namespace Editor.Panels;
/// <summary>
/// Interface for the [PanelName] panel.
/// </summary>
public interface IMyNewPanel
{
/// <summary>
/// Renders the panel using ImGui.
/// </summary>
void OnImGuiRender();
/// <summary>
/// Gets or sets whether the panel is currently open.
/// </summary>
bool IsOpen { get; set; }
}
Naming Convention:
- Interface:
I[PanelName]PanelorI[PanelName] - Implementation:
[PanelName]Panelor[PanelName] - Examples:
ISceneHierarchyPanel,IConsolePanel,ITileMapPanel
Step 2: Implement Panel Class
Location: Editor/Panels/
Guidelines:
- MUST use UI Drawers (ButtonDrawer, ModalDrawer, etc.) instead of manual ImGui code
- MUST use UI Elements (TextureDropTarget, ComponentSelector, etc.) for complex interactions
- Use constructor injection for ALL dependencies
- Use
EditorUIConstantsfor sizing, spacing, colors (Drawers handle this automatically) - Maintain panel state in private fields
- Implement proper disposal if managing resources
- Follow ImGui immediate-mode UI patterns
Panel Template:
namespace Editor.Panels;
using Editor.UI;
using Editor.UI.Drawers;
using Editor.UI.Elements;
using Editor.UI.FieldEditors;
using ImGuiNET;
using Editor.Managers;
/// <summary>
/// Panel for managing and displaying [functionality].
/// </summary>
public class MyNewPanel(
ISceneManager sceneManager,
IProjectManager projectManager) : IMyNewPanel
{
// Panel state
private bool _isOpen = true;
private bool _showConfirmModal = false;
private string _filterText = string.Empty;
private int _selectedIndex = -1;
// Input buffers (use EditorUIConstants for sizes)
private readonly byte[] _nameBuffer = new byte[EditorUIConstants.MaxNameLength];
/// <inheritdoc/>
public bool IsOpen
{
get => _isOpen;
set => _isOpen = value;
}
/// <inheritdoc/>
public void OnImGuiRender()
{
if (!_isOpen)
return;
ImGuiWindowFlags flags = ImGuiWindowFlags.None;
if (ImGui.Begin("My Panel", ref _isOpen, flags))
{
DrawToolbar();
LayoutDrawer.DrawSeparator();
DrawContent();
}
ImGui.End();
// Render modals (must be outside Begin/End)
ModalDrawer.RenderConfirmationModal(
title: "Confirm Action",
showModal: ref _showConfirmModal,
message: "Are you sure?",
onOk: () => PerformAction());
}
private void DrawToolbar()
{
// Use ButtonDrawer for styled buttons
if (ButtonDrawer.DrawButton("Save", ButtonDrawer.ButtonType.Primary))
{
SaveData();
}
ImGui.SameLine();
if (ButtonDrawer.DrawButton("Clear", ButtonDrawer.ButtonType.Secondary))
{
_showConfirmModal = true;
}
}
private void DrawContent()
{
// Use LayoutDrawer for spacing
LayoutDrawer.DrawSpacing(EditorUIConstants.StandardPadding);
// Panel-specific content here
}
private void SaveData()
{
// Implementation
}
private void PerformAction()
{
// Implementation
}
}
Step 3: Register in Dependency Injection
Location: Editor/Program.cs
Registration Pattern:
private static void ConfigureServices(Container container)
{
// ... existing registrations
// Register new panel
container.Register<IMyNewPanel, MyNewPanel>(Reuse.Singleton);
}
Guidelines:
- Always register as singleton (one instance per editor session)
- Register interface → implementation mapping
- Ensure all dependencies are registered before the panel
Step 4: Inject into EditorLayer
Location: Editor/EditorLayer.cs
Constructor Injection:
public class EditorLayer(
// ... existing parameters
ISceneHierarchyPanel sceneHierarchyPanel,
IPropertiesPanel propertiesPanel,
IConsolePanel consolePanel,
IMyNewPanel myNewPanel) : Layer
{
public override void OnImGuiRender()
{
// ... existing panel renders
sceneHierarchyPanel.OnImGuiRender();
propertiesPanel.OnImGuiRender();
consolePanel.OnImGuiRender();
myNewPanel.OnImGuiRender();
}
}
Step 5: Add Menu Integration
Location: Editor/EditorLayer.cs (in menu bar rendering)
Add Panel Toggle Menu:
private void DrawMenuBar()
{
if (ImGui.BeginMenu("Window"))
{
// Existing menu items
if (ImGui.MenuItem("Scene Hierarchy", "", _sceneHierarchyPanel.IsOpen))
_sceneHierarchyPanel.IsOpen = !_sceneHierarchyPanel.IsOpen;
if (ImGui.MenuItem("Properties", "", _propertiesPanel.IsOpen))
_propertiesPanel.IsOpen = !_propertiesPanel.IsOpen;
// New panel menu item
if (ImGui.MenuItem("My Panel", "", _myNewPanel.IsOpen))
_myNewPanel.IsOpen = !_myNewPanel.IsOpen;
ImGui.EndMenu();
}
}
Keyboard Shortcut (optional):
private void HandleShortcuts()
{
// Existing shortcuts
// Ctrl+Shift+M to toggle My Panel
if (ImGui.IsKeyDown(ImGuiKey.LeftCtrl) &&
ImGui.IsKeyDown(ImGuiKey.LeftShift) &&
ImGui.IsKeyPressed(ImGuiKey.M))
{
_myNewPanel.IsOpen = !_myNewPanel.IsOpen;
}
}
Step 6: Use UI Infrastructure (MANDATORY)
CRITICAL: All panels MUST use the UI infrastructure - never write manual ImGui code for patterns covered by Drawers, Elements, or FieldEditors!
Common UI Components:
- Buttons - Use
ButtonDrawer.DrawButton()with button types (Primary, Secondary, Danger, Success) - Modals - Use
ModalDrawer.RenderConfirmationModal()for all confirmation dialogs - Tables - Use
TableDrawer.BeginTable()/TableDrawer.DrawRow()/TableDrawer.EndTable() - Spacing - Use
LayoutDrawer.DrawSpacing()/LayoutDrawer.DrawSeparator() - Asset References - Use
TextureDropTarget.Draw(),AudioDropTarget.Draw(), etc. - Property Editing - Use ImGui widgets directly (ImGui.DragFloat, ImGui.InputText, etc.) or create custom UI patterns
Example: Minimal Panel with UI Infrastructure
using Editor.UI.Drawers;
using Editor.UI.Elements;
public class MyPanel : IMyPanel
{
private bool _showConfirmModal;
private float _speed = 1.0f;
private string _iconPath = "";
public void OnImGuiRender()
{
if (!_isOpen) return;
if (ImGui.Begin("My Panel", ref _isOpen))
{
// Use ButtonDrawer for styled buttons
if (ButtonDrawer.DrawButton("Save", ButtonDrawer.ButtonType.Primary))
{
Save();
}
LayoutDrawer.DrawSeparator();
// Use ImGui for properties
ImGui.DragFloat("Speed", ref _speed, 0.1f);
// Use drag-drop targets for assets
TextureDropTarget.Draw("Icon", _iconPath, (newPath) => _iconPath = newPath);
}
ImGui.End();
// Modals outside Begin/End
ModalDrawer.RenderConfirmationModal(
title: "Confirm",
showModal: ref _showConfirmModal,
message: "Are you sure?",
onOk: () => PerformAction());
}
}
Key Rules:
- ❌ Never use
ImGui.Button()- useButtonDrawer.DrawButton() - ❌ Never use
ImGui.BeginPopupModal()- useModalDrawer.RenderConfirmationModal() - ✅ Use
ImGui.DragFloat(),ImGui.InputText(), etc. for property editing (IFieldEditor is for script inspector only) - ❌ Never manually implement drag-drop - use
TextureDropTarget.Draw(), etc.
Advanced Panel Patterns
Dockable Panel
public void OnImGuiRender()
{
if (!_isOpen)
return;
ImGuiWindowFlags flags = ImGuiWindowFlags.None;
if (ImGui.Begin("My Panel", ref _isOpen, flags))
{
// Panel is dockable by default in ImGui
DrawContent();
}
ImGui.End();
}
Panel with Tabs
private void DrawContent()
{
if (ImGui.BeginTabBar("##MyTabs"))
{
if (ImGui.BeginTabItem("Tab 1"))
{
DrawTab1Content();
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Tab 2"))
{
DrawTab2Content();
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
}
Panel with Context Menu
private void DrawItem(string itemName)
{
ImGui.Selectable(itemName);
if (ImGui.BeginPopupContextItem($"##{itemName}Context"))
{
if (ImGui.MenuItem("Edit"))
EditItem(itemName);
if (ImGui.MenuItem("Delete"))
DeleteItem(itemName);
ImGui.EndPopup();
}
}
Panel with Modal Dialog
ALWAYS use ModalDrawer instead of manual ImGui popups.
using Editor.UI.Drawers;
private bool _showDeleteConfirmation = false;
private void DrawContent()
{
// Trigger modal with styled button
if (ButtonDrawer.DrawButton("Delete", ButtonDrawer.ButtonType.Danger))
_showDeleteConfirmation = true;
// Render modal using ModalDrawer
ModalDrawer.RenderConfirmationModal(
title: "Delete Confirmation",
showModal: ref _showDeleteConfirmation,
message: "Are you sure you want to delete?",
onOk: () => PerformDelete());
}
Existing Panels Reference
The editor has 17 panels in Editor/Panels/ and Editor/Features/. Reference these for implementation patterns:
- Core: SceneHierarchyPanel, PropertiesPanel, ViewportPanel, GameViewPanel
- Assets: ContentBrowserPanel, AssetPanel, TileMapPanel
- Tools: ConsolePanel, StatsPanel, AudioPanel, PhysicsPanel
- Settings: ProjectSettingsPanel, BuildSettingsPanel, PreferencesPanel, SceneSettingsPanel
- Utilities: ShortcutsPanel, AboutPanel
See implementations in Editor/Panels/ for UI consistency patterns.
Dependency Injection Best Practices
Common Service Dependencies
// Scene management
private readonly ISceneManager _sceneManager;
// Project management
private readonly IProjectManager _projectManager;
// Factories
private readonly ITextureFactory _textureFactory;
private readonly IShaderFactory _shaderFactory;
private readonly IAudioClipFactory _audioClipFactory;
// Systems
private readonly SystemManager _systemManager;
// Other panels (for cross-panel communication)
private readonly ISceneHierarchyPanel _sceneHierarchyPanel;
Constructor Pattern (Use Primary Constructor)
public class MyPanel(
ISceneManager sceneManager,
IProjectManager projectManager,
ITextureFactory textureFactory) : IMyPanel
{
// Dependencies are automatically available as private readonly fields
// No null validation needed - non-nullable reference types handle this
}
Never Create Static Singletons
// ❌ WRONG - Do not create static singletons
public static class MyPanelManager
{
public static MyPanelManager Instance { get; } = new();
}
// ✅ CORRECT - Use DI container registration
container.Register<IMyPanel, MyPanel>(Reuse.Singleton);
Testing Checklist
- Panel interface defined
- Panel implementation with constructor injection
- All dependencies properly injected (no nulls)
- UI Drawers used instead of manual ImGui code (ButtonDrawer, ModalDrawer, etc.)
- UI Elements used for complex interactions (drag-drop targets, component selector)
- ImGui widgets used for property editing (IFieldEditor not needed in most panels)
-
EditorUIConstantsused throughout (no magic numbers) - Registered in
Program.csDI container - Injected into
EditorLayer - Menu item added to Window menu
- Panel opens and closes correctly
- Panel state persists during session
- Panel works with docking system
- Keyboard shortcuts added (if applicable)
- Panel performs expected functionality
- Cross-panel communication works (if needed)
Documentation Requirements
Code Documentation
- XML comments on interface and public methods
- Clear parameter descriptions
- Usage examples in comments
Common Pitfalls to Avoid
- ❌ Manual ImGui code instead of Drawers - ALWAYS use ButtonDrawer, ModalDrawer, TableDrawer, etc.
- ❌ Manual drag-drop instead of Elements - Use TextureDropTarget, AudioDropTarget, etc.
- ❌ Confusing IFieldEditor usage - IFieldEditor is for script inspector only, use ImGui widgets for panel properties
- ❌ Hardcoded UI values - Always use EditorUIConstants
- ❌ Static state - Use instance fields, inject dependencies
- ❌ Missing null checks - Validate constructor parameters
- ❌ Inconsistent styling - Follow existing panel patterns and use UI infrastructure
- ❌ Direct service access - Use dependency injection
- ❌ Forgetting IsOpen check - Always check before rendering
- ❌ ImGui misuse - Follow Begin/End pairing strictly
- ❌ Performance issues - Avoid heavy computation in OnImGuiRender
- ❌ Duplicating UI patterns - Check if a Drawer/Element already exists first