Skip to main content

Overview

Frosty Toolsuite provides two main approaches to customizing the editing experience:
  1. Asset Editors: Full custom editors for specific asset types (inherit from FrostyAssetEditor)
  2. Type Editors: Custom property grid editors for specific field types (inherit from FrostyTypeEditor<T>)

FrostyAssetEditor

Create custom editors for entire asset types by inheriting from FrostyAssetEditor.

Base Class

public class FrostyAssetEditor : Control

Constructor

public FrostyAssetEditor(ILogger inLogger)
inLogger
ILogger
required
The logger instance for the editor to use

Properties

RootObject

public object RootObject { get; }
Gets the root object of the currently loaded asset.

RootObjects

public IEnumerable<object> RootObjects { get; }
Gets all root objects in the asset (some assets may have multiple roots).

Objects

public IEnumerable<object> Objects { get; }
Gets all objects in the asset, including nested objects.

AssetEntry

public AssetEntry AssetEntry { get; }
Gets the asset entry for the currently loaded asset.

Asset

public EbxAsset Asset { get; }
Gets the loaded EBX asset.

AssetModified

public bool AssetModified { get; set; }
Set to true to mark the asset as modified and save changes to the AssetManager.

Methods

SetAsset

public int SetAsset(AssetEntry entry)
Loads the specified asset into the editor. Called automatically by the editor framework.
entry
AssetEntry
required
The asset entry to load

LoadAsset

protected virtual EbxAsset LoadAsset(EbxAssetEntry entry)
Override to customize how the asset is loaded.
entry
EbxAssetEntry
required
The asset entry to load
return
EbxAsset
The loaded asset

AddDependentObject

public void AddDependentObject(Guid guid)
Adds a dependent object to the asset and loads it.
guid
Guid
required
The GUID of the asset to add as a dependency

GetDependentObject

public virtual EbxAsset GetDependentObject(Guid guid)
Retrieves a dependent object by GUID.
guid
Guid
required
The GUID of the dependent asset
return
EbxAsset
The dependent asset, or null if not found

RefreshDependentObject

public virtual EbxAsset RefreshDependentObject(Guid guid)
Reloads a dependent object from the AssetManager.
guid
Guid
required
The GUID of the dependent asset to refresh

AddObject

public virtual void AddObject(object obj)
Adds a new object to the asset.

RemoveObject

public virtual void RemoveObject(object obj)
Removes an object from the asset.

RegisterToolbarItems

public virtual List<ToolbarItem> RegisterToolbarItems()
Override to add custom toolbar buttons to the editor.
return
List<ToolbarItem>
List of toolbar items to add to the editor

Closed

public virtual void Closed()
Override to perform cleanup when the editor is closed.

Events

OnAssetModified

public event RoutedEventHandler OnAssetModified
Fired when the asset is modified.

Complete Example

using Frosty.Core.Controls;
using FrostySdk.Interfaces;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

public class MeshAssetEditor : FrostyAssetEditor
{
    private MeshViewport viewport;
    private FrostyPropertyGrid propertyGrid;
    
    static MeshAssetEditor()
    {
        DefaultStyleKeyProperty.OverrideMetadata(
            typeof(MeshAssetEditor),
            new FrameworkPropertyMetadata(typeof(MeshAssetEditor)));
    }
    
    public MeshAssetEditor(ILogger inLogger) : base(inLogger)
    {
    }
    
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        
        viewport = GetTemplateChild("PART_Viewport") as MeshViewport;
        propertyGrid = GetTemplateChild("PART_PropertyGrid") as FrostyPropertyGrid;
        
        if (viewport != null && Asset != null)
        {
            viewport.LoadMesh(Asset.RootObject);
        }
        
        if (propertyGrid != null && Asset != null)
        {
            propertyGrid.Object = Asset.RootObject;
        }
    }
    
    protected override void InvokeOnAssetModified()
    {
        base.InvokeOnAssetModified();
        
        // Refresh viewport when asset changes
        if (viewport != null)
        {
            viewport.LoadMesh(Asset.RootObject);
        }
    }
    
    public override List<ToolbarItem> RegisterToolbarItems()
    {
        var items = base.RegisterToolbarItems();
        
        items.Add(new ToolbarItem(
            "Export Mesh",
            "Export mesh to FBX",
            "Images/ExportIcon.png",
            new RelayCommand(ExportMesh_Click, ExportMesh_CanClick)));
            
        items.Add(new ToolbarItem(
            "Import Mesh",
            "Import mesh from FBX",
            "Images/ImportIcon.png",
            new RelayCommand(ImportMesh_Click, ImportMesh_CanClick)));
            
        return items;
    }
    
    private void ExportMesh_Click(object state)
    {
        // Export logic
        Microsoft.Win32.SaveFileDialog dialog = new Microsoft.Win32.SaveFileDialog()
        {
            Filter = "FBX Files (*.fbx)|*.fbx",
            DefaultExt = "fbx"
        };
        
        if (dialog.ShowDialog() == true)
        {
            // Export mesh
            ExportToFbx(Asset.RootObject, dialog.FileName);
        }
    }
    
    private bool ExportMesh_CanClick(object state) => true;
    
    private void ImportMesh_Click(object state)
    {
        // Import logic
        Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog()
        {
            Filter = "FBX Files (*.fbx)|*.fbx",
            DefaultExt = "fbx"
        };
        
        if (dialog.ShowDialog() == true)
        {
            // Import mesh
            ImportFromFbx(Asset.RootObject, dialog.FileName);
            AssetModified = true;
        }
    }
    
    private bool ImportMesh_CanClick(object state) => true;
    
    public override void Closed()
    {
        base.Closed();
        
        // Clean up viewport resources
        viewport?.Dispose();
    }
}

FrostyTypeEditor

Create custom property grid editors for specific field types.

Base Class

public abstract class FrostyTypeEditor<T> : FrostyBaseTypeEditor where T : FrameworkElement, new()

Properties

ValuePath
string
default:"Value"
The property path to bind to on the item data
BindingMode
BindingMode
default:"TwoWay"
The binding mode for the value property
ValueProperty
DependencyProperty
The dependency property to bind to on the editor control
ValueConverter
IValueConverter
Optional value converter for the binding
ValueConverterParameter
object
Parameter to pass to the value converter
ValidationRule
ValidationRule
Optional validation rule for the binding
NotifyOnTargetUpdated
bool
default:"false"
Whether to notify when the binding target is updated

Methods

CreateEditor

public T CreateEditor(FrostyPropertyGridItemData item)
Creates the editor control. Called automatically by the property grid.

CustomizeEditor

protected virtual void CustomizeEditor(T editor, FrostyPropertyGridItemData item)
Override to customize the editor after creation.

RefreshEditor

protected virtual void RefreshEditor(T editor)
Override to refresh the editor when the value changes.

Registration

Register type editors globally using the RegisterGlobalTypeEditorAttribute:
using Frosty.Core.Attributes;

[assembly: RegisterGlobalTypeEditor("Vec3", typeof(Vec3Editor))]
[assembly: RegisterGlobalTypeEditor("LinearTransform", typeof(LinearTransformEditor))]

Example: Vec3 Editor

using Frosty.Core.Controls.Editors;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

public class Vec3Editor : FrostyTypeEditor<Vec3EditorControl>
{
    public Vec3Editor()
    {
        ValueProperty = Vec3EditorControl.ValueProperty;
    }
    
    protected override void CustomizeEditor(Vec3EditorControl editor, FrostyPropertyGridItemData item)
    {
        base.CustomizeEditor(editor, item);
        
        // Additional customization
        editor.ShowLabels = true;
    }
}

public class Vec3EditorControl : Control
{
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register(
            "Value",
            typeof(object),
            typeof(Vec3EditorControl),
            new PropertyMetadata(null, OnValueChanged));
    
    public object Value
    {
        get => GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }
    
    public bool ShowLabels { get; set; } = true;
    
    private TextBox xBox, yBox, zBox;
    
    static Vec3EditorControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(
            typeof(Vec3EditorControl),
            new FrameworkPropertyMetadata(typeof(Vec3EditorControl)));
    }
    
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        
        xBox = GetTemplateChild("PART_X") as TextBox;
        yBox = GetTemplateChild("PART_Y") as TextBox;
        zBox = GetTemplateChild("PART_Z") as TextBox;
        
        if (xBox != null)
        {
            xBox.TextChanged += (s, e) => UpdateValue();
        }
        if (yBox != null)
        {
            yBox.TextChanged += (s, e) => UpdateValue();
        }
        if (zBox != null)
        {
            zBox.TextChanged += (s, e) => UpdateValue();
        }
        
        UpdateDisplay();
    }
    
    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        (d as Vec3EditorControl)?.UpdateDisplay();
    }
    
    private void UpdateDisplay()
    {
        if (Value == null || xBox == null)
            return;
            
        dynamic vec = Value;
        xBox.Text = vec.x.ToString();
        yBox.Text = vec.y.ToString();
        zBox.Text = vec.z.ToString();
    }
    
    private void UpdateValue()
    {
        if (Value == null || xBox == null)
            return;
            
        dynamic vec = Value;
        
        if (float.TryParse(xBox.Text, out float x))
            vec.x = x;
        if (float.TryParse(yBox.Text, out float y))
            vec.y = y;
        if (float.TryParse(zBox.Text, out float z))
            vec.z = z;
    }
}

ToolbarItem

Define custom toolbar buttons for asset editors.
public class ToolbarItem
{
    public string Text { get; }
    public string ToolTip { get; }
    public ImageSource Icon { get; }
    public RelayCommand Command { get; }
    
    public ToolbarItem(string text, string tooltip, string icon, RelayCommand inCommand)
}
text
string
required
Button text
tooltip
string
required
Tooltip text
icon
string
Path to icon image relative to plugin assembly
inCommand
RelayCommand
required
Command to execute when clicked

See Also

Build docs developers (and LLMs) love