Step-by-step workflow for creating new editor panels including interface design, DI registration, EditorLayer integration, and menu bar setup...
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.
The editor provides comprehensive UI infrastructure:
Invoke this skill when:
Follow these 6 steps to create a new panel. Use the Testing Checklist to verify completeness.
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:
I[PanelName]Panel or I[PanelName][PanelName]Panel or [PanelName]IConsolePanel, IContentBrowserPanel, ITileMapPanelLocation: Editor/Panels/
Guidelines:
EditorUIConstants for sizing, spacing, colors (Drawers handle this automatically)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"))
{
SaveData();
}
ImGui.SameLine();
if (ButtonDrawer.DrawColoredButton("Clear", MessageType.Warning))
{
_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
}
}
Location: Editor/Program.cs
Registration Pattern:
private static void ConfigureServices(Container container)
{
// ... existing registrations
// Register new panel
container.Register<IMyNewPanel, MyNewPanel>(Reuse.Singleton);
}
Guidelines:
Location: Editor/EditorPanels.cs
Constructor Injection:
public class EditorPanels(
// ... existing panels
IMyNewPanel myNewPanel)
{
// ... existing properties
public void Draw(Entity? hoveredEntity, EditorCamera camera)
{
// ... existing panel renders
myNewPanel.Draw();
}
}
Register the panel in Editor/DI/EditorIoCContainer.cs and add it to EditorPanels constructor and Draw().
Location: Editor/Features/Application/EditorMenuBar.cs
Add Panel Toggle Menu (e.g. in View menu or add Window menu):
private void RenderViewMenu()
{
if (!ImGui.BeginMenu("View")) return;
if (ImGui.MenuItem("My Panel", "", myNewPanel.IsVisible))
myNewPanel.IsVisible = !myNewPanel.IsVisible;
ImGui.EndMenu();
}
Keyboard Shortcut (optional): register via EditorShortcutRegistrar in Editor/Input/EditorShortcutRegistrar.cs.
CRITICAL: All panels MUST use the UI infrastructure - never write manual ImGui code for patterns covered by Drawers, Elements, or FieldEditors!
Common UI Components:
ButtonDrawer.DrawButton() with button types (Primary, Secondary, Danger, Success)ModalDrawer.RenderConfirmationModal() for all confirmation dialogsTableDrawer.BeginTable() / TableDrawer.DrawRow() / TableDrawer.EndTable()LayoutDrawer.DrawSpacing() / LayoutDrawer.DrawSeparator()TextureDropTarget.Draw(), AudioDropTarget.Draw(), etc.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"))
{
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:
ImGui.Button() - use ButtonDrawer.DrawButton()ImGui.BeginPopupModal() - use ModalDrawer.RenderConfirmationModal()ImGui.DragFloat(), ImGui.InputText(), etc. for property editing (IFieldEditor is for script inspector only)TextureDropTarget.Draw(), etc.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();
}
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();
}
}
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();
}
}
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());
}
The editor has 17 panels in Editor/Panels/ and Editor/Features/. Reference these for implementation patterns:
See implementations in Editor/Panels/ for UI consistency patterns.
// 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 SceneHierarchyPanel _sceneHierarchyPanel;
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
}
// ❌ 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);
EditorUIConstants used throughout (no magic numbers)Program.cs DI containerEditorPanels and called from Draw()EditorMenuBar (if applicable)