Skip to content
editor-component-editors logo

Editor Component Editors

editor-component-editors

Create ECS component editors using IComponentEditor interface, ComponentEditorRegistry.DrawComponent wrapper, VectorPanel for vectors, and UIPropertyRenderer for simple properties. Covers registration in DI container and manual change detection patterns.

SKILL.md

Full skill instructions

Editor Component Editors

Overview

Component editors render ECS component properties in the editor's Properties panel. They use the IComponentEditor interface with static utility methods for consistent UI styling.

When to Use This Skill

  • Creating a new component editor for an ECS component
  • Editing vector properties (Vector2/​Vector3) with axis color coding
  • Editing primitive properties (int, float, bool, string)
  • Need collapsible component UI with remove button
  • Building custom property controls for components

Core Architecture

IComponentEditor Interface

// Editor/​ComponentEditors/​Core/​IComponentEditor.cs
public interface IComponentEditor
{
    void DrawComponent(Entity entity);
}

Key Points:

  • Takes Entity, not the component directly
  • Component is retrieved inside using entity.GetComponent<T>()
  • No return value - mutates component properties directly

Essential Pattern: ComponentEditorRegistry.DrawComponent<T>()

Every component editor uses this static wrapper method for consistent UI:

ComponentEditorRegistry.DrawComponent<ComponentType>("Display Name", entity, entity =>
{
    var component = entity.GetComponent<ComponentType>();

    // Draw property editors here
});

What it provides:

  • ✅ Collapsible tree node (DefaultOpen)
  • ✅ Component name header
  • ✅ Remove component button (-)
  • ✅ Consistent padding and spacing
  • ✅ Framed appearance
  • ✅ Only renders if entity has the component

Implementation (ComponentEditorRegistry.cs:60-87):

  • Uses ImGuiTreeNodeFlags for styling
  • Adds remove button in top-right corner
  • Calls your lambda only if component exists
  • Handles tree pop automatically

Property Editing Utilities

1. UIPropertyRenderer.DrawPropertyField()

Best for: Simple primitive properties (int, float, bool, string)

UIPropertyRenderer.DrawPropertyField("Label", currentValue,
    newValue => component.Property = (TypeCast)newValue);

Features:

  • Automatic type detection via FieldEditorRegistry
  • Consistent label/​input width ratio (33%/​67%)
  • Supports: int, float, double, bool, string, Vector2, Vector3, Vector4
  • Boxing-based (object newValue)

Example (CameraComponentEditor.cs:22-23):

UIPropertyRenderer.DrawPropertyField("Primary", cameraComponent.Primary,
    newValue => cameraComponent.Primary = (bool)newValue);

2. VectorPanel Static Methods

Best for: Vector properties needing axis color coding or reset buttons

Vector3 with Axis Colors
var newPosition = component.Position;
VectorPanel.DrawVec3Control("Position", ref newPosition);

if (newPosition != component.Position)
    component.Position = newPosition;

Features:

  • Colored axis buttons: X (red), Y (green), Z (blue)
  • Click button to reset axis to default value
  • Drag float inputs for each axis
  • Consistent 33%/​67% label/​input ratio

With Reset Value (TransformComponentEditor.cs:32):

var newScale = component.Scale;
VectorPanel.DrawVec3Control("Scale", ref newScale, resetValue: 1.0f);

if (newScale != component.Scale)
    component.Scale = newScale;
Vector2 Controls
var newSize = component.Size;
VectorPanel.DrawVec2Control("Size", ref newSize);

if (newSize != component.Size)
    component.Size = newSize;

VectorPanel.cs methods:

  • DrawVec3Control(string label, ref Vector3 values, float resetValue = 0.0f)
  • DrawVec2Control(string label, ref Vector2 values, float resetValue = 0.0f)

3. LayoutDrawer.DrawComboBox()

Best for: Enum or string selection dropdowns

private static readonly string[] ProjectionTypeStrings = { "Perspective", "Orthographic" };

LayoutDrawer.DrawComboBox("Projection",
    ProjectionTypeStrings[(int)camera.ProjectionType],
    ProjectionTypeStrings,
    selectedType =>
    {
        camera.ProjectionType = selectedType switch
        {
            "Perspective" => ProjectionType.Perspective,
            "Orthographic" => ProjectionType.Orthographic,
            _ => camera.ProjectionType
        };
    });

Complete Working Examples

Example 1: Simple Component Editor (Camera)

// CameraComponentEditor.cs (simplified)
using ECS;
using Editor.ComponentEditors.Core;
using Editor.UI.Drawers;
using Editor.UI.Elements;
using Engine.Scene.Components;

namespace Editor.ComponentEditors;

public class CameraComponentEditor : IComponentEditor
{
    private static readonly string[] ProjectionTypeStrings = { "Perspective", "Orthographic" };

    public void DrawComponent(Entity e)
    {
        ComponentEditorRegistry.DrawComponent<CameraComponent>("Camera", e, entity =>
        {
            var camera = entity.GetComponent<CameraComponent>().Camera;

            UIPropertyRenderer.DrawPropertyField("Size", camera.OrthographicSize,
                newValue => camera.OrthographicSize = (float)newValue);

            UIPropertyRenderer.DrawPropertyField("Near", camera.OrthographicNear,
                newValue => camera.OrthographicNear = (float)newValue);

            UIPropertyRenderer.DrawPropertyField("Far", camera.OrthographicFar,
                newValue => camera.OrthographicFar = (float)newValue);
        });
    }
}

Example 2: Vector Component Editor (Transform)

// TransformComponentEditor.cs (actual implementation)
using ECS;
using Editor.ComponentEditors.Core;
using Engine.Math;
using Engine.Scene.Components;

namespace Editor.ComponentEditors;

public class TransformComponentEditor : IComponentEditor
{
    public void DrawComponent(Entity e)
    {
        ComponentEditorRegistry.DrawComponent<TransformComponent>("Transform", e, entity =>
        {
            var tc = entity.GetComponent<TransformComponent>();

            // Translation
            var newTranslation = tc.Translation;
            VectorPanel.DrawVec3Control("Translation", ref newTranslation);
            if (newTranslation != tc.Translation)
                tc.Translation = newTranslation;

            // Rotation (convert radians to degrees for UI)
            var rotationRadians = tc.Rotation;
            Vector3 rotationDegrees = MathHelpers.ToDegrees(rotationRadians);
            VectorPanel.DrawVec3Control("Rotation", ref rotationDegrees);
            var newRotationRadians = MathHelpers.ToRadians(rotationDegrees);
            if (newRotationRadians != tc.Rotation)
                tc.Rotation = newRotationRadians;

            // Scale (reset to 1.0 instead of 0.0)
            var newScale = tc.Scale;
            VectorPanel.DrawVec3Control("Scale", ref newScale, resetValue: 1.0f);
            if (newScale != tc.Scale)
                tc.Scale = newScale;
        });
    }
}

Key Pattern: Copy to temp variable → modify → check if changed → assign back


Example 3: Complex Component with Multiple Property Types

public class MyComponentEditor : IComponentEditor
{
    public void DrawComponent(Entity e)
    {
        ComponentEditorRegistry.DrawComponent<MyComponent>("My Component", e, entity =>
        {
            var component = entity.GetComponent<MyComponent>();

            // Simple properties with UIPropertyRenderer
            UIPropertyRenderer.DrawPropertyField("Enabled", component.IsEnabled,
                newValue => component.IsEnabled = (bool)newValue);

            UIPropertyRenderer.DrawPropertyField("Speed", component.Speed,
                newValue => component.Speed = (float)newValue);

            // Vector with axis controls
            var newPosition = component.Position;
            VectorPanel.DrawVec3Control("Position", ref newPosition);
            if (newPosition != component.Position)
                component.Position = newPosition;

            // Dropdown selection
            string[] options = { "Option1", "Option2", "Option3" };
            LayoutDrawer.DrawComboBox("Mode", options[component.ModeIndex], options,
                selected =>
                {
                    component.ModeIndex = Array.IndexOf(options, selected);
                });

            // Custom UI elements with Drawers
            if (ButtonDrawer.DrawButton("Reset", ButtonDrawer.ButtonType.Primary))
            {
                component.Reset();
            }
        });
    }
}

Dependency Injection Registration

Component editors are registered in the DI container and injected into ComponentEditorRegistry.

Registration (Program.cs or similar)

// Register individual component editors
container.Register<TransformComponentEditor>(Reuse.Singleton);
container.Register<CameraComponentEditor>(Reuse.Singleton);
container.Register<MyComponentEditor>(Reuse.Singleton);

// ComponentEditorRegistry constructor receives all editors
container.Register<ComponentEditorRegistry>(Reuse.Singleton);

ComponentEditorRegistry Constructor Pattern

public class ComponentEditorRegistry(
    TransformComponentEditor transformComponentEditor,
    CameraComponentEditor cameraComponentEditor,
    MyComponentEditor myComponentEditor) : IComponentEditorRegistry  // Add your editor here
{
    private readonly Dictionary<Type, IComponentEditor> _editors = new()
    {
        { typeof(TransformComponent), transformComponentEditor },
        { typeof(CameraComponent), cameraComponentEditor },
        { typeof(MyComponent), myComponentEditor }  // Register component type
    };

    public void DrawAllComponents(Entity entity)
    {
        foreach (var (componentType, editor) in _editors)
        {
            editor.DrawComponent(entity);
        }
    }
}

Change Detection Patterns

Pattern 1: Copy-Modify-Assign (for VectorPanel)

var oldValue = component.Position;
VectorPanel.DrawVec3Control("Position", ref oldValue);

if (oldValue != component.Position)  // Value comparison
    component.Position = oldValue;

Why: VectorPanel modifies the ref parameter directly, so we need manual change detection.


Pattern 2: Callback Assignment (for UIPropertyRenderer)

UIPropertyRenderer.DrawPropertyField("Speed", component.Speed,
    newValue => component.Speed = (float)newValue);

Why: UIPropertyRenderer only calls callback if value changed. No manual check needed.


Anti-Patterns

❌ Anti-Pattern 1: Not Using DrawComponent Wrapper

// ❌ WRONG - Manual tree node management
public void DrawComponent(Entity e)
{
    if (ImGui.TreeNode("My Component"))
    {
        var component = e.GetComponent<MyComponent>();
        // ... draw properties
        ImGui.TreePop();
    }
}

// ✅ CORRECT - Use DrawComponent wrapper
public void DrawComponent(Entity e)
{
    ComponentEditorRegistry.DrawComponent<MyComponent>("My Component", e, entity =>
    {
        var component = entity.GetComponent<MyComponent>();
        // ... draw properties
    });
}

Why: DrawComponent provides consistent styling, remove button, and safety checks.


❌ Anti-Pattern 2: Direct ImGui Calls for Vectors

// ❌ WRONG - Raw ImGui calls
ImGui.DragFloat3("Position", ref component.Position);

// ✅ CORRECT - Use VectorPanel for axis colors and reset buttons
var newPosition = component.Position;
VectorPanel.DrawVec3Control("Position", ref newPosition);
if (newPosition != component.Position)
    component.Position = newPosition;

Why: VectorPanel provides axis color coding, reset buttons, and consistent styling.


❌ Anti-Pattern 3: Direct Component Property Mutation with ref

// ❌ WRONG - Modifying component property directly
VectorPanel.DrawVec3Control("Position", ref component.Position);  // May not work as expected

// ✅ CORRECT - Copy to temp variable first
var newPosition = component.Position;
VectorPanel.DrawVec3Control("Position", ref newPosition);
if (newPosition != component.Position)
    component.Position = newPosition;

Why: Component properties may be getters with backing fields or have change tracking.


❌ Anti-Pattern 4: Forgetting DI Registration

// ❌ WRONG - Editor won't be found at runtime
public class MyComponentEditor : IComponentEditor { ... }
// (not registered in Program.cs)

// ✅ CORRECT - Register in DI container
container.Register<MyComponentEditor>(Reuse.Singleton);

// AND add to ComponentEditorRegistry constructor + dictionary

Workflow: Creating a New Component Editor

Step 1: Create Editor Class

// Editor/​ComponentEditors/​MyComponentEditor.cs
using ECS;
using Editor.ComponentEditors.Core;
using Editor.Panels;
using Editor.UI.Drawers;
using Editor.UI.Elements;
using Engine.Scene.Components;

namespace Editor.ComponentEditors;

public class MyComponentEditor : IComponentEditor
{
    public void DrawComponent(Entity e)
    {
        ComponentEditorRegistry.DrawComponent<MyComponent>("My Component", e, entity =>
        {
            var component = entity.GetComponent<MyComponent>();

            // TODO: Add property editors here
        });
    }
}

Step 2: Register in DI Container (Program.cs)

// In editor startup
container.Register<MyComponentEditor>(Reuse.Singleton);

Step 3: Add to ComponentEditorRegistry

// ComponentEditorRegistry.cs - use primary constructor
public class ComponentEditorRegistry(
    // ... existing editors
    MyComponentEditor myComponentEditor) // Add parameter
{
    private readonly Dictionary<Type, IComponentEditor> _editors = new()
    {
        // ... existing registrations
        { typeof(MyComponent), myComponentEditor }  // Add to dictionary
    };
}

Step 4: Implement Property Editors

Choose the appropriate method for each property type:

// Primitives: Use UIPropertyRenderer
UIPropertyRenderer.DrawPropertyField("Health", component.Health,
    newValue => component.Health = (int)newValue);

// Vectors: Use VectorPanel
var newPos = component.Position;
VectorPanel.DrawVec3Control("Position", ref newPos);
if (newPos != component.Position)
    component.Position = newPos;

// Enums: Use LayoutDrawer
string[] options = Enum.GetNames<MyEnum>();
LayoutDrawer.DrawComboBox("Mode", options[(int)component.Mode], options,
    selected => component.Mode = (MyEnum)Array.IndexOf(options, selected));

Available UI Utilities

From UIPropertyRenderer

  • DrawPropertyField(string label, object value, Action<object> onValueChanged)
    • Supports: int, float, double, bool, string, Vector2, Vector3, Vector4
    • Uses FieldEditorRegistry internally

From VectorPanel

  • DrawVec3Control(string label, ref Vector3 values, float resetValue = 0.0f)
  • DrawVec2Control(string label, ref Vector2 values, float resetValue = 0.0f)

From LayoutDrawer

  • DrawComboBox(string label, string current, string[] options, Action<string> onSelected)

From ButtonDrawer

  • DrawButton(string label, ButtonType type = ButtonType.Default) → returns bool
  • DrawButton(string label, float width, float height, Action? onClick = null)

From TextDrawer

  • DrawErrorText(string text)
  • DrawWarningText(string text)
  • DrawSuccessText(string text)

From ModalDrawer

  • RenderConfirmationModal(string id, ref bool show, string message, Action onConfirm)

Summary

Component Editor Checklist:

  1. ✅ Implement IComponentEditor interface
  2. ✅ Use ComponentEditorRegistry.DrawComponent<T>() wrapper
  3. ✅ Use VectorPanel for vectors (axis colors, reset buttons)
  4. ✅ Use UIPropertyRenderer for simple primitives
  5. ✅ Use LayoutDrawer for dropdowns
  6. ✅ Manual change detection for ref parameters
  7. ✅ Register in DI container (Program.cs)
  8. ✅ Add to ComponentEditorRegistry constructor + dictionary

Key Files:

  • Editor/​ComponentEditors/​Core/​IComponentEditor.cs - Interface
  • Editor/​ComponentEditors/​Core/​ComponentEditorRegistry.cs - Registry and wrapper
  • Editor/​Panels/​VectorPanel.cs - Vector controls
  • Editor/​UI/​Elements/​UIPropertyRenderer.cs - Property field wrapper
  • Editor/​UI/​Drawers/​LayoutDrawer.cs - Combo boxes
  • Editor/​ComponentEditors/​TransformComponentEditor.cs - Vector example
  • Editor/​ComponentEditors/​CameraComponentEditor.cs - Mixed properties example

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View