---
name: editor-field-creation
description: Implement IFieldEditor interface for custom type rendering in script inspector. Covers reflection-based field editing, FieldEditorRegistry registration, boxing/unboxing patterns, and extending the field editor system for new types.
---
# Editor Field Editors (IFieldEditor)
## Overview
Field Editors (`IFieldEditor`) provide **runtime polymorphic rendering** for script properties discovered via reflection. They use a **non-generic, boxing-based interface** to handle arbitrary types at runtime.
## When to Use This Skill
- **Creating custom field editors** for new types (Quaternion, Color, custom structs)
- Understanding how script properties are rendered in Script Inspector
- Extending `FieldEditorRegistry` with new type support
- Working with `UIPropertyRenderer.DrawPropertyField()` infrastructure
- **NOT for component editors**
---
## Purpose & Context
### Two Different Systems
The engine has **two separate property editing systems**:
| System | Purpose | Interface | Usage |
|--------|---------|-----------|-------|
| **IFieldEditor** | Runtime script properties (reflection) | `IFieldEditor` (non-generic, boxing) | Script Inspector |
| **VectorPanel/UIPropertyRenderer** | Compile-time component properties | Static methods | Component Editors |
---
## Core Interface
```csharp
// Editor/UI/FieldEditors/IFieldEditor.cs
public interface IFieldEditor
{
///
/// Draws the editor UI for the field and returns true if the value was changed.
///
/// The ImGui label for the field (should include unique ID)
/// The current field value (boxed)
/// The new value if changed
/// True if the value was modified by user interaction
bool Draw(string label, object value, out object newValue);
}
```
**Key Characteristics:**
- ❌ **Not generic** - no `IFieldEditor`
- ✅ **Boxing-based** - uses `object` for value and newValue
- ✅ **Returns bool** - true if user changed the value
- ✅ **Out parameter** - newValue is the modified value
---
## Built-in Field Editors
### Primitive Type Editors
| Type | Implementation | File |
|------|---------------|------|
| `int` | IntFieldEditor | IntFieldEditor.cs |
| `float` | FloatFieldEditor | FloatFieldEditor.cs |
| `double` | DoubleFieldEditor | DoubleFieldEditor.cs |
| `bool` | BoolFieldEditor | BoolFieldEditor.cs |
| `string` | StringFieldEditor | StringFieldEditor.cs |
### Vector Type Editors
| Type | Implementation | File |
|------|---------------|------|
| `Vector2` | Vector2FieldEditor | Vector2FieldEditor.cs |
| `Vector3` | Vector3FieldEditor | Vector3FieldEditor.cs |
| `Vector4` | Vector4FieldEditor | Vector4FieldEditor.cs |
All registered in `FieldEditorRegistry` static dictionary.
---
## FieldEditorRegistry
**Central registry mapping types to editors:**
```csharp
// Editor/UI/FieldEditors/FieldEditorRegistry.cs
public static class FieldEditorRegistry
{
private static readonly Dictionary _editors = new()
{
{ typeof(int), new IntFieldEditor() },
{ typeof(float), new FloatFieldEditor() },
{ typeof(double), new DoubleFieldEditor() },
{ typeof(bool), new BoolFieldEditor() },
{ typeof(string), new StringFieldEditor() },
{ typeof(Vector2), new Vector2FieldEditor() },
{ typeof(Vector3), new Vector3FieldEditor() },
{ typeof(Vector4), new Vector4FieldEditor() }
};
public static IFieldEditor? GetEditor(Type type)
{
return _editors.TryGetValue(type, out var editor) ? editor : null;
}
public static bool HasEditor(Type type)
{
return _editors.ContainsKey(type);
}
}
```
**Usage Pattern (ScriptComponentEditor.cs:111-113):**
```csharp
var editor = FieldEditorRegistry.GetEditor(fieldType);
if (editor != null)
return editor.Draw(label, value, out newValue);
```
---
## Implementing a Custom Field Editor
### Example 1: Simple Primitive Editor (IntFieldEditor)
```csharp
// Editor/UI/FieldEditors/IntFieldEditor.cs
using ImGuiNET;
namespace Editor.UI.FieldEditors;
public class IntFieldEditor : IFieldEditor
{
public bool Draw(string label, object value, out object newValue)
{
var intValue = (int)value; // Unbox
var changed = ImGui.DragInt(label, ref intValue);
newValue = intValue; // Box
return changed;
}
}
```
**Pattern:**
1. Unbox `object value` to concrete type
2. Call ImGui widget with `ref` parameter
3. Box result into `out object newValue`
4. Return changed flag
---
### Example 2: Vector Editor (Vector3FieldEditor)
```csharp
// Editor/UI/FieldEditors/Vector3FieldEditor.cs
using System.Numerics;
using ImGuiNET;
namespace Editor.UI.FieldEditors;
public class Vector3FieldEditor : IFieldEditor
{
public bool Draw(string label, object value, out object newValue)
{
var v = (Vector3)value; // Unbox
var changed = ImGui.DragFloat3(label, ref v);
newValue = v; // Box
return changed;
}
}
```
---
### Example 3: Custom Type Editor (Quaternion)
```csharp
using System.Numerics;
using ImGuiNET;
namespace Editor.UI.FieldEditors;
public class QuaternionFieldEditor : IFieldEditor
{
public bool Draw(string label, object value, out object newValue)
{
var quat = (Quaternion)value;
// Convert to Euler angles for editing
var euler = QuaternionToEuler(quat);
var changed = ImGui.DragFloat3(label, ref euler);
if (changed)
{
// Convert back to quaternion
newValue = EulerToQuaternion(euler);
}
else
{
newValue = quat;
}
return changed;
}
private static Vector3 QuaternionToEuler(Quaternion q)
{
// Implementation...
}
private static Quaternion EulerToQuaternion(Vector3 euler)
{
// Implementation...
}
}
```
---
## Registering Custom Field Editors
### Step 1: Implement IFieldEditor
```csharp
public class MyCustomTypeEditor : IFieldEditor
{
public bool Draw(string label, object value, out object newValue)
{
var typed = (MyCustomType)value;
// Draw UI and modify 'typed'
bool changed = /* ... */;
newValue = typed;
return changed;
}
}
```
### Step 2: Register in FieldEditorRegistry
**Option A: Add to static dictionary (modify FieldEditorRegistry.cs)**
```csharp
private static readonly Dictionary _editors = new()
{
// ... existing editors
{ typeof(MyCustomType), new MyCustomTypeEditor() }
};
```
**Option B: Add runtime registration method (extensible)**
```csharp
// Add to FieldEditorRegistry.cs
public static void RegisterEditor(Type type, IFieldEditor editor)
{
_editors[type] = editor;
}
// Usage in initialization code
FieldEditorRegistry.RegisterEditor(typeof(Quaternion), new QuaternionFieldEditor());
```
---
## Usage in Script Inspector
### How ScriptComponentEditor Uses IFieldEditor
```csharp
// ScriptComponentEditor.cs (simplified)
private bool TryDrawFieldEditor(string label, Type type, object value, out object newValue)
{
newValue = value;
var editor = FieldEditorRegistry.GetEditor(type);
if (editor != null)
return editor.Draw(label, value, out newValue);
// Fallback: unsupported type
ImGui.TextDisabled($"Unsupported type: {type.Name}");
return false;
}
// Called per script field
if (TryDrawFieldEditor(fieldLabel, fieldType, fieldValue, out var newValue))
{
script.SetFieldValue(fieldName, newValue); // Reflection-based assignment
}
```
**Flow:**
1. Script reflection discovers field type at runtime
2. `FieldEditorRegistry.GetEditor(type)` looks up editor
3. If found, call `editor.Draw()` with boxed value
4. If changed, use reflection to assign new value back to script field
---
## Usage with UIPropertyRenderer
### UIPropertyRenderer.DrawPropertyField()
**Convenience wrapper** for simple use cases:
```csharp
// UIPropertyRenderer.cs:26-56
public static bool DrawPropertyField(string label, object? value, Action