// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors
{
///
/// The per-feature flags for custom editors system.
///
[HideInEditor, Flags]
public enum FeatureFlags
{
///
/// Nothing.
///
None = 0,
///
/// Enables caching the expanded groups in this presenter. Used to preserve the expanded groups using project cache.
///
CacheExpandedGroups = 1 << 0,
///
/// Enables using prefab-related features of the properties editor (eg. revert to prefab option).
///
UsePrefab = 1 << 1,
///
/// Enables using default-value-related features of the properties editor (eg. revert to default option).
///
UseDefault = 1 << 2,
}
///
/// The interface for Editor context that owns the presenter. Can be or or other window/panel - custom editor scan use it for more specific features.
///
public interface IPresenterOwner
{
///
/// Gets the viewport linked with properties presenter (optional, null if unused).
///
public Viewport.EditorViewport PresenterViewport { get; }
///
/// Selects the scene objects.
///
/// The nodes to select
public void Select(List nodes);
}
///
/// Main class for Custom Editors used to present selected objects properties and allow to modify them.
///
///
[HideInEditor]
public class CustomEditorPresenter : LayoutElementsContainer
{
///
/// The panel control.
///
///
public class PresenterPanel : VerticalPanel
{
private CustomEditorPresenter _presenter;
///
/// Gets the presenter.
///
public CustomEditorPresenter Presenter => _presenter;
internal PresenterPanel(CustomEditorPresenter presenter)
{
_presenter = presenter;
AnchorPreset = AnchorPresets.StretchAll;
Offsets = Margin.Zero;
IsScrollable = true;
}
///
public override void Update(float deltaTime)
{
try
{
// Update editors
_presenter.Update();
}
catch (Exception ex)
{
FlaxEditor.Editor.LogWarning(ex);
}
base.Update(deltaTime);
}
///
public override void OnDestroy()
{
base.OnDestroy();
_presenter = null;
}
}
///
/// The root editor. Mocks some custom editors events. Created a child editor for the selected objects.
///
///
protected class RootEditor : SyncPointEditor
{
private CustomEditor _overrideEditor;
///
/// The selected objects editor.
///
public CustomEditor Editor;
///
/// Gets or sets the override custom editor used to edit selected objects.
///
public CustomEditor OverrideEditor
{
get => _overrideEditor;
set
{
_overrideEditor = value;
RebuildLayout();
}
}
///
/// The text to show when no object is selected.
///
public string NoSelectionText;
///
/// Initializes a new instance of the class.
///
/// The text to show when no item is selected.
public RootEditor(string noSelectionText)
{
NoSelectionText = noSelectionText ?? "No selection";
}
///
/// Setups editor for selected objects.
///
/// The presenter.
public void Setup(CustomEditorPresenter presenter)
{
Cleanup();
Initialize(presenter, presenter, null);
}
///
public override void Initialize(LayoutElementsContainer layout)
{
Presenter.BeforeLayout?.Invoke(layout);
var selection = Presenter.Selection;
selection.ClearReferenceValue();
if (selection.Count > 0)
{
if (_overrideEditor != null)
{
Editor = _overrideEditor;
}
else
{
var type = ScriptType.Object;
if (selection.HasDifferentTypes == false)
type = TypeUtils.GetObjectType(selection[0]);
Editor = CustomEditorsUtil.CreateEditor(type, false);
}
Editor.Initialize(Presenter, Presenter, selection);
OnChildCreated(Editor);
}
else
{
var label = layout.Label(NoSelectionText, TextAlignment.Center);
label.Label.Height = 20.0f;
}
base.Initialize(layout);
Presenter.AfterLayout?.Invoke(layout);
}
///
protected override void Deinitialize()
{
Editor = null;
_overrideEditor = null;
base.Deinitialize();
}
///
protected override void OnModified()
{
Presenter.OnModified();
base.OnModified();
}
}
///
/// The panel.
///
public readonly PresenterPanel Panel;
///
/// The selected objects editor (root, it generates actual editor for selection).
///
protected readonly RootEditor Editor;
///
/// The selected objects list (read-only).
///
public readonly ValueContainer Selection = new ValueContainer(ScriptMemberInfo.Null);
///
/// The undo object used by this editor.
///
public readonly Undo Undo;
///
/// Occurs when selection gets changed.
///
public event Action SelectionChanged;
///
/// Occurs when any property gets changed.
///
public event Action Modified;
///
/// Occurs when presenter wants to gather undo objects to record changes. Can be overriden to provide custom objects collection.
///
public Func> GetUndoObjects = presenter => presenter.Selection;
///
/// Gets the amount of objects being selected.
///
public int SelectionCount => Selection.Count;
///
/// Gets or sets the override custom editor used to edit selected objects.
///
public CustomEditor OverrideEditor
{
get => Editor.OverrideEditor;
set => Editor.OverrideEditor = value;
}
///
/// Gets the root editor.
///
public CustomEditor Root => Editor;
///
/// Gets a value indicating whether build on update flag is set and layout will be updated during presenter update.
///
public bool BuildOnUpdate => _buildOnUpdate;
///
/// The features to use for properties editor.
///
public FeatureFlags Features = FeatureFlags.UsePrefab | FeatureFlags.UseDefault;
///
/// Occurs when before creating layout for the selected objects editor UI. Can be used to inject custom UI to the layout.
///
public event Action BeforeLayout;
///
/// Occurs when after creating layout for the selected objects editor UI. Can be used to inject custom UI to the layout.
///
public event Action AfterLayout;
///
/// The Editor context that owns this presenter. Can be or or other window/panel - custom editor scan use it for more specific features.
///
public IPresenterOwner Owner;
///
/// Gets or sets the text to show when no object is selected.
///
public string NoSelectionText
{
get => Editor.NoSelectionText;
set
{
Editor.NoSelectionText = value;
if (SelectionCount == 0)
BuildLayoutOnUpdate();
}
}
///
/// Gets or sets the value indicating whether properties are read-only.
///
public bool ReadOnly
{
get => _readOnly;
set
{
if (_readOnly != value)
{
_readOnly = value;
UpdateReadOnly();
}
}
}
private bool _buildOnUpdate;
private bool _readOnly;
///
/// Initializes a new instance of the class.
///
/// The undo. It's optional.
/// The custom text to display when no object is selected. Default is No selection.
/// The owner of the presenter.
public CustomEditorPresenter(Undo undo, string noSelectionText = null, IPresenterOwner owner = null)
{
Undo = undo;
Owner = owner;
Panel = new PresenterPanel(this);
Editor = new RootEditor(noSelectionText);
Editor.Initialize(this, this, null);
}
///
/// Selects the specified object.
///
/// The object.
public void Select(object obj)
{
if (obj == null)
{
Deselect();
return;
}
if (Selection.Count == 1 && Selection[0] == obj)
return;
Selection.Clear();
Selection.Add(obj);
Selection.SetType(new ScriptType(obj.GetType()));
OnSelectionChanged();
}
///
/// Selects the specified objects.
///
/// The objects.
public void Select(IEnumerable