You're breathtaking!
This commit is contained in:
746
Source/Editor/CustomEditors/CustomEditor.cs
Normal file
746
Source/Editor/CustomEditors/CustomEditor.cs
Normal file
@@ -0,0 +1,746 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Newtonsoft.Json;
|
||||
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor layout style modes.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public enum DisplayStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a separate group for the editor (drop down element). This is a default value.
|
||||
/// </summary>
|
||||
Group,
|
||||
|
||||
/// <summary>
|
||||
/// Inlines editor contents into the property area without creating a drop-down group.
|
||||
/// </summary>
|
||||
Inline,
|
||||
|
||||
/// <summary>
|
||||
/// Inlines editor contents into the parent editor layout. Won;t use property with label name.
|
||||
/// </summary>
|
||||
InlineIntoParent,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all custom editors used to present object(s) properties. Allows to extend game objects editing with more logic and customization.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public abstract class CustomEditor
|
||||
{
|
||||
private LayoutElementsContainer _layout;
|
||||
private CustomEditorPresenter _presenter;
|
||||
private CustomEditor _parent;
|
||||
private readonly List<CustomEditor> _children = new List<CustomEditor>();
|
||||
private ValueContainer _values;
|
||||
private bool _isSetBlocked;
|
||||
private bool _skipChildrenRefresh;
|
||||
private bool _hasValueDirty;
|
||||
private bool _rebuildOnRefresh;
|
||||
private object _valueToSet;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the custom editor style.
|
||||
/// </summary>
|
||||
public virtual DisplayStyle Style => DisplayStyle.Group;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether single object is selected.
|
||||
/// </summary>
|
||||
public bool IsSingleObject => _values.IsSingleObject;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether selected objects are different values.
|
||||
/// </summary>
|
||||
public bool HasDifferentValues => _values.HasDifferentValues;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether selected objects are different types.
|
||||
/// </summary>
|
||||
public bool HasDifferentTypes => _values.HasDifferentTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values types array (without duplicates).
|
||||
/// </summary>
|
||||
public ScriptType[] ValuesTypes => _values.ValuesTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values.
|
||||
/// </summary>
|
||||
public ValueContainer Values => _values;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent editor.
|
||||
/// </summary>
|
||||
public CustomEditor ParentEditor => _parent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the children editors (readonly).
|
||||
/// </summary>
|
||||
public List<CustomEditor> ChildrenEditors => _children;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the presenter control. It's the top most part of the Custom Editors layout.
|
||||
/// </summary>
|
||||
public CustomEditorPresenter Presenter => _presenter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether setting value is blocked (during refresh).
|
||||
/// </summary>
|
||||
protected bool IsSetBlocked => _isSetBlocked;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this editor needs value propagation up (value synchronization when one of the child editors changes value, used by the struct types).
|
||||
/// </summary>
|
||||
protected virtual bool NeedsValuePropagationUp => Values.HasValueType;
|
||||
|
||||
/// <summary>
|
||||
/// The linked label used to show this custom editor. Can be null if not used (eg. editor is inlined or is using a very customized UI layout).
|
||||
/// </summary>
|
||||
public PropertyNameLabel LinkedLabel;
|
||||
|
||||
internal virtual void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values)
|
||||
{
|
||||
_layout = layout;
|
||||
_presenter = presenter;
|
||||
_values = values;
|
||||
|
||||
var prev = CurrentCustomEditor;
|
||||
CurrentCustomEditor = this;
|
||||
|
||||
_isSetBlocked = true;
|
||||
Initialize(layout);
|
||||
Refresh();
|
||||
_isSetBlocked = false;
|
||||
|
||||
CurrentCustomEditor = prev;
|
||||
}
|
||||
|
||||
internal static CustomEditor CurrentCustomEditor;
|
||||
|
||||
internal void OnChildCreated(CustomEditor child)
|
||||
{
|
||||
// Link
|
||||
_children.Add(child);
|
||||
child._parent = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the editor layout. Cleans the whole UI with child elements/editors and then builds new hierarchy. Call it only when necessary.
|
||||
/// </summary>
|
||||
public void RebuildLayout()
|
||||
{
|
||||
// Special case for root objects to run normal layout build
|
||||
if (_presenter.Selection == Values)
|
||||
{
|
||||
_presenter.BuildLayout();
|
||||
return;
|
||||
}
|
||||
|
||||
var values = _values;
|
||||
var presenter = _presenter;
|
||||
var layout = _layout;
|
||||
var control = layout.ContainerControl;
|
||||
var parent = _parent;
|
||||
var parentScrollV = (_presenter.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
|
||||
|
||||
control.IsLayoutLocked = true;
|
||||
control.DisposeChildren();
|
||||
|
||||
layout.ClearLayout();
|
||||
Cleanup();
|
||||
|
||||
_parent = parent;
|
||||
Initialize(presenter, layout, values);
|
||||
|
||||
control.IsLayoutLocked = false;
|
||||
control.PerformLayout();
|
||||
|
||||
// Restore scroll value
|
||||
if (parentScrollV > -1 && _presenter != null && _presenter.Panel.Parent is Panel panel && panel.VScrollBar != null)
|
||||
panel.VScrollBar.Value = parentScrollV;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the editor layout on editor refresh. Postponed after dirty value gets synced. Call it after <see cref="SetValue"/> to update editor after actual value assign.
|
||||
/// </summary>
|
||||
public void RebuildLayoutOnRefresh()
|
||||
{
|
||||
_rebuildOnRefresh = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the request to skip the custom editor children refresh during next update. Can be used when editor layout has to be rebuild during the update itself.
|
||||
/// </summary>
|
||||
public void SkipChildrenRefresh()
|
||||
{
|
||||
_skipChildrenRefresh = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this editor.
|
||||
/// </summary>
|
||||
/// <param name="layout">The layout builder.</param>
|
||||
public abstract void Initialize(LayoutElementsContainer layout);
|
||||
|
||||
/// <summary>
|
||||
/// Deinitializes this editor (unbind events and cleanup).
|
||||
/// </summary>
|
||||
protected virtual void Deinitialize()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups this editor resources and child editors.
|
||||
/// </summary>
|
||||
internal void Cleanup()
|
||||
{
|
||||
Deinitialize();
|
||||
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
{
|
||||
_children[i].Cleanup();
|
||||
}
|
||||
|
||||
_children.Clear();
|
||||
_presenter = null;
|
||||
_parent = null;
|
||||
_hasValueDirty = false;
|
||||
_valueToSet = null;
|
||||
_values = null;
|
||||
_isSetBlocked = false;
|
||||
_rebuildOnRefresh = false;
|
||||
LinkedLabel = null;
|
||||
}
|
||||
|
||||
internal void RefreshRoot()
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
_children[i].RefreshRootChild();
|
||||
}
|
||||
catch (TargetException ex)
|
||||
{
|
||||
// This happens when something (from root editor) calls the error.
|
||||
// Just handle it and rebuild UI. Some parts of the pipeline can report data problems via that exception.
|
||||
Editor.LogWarning("Exception while updating the root editors");
|
||||
Editor.LogWarning(ex);
|
||||
Presenter.BuildLayoutOnUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
internal void RefreshRootChild()
|
||||
{
|
||||
Refresh();
|
||||
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
_children[i].RefreshInternal();
|
||||
}
|
||||
|
||||
internal virtual void RefreshInternal()
|
||||
{
|
||||
if (_values == null)
|
||||
return;
|
||||
|
||||
// Check if need to update value
|
||||
if (_hasValueDirty)
|
||||
{
|
||||
// Cleanup (won't retry update in case of exception)
|
||||
object val = _valueToSet;
|
||||
_hasValueDirty = false;
|
||||
_valueToSet = null;
|
||||
|
||||
// Assign value
|
||||
_values.Set(_parent.Values, val);
|
||||
|
||||
// Propagate values up (eg. when member of structure gets modified, also structure should be updated as a part of the other object)
|
||||
var obj = _parent;
|
||||
while (obj._parent != null && !(obj._parent is SyncPointEditor)) // && obj.NeedsValuePropagationUp)
|
||||
{
|
||||
obj.Values.Set(obj._parent.Values, obj.Values);
|
||||
obj = obj._parent;
|
||||
}
|
||||
|
||||
OnUnDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update values
|
||||
_values.Refresh(_parent.Values);
|
||||
}
|
||||
|
||||
// Update itself
|
||||
_isSetBlocked = true;
|
||||
Refresh();
|
||||
_isSetBlocked = false;
|
||||
|
||||
// Update children
|
||||
try
|
||||
{
|
||||
var childrenCount = _skipChildrenRefresh ? 0 : _children.Count;
|
||||
for (int i = 0; i < childrenCount; i++)
|
||||
_children[i].RefreshInternal();
|
||||
_skipChildrenRefresh = false;
|
||||
}
|
||||
catch (TargetException ex)
|
||||
{
|
||||
// This happens when one of the edited objects changes its type.
|
||||
// Eg. from Label to TextBox, while both types were assigned to the same field of type Control.
|
||||
// It's valid, just rebuild the child editors and log the warning to keep it tracking.
|
||||
Editor.LogWarning("Exception while updating the child editors");
|
||||
Editor.LogWarning(ex);
|
||||
RebuildLayoutOnRefresh();
|
||||
}
|
||||
|
||||
// Rebuild if flag is set
|
||||
if (_rebuildOnRefresh)
|
||||
{
|
||||
_rebuildOnRefresh = false;
|
||||
RebuildLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes this editor.
|
||||
/// </summary>
|
||||
public virtual void Refresh()
|
||||
{
|
||||
if (LinkedLabel != null)
|
||||
{
|
||||
// Prefab value diff
|
||||
if (Values.HasReferenceValue)
|
||||
{
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
LinkedLabel.HighlightStripColor = CanRevertReferenceValue ? style.BackgroundSelected * 0.8f : Color.Transparent;
|
||||
}
|
||||
// Default value diff
|
||||
else if (Values.HasDefaultValue)
|
||||
{
|
||||
LinkedLabel.HighlightStripColor = CanRevertDefaultValue ? Color.Yellow * 0.8f : Color.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void LinkLabel(PropertyNameLabel label)
|
||||
{
|
||||
LinkedLabel = label;
|
||||
}
|
||||
|
||||
private void RevertDiffToDefault(CustomEditor editor)
|
||||
{
|
||||
if (editor.ChildrenEditors.Count == 0)
|
||||
{
|
||||
// Skip if no change detected
|
||||
if (!editor.Values.IsDefaultValueModified)
|
||||
return;
|
||||
|
||||
editor.SetValueToDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
|
||||
{
|
||||
RevertDiffToDefault(editor.ChildrenEditors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this editor can revert the value to default value.
|
||||
/// </summary>
|
||||
public bool CanRevertDefaultValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Values.IsDefaultValueModified)
|
||||
return false;
|
||||
|
||||
// Skip array items (show diff only on a bottom level properties and fields)
|
||||
if (ParentEditor != null && ParentEditor.Values.Type != ScriptType.Null && ParentEditor.Values.Type.IsArray)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverts the property value to default value (if has). Handles undo.
|
||||
/// </summary>
|
||||
public void RevertToDefaultValue()
|
||||
{
|
||||
if (!Values.HasDefaultValue)
|
||||
return;
|
||||
|
||||
Editor.Log("Reverting object changes to default");
|
||||
|
||||
RevertDiffToDefault(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the default value assigned to the editor's values container. Sends the event down the custom editors hierarchy to propagate the change.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Has no effect on editors that don't have default value assigned.
|
||||
/// </remarks>
|
||||
public void RefreshDefaultValue()
|
||||
{
|
||||
if (!Values.HasDefaultValue)
|
||||
return;
|
||||
|
||||
if (ParentEditor?.Values?.HasDefaultValue ?? false)
|
||||
{
|
||||
Values.RefreshDefaultValue(ParentEditor.Values.DefaultValue);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ChildrenEditors.Count; i++)
|
||||
{
|
||||
ChildrenEditors[i].RefreshDefaultValue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all the default value of the container in the whole custom editors tree (this container and all children).
|
||||
/// </summary>
|
||||
public void ClearDefaultValueAll()
|
||||
{
|
||||
Values.ClearDefaultValue();
|
||||
|
||||
for (int i = 0; i < ChildrenEditors.Count; i++)
|
||||
{
|
||||
ChildrenEditors[i].ClearDefaultValueAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void RevertDiffToReference(CustomEditor editor)
|
||||
{
|
||||
if (editor.ChildrenEditors.Count == 0)
|
||||
{
|
||||
// Skip if no change detected
|
||||
if (!editor.Values.IsReferenceValueModified)
|
||||
return;
|
||||
|
||||
editor.SetValueToReference();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
|
||||
{
|
||||
RevertDiffToReference(editor.ChildrenEditors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this editor can revert the value to reference value.
|
||||
/// </summary>
|
||||
public bool CanRevertReferenceValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Values.IsReferenceValueModified)
|
||||
return false;
|
||||
|
||||
// Skip array items (show diff only on a bottom level properties and fields)
|
||||
if (ParentEditor != null && ParentEditor.Values.Type != ScriptType.Null && ParentEditor.Values.Type.IsArray)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverts the property value to reference value (if has). Handles undo.
|
||||
/// </summary>
|
||||
public void RevertToReferenceValue()
|
||||
{
|
||||
if (!Values.HasReferenceValue)
|
||||
return;
|
||||
|
||||
Editor.Log("Reverting object changes to prefab");
|
||||
|
||||
RevertDiffToReference(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the reference value assigned to the editor's values container. Sends the event down the custom editors hierarchy to propagate the change.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Has no effect on editors that don't have reference value assigned.
|
||||
/// </remarks>
|
||||
public void RefreshReferenceValue()
|
||||
{
|
||||
if (!Values.HasReferenceValue)
|
||||
return;
|
||||
|
||||
if (ParentEditor?.Values?.HasReferenceValue ?? false)
|
||||
{
|
||||
Values.RefreshReferenceValue(ParentEditor.Values.ReferenceValue);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ChildrenEditors.Count; i++)
|
||||
{
|
||||
ChildrenEditors[i].RefreshReferenceValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clears all the reference value of the container in the whole custom editors tree (this container and all children).
|
||||
/// </summary>
|
||||
public void ClearReferenceValueAll()
|
||||
{
|
||||
Values.ClearReferenceValue();
|
||||
|
||||
for (int i = 0; i < ChildrenEditors.Count; i++)
|
||||
{
|
||||
ChildrenEditors[i].ClearReferenceValueAll();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the value to the system clipboard.
|
||||
/// </summary>
|
||||
public void Copy()
|
||||
{
|
||||
Editor.Log("Copy custom editor value");
|
||||
|
||||
try
|
||||
{
|
||||
string text;
|
||||
if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(Values.Type))
|
||||
{
|
||||
text = JsonSerializer.GetStringID(Values[0] as FlaxEngine.Object);
|
||||
}
|
||||
else
|
||||
{
|
||||
text = JsonSerializer.Serialize(Values[0]);
|
||||
}
|
||||
|
||||
Clipboard.Text = text;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogWarning(ex);
|
||||
Editor.LogError("Cannot copy property. See log for more info.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetClipboardObject(out object result)
|
||||
{
|
||||
result = null;
|
||||
var text = Clipboard.Text;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return false;
|
||||
|
||||
object obj;
|
||||
if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(Values.Type))
|
||||
{
|
||||
if (text.Length != 32)
|
||||
return false;
|
||||
JsonSerializer.ParseID(text, out var id);
|
||||
obj = FlaxEngine.Object.Find<FlaxEngine.Object>(ref id);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = JsonConvert.DeserializeObject(text, TypeUtils.GetType(Values.Type), JsonSerializer.Settings);
|
||||
}
|
||||
|
||||
if (obj == null || Values.Type.IsInstanceOfType(obj))
|
||||
{
|
||||
result = obj;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether can paste value from the system clipboard to the property value container.
|
||||
/// </summary>
|
||||
public bool CanPaste
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetClipboardObject(out _);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value from the system clipboard.
|
||||
/// </summary>
|
||||
public void Paste()
|
||||
{
|
||||
Editor.Log("Paste custom editor value");
|
||||
|
||||
try
|
||||
{
|
||||
if (GetClipboardObject(out var obj))
|
||||
{
|
||||
SetValue(obj);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogWarning(ex);
|
||||
Editor.LogError("Cannot paste property value. See log for more info.");
|
||||
}
|
||||
}
|
||||
|
||||
private Actor FindPrefabRoot(CustomEditor editor)
|
||||
{
|
||||
if (editor.Values[0] is Actor actor)
|
||||
return FindPrefabRoot(actor);
|
||||
if (editor.ParentEditor != null)
|
||||
return FindPrefabRoot(editor.ParentEditor);
|
||||
return null;
|
||||
}
|
||||
|
||||
private Actor FindPrefabRoot(Actor actor)
|
||||
{
|
||||
if (actor is Scene)
|
||||
return null;
|
||||
if (actor.IsPrefabRoot)
|
||||
return actor;
|
||||
return FindPrefabRoot(actor.Parent);
|
||||
}
|
||||
|
||||
private ISceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId)
|
||||
{
|
||||
if (actor.PrefabObjectID == prefabObjectId)
|
||||
return actor;
|
||||
|
||||
for (int i = 0; i < actor.ScriptsCount; i++)
|
||||
{
|
||||
if (actor.GetScript(i).PrefabObjectID == prefabObjectId)
|
||||
{
|
||||
var a = actor.GetScript(i);
|
||||
if (a != null)
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < actor.ChildrenCount; i++)
|
||||
{
|
||||
if (actor.GetChild(i).PrefabObjectID == prefabObjectId)
|
||||
{
|
||||
var a = actor.GetChild(i);
|
||||
if (a != null)
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor value to the default value (if assigned).
|
||||
/// </summary>
|
||||
public void SetValueToDefault()
|
||||
{
|
||||
SetValue(Values.DefaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor value to the reference value (if assigned).
|
||||
/// </summary>
|
||||
public void SetValueToReference()
|
||||
{
|
||||
// Special case for object references
|
||||
// If prefab object has reference to other object in prefab needs to revert to matching prefab instance object not the reference prefab object value
|
||||
if (Values.ReferenceValue is ISceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink)
|
||||
{
|
||||
if (Values.Count > 1)
|
||||
{
|
||||
Editor.LogError("Cannot revert to reference value for more than one object selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
Actor prefabInstanceRoot = FindPrefabRoot(this);
|
||||
if (prefabInstanceRoot == null)
|
||||
{
|
||||
Editor.LogError("Cannot revert to reference value. Missing prefab instance root actor.");
|
||||
return;
|
||||
}
|
||||
|
||||
var prefabObjectId = referenceSceneObject.PrefabObjectID;
|
||||
var prefabInstanceRef = FindObjectWithPrefabObjectId(prefabInstanceRoot, ref prefabObjectId);
|
||||
if (prefabInstanceRef == null)
|
||||
{
|
||||
Editor.LogWarning("Missing prefab instance reference in the prefab instance. Cannot revert to it.");
|
||||
}
|
||||
|
||||
SetValue(prefabInstanceRef);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SetValue(Values.ReferenceValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the object value. Actual update is performed during editor refresh in sync.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="token">The source editor token used by the value setter to support batching Undo actions (eg. for sliders or color pickers).</param>
|
||||
protected void SetValue(object value, object token = null)
|
||||
{
|
||||
if (_isSetBlocked)
|
||||
return;
|
||||
|
||||
if (OnDirty(this, value, token))
|
||||
{
|
||||
_hasValueDirty = true;
|
||||
_valueToSet = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when custom editor gets dirty (UI value has been modified).
|
||||
/// Allows to filter the event, block it or handle in a custom way.
|
||||
/// </summary>
|
||||
/// <param name="editor">The editor.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="token">The source editor token used by the value setter to support batching Undo actions (eg. for sliders or color pickers).</param>
|
||||
/// <returns>True if allow to handle this event, otherwise false.</returns>
|
||||
protected virtual bool OnDirty(CustomEditor editor, object value, object token = null)
|
||||
{
|
||||
ParentEditor.OnDirty(editor, value, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when custom editor sets the value to the object and resets the dirty state. Can be sued to perform custom work after editing the target object.
|
||||
/// </summary>
|
||||
protected virtual void OnUnDirty()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the token assigned with <see cref="OnDirty"/> parameter. Called on merged undo action end (eg. users stops using slider).
|
||||
/// </summary>
|
||||
protected virtual void ClearToken()
|
||||
{
|
||||
ParentEditor.ClearToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
380
Source/Editor/CustomEditors/CustomEditorPresenter.cs
Normal file
380
Source/Editor/CustomEditors/CustomEditorPresenter.cs
Normal file
@@ -0,0 +1,380 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class for Custom Editors used to present selected objects properties and allow to modify them.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElementsContainer" />
|
||||
[HideInEditor]
|
||||
public class CustomEditorPresenter : LayoutElementsContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The panel control.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.VerticalPanel" />
|
||||
public class PresenterPanel : VerticalPanel
|
||||
{
|
||||
private CustomEditorPresenter _presenter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the presenter.
|
||||
/// </summary>
|
||||
public CustomEditorPresenter Presenter => _presenter;
|
||||
|
||||
internal PresenterPanel(CustomEditorPresenter presenter)
|
||||
{
|
||||
_presenter = presenter;
|
||||
AnchorPreset = AnchorPresets.StretchAll;
|
||||
Offsets = Margin.Zero;
|
||||
IsScrollable = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
// Update editors
|
||||
_presenter.Update();
|
||||
|
||||
base.Update(deltaTime);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
_presenter = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The root editor. Mocks some custom editors events. Created a child editor for the selected objects.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.SyncPointEditor" />
|
||||
protected class RootEditor : SyncPointEditor
|
||||
{
|
||||
private readonly string _noSelectionText;
|
||||
private CustomEditor _overrideEditor;
|
||||
|
||||
/// <summary>
|
||||
/// The selected objects editor.
|
||||
/// </summary>
|
||||
public CustomEditor Editor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the override custom editor used to edit selected objects.
|
||||
/// </summary>
|
||||
public CustomEditor OverrideEditor
|
||||
{
|
||||
get => _overrideEditor;
|
||||
set
|
||||
{
|
||||
_overrideEditor = value;
|
||||
RebuildLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RootEditor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="noSelectionText">The text to show when no item is selected.</param>
|
||||
public RootEditor(string noSelectionText)
|
||||
{
|
||||
_noSelectionText = noSelectionText ?? "No selection";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setups editor for selected objects.
|
||||
/// </summary>
|
||||
/// <param name="presenter">The presenter.</param>
|
||||
public void Setup(CustomEditorPresenter presenter)
|
||||
{
|
||||
Cleanup();
|
||||
Initialize(presenter, presenter, null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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 = new ScriptType(typeof(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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnModified()
|
||||
{
|
||||
Presenter.Modified?.Invoke();
|
||||
|
||||
base.OnModified();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The panel.
|
||||
/// </summary>
|
||||
public readonly PresenterPanel Panel;
|
||||
|
||||
/// <summary>
|
||||
/// The selected objects editor (root, it generates actual editor for selection).
|
||||
/// </summary>
|
||||
protected readonly RootEditor Editor;
|
||||
|
||||
/// <summary>
|
||||
/// The selected objects list (read-only).
|
||||
/// </summary>
|
||||
public readonly ValueContainer Selection = new ValueContainer(ScriptMemberInfo.Null);
|
||||
|
||||
/// <summary>
|
||||
/// The undo object used by this editor.
|
||||
/// </summary>
|
||||
public readonly Undo Undo;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when selection gets changed.
|
||||
/// </summary>
|
||||
public event Action SelectionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when any property gets changed.
|
||||
/// </summary>
|
||||
public event Action Modified;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when presenter wants to gather undo objects to record changes. Can be overriden to provide custom objects collection.
|
||||
/// </summary>
|
||||
public Func<CustomEditorPresenter, IEnumerable<object>> GetUndoObjects = presenter => presenter.Selection;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of objects being selected.
|
||||
/// </summary>
|
||||
public int SelectionCount => Selection.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the override custom editor used to edit selected objects.
|
||||
/// </summary>
|
||||
public CustomEditor OverrideEditor
|
||||
{
|
||||
get => Editor.OverrideEditor;
|
||||
set => Editor.OverrideEditor = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root editor.
|
||||
/// </summary>
|
||||
public CustomEditor Root => Editor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether build on update flag is set and layout will be updated during presenter update.
|
||||
/// </summary>
|
||||
public bool BuildOnUpdate => _buildOnUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// True if cache the expanded groups in this presenter, otherwise will disable this feature. Used to preserve the expanded groups using project cache.
|
||||
/// </summary>
|
||||
public bool CacheExpandedGroups;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when before creating layout for the selected objects editor UI. Can be used to inject custom UI to the layout.
|
||||
/// </summary>
|
||||
public event Action<LayoutElementsContainer> BeforeLayout;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when after creating layout for the selected objects editor UI. Can be used to inject custom UI to the layout.
|
||||
/// </summary>
|
||||
public event Action<LayoutElementsContainer> AfterLayout;
|
||||
|
||||
private bool _buildOnUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomEditorPresenter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="undo">The undo. It's optional.</param>
|
||||
/// <param name="noSelectionText">The custom text to display when no object is selected. Default is No selection.</param>
|
||||
public CustomEditorPresenter(Undo undo, string noSelectionText = null)
|
||||
{
|
||||
Undo = undo;
|
||||
Panel = new PresenterPanel(this);
|
||||
Editor = new RootEditor(noSelectionText);
|
||||
Editor.Initialize(this, this, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects the specified object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
public void Select(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
Deselect();
|
||||
return;
|
||||
}
|
||||
if (Selection.Count == 1 && Selection[0] == obj)
|
||||
return;
|
||||
|
||||
Selection.Clear();
|
||||
Selection.Add(obj);
|
||||
|
||||
OnSelectionChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects the specified objects.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects.</param>
|
||||
public void Select(IEnumerable<object> objects)
|
||||
{
|
||||
if (objects == null)
|
||||
{
|
||||
Deselect();
|
||||
return;
|
||||
}
|
||||
var objectsArray = objects as object[] ?? objects.ToArray();
|
||||
if (Utils.ArraysEqual(objectsArray, Selection))
|
||||
return;
|
||||
|
||||
Selection.Clear();
|
||||
Selection.AddRange(objectsArray);
|
||||
|
||||
OnSelectionChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the selected objects.
|
||||
/// </summary>
|
||||
public void Deselect()
|
||||
{
|
||||
if (Selection.Count == 0)
|
||||
return;
|
||||
|
||||
Selection.Clear();
|
||||
|
||||
OnSelectionChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the editors layout.
|
||||
/// </summary>
|
||||
public virtual void BuildLayout()
|
||||
{
|
||||
// Clear layout
|
||||
var panel = Panel.Parent as Panel;
|
||||
var parentScrollV = panel?.VScrollBar?.Value ?? -1;
|
||||
Panel.IsLayoutLocked = true;
|
||||
Panel.DisposeChildren();
|
||||
|
||||
ClearLayout();
|
||||
Editor.Setup(this);
|
||||
|
||||
Panel.IsLayoutLocked = false;
|
||||
Panel.PerformLayout();
|
||||
|
||||
// Restore scroll value
|
||||
if (parentScrollV > -1)
|
||||
panel.VScrollBar.Value = parentScrollV;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the request to build the editor layout on the next update.
|
||||
/// </summary>
|
||||
public void BuildLayoutOnUpdate()
|
||||
{
|
||||
_buildOnUpdate = true;
|
||||
}
|
||||
|
||||
private void ExpandGroups(LayoutElementsContainer c, bool open)
|
||||
{
|
||||
if (c is Elements.GroupElement group)
|
||||
{
|
||||
if (open)
|
||||
group.Panel.Open(false);
|
||||
else
|
||||
group.Panel.Close(false);
|
||||
}
|
||||
|
||||
foreach (var child in c.Children)
|
||||
{
|
||||
if (child is LayoutElementsContainer cc)
|
||||
ExpandGroups(cc, open);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands all the groups in this editor.
|
||||
/// </summary>
|
||||
public void OpenAllGroups()
|
||||
{
|
||||
ExpandGroups(this, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes all the groups in this editor.
|
||||
/// </summary>
|
||||
public void ClosesAllGroups()
|
||||
{
|
||||
ExpandGroups(this, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when selection gets changed.
|
||||
/// </summary>
|
||||
protected virtual void OnSelectionChanged()
|
||||
{
|
||||
BuildLayout();
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates custom editors. Refreshes UI values and applies changes to the selected objects.
|
||||
/// </summary>
|
||||
internal void Update()
|
||||
{
|
||||
if (_buildOnUpdate)
|
||||
{
|
||||
_buildOnUpdate = false;
|
||||
BuildLayout();
|
||||
}
|
||||
|
||||
Editor?.RefreshInternal();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ContainerControl ContainerControl => Panel;
|
||||
}
|
||||
}
|
||||
208
Source/Editor/CustomEditors/CustomEditorsUtil.cpp
Normal file
208
Source/Editor/CustomEditors/CustomEditorsUtil.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CustomEditorsUtil.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MField.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/reflection.h>
|
||||
|
||||
#define TRACK_ASSEMBLY(assembly) \
|
||||
if (assembly->IsLoaded()) \
|
||||
{ \
|
||||
OnAssemblyLoaded(assembly); \
|
||||
} \
|
||||
assembly->Loaded.Bind(OnAssemblyLoaded); \
|
||||
assembly->Unloading.Bind(OnAssemblyUnloading)
|
||||
|
||||
class CustomEditorsUtilService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
CustomEditorsUtilService()
|
||||
: EngineService(TEXT("Custom Editors Util"))
|
||||
{
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
};
|
||||
|
||||
CustomEditorsUtilService CustomEditorsUtilServiceInstance;
|
||||
|
||||
struct Entry
|
||||
{
|
||||
MClass* DefaultEditor;
|
||||
MClass* CustomEditor;
|
||||
|
||||
Entry()
|
||||
{
|
||||
DefaultEditor = nullptr;
|
||||
CustomEditor = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
Dictionary<MonoType*, Entry> Cache(512);
|
||||
|
||||
void OnAssemblyLoaded(MAssembly* assembly);
|
||||
void OnAssemblyUnloading(MAssembly* assembly);
|
||||
void OnBinaryModuleLoaded(BinaryModule* module);
|
||||
|
||||
MonoReflectionType* CustomEditorsUtil::GetCustomEditor(MonoReflectionType* refType)
|
||||
{
|
||||
MonoType* type = mono_reflection_type_get_type(refType);
|
||||
|
||||
Entry result;
|
||||
if (Cache.TryGet(type, result))
|
||||
{
|
||||
const auto editor = result.CustomEditor ? result.CustomEditor : result.DefaultEditor;
|
||||
if (editor)
|
||||
{
|
||||
return MUtils::GetType(editor->GetNative());
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CustomEditorsUtilService::Init()
|
||||
{
|
||||
TRACK_ASSEMBLY(GetBinaryModuleFlaxEngine()->Assembly);
|
||||
Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void OnAssemblyLoaded(MAssembly* assembly)
|
||||
{
|
||||
const auto startTime = DateTime::NowUTC();
|
||||
|
||||
// Prepare FlaxEngine
|
||||
auto engineAssembly = GetBinaryModuleFlaxEngine()->Assembly;
|
||||
if (!engineAssembly->IsLoaded())
|
||||
{
|
||||
LOG(Warning, "Cannot load custom editors meta for assembly {0} because FlaxEngine is not loaded.", assembly->ToString());
|
||||
return;
|
||||
}
|
||||
auto customEditorAttribute = engineAssembly->GetClass("FlaxEngine.CustomEditorAttribute");
|
||||
if (customEditorAttribute == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing CustomEditorAttribute class.");
|
||||
return;
|
||||
}
|
||||
auto customEditorTypeField = customEditorAttribute->GetField("Type");
|
||||
ASSERT(customEditorTypeField);
|
||||
const auto defaultEditorAttribute = engineAssembly->GetClass("FlaxEngine.DefaultEditorAttribute");
|
||||
if (defaultEditorAttribute == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing DefaultEditorAttribute class.");
|
||||
return;
|
||||
}
|
||||
const auto customEditor = engineAssembly->GetClass("FlaxEditor.CustomEditors.CustomEditor");
|
||||
if (customEditor == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing CustomEditor class.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Process all classes to find custom editors
|
||||
auto& classes = assembly->GetClasses();
|
||||
for (auto i = classes.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
const auto mclass = i->Value;
|
||||
|
||||
// Skip generic classes
|
||||
if (mclass->IsGeneric())
|
||||
continue;
|
||||
|
||||
const auto attribute = mclass->GetAttribute(customEditorAttribute);
|
||||
if (attribute == nullptr || mono_object_get_class(attribute) != customEditorAttribute->GetNative())
|
||||
continue;
|
||||
|
||||
// Check if attribute references a valid class
|
||||
MonoReflectionType* refType = nullptr;
|
||||
customEditorTypeField->GetValue(attribute, &refType);
|
||||
if (refType == nullptr)
|
||||
continue;
|
||||
|
||||
MonoType* type = mono_reflection_type_get_type(refType);
|
||||
if (type == nullptr)
|
||||
continue;
|
||||
MonoClass* typeClass = mono_type_get_class(type);
|
||||
|
||||
// Check if it's a custom editor class
|
||||
if (mclass->IsSubClassOf(customEditor))
|
||||
{
|
||||
auto& entry = Cache[type];
|
||||
|
||||
const bool isDefault = mclass->HasAttribute(defaultEditorAttribute);
|
||||
if (isDefault)
|
||||
{
|
||||
entry.DefaultEditor = mclass;
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.CustomEditor = mclass;
|
||||
}
|
||||
|
||||
//LOG(Info, "Custom Editor {0} for type {1} (default: {2})", String(mclass->GetFullName()), String(mono_type_get_name(type)), isDefault);
|
||||
}
|
||||
else if (typeClass)
|
||||
{
|
||||
MClass* referencedClass = Scripting::FindClass(typeClass);
|
||||
if (referencedClass)
|
||||
{
|
||||
auto& entry = Cache[mono_class_get_type(mclass->GetNative())];
|
||||
entry.CustomEditor = referencedClass;
|
||||
|
||||
//LOG(Info, "Custom Editor {0} for type {1}", String(referencedClass->GetFullName()), String(mclass->GetFullName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto endTime = DateTime::NowUTC();
|
||||
LOG(Info, "Assembly \'{0}\' scanned for custom editors in {1} ms", assembly->ToString(), (int32)(endTime - startTime).GetTotalMilliseconds());
|
||||
}
|
||||
|
||||
void OnAssemblyUnloading(MAssembly* assembly)
|
||||
{
|
||||
// Fast clear for editor unloading
|
||||
if (assembly == GetBinaryModuleFlaxEngine()->Assembly)
|
||||
{
|
||||
Cache.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove entries with user classes
|
||||
for (auto i = Cache.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
MonoClass* monoClass = (MonoClass*)(void*)i->Key;
|
||||
|
||||
if (assembly->GetClass(monoClass))
|
||||
{
|
||||
Cache.Remove(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i->Value.DefaultEditor && assembly->GetClass(i->Value.DefaultEditor->GetNative()))
|
||||
i->Value.DefaultEditor = nullptr;
|
||||
if (i->Value.CustomEditor && assembly->GetClass(i->Value.CustomEditor->GetNative()))
|
||||
i->Value.CustomEditor = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnBinaryModuleLoaded(BinaryModule* module)
|
||||
{
|
||||
auto managedModule = dynamic_cast<ManagedBinaryModule*>(module);
|
||||
if (managedModule)
|
||||
{
|
||||
TRACK_ASSEMBLY(managedModule->Assembly);
|
||||
}
|
||||
}
|
||||
179
Source/Editor/CustomEditors/CustomEditorsUtil.cs
Normal file
179
Source/Editor/CustomEditors/CustomEditorsUtil.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
internal static class CustomEditorsUtil
|
||||
{
|
||||
private static readonly StringBuilder CachedSb = new StringBuilder(256);
|
||||
|
||||
internal static readonly Dictionary<Type, string> InBuildTypeNames = new Dictionary<Type, string>()
|
||||
{
|
||||
{ typeof(bool), "bool" },
|
||||
{ typeof(byte), "byte" },
|
||||
{ typeof(sbyte), "sbyte" },
|
||||
{ typeof(char), "char" },
|
||||
{ typeof(short), "short" },
|
||||
{ typeof(ushort), "ushort" },
|
||||
{ typeof(int), "int" },
|
||||
{ typeof(uint), "uint" },
|
||||
{ typeof(long), "ulong" },
|
||||
{ typeof(float), "float" },
|
||||
{ typeof(double), "double" },
|
||||
{ typeof(decimal), "decimal" },
|
||||
{ typeof(string), "string" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type name for UI. Handles in-build types like System.Single and returns float.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>The result.</returns>
|
||||
public static string GetTypeNameUI(Type type)
|
||||
{
|
||||
if (!InBuildTypeNames.TryGetValue(type, out var result))
|
||||
{
|
||||
result = type.Name;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>The result.</returns>
|
||||
public static string GetPropertyNameUI(string name)
|
||||
{
|
||||
int length = name.Length;
|
||||
StringBuilder sb = CachedSb;
|
||||
sb.Clear();
|
||||
sb.EnsureCapacity(length + 8);
|
||||
int startIndex = 0;
|
||||
|
||||
// Skip some prefixes
|
||||
if (name.StartsWith("g_") || name.StartsWith("m_"))
|
||||
startIndex = 2;
|
||||
|
||||
// Filter text
|
||||
for (int i = startIndex; i < length; i++)
|
||||
{
|
||||
var c = name[i];
|
||||
|
||||
// Space before word starting with uppercase letter
|
||||
if (char.IsUpper(c) && i > 0)
|
||||
{
|
||||
if (i + 2 < length && !char.IsUpper(name[i + 1]) && !char.IsUpper(name[i + 2]))
|
||||
sb.Append(' ');
|
||||
}
|
||||
// Space instead of underscore
|
||||
else if (c == '_')
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
sb.Append(' ');
|
||||
continue;
|
||||
}
|
||||
// Space before digits sequence
|
||||
else if (i > 1 && char.IsDigit(c) && !char.IsDigit(name[i - 1]))
|
||||
sb.Append(' ');
|
||||
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
internal static CustomEditor CreateEditor(ValueContainer values, CustomEditor overrideEditor, bool canUseRefPicker = true)
|
||||
{
|
||||
// Check if use provided editor
|
||||
if (overrideEditor != null)
|
||||
return overrideEditor;
|
||||
|
||||
// Special case if property is a pure object type and all values are the same type
|
||||
if (values.Type.Type == typeof(object) && values.Count > 0 && values[0] != null && !values.HasDifferentTypes)
|
||||
return CreateEditor(TypeUtils.GetObjectType(values[0]), canUseRefPicker);
|
||||
|
||||
// Use editor for the property type
|
||||
return CreateEditor(values.Type, canUseRefPicker);
|
||||
}
|
||||
|
||||
internal static CustomEditor CreateEditor(ScriptType targetType, bool canUseRefPicker = true)
|
||||
{
|
||||
if (targetType.IsArray)
|
||||
{
|
||||
return new ArrayEditor();
|
||||
}
|
||||
var targetTypeType = TypeUtils.GetType(targetType);
|
||||
if (canUseRefPicker)
|
||||
{
|
||||
if (typeof(Asset).IsAssignableFrom(targetTypeType))
|
||||
{
|
||||
return new AssetRefEditor();
|
||||
}
|
||||
if (typeof(FlaxEngine.Object).IsAssignableFrom(targetTypeType))
|
||||
{
|
||||
return new FlaxObjectRefEditor();
|
||||
}
|
||||
}
|
||||
|
||||
// Use custom editor
|
||||
{
|
||||
var checkType = targetTypeType;
|
||||
do
|
||||
{
|
||||
var type = Internal_GetCustomEditor(checkType);
|
||||
if (type != null)
|
||||
{
|
||||
return (CustomEditor)Activator.CreateInstance(type);
|
||||
}
|
||||
checkType = checkType.BaseType;
|
||||
|
||||
// Skip if cannot use ref editors
|
||||
if (!canUseRefPicker && checkType == typeof(FlaxEngine.Object))
|
||||
break;
|
||||
} while (checkType != null);
|
||||
}
|
||||
|
||||
// Use attribute editor
|
||||
var attributes = targetType.GetAttributes(false);
|
||||
var customEditorAttribute = (CustomEditorAttribute)attributes.FirstOrDefault(x => x is CustomEditorAttribute);
|
||||
if (customEditorAttribute != null)
|
||||
return (CustomEditor)Activator.CreateInstance(customEditorAttribute.Type);
|
||||
|
||||
// Select default editor (based on type)
|
||||
if (targetType.IsEnum)
|
||||
{
|
||||
return new EnumEditor();
|
||||
}
|
||||
if (targetType.IsGenericType)
|
||||
{
|
||||
if (DictionaryEditor.CanEditType(targetTypeType))
|
||||
{
|
||||
return new DictionaryEditor();
|
||||
}
|
||||
|
||||
// Use custom editor
|
||||
var genericTypeDefinition = targetType.GetGenericTypeDefinition();
|
||||
var type = Internal_GetCustomEditor(genericTypeDefinition);
|
||||
if (type != null)
|
||||
{
|
||||
return (CustomEditor)Activator.CreateInstance(type);
|
||||
}
|
||||
}
|
||||
|
||||
// The most generic editor
|
||||
return new GenericEditor();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern Type Internal_GetCustomEditor(Type targetType);
|
||||
}
|
||||
}
|
||||
15
Source/Editor/CustomEditors/CustomEditorsUtil.h
Normal file
15
Source/Editor/CustomEditors/CustomEditorsUtil.h
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ManagedCLR/MTypes.h"
|
||||
|
||||
/// <summary>
|
||||
/// Helper utility class to quickly scan assemblies to gather metadata for custom editor feature.
|
||||
/// </summary>
|
||||
class CustomEditorsUtil
|
||||
{
|
||||
public:
|
||||
|
||||
static MonoReflectionType* GetCustomEditor(MonoReflectionType* refType);
|
||||
};
|
||||
439
Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
Normal file
439
Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
Normal file
@@ -0,0 +1,439 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Actions;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Json;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Dedicated custom editor for <see cref="Actor"/> objects.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.Editors.GenericEditor" />
|
||||
[CustomEditor(typeof(Actor)), DefaultEditor]
|
||||
public class ActorEditor : GenericEditor
|
||||
{
|
||||
private Guid _linkedPrefabId;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
|
||||
{
|
||||
// Note: we cannot specify actor properties editor types directly because we want to keep editor classes in FlaxEditor assembly
|
||||
int order = item.Order?.Order ?? int.MinValue;
|
||||
switch (order)
|
||||
{
|
||||
// Override static flags editor
|
||||
case -80:
|
||||
item.CustomEditor = new CustomEditorAttribute(typeof(ActorStaticFlagsEditor));
|
||||
break;
|
||||
|
||||
// Override layer editor
|
||||
case -69:
|
||||
item.CustomEditor = new CustomEditorAttribute(typeof(ActorLayerEditor));
|
||||
break;
|
||||
|
||||
// Override tag editor
|
||||
case -68:
|
||||
item.CustomEditor = new CustomEditorAttribute(typeof(ActorTagEditor));
|
||||
break;
|
||||
|
||||
// Override position/scale editor
|
||||
case -30:
|
||||
case -10:
|
||||
item.CustomEditor = new CustomEditorAttribute(typeof(ActorTransformEditor.PositionScaleEditor));
|
||||
break;
|
||||
|
||||
// Override orientation editor
|
||||
case -20:
|
||||
item.CustomEditor = new CustomEditorAttribute(typeof(ActorTransformEditor.OrientationEditor));
|
||||
break;
|
||||
}
|
||||
|
||||
base.SpawnProperty(itemLayout, itemValues, item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override List<ItemInfo> GetItemsForType(ScriptType type)
|
||||
{
|
||||
var items = base.GetItemsForType(type);
|
||||
|
||||
// Inject scripts editor
|
||||
var scriptsMember = type.GetProperty("Scripts");
|
||||
if (scriptsMember != ScriptMemberInfo.Null)
|
||||
{
|
||||
var item = new ItemInfo(scriptsMember);
|
||||
item.CustomEditor = new CustomEditorAttribute(typeof(ScriptsEditor));
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
// Check for prefab link
|
||||
if (Values.IsSingleObject && Values[0] is Actor actor && actor.HasPrefabLink)
|
||||
{
|
||||
// TODO: consider editing more than one instance of the same prefab asset at once
|
||||
|
||||
var prefab = FlaxEngine.Content.LoadAsync<Prefab>(actor.PrefabID);
|
||||
// TODO: don't stall here?
|
||||
if (prefab && !prefab.WaitForLoaded())
|
||||
{
|
||||
var prefabObjectId = actor.PrefabObjectID;
|
||||
var prefabInstance = prefab.GetDefaultInstance(ref prefabObjectId);
|
||||
if (prefabInstance != null)
|
||||
{
|
||||
// Use default prefab instance as a reference for the editor
|
||||
Values.SetReferenceValue(prefabInstance);
|
||||
|
||||
// Add some UI
|
||||
var panel = layout.CustomContainer<UniformGridPanel>();
|
||||
panel.CustomControl.Height = 20.0f;
|
||||
panel.CustomControl.SlotsVertically = 1;
|
||||
panel.CustomControl.SlotsHorizontally = 2;
|
||||
|
||||
// Selecting actor prefab asset
|
||||
var selectPrefab = panel.Button("Select Prefab");
|
||||
selectPrefab.Button.Clicked += () => Editor.Instance.Windows.ContentWin.Select(prefab);
|
||||
|
||||
// Viewing changes applied to this actor
|
||||
var viewChanges = panel.Button("View Changes");
|
||||
viewChanges.Button.Clicked += () => ViewChanges(viewChanges.Button, new Vector2(0.0f, 20.0f));
|
||||
|
||||
// Link event to update editor on prefab apply
|
||||
_linkedPrefabId = prefab.ID;
|
||||
Editor.Instance.Prefabs.PrefabApplying += OnPrefabApplying;
|
||||
Editor.Instance.Prefabs.PrefabApplied += OnPrefabApplied;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.Initialize(layout);
|
||||
|
||||
// Add custom settings button to General group
|
||||
for (int i = 0; i < layout.Children.Count; i++)
|
||||
{
|
||||
if (layout.Children[i] is GroupElement group && group.Panel.HeaderText == "General")
|
||||
{
|
||||
const float settingsButtonSize = 14;
|
||||
var settingsButton = new Image
|
||||
{
|
||||
TooltipText = "Settings",
|
||||
AutoFocus = true,
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Parent = group.Panel,
|
||||
Bounds = new Rectangle(group.Panel.Width - settingsButtonSize, 0, settingsButtonSize, settingsButtonSize),
|
||||
IsScrollable = false,
|
||||
Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
|
||||
Margin = new Margin(1),
|
||||
Brush = new SpriteBrush(FlaxEngine.GUI.Style.Current.Settings),
|
||||
};
|
||||
settingsButton.Clicked += OnSettingsButtonClicked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSettingsButtonClicked(Image image, MouseButton mouseButton)
|
||||
{
|
||||
if (mouseButton != MouseButton.Left)
|
||||
return;
|
||||
|
||||
var cm = new ContextMenu();
|
||||
var actor = (Actor)Values[0];
|
||||
var scriptType = TypeUtils.GetType(actor.TypeName);
|
||||
var item = scriptType.ContentItem;
|
||||
cm.AddButton("Copy ID", OnClickCopyId);
|
||||
cm.AddButton("Edit actor type", OnClickEditActorType).Enabled = item != null;
|
||||
var showButton = cm.AddButton("Show in content window", OnClickShowActorType);
|
||||
showButton.Enabled = item != null;
|
||||
showButton.Icon = Editor.Instance.Icons.Search12;
|
||||
cm.Show(image, image.Size);
|
||||
}
|
||||
|
||||
private void OnClickCopyId()
|
||||
{
|
||||
var actor = (Actor)Values[0];
|
||||
Clipboard.Text = JsonSerializer.GetStringID(actor.ID);
|
||||
}
|
||||
|
||||
private void OnClickEditActorType()
|
||||
{
|
||||
var actor = (Actor)Values[0];
|
||||
var scriptType = TypeUtils.GetType(actor.TypeName);
|
||||
var item = scriptType.ContentItem;
|
||||
if (item != null)
|
||||
Editor.Instance.ContentEditing.Open(item);
|
||||
}
|
||||
|
||||
private void OnClickShowActorType()
|
||||
{
|
||||
var actor = (Actor)Values[0];
|
||||
var scriptType = TypeUtils.GetType(actor.TypeName);
|
||||
var item = scriptType.ContentItem;
|
||||
if (item != null)
|
||||
Editor.Instance.Windows.ContentWin.Select(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
base.Deinitialize();
|
||||
|
||||
if (_linkedPrefabId != Guid.Empty)
|
||||
{
|
||||
_linkedPrefabId = Guid.Empty;
|
||||
Editor.Instance.Prefabs.PrefabApplied -= OnPrefabApplying;
|
||||
Editor.Instance.Prefabs.PrefabApplied -= OnPrefabApplied;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPrefabApplied(Prefab prefab, Actor instance)
|
||||
{
|
||||
if (prefab.ID == _linkedPrefabId)
|
||||
{
|
||||
// This works fine but in PrefabWindow when using live update it crashes on using color picker/float slider because UI is being rebuild
|
||||
//Presenter.BuildLayoutOnUpdate();
|
||||
|
||||
// Better way is to just update the reference value using the new default instance of the prefab, created after changes apply
|
||||
if (prefab && !prefab.WaitForLoaded())
|
||||
{
|
||||
var actor = (Actor)Values[0];
|
||||
var prefabObjectId = actor.PrefabObjectID;
|
||||
var prefabInstance = prefab.GetDefaultInstance(ref prefabObjectId);
|
||||
if (prefabInstance != null)
|
||||
{
|
||||
Values.SetReferenceValue(prefabInstance);
|
||||
RefreshReferenceValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPrefabApplying(Prefab prefab, Actor instance)
|
||||
{
|
||||
if (prefab.ID == _linkedPrefabId)
|
||||
{
|
||||
// Unlink reference value (it gets deleted by the prefabs system during apply)
|
||||
ClearReferenceValueAll();
|
||||
}
|
||||
}
|
||||
|
||||
private TreeNode CreateDiffNode(CustomEditor editor)
|
||||
{
|
||||
var node = new TreeNode(false);
|
||||
|
||||
node.Tag = editor;
|
||||
|
||||
// Removed Script
|
||||
if (editor is RemovedScriptDummy removed)
|
||||
{
|
||||
node.TextColor = Color.OrangeRed;
|
||||
node.Text = CustomEditorsUtil.GetPropertyNameUI(removed.PrefabObject.GetType().Name);
|
||||
}
|
||||
// Actor or Script
|
||||
else if (editor.Values[0] is ISceneObject sceneObject)
|
||||
{
|
||||
node.TextColor = sceneObject.HasPrefabLink ? FlaxEngine.GUI.Style.Current.ProgressNormal : FlaxEngine.GUI.Style.Current.BackgroundSelected;
|
||||
node.Text = CustomEditorsUtil.GetPropertyNameUI(sceneObject.GetType().Name);
|
||||
}
|
||||
// Array Item
|
||||
else if (editor.ParentEditor?.Values?.Type.IsArray ?? false)
|
||||
{
|
||||
node.Text = "Element " + editor.ParentEditor.ChildrenEditors.IndexOf(editor);
|
||||
}
|
||||
// Common type
|
||||
else if (editor.Values.Info != ScriptMemberInfo.Null)
|
||||
{
|
||||
node.Text = CustomEditorsUtil.GetPropertyNameUI(editor.Values.Info.Name);
|
||||
}
|
||||
// Custom type
|
||||
else if (editor.Values[0] != null)
|
||||
{
|
||||
node.Text = editor.Values[0].ToString();
|
||||
}
|
||||
|
||||
node.Expand(true);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private class RemovedScriptDummy : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The removed prefab object (from the prefab default instance).
|
||||
/// </summary>
|
||||
public Script PrefabObject;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
// Not used
|
||||
}
|
||||
}
|
||||
|
||||
private TreeNode ProcessDiff(CustomEditor editor, bool skipIfNotModified = true)
|
||||
{
|
||||
// Special case for new Script added to actor
|
||||
if (editor.Values[0] is Script script && !script.HasPrefabLink)
|
||||
{
|
||||
return CreateDiffNode(editor);
|
||||
}
|
||||
|
||||
// Skip if no change detected
|
||||
if (!editor.Values.IsReferenceValueModified && skipIfNotModified)
|
||||
return null;
|
||||
|
||||
TreeNode result = null;
|
||||
|
||||
if (editor.ChildrenEditors.Count == 0)
|
||||
result = CreateDiffNode(editor);
|
||||
|
||||
bool isScriptEditorWithRefValue = editor is ScriptsEditor && editor.Values.HasReferenceValue;
|
||||
|
||||
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
|
||||
{
|
||||
var child = ProcessDiff(editor.ChildrenEditors[i], !isScriptEditorWithRefValue);
|
||||
if (child != null)
|
||||
{
|
||||
if (result == null)
|
||||
result = CreateDiffNode(editor);
|
||||
|
||||
result.AddChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
// Show scripts removed from prefab instance (user may want to restore them)
|
||||
if (editor is ScriptsEditor && editor.Values.HasReferenceValue && editor.Values.ReferenceValue is Script[] prefabObjectScripts)
|
||||
{
|
||||
for (int j = 0; j < prefabObjectScripts.Length; j++)
|
||||
{
|
||||
var prefabObjectScript = prefabObjectScripts[j];
|
||||
bool isRemoved = true;
|
||||
|
||||
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
|
||||
{
|
||||
if (editor.ChildrenEditors[i].Values is ScriptsEditor.ScriptsContainer container && container.PrefabObjectId == prefabObjectScript.PrefabObjectID)
|
||||
{
|
||||
// Found
|
||||
isRemoved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRemoved)
|
||||
{
|
||||
var dummy = new RemovedScriptDummy
|
||||
{
|
||||
PrefabObject = prefabObjectScript
|
||||
};
|
||||
|
||||
var child = CreateDiffNode(dummy);
|
||||
if (result == null)
|
||||
result = CreateDiffNode(editor);
|
||||
result.AddChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ViewChanges(Control target, Vector2 targetLocation)
|
||||
{
|
||||
// Build a tree out of modified properties
|
||||
var rootNode = ProcessDiff(this, false);
|
||||
|
||||
// Skip if no changes detected
|
||||
if (rootNode == null || rootNode.ChildrenCount == 0)
|
||||
{
|
||||
var cm1 = new ContextMenu();
|
||||
cm1.AddButton("No changes detected");
|
||||
cm1.Show(target, targetLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create context menu
|
||||
var cm = new PrefabDiffContextMenu();
|
||||
cm.Tree.AddChild(rootNode);
|
||||
cm.Tree.RightClick += OnDiffNodeRightClick;
|
||||
cm.Tree.Tag = cm;
|
||||
cm.RevertAll += OnDiffRevertAll;
|
||||
cm.ApplyAll += OnDiffApplyAll;
|
||||
cm.Show(target, targetLocation);
|
||||
}
|
||||
|
||||
private void OnDiffNodeRightClick(TreeNode node, Vector2 location)
|
||||
{
|
||||
var diffMenu = (PrefabDiffContextMenu)node.ParentTree.Tag;
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.AddButton("Revert", () => OnDiffRevert((CustomEditor)node.Tag));
|
||||
menu.AddSeparator();
|
||||
menu.AddButton("Revert All", OnDiffRevertAll);
|
||||
menu.AddButton("Apply All", OnDiffApplyAll);
|
||||
|
||||
diffMenu.ShowChild(menu, node.PointToParent(diffMenu, new Vector2(location.X, node.HeaderHeight)));
|
||||
}
|
||||
|
||||
private void OnDiffRevertAll()
|
||||
{
|
||||
RevertToReferenceValue();
|
||||
}
|
||||
|
||||
private void OnDiffApplyAll()
|
||||
{
|
||||
Editor.Instance.Prefabs.ApplyAll((Actor)Values[0]);
|
||||
|
||||
// Ensure to refresh the layout
|
||||
Presenter.BuildLayoutOnUpdate();
|
||||
}
|
||||
|
||||
private void OnDiffRevert(CustomEditor editor)
|
||||
{
|
||||
// Special case for removed Script from actor
|
||||
if (editor is RemovedScriptDummy removed)
|
||||
{
|
||||
Editor.Log("Reverting removed script changes to prefab (adding it)");
|
||||
|
||||
var actor = (Actor)Values[0];
|
||||
var restored = actor.AddScript(removed.PrefabObject.GetType());
|
||||
var prefabId = actor.PrefabID;
|
||||
var prefabObjectId = restored.PrefabObjectID;
|
||||
Script.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(restored), ref prefabId, ref prefabObjectId);
|
||||
string data = JsonSerializer.Serialize(removed.PrefabObject);
|
||||
JsonSerializer.Deserialize(restored, data);
|
||||
|
||||
var action = AddRemoveScript.Added(restored);
|
||||
Presenter.Undo?.AddAction(action);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case for new Script added to actor
|
||||
if (editor.Values[0] is Script script && !script.HasPrefabLink)
|
||||
{
|
||||
Editor.Log("Reverting added script changes to prefab (removing it)");
|
||||
|
||||
var action = AddRemoveScript.Remove(script);
|
||||
action.Do();
|
||||
Presenter.Undo?.AddAction(action);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
editor.RevertToReferenceValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Source/Editor/CustomEditors/Dedicated/AnimatedModelEditor.cs
Normal file
37
Source/Editor/CustomEditors/Dedicated/AnimatedModelEditor.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Surface;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="AnimatedModel"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(AnimatedModel)), DefaultEditor]
|
||||
public class AnimatedModelEditor : ActorEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
// Show instanced parameters to view/edit at runtime
|
||||
if (Values.IsSingleObject && Editor.Instance.StateMachine.IsPlayMode)
|
||||
{
|
||||
var group = layout.Group("Parameters");
|
||||
group.Panel.Open(false);
|
||||
group.Panel.IndexInParent -= 2;
|
||||
|
||||
var animatedModel = (AnimatedModel)Values[0];
|
||||
var parameters = animatedModel.Parameters;
|
||||
var data = SurfaceUtils.InitGraphParameters(parameters);
|
||||
SurfaceUtils.DisplayGraphParameters(group, data,
|
||||
(instance, parameter, tag) => ((AnimatedModel)instance).GetParameterValue(parameter.Identifier),
|
||||
(instance, value, parameter, tag) => ((AnimatedModel)instance).SetParameterValue(parameter.Identifier, value),
|
||||
Values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
177
Source/Editor/CustomEditors/Dedicated/CurveObjectEditor.cs
Normal file
177
Source/Editor/CustomEditors/Dedicated/CurveObjectEditor.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="BezierCurve{T}"/>.
|
||||
/// </summary>
|
||||
class BezierCurveObjectEditor<T> : CustomEditor where T : struct
|
||||
{
|
||||
private bool _isSetting;
|
||||
private BezierCurveEditor<T> _curve;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var item = layout.CustomContainer<BezierCurveEditor<T>>();
|
||||
_curve = item.CustomControl;
|
||||
_curve.Height = 120.0f;
|
||||
_curve.Edited += OnCurveEdited;
|
||||
}
|
||||
|
||||
private void OnCurveEdited()
|
||||
{
|
||||
if (_isSetting)
|
||||
return;
|
||||
|
||||
_isSetting = true;
|
||||
SetValue(new BezierCurve<T>(_curve.Keyframes));
|
||||
_isSetting = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
var value = (BezierCurve<T>)Values[0];
|
||||
if (value != null && !_curve.IsUserEditing && !Utils.ArraysEqual(value.Keyframes, _curve.Keyframes))
|
||||
{
|
||||
_isSetting = true;
|
||||
_curve.SetKeyframes(value.Keyframes);
|
||||
_isSetting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
_curve = null;
|
||||
|
||||
base.Deinitialize();
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(BezierCurve<int>)), DefaultEditor]
|
||||
sealed class IntBezierCurveObjectEditor : BezierCurveObjectEditor<int>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(BezierCurve<float>)), DefaultEditor]
|
||||
sealed class FloatBezierCurveObjectEditor : BezierCurveObjectEditor<float>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(BezierCurve<Vector2>)), DefaultEditor]
|
||||
sealed class Vector2BezierCurveObjectEditor : BezierCurveObjectEditor<Vector2>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(BezierCurve<Vector3>)), DefaultEditor]
|
||||
sealed class Vector3BezierCurveObjectEditor : BezierCurveObjectEditor<Vector3>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(BezierCurve<Vector4>)), DefaultEditor]
|
||||
sealed class Vector4BezierCurveObjectEditor : BezierCurveObjectEditor<Vector4>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(BezierCurve<Quaternion>)), DefaultEditor]
|
||||
sealed class QuaternionBezierCurveObjectEditor : BezierCurveObjectEditor<Quaternion>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(BezierCurve<Color>)), DefaultEditor]
|
||||
sealed class ColorBezierCurveObjectEditor : BezierCurveObjectEditor<Color>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="LinearCurve{T}"/>.
|
||||
/// </summary>
|
||||
class LinearCurveObjectEditor<T> : CustomEditor where T : struct
|
||||
{
|
||||
private bool _isSetting;
|
||||
private LinearCurveEditor<T> _curve;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var item = layout.CustomContainer<LinearCurveEditor<T>>();
|
||||
_curve = item.CustomControl;
|
||||
_curve.Height = 120.0f;
|
||||
_curve.Edited += OnCurveEdited;
|
||||
}
|
||||
|
||||
private void OnCurveEdited()
|
||||
{
|
||||
if (_isSetting)
|
||||
return;
|
||||
|
||||
_isSetting = true;
|
||||
SetValue(new LinearCurve<T>(_curve.Keyframes));
|
||||
_isSetting = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
var value = (LinearCurve<T>)Values[0];
|
||||
if (value != null && !_curve.IsUserEditing && !Utils.ArraysEqual(value.Keyframes, _curve.Keyframes))
|
||||
{
|
||||
_isSetting = true;
|
||||
_curve.SetKeyframes(value.Keyframes);
|
||||
_isSetting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
_curve = null;
|
||||
|
||||
base.Deinitialize();
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(LinearCurve<int>)), DefaultEditor]
|
||||
sealed class IntLinearCurveObjectEditor : LinearCurveObjectEditor<int>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(LinearCurve<float>)), DefaultEditor]
|
||||
sealed class FloatLinearCurveObjectEditor : LinearCurveObjectEditor<float>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(LinearCurve<Vector2>)), DefaultEditor]
|
||||
sealed class Vector2LinearCurveObjectEditor : LinearCurveObjectEditor<Vector2>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(LinearCurve<Vector3>)), DefaultEditor]
|
||||
sealed class Vector3LinearCurveObjectEditor : LinearCurveObjectEditor<Vector3>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(LinearCurve<Vector4>)), DefaultEditor]
|
||||
sealed class Vector4LinearCurveObjectEditor : LinearCurveObjectEditor<Vector4>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(LinearCurve<Quaternion>)), DefaultEditor]
|
||||
sealed class QuaternionLinearCurveObjectEditor : LinearCurveObjectEditor<Quaternion>
|
||||
{
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(LinearCurve<Color>)), DefaultEditor]
|
||||
sealed class ColorLinearCurveObjectEditor : LinearCurveObjectEditor<Color>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="EnvironmentProbe"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(EnvironmentProbe)), DefaultEditor]
|
||||
public class EnvironmentProbeEditor : ActorEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
if (Values.HasDifferentTypes == false)
|
||||
{
|
||||
// Add 'Bake' button
|
||||
layout.Space(10);
|
||||
var button = layout.Button("Bake");
|
||||
button.Button.Clicked += BakeButtonClicked;
|
||||
}
|
||||
}
|
||||
|
||||
private void BakeButtonClicked()
|
||||
{
|
||||
for (int i = 0; i < Values.Count; i++)
|
||||
{
|
||||
if (Values[i] is EnvironmentProbe envProbe)
|
||||
{
|
||||
envProbe.Bake();
|
||||
Editor.Instance.Scene.MarkSceneEdited(envProbe.Scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Source/Editor/CustomEditors/Dedicated/FontReferenceEditor.cs
Normal file
24
Source/Editor/CustomEditors/Dedicated/FontReferenceEditor.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="FontReference"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="GenericEditor" />
|
||||
[CustomEditor(typeof(FontReference)), DefaultEditor]
|
||||
public class FontReferenceEditor : GenericEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override List<ItemInfo> GetItemsForType(ScriptType type)
|
||||
{
|
||||
// Show properties
|
||||
return GetItemsForType(type, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
184
Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs
Normal file
184
Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Content.Settings;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for layers matrix editor. Used to define layer-based collision detection for <see cref="PhysicsSettings.LayerMasks"/>
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.CustomEditor" />
|
||||
public sealed class LayersMatrixEditor : CustomEditor
|
||||
{
|
||||
private int _layersCount;
|
||||
private List<CheckBox> _checkBoxes;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.InlineIntoParent;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
string[] layerNames = LayersAndTagsSettings.GetCurrentLayers();
|
||||
int layersCount = Math.Max(4, layerNames.Length);
|
||||
_checkBoxes = new List<CheckBox>();
|
||||
_layersCount = layersCount;
|
||||
|
||||
float labelsWidth = 100.0f;
|
||||
float labelsHeight = 18;
|
||||
|
||||
var panel = layout.Space(100).Spacer;
|
||||
var gridPanel = new GridPanel(0)
|
||||
{
|
||||
Parent = panel,
|
||||
};
|
||||
|
||||
var upperLeftCell = new Label
|
||||
{
|
||||
Parent = gridPanel,
|
||||
};
|
||||
|
||||
var upperRightCell = new VerticalPanel
|
||||
{
|
||||
ClipChildren = false,
|
||||
Pivot = new Vector2(0.0f, 0.0f),
|
||||
Offset = new Vector2(-labelsWidth, 0),
|
||||
Rotation = -90,
|
||||
Spacing = 0,
|
||||
TopMargin = 0,
|
||||
BottomMargin = 0,
|
||||
Parent = gridPanel,
|
||||
};
|
||||
|
||||
var bottomLeftCell = new VerticalPanel
|
||||
{
|
||||
Spacing = 0,
|
||||
TopMargin = 0,
|
||||
BottomMargin = 0,
|
||||
Parent = gridPanel,
|
||||
};
|
||||
|
||||
var grid = new UniformGridPanel(0)
|
||||
{
|
||||
SlotsHorizontally = layersCount,
|
||||
SlotsVertically = layersCount,
|
||||
Parent = gridPanel,
|
||||
};
|
||||
|
||||
// Set layer names
|
||||
int layerIndex = 0;
|
||||
for (; layerIndex < layerNames.Length; layerIndex++)
|
||||
{
|
||||
upperRightCell.AddChild(new Label
|
||||
{
|
||||
Height = labelsHeight,
|
||||
Text = layerNames[layerIndex],
|
||||
HorizontalAlignment = TextAlignment.Near,
|
||||
});
|
||||
bottomLeftCell.AddChild(new Label
|
||||
{
|
||||
Height = labelsHeight,
|
||||
Text = layerNames[layerIndex],
|
||||
HorizontalAlignment = TextAlignment.Far,
|
||||
});
|
||||
}
|
||||
for (; layerIndex < layersCount; layerIndex++)
|
||||
{
|
||||
string name = "Layer " + layerIndex;
|
||||
upperRightCell.AddChild(new Label
|
||||
{
|
||||
Height = labelsHeight,
|
||||
Text = name,
|
||||
HorizontalAlignment = TextAlignment.Near,
|
||||
});
|
||||
bottomLeftCell.AddChild(new Label
|
||||
{
|
||||
Height = labelsHeight,
|
||||
Text = name,
|
||||
HorizontalAlignment = TextAlignment.Far,
|
||||
});
|
||||
}
|
||||
|
||||
// Arrange
|
||||
panel.Height = gridPanel.Height = gridPanel.Width = labelsWidth + layersCount * 18;
|
||||
gridPanel.RowFill[0] = gridPanel.ColumnFill[0] = labelsWidth / gridPanel.Width;
|
||||
gridPanel.RowFill[1] = gridPanel.ColumnFill[1] = 1 - gridPanel.ColumnFill[0];
|
||||
|
||||
// Create matrix
|
||||
for (int row = 0; row < layersCount; row++)
|
||||
{
|
||||
int column = 0;
|
||||
for (; column < layersCount - row; column++)
|
||||
{
|
||||
var box = new CheckBox(0, 0, true)
|
||||
{
|
||||
Tag = new Vector2(_layersCount - column - 1, row),
|
||||
Parent = grid,
|
||||
Checked = GetBit(column, row),
|
||||
};
|
||||
box.StateChanged += OnCheckBoxChanged;
|
||||
_checkBoxes.Add(box);
|
||||
}
|
||||
for (; column < layersCount; column++)
|
||||
{
|
||||
grid.AddChild(new Label());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCheckBoxChanged(CheckBox box)
|
||||
{
|
||||
int column = (int)((Vector2)box.Tag).X;
|
||||
int row = (int)((Vector2)box.Tag).Y;
|
||||
SetBit(column, row, box.Checked);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
// Sync check boxes
|
||||
for (int i = 0; i < _checkBoxes.Count; i++)
|
||||
{
|
||||
var box = _checkBoxes[i];
|
||||
int column = (int)((Vector2)box.Tag).X;
|
||||
int row = (int)((Vector2)box.Tag).Y;
|
||||
box.Checked = GetBit(column, row);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
base.Deinitialize();
|
||||
|
||||
_checkBoxes.Clear();
|
||||
_checkBoxes = null;
|
||||
}
|
||||
|
||||
private bool GetBit(int column, int row)
|
||||
{
|
||||
var values = (int[])Values[0];
|
||||
var mask = 1 << column;
|
||||
return (values[row] & mask) != 0;
|
||||
}
|
||||
|
||||
private void SetBit(int column, int row, bool flag)
|
||||
{
|
||||
var values = (int[])((int[])Values[0]).Clone();
|
||||
|
||||
values[row] = SetMaskBit(values[row], 1 << column, flag);
|
||||
values[column] = SetMaskBit(values[column], 1 << row, flag);
|
||||
|
||||
SetValue(values);
|
||||
}
|
||||
|
||||
private static int SetMaskBit(int value, int mask, bool flag)
|
||||
{
|
||||
return (value & ~mask) | (flag ? mask : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.Surface;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="ParticleEffect"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(ParticleEffect)), DefaultEditor]
|
||||
public class ParticleEffectEditor : ActorEditor
|
||||
{
|
||||
private bool _isValid;
|
||||
|
||||
private bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
// All selected particle effects use the same system
|
||||
var effect = (ParticleEffect)Values[0];
|
||||
var system = effect.ParticleSystem;
|
||||
return system != null && Values.TrueForAll(x => (x as ParticleEffect)?.ParticleSystem == system);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
_isValid = IsValid;
|
||||
if (!_isValid)
|
||||
return;
|
||||
|
||||
// Show all effect parameters grouped by the emitter track name
|
||||
var effect = (ParticleEffect)Values[0];
|
||||
var groups = layout.Group("Parameters");
|
||||
groups.Panel.Open(false);
|
||||
var parameters = effect.Parameters;
|
||||
var parametersGroups = parameters.GroupBy(x => x.EmitterIndex);
|
||||
foreach (var parametersGroup in parametersGroups)
|
||||
{
|
||||
var trackName = parametersGroup.First().TrackName;
|
||||
var group = groups.Group(trackName);
|
||||
group.Panel.Open(false);
|
||||
|
||||
var data = SurfaceUtils.InitGraphParameters(parametersGroup);
|
||||
SurfaceUtils.DisplayGraphParameters(group, data,
|
||||
(instance, parameter, tag) => ((ParticleEffect)instance).GetParameterValue(trackName, parameter.Name),
|
||||
(instance, value, parameter, tag) => ((ParticleEffect)instance).SetParameterValue(trackName, parameter.Name, value),
|
||||
Values,
|
||||
(instance, parameter, tag) => ((ParticleEffectParameter)tag).DefaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
if (_isValid != IsValid)
|
||||
RebuildLayout();
|
||||
|
||||
base.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="PhysicalMaterial"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="GenericEditor" />
|
||||
[CustomEditor(typeof(PhysicalMaterial)), DefaultEditor]
|
||||
public class PhysicalMaterialEditor : GenericEditor
|
||||
{
|
||||
private readonly List<CheckablePropertyNameLabel> _labels = new List<CheckablePropertyNameLabel>(64);
|
||||
private const int FrictionCombineModeOrder = 1;
|
||||
private const int RestitutionCombineModeOrder = 4;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
|
||||
{
|
||||
var order = item.Order.Order;
|
||||
|
||||
if (order != FrictionCombineModeOrder && order != RestitutionCombineModeOrder)
|
||||
{
|
||||
base.SpawnProperty(itemLayout, itemValues, item);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add labels with a check box
|
||||
var label = new CheckablePropertyNameLabel(item.DisplayName);
|
||||
label.CheckBox.Tag = order;
|
||||
label.CheckChanged += CheckBoxOnCheckChanged;
|
||||
_labels.Add(label);
|
||||
itemLayout.Property(label, itemValues, item.OverrideEditor, item.TooltipText);
|
||||
}
|
||||
|
||||
private void CheckBoxOnCheckChanged(CheckablePropertyNameLabel label)
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var order = (int)label.CheckBox.Tag;
|
||||
var value = (PhysicalMaterial)Values[0];
|
||||
if (order == FrictionCombineModeOrder)
|
||||
value.OverrideFrictionCombineMode = label.CheckBox.Checked;
|
||||
else if (order == RestitutionCombineModeOrder)
|
||||
value.OverrideRestitutionCombineMode = label.CheckBox.Checked;
|
||||
SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
// Update all labels
|
||||
var value = (PhysicalMaterial)Values[0];
|
||||
for (int i = 0; i < _labels.Count; i++)
|
||||
{
|
||||
var order = (int)_labels[i].CheckBox.Tag;
|
||||
bool check = false;
|
||||
if (order == FrictionCombineModeOrder)
|
||||
check = value.OverrideFrictionCombineMode;
|
||||
else if (order == RestitutionCombineModeOrder)
|
||||
check = value.OverrideRestitutionCombineMode;
|
||||
_labels[i].CheckBox.Checked = check;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_labels.Clear();
|
||||
|
||||
base.Initialize(layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all post process settings structures editors.
|
||||
/// </summary>
|
||||
abstract class PostProcessSettingsEditor : GenericEditor
|
||||
{
|
||||
private List<CheckablePropertyNameLabel> _labels;
|
||||
|
||||
protected abstract int OverrideFlags { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.InlineIntoParent;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override List<ItemInfo> GetItemsForType(ScriptType type)
|
||||
{
|
||||
// Show structure properties
|
||||
return GetItemsForType(type, true, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
|
||||
{
|
||||
var setting = item.Info.GetAttribute<PostProcessSettingAttribute>();
|
||||
if (setting == null)
|
||||
{
|
||||
base.SpawnProperty(itemLayout, itemValues, item);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add labels with a check box
|
||||
var label = new CheckablePropertyNameLabel(item.DisplayName);
|
||||
label.CheckBox.Tag = setting.Bit;
|
||||
label.CheckChanged += CheckBoxOnCheckChanged;
|
||||
_labels.Add(label);
|
||||
itemLayout.Property(label, itemValues, item.OverrideEditor, item.TooltipText);
|
||||
}
|
||||
|
||||
private void CheckBoxOnCheckChanged(CheckablePropertyNameLabel label)
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var bit = (int)label.CheckBox.Tag;
|
||||
var overrideFlags = OverrideFlags;
|
||||
if (label.CheckBox.Checked)
|
||||
overrideFlags |= bit;
|
||||
else
|
||||
overrideFlags &= ~bit;
|
||||
OverrideFlags = overrideFlags;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
// Update all labels checkboxes
|
||||
var overrideFlags = OverrideFlags;
|
||||
for (int i = 0; i < _labels.Count; i++)
|
||||
{
|
||||
var bit = (int)_labels[i].CheckBox.Tag;
|
||||
_labels[i].CheckBox.Checked = (overrideFlags & bit) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_labels = new List<CheckablePropertyNameLabel>(32);
|
||||
|
||||
base.Initialize(layout);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
_labels.Clear();
|
||||
_labels = null;
|
||||
|
||||
base.Deinitialize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="AmbientOcclusionSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(AmbientOcclusionSettings)), DefaultEditor]
|
||||
sealed class AmbientOcclusionSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((AmbientOcclusionSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (AmbientOcclusionSettings)Values[0];
|
||||
settings.OverrideFlags = (AmbientOcclusionSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="BloomSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(BloomSettings)), DefaultEditor]
|
||||
sealed class BloomSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((BloomSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (BloomSettings)Values[0];
|
||||
settings.OverrideFlags = (BloomSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="ToneMappingSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(ToneMappingSettings)), DefaultEditor]
|
||||
sealed class ToneMappingSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((ToneMappingSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (ToneMappingSettings)Values[0];
|
||||
settings.OverrideFlags = (ToneMappingSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="ColorGradingSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(ColorGradingSettings)), DefaultEditor]
|
||||
sealed class ColorGradingSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((ColorGradingSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (ColorGradingSettings)Values[0];
|
||||
settings.OverrideFlags = (ColorGradingSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="EyeAdaptationSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(EyeAdaptationSettings)), DefaultEditor]
|
||||
sealed class EyeAdaptationSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((EyeAdaptationSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (EyeAdaptationSettings)Values[0];
|
||||
settings.OverrideFlags = (EyeAdaptationSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="CameraArtifactsSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(CameraArtifactsSettings)), DefaultEditor]
|
||||
sealed class CameraArtifactsSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((CameraArtifactsSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (CameraArtifactsSettings)Values[0];
|
||||
settings.OverrideFlags = (CameraArtifactsSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="LensFlaresSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(LensFlaresSettings)), DefaultEditor]
|
||||
sealed class LensFlaresSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((LensFlaresSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (LensFlaresSettings)Values[0];
|
||||
settings.OverrideFlags = (LensFlaresSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="AmbientOcclusionSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(DepthOfFieldSettings)), DefaultEditor]
|
||||
sealed class DepthOfFieldSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((DepthOfFieldSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (DepthOfFieldSettings)Values[0];
|
||||
settings.OverrideFlags = (DepthOfFieldSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="MotionBlurSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(MotionBlurSettings)), DefaultEditor]
|
||||
sealed class MotionBlurSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((MotionBlurSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (MotionBlurSettings)Values[0];
|
||||
settings.OverrideFlags = (MotionBlurSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="ScreenSpaceReflectionsSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(ScreenSpaceReflectionsSettings)), DefaultEditor]
|
||||
sealed class ScreenSpaceReflectionsSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((ScreenSpaceReflectionsSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (ScreenSpaceReflectionsSettings)Values[0];
|
||||
settings.OverrideFlags = (ScreenSpaceReflectionsSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="AntiAliasingSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(AntiAliasingSettings)), DefaultEditor]
|
||||
sealed class AntiAliasingSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => (int)((AntiAliasingSettings)Values[0]).OverrideFlags;
|
||||
set
|
||||
{
|
||||
var settings = (AntiAliasingSettings)Values[0];
|
||||
settings.OverrideFlags = (AntiAliasingSettingsOverride)value;
|
||||
SetValue(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor for <see cref="PostFxMaterialsSettings"/> type.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PostFxMaterialsSettings)), DefaultEditor]
|
||||
sealed class PostFxMaterialsSettingsEditor : PostProcessSettingsEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override int OverrideFlags
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
}
|
||||
}
|
||||
76
Source/Editor/CustomEditors/Dedicated/RigidBodyEditor.cs
Normal file
76
Source/Editor/CustomEditors/Dedicated/RigidBodyEditor.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="RigidBody"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(RigidBody)), DefaultEditor]
|
||||
public class RigidBodyEditor : ActorEditor
|
||||
{
|
||||
private readonly List<CheckablePropertyNameLabel> _labels = new List<CheckablePropertyNameLabel>(64);
|
||||
private const int MassOrder = 110;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
|
||||
{
|
||||
var order = item.Order?.Order ?? 0;
|
||||
|
||||
if (order != MassOrder)
|
||||
{
|
||||
base.SpawnProperty(itemLayout, itemValues, item);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add labels with a check box
|
||||
var label = new CheckablePropertyNameLabel(item.DisplayName);
|
||||
label.CheckBox.Tag = order;
|
||||
label.CheckChanged += CheckBoxOnCheckChanged;
|
||||
_labels.Add(label);
|
||||
itemLayout.Property(label, itemValues, item.OverrideEditor, item.TooltipText);
|
||||
label.UpdateStyle();
|
||||
}
|
||||
|
||||
private void CheckBoxOnCheckChanged(CheckablePropertyNameLabel label)
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var order = (int)label.CheckBox.Tag;
|
||||
var value = (RigidBody)Values[0];
|
||||
if (order == MassOrder)
|
||||
value.OverrideMass = label.CheckBox.Checked;
|
||||
SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
// Update all labels
|
||||
var value = (RigidBody)Values[0];
|
||||
for (int i = 0; i < _labels.Count; i++)
|
||||
{
|
||||
var order = (int)_labels[i].CheckBox.Tag;
|
||||
bool check = false;
|
||||
if (order == MassOrder)
|
||||
check = value.OverrideMass;
|
||||
_labels[i].CheckBox.Checked = check;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_labels.Clear();
|
||||
|
||||
base.Initialize(layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
845
Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
Normal file
845
Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
Normal file
@@ -0,0 +1,845 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Actions;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Drag and drop scripts area control.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Control" />
|
||||
public class DragAreaControl : ContainerControl
|
||||
{
|
||||
private DragHandlers _dragHandlers;
|
||||
private DragScriptItems _dragScripts;
|
||||
private DragAssets _dragAssets;
|
||||
|
||||
/// <summary>
|
||||
/// The parent scripts editor.
|
||||
/// </summary>
|
||||
public ScriptsEditor ScriptsEditor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DragAreaControl"/> class.
|
||||
/// </summary>
|
||||
public DragAreaControl()
|
||||
: base(0, 0, 120, 40)
|
||||
{
|
||||
AutoFocus = false;
|
||||
|
||||
// Add script button
|
||||
float addScriptButtonWidth = 60.0f;
|
||||
var addScriptButton = new Button
|
||||
{
|
||||
TooltipText = "Add new scripts to the actor",
|
||||
AnchorPreset = AnchorPresets.MiddleCenter,
|
||||
Text = "Add script",
|
||||
Parent = this,
|
||||
Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, 18),
|
||||
};
|
||||
addScriptButton.ButtonClicked += AddScriptButtonOnClicked;
|
||||
}
|
||||
|
||||
private void AddScriptButtonOnClicked(Button button)
|
||||
{
|
||||
var scripts = Editor.Instance.CodeEditing.Scripts.Get();
|
||||
if (scripts.Count == 0)
|
||||
{
|
||||
// No scripts
|
||||
var cm1 = new ContextMenu();
|
||||
cm1.AddButton("No scripts in project");
|
||||
cm1.Show(this, button.BottomLeft);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show context menu with list of scripts to add
|
||||
var cm = new ItemsListContextMenu(180);
|
||||
for (int i = 0; i < scripts.Count; i++)
|
||||
{
|
||||
var scriptType = scripts[i];
|
||||
var item = new ItemsListContextMenu.Item(scriptType.Name, scriptType)
|
||||
{
|
||||
TooltipText = scriptType.TypeName,
|
||||
};
|
||||
var attributes = scriptType.GetAttributes(false);
|
||||
var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute);
|
||||
if (tooltipAttribute != null)
|
||||
{
|
||||
item.TooltipText += '\n';
|
||||
item.TooltipText += tooltipAttribute.Text;
|
||||
}
|
||||
cm.AddItem(item);
|
||||
}
|
||||
cm.ItemClicked += item => AddScript((ScriptType)item.Tag);
|
||||
cm.SortChildren();
|
||||
cm.Show(this, button.BottomLeft);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
var style = Style.Current;
|
||||
var size = Size;
|
||||
|
||||
// Info
|
||||
Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, 22, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
|
||||
|
||||
// Check if drag is over
|
||||
if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag)
|
||||
{
|
||||
var area = new Rectangle(Vector2.Zero, size);
|
||||
Render2D.FillRectangle(area, Color.Orange * 0.5f);
|
||||
Render2D.DrawRectangle(area, Color.Black);
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
}
|
||||
|
||||
private bool ValidateScript(ScriptItem scriptItem)
|
||||
{
|
||||
var scriptName = scriptItem.ScriptName;
|
||||
var scriptType = ScriptsBuilder.FindScript(scriptName);
|
||||
return scriptType != null;
|
||||
}
|
||||
|
||||
private bool ValidateAsset(AssetItem assetItem)
|
||||
{
|
||||
if (assetItem is VisualScriptItem scriptItem)
|
||||
return scriptItem.ScriptType != ScriptType.Null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragEnter(ref Vector2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragEnter(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
if (_dragHandlers == null)
|
||||
{
|
||||
_dragScripts = new DragScriptItems(ValidateScript);
|
||||
_dragAssets = new DragAssets(ValidateAsset);
|
||||
_dragHandlers = new DragHandlers
|
||||
{
|
||||
_dragScripts,
|
||||
_dragAssets,
|
||||
};
|
||||
}
|
||||
return _dragHandlers.OnDragEnter(data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragMove(ref Vector2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragMove(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
return _dragHandlers.Effect;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_dragHandlers.OnDragLeave();
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragDrop(ref Vector2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragDrop(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
if (_dragHandlers.HasValidDrag)
|
||||
{
|
||||
if (_dragScripts.HasValidDrag)
|
||||
{
|
||||
result = _dragScripts.Effect;
|
||||
AddScripts(_dragScripts.Objects);
|
||||
}
|
||||
else if (_dragAssets.HasValidDrag)
|
||||
{
|
||||
result = _dragAssets.Effect;
|
||||
AddScripts(_dragAssets.Objects);
|
||||
}
|
||||
|
||||
_dragHandlers.OnDragDrop(null);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void AddScript(ScriptType item)
|
||||
{
|
||||
var list = new List<ScriptType>(1) { item };
|
||||
AddScripts(list);
|
||||
}
|
||||
|
||||
private void AddScripts(List<AssetItem> items)
|
||||
{
|
||||
var list = new List<ScriptType>(items.Count);
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = (VisualScriptItem)items[i];
|
||||
var scriptType = item.ScriptType;
|
||||
if (scriptType == ScriptType.Null)
|
||||
{
|
||||
Editor.LogWarning("Invalid script type " + item.ShortName);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(scriptType);
|
||||
}
|
||||
}
|
||||
AddScripts(list);
|
||||
}
|
||||
|
||||
private void AddScripts(List<ScriptItem> items)
|
||||
{
|
||||
var list = new List<ScriptType>(items.Count);
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
var scriptName = item.ScriptName;
|
||||
var scriptType = ScriptsBuilder.FindScript(scriptName);
|
||||
if (scriptType == null)
|
||||
{
|
||||
Editor.LogWarning("Invalid script type " + scriptName);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(new ScriptType(scriptType));
|
||||
}
|
||||
}
|
||||
AddScripts(list);
|
||||
}
|
||||
|
||||
private void AddScripts(List<ScriptType> items)
|
||||
{
|
||||
var actions = new List<IUndoAction>(4);
|
||||
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
var scriptType = items[i];
|
||||
var actors = ScriptsEditor.ParentEditor.Values;
|
||||
for (int j = 0; j < actors.Count; j++)
|
||||
{
|
||||
var actor = (Actor)actors[j];
|
||||
actions.Add(AddRemoveScript.Add(actor, scriptType));
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.Count == 0)
|
||||
{
|
||||
Editor.LogWarning("Failed to spawn scripts");
|
||||
return;
|
||||
}
|
||||
|
||||
var multiAction = new MultiUndoAction(actions);
|
||||
multiAction.Do();
|
||||
ScriptsEditor.Presenter?.Undo.AddAction(multiAction);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Small image control added per script group that allows to drag and drop a reference to it. Also used to reorder the scripts.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Image" />
|
||||
internal class ScriptDragIcon : Image
|
||||
{
|
||||
private ScriptsEditor _editor;
|
||||
private bool _isMouseDown;
|
||||
private Vector2 _mouseDownPos;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target script.
|
||||
/// </summary>
|
||||
public Script Script => (Script)Tag;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScriptDragIcon"/> class.
|
||||
/// </summary>
|
||||
/// <param name="editor">The script editor.</param>
|
||||
/// <param name="script">The target script.</param>
|
||||
public ScriptDragIcon(ScriptsEditor editor, Script script)
|
||||
{
|
||||
Tag = script;
|
||||
_editor = editor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Vector2 location)
|
||||
{
|
||||
_mouseDownPos = Vector2.Minimum;
|
||||
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
// Check if start drag drop
|
||||
if (_isMouseDown)
|
||||
{
|
||||
DoDrag();
|
||||
_isMouseDown = false;
|
||||
}
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Vector2 location)
|
||||
{
|
||||
// Check if start drag drop
|
||||
if (_isMouseDown && Vector2.Distance(location, _mouseDownPos) > 10.0f)
|
||||
{
|
||||
DoDrag();
|
||||
_isMouseDown = false;
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
// Clear flag
|
||||
_isMouseDown = false;
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
// Set flag
|
||||
_isMouseDown = true;
|
||||
_mouseDownPos = location;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
private void DoDrag()
|
||||
{
|
||||
var script = Script;
|
||||
_editor.OnScriptDragChange(true, script);
|
||||
DoDragDrop(DragScripts.GetDragData(script));
|
||||
_editor.OnScriptDragChange(false, script);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ScriptArrangeBar : Control
|
||||
{
|
||||
private ScriptsEditor _editor;
|
||||
private int _index;
|
||||
private Script _script;
|
||||
private DragDropEffect _dragEffect;
|
||||
|
||||
public ScriptArrangeBar()
|
||||
: base(0, 0, 120, 6)
|
||||
{
|
||||
AutoFocus = false;
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
public void Init(int index, ScriptsEditor editor)
|
||||
{
|
||||
_editor = editor;
|
||||
_index = index;
|
||||
_editor.ScriptDragChange += OnScriptDragChange;
|
||||
}
|
||||
|
||||
private void OnScriptDragChange(bool start, Script script)
|
||||
{
|
||||
_script = start ? script : null;
|
||||
Visible = start;
|
||||
OnDragLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var color = FlaxEngine.GUI.Style.Current.BackgroundSelected * (IsDragOver ? 0.9f : 0.1f);
|
||||
Render2D.FillRectangle(new Rectangle(Vector2.Zero, Size), color);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragEnter(ref Vector2 location, DragData data)
|
||||
{
|
||||
_dragEffect = DragDropEffect.None;
|
||||
|
||||
var result = base.OnDragEnter(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
if (data is DragDataText textData && DragScripts.IsValidData(textData))
|
||||
return _dragEffect = DragDropEffect.Move;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragMove(ref Vector2 location, DragData data)
|
||||
{
|
||||
return _dragEffect;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_dragEffect = DragDropEffect.None;
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragDrop(ref Vector2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragDrop(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
if (_dragEffect != DragDropEffect.None)
|
||||
{
|
||||
result = _dragEffect;
|
||||
_dragEffect = DragDropEffect.None;
|
||||
|
||||
_editor.ReorderScript(_script, _index);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom editor for actor scripts collection.
|
||||
/// </summary>
|
||||
/// <seealso cref="CustomEditor" />
|
||||
public sealed class ScriptsEditor : SyncPointEditor
|
||||
{
|
||||
private CheckBox[] _scriptToggles;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for script drag start and event events.
|
||||
/// </summary>
|
||||
/// <param name="start">Set to true if drag started, otherwise false.</param>
|
||||
/// <param name="script">The target script to reorder.</param>
|
||||
public delegate void ScriptDragDelegate(bool start, Script script);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when script drag changes (starts or ends).
|
||||
/// </summary>
|
||||
public event ScriptDragDelegate ScriptDragChange;
|
||||
|
||||
/// <summary>
|
||||
/// The scripts collection. Undo operations are recorder for scripts.
|
||||
/// </summary>
|
||||
private readonly List<Script> _scripts = new List<Script>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<object> UndoObjects => _scripts;
|
||||
|
||||
private void AddMissingScript(int index, LayoutElementsContainer layout)
|
||||
{
|
||||
var group = layout.Group("Missing script");
|
||||
|
||||
// Add settings button to the group
|
||||
const float settingsButtonSize = 14;
|
||||
var settingsButton = new Image
|
||||
{
|
||||
TooltipText = "Settings",
|
||||
AutoFocus = true,
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Parent = group.Panel,
|
||||
Bounds = new Rectangle(group.Panel.Width - settingsButtonSize, 0, settingsButtonSize, settingsButtonSize),
|
||||
IsScrollable = false,
|
||||
Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
|
||||
Margin = new Margin(1),
|
||||
Brush = new SpriteBrush(FlaxEngine.GUI.Style.Current.Settings),
|
||||
Tag = index,
|
||||
};
|
||||
settingsButton.Clicked += MissingSettingsButtonOnClicked;
|
||||
}
|
||||
|
||||
private void MissingSettingsButtonOnClicked(Image image, MouseButton mouseButton)
|
||||
{
|
||||
if (mouseButton != MouseButton.Left)
|
||||
return;
|
||||
|
||||
var index = (int)image.Tag;
|
||||
|
||||
var cm = new ContextMenu();
|
||||
cm.Tag = index;
|
||||
cm.AddButton("Remove", OnClickMissingRemove);
|
||||
cm.Show(image, image.Size);
|
||||
}
|
||||
|
||||
private void OnClickMissingRemove(ContextMenuButton button)
|
||||
{
|
||||
var index = (int)button.ParentContextMenu.Tag;
|
||||
// TODO: support undo
|
||||
var actors = ParentEditor.Values;
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
var actor = (Actor)actors[i];
|
||||
var script = actor.GetScript(index);
|
||||
if (script)
|
||||
{
|
||||
script.Parent = null;
|
||||
Object.Destroy(script);
|
||||
}
|
||||
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Values container for the collection of the scripts. Helps with prefab linkage and reference value usage (uses Prefab Instance ID rather than index in array).
|
||||
/// </summary>
|
||||
public sealed class ScriptsContainer : ListValueContainer
|
||||
{
|
||||
private readonly Guid _prefabObjectId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the prefab object identifier used by the container scripts. Empty if there is no valid linkage to the prefab object.
|
||||
/// </summary>
|
||||
public Guid PrefabObjectId => _prefabObjectId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScriptsContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="elementType">Type of the collection elements (script type).</param>
|
||||
/// <param name="index">The script index in the actor scripts collection.</param>
|
||||
/// <param name="values">The collection values (scripts array).</param>
|
||||
public ScriptsContainer(ScriptType elementType, int index, ValueContainer values)
|
||||
: base(elementType, index)
|
||||
{
|
||||
Capacity = values.Count;
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
{
|
||||
var v = (IList)values[i];
|
||||
Add(v[index]);
|
||||
}
|
||||
|
||||
if (values.HasReferenceValue && Count > 0 && this[0] is Script script && script.HasPrefabLink)
|
||||
{
|
||||
_prefabObjectId = script.PrefabObjectID;
|
||||
RefreshReferenceValue(values.ReferenceValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RefreshReferenceValue(object instanceValue)
|
||||
{
|
||||
// Clear
|
||||
_referenceValue = null;
|
||||
_hasReferenceValue = false;
|
||||
|
||||
if (instanceValue is IList v)
|
||||
{
|
||||
// Get the reference value if script with the given link id exists in the reference values collection
|
||||
for (int i = 0; i < v.Count; i++)
|
||||
{
|
||||
if (v[i] is Script script && script.PrefabObjectID == _prefabObjectId)
|
||||
{
|
||||
_referenceValue = script;
|
||||
_hasReferenceValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
// Area for drag&drop scripts
|
||||
var dragArea = layout.CustomContainer<DragAreaControl>();
|
||||
dragArea.CustomControl.ScriptsEditor = this;
|
||||
|
||||
// No support for showing scripts from multiple actors that have different set of scripts
|
||||
var scripts = (Script[])Values[0];
|
||||
_scripts.Clear();
|
||||
_scripts.AddRange(scripts);
|
||||
for (int i = 1; i < Values.Count; i++)
|
||||
{
|
||||
var e = (Script[])Values[i];
|
||||
if (scripts.Length != e.Length)
|
||||
return;
|
||||
for (int j = 0; j < e.Length; j++)
|
||||
{
|
||||
var t1 = scripts[j]?.TypeName;
|
||||
var t2 = e[j]?.TypeName;
|
||||
if (t1 != t2)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Scripts arrange bar
|
||||
var dragBar = layout.Custom<ScriptArrangeBar>();
|
||||
dragBar.CustomControl.Init(0, this);
|
||||
|
||||
// Scripts
|
||||
var elementType = new ScriptType(typeof(Script));
|
||||
_scriptToggles = new CheckBox[scripts.Length];
|
||||
for (int i = 0; i < scripts.Length; i++)
|
||||
{
|
||||
var script = scripts[i];
|
||||
if (script == null)
|
||||
{
|
||||
AddMissingScript(i, layout);
|
||||
continue;
|
||||
}
|
||||
|
||||
var values = new ScriptsContainer(elementType, i, Values);
|
||||
var scriptType = TypeUtils.GetObjectType(script);
|
||||
var editor = CustomEditorsUtil.CreateEditor(scriptType, false);
|
||||
|
||||
// Create group
|
||||
var title = CustomEditorsUtil.GetPropertyNameUI(scriptType.Name);
|
||||
var group = layout.Group(title);
|
||||
if (Presenter.CacheExpandedGroups)
|
||||
{
|
||||
if (Editor.Instance.ProjectCache.IsCollapsedGroup(title))
|
||||
group.Panel.Close(false);
|
||||
else
|
||||
group.Panel.Open(false);
|
||||
group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed);
|
||||
}
|
||||
else
|
||||
group.Panel.Open(false);
|
||||
|
||||
// Customize
|
||||
var typeAttributes = scriptType.GetAttributes(true);
|
||||
var tooltip = (TooltipAttribute)typeAttributes.FirstOrDefault(x => x is TooltipAttribute);
|
||||
if (tooltip != null)
|
||||
group.Panel.TooltipText = tooltip.Text;
|
||||
if (script.HasPrefabLink)
|
||||
group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.ProgressNormal;
|
||||
|
||||
// Add toggle button to the group
|
||||
var scriptToggle = new CheckBox
|
||||
{
|
||||
TooltipText = "If checked, script will be enabled.",
|
||||
IsScrollable = false,
|
||||
Checked = script.Enabled,
|
||||
Parent = group.Panel,
|
||||
Size = new Vector2(14, 14),
|
||||
Bounds = new Rectangle(2, 0, 14, 14),
|
||||
BoxSize = 12.0f,
|
||||
Tag = script,
|
||||
};
|
||||
scriptToggle.StateChanged += OnScriptToggleCheckChanged;
|
||||
_scriptToggles[i] = scriptToggle;
|
||||
|
||||
// Add drag button to the group
|
||||
const float dragIconSize = 14;
|
||||
var scriptDrag = new ScriptDragIcon(this, script)
|
||||
{
|
||||
TooltipText = "Script reference",
|
||||
AutoFocus = true,
|
||||
IsScrollable = false,
|
||||
Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
|
||||
Parent = group.Panel,
|
||||
Bounds = new Rectangle(scriptToggle.Right, 0.5f, dragIconSize, dragIconSize),
|
||||
Margin = new Margin(1),
|
||||
Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12),
|
||||
Tag = script,
|
||||
};
|
||||
|
||||
// Add settings button to the group
|
||||
const float settingsButtonSize = 14;
|
||||
var settingsButton = new Image
|
||||
{
|
||||
TooltipText = "Settings",
|
||||
AutoFocus = true,
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Parent = group.Panel,
|
||||
Bounds = new Rectangle(group.Panel.Width - settingsButtonSize, 0, settingsButtonSize, settingsButtonSize),
|
||||
IsScrollable = false,
|
||||
Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
|
||||
Margin = new Margin(1),
|
||||
Brush = new SpriteBrush(FlaxEngine.GUI.Style.Current.Settings),
|
||||
Tag = script,
|
||||
};
|
||||
settingsButton.Clicked += OnSettingsButtonClicked;
|
||||
|
||||
group.Panel.HeaderTextMargin = new Margin(scriptDrag.Right, 15, 2, 2);
|
||||
group.Object(values, editor);
|
||||
|
||||
// Scripts arrange bar
|
||||
dragBar = layout.Custom<ScriptArrangeBar>();
|
||||
dragBar.CustomControl.Init(i + 1, this);
|
||||
}
|
||||
|
||||
base.Initialize(layout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when script drag changes.
|
||||
/// </summary>
|
||||
/// <param name="start">if set to <c>true</c> drag just started, otherwise ended.</param>
|
||||
/// <param name="script">The target script.</param>
|
||||
public void OnScriptDragChange(bool start, Script script)
|
||||
{
|
||||
ScriptDragChange.Invoke(start, script);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the script order (with undo).
|
||||
/// </summary>
|
||||
/// <param name="script">The script to reorder.</param>
|
||||
/// <param name="targetIndex">The target index to move script.</param>
|
||||
public void ReorderScript(Script script, int targetIndex)
|
||||
{
|
||||
// Skip if no change
|
||||
if (script.OrderInParent == targetIndex)
|
||||
return;
|
||||
|
||||
var action = ChangeScriptAction.ChangeOrder(script, targetIndex);
|
||||
action.Do();
|
||||
Presenter?.Undo.AddAction(action);
|
||||
}
|
||||
|
||||
private void OnScriptToggleCheckChanged(CheckBox box)
|
||||
{
|
||||
var script = (Script)box.Tag;
|
||||
if (script.Enabled == box.Checked)
|
||||
return;
|
||||
|
||||
var action = ChangeScriptAction.ChangeEnabled(script, box.Checked);
|
||||
action.Do();
|
||||
Presenter?.Undo.AddAction(action);
|
||||
}
|
||||
|
||||
private void OnSettingsButtonClicked(Image image, MouseButton mouseButton)
|
||||
{
|
||||
if (mouseButton != MouseButton.Left)
|
||||
return;
|
||||
|
||||
var script = (Script)image.Tag;
|
||||
var scriptType = TypeUtils.GetType(script.TypeName);
|
||||
var item = scriptType.ContentItem;
|
||||
|
||||
var cm = new ContextMenu();
|
||||
cm.Tag = script;
|
||||
cm.AddButton("Remove", OnClickRemove).Icon = Editor.Instance.Icons.Cross12;
|
||||
cm.AddButton("Move up", OnClickMoveUp).Enabled = script.OrderInParent > 0;
|
||||
cm.AddButton("Move down", OnClickMoveDown).Enabled = script.OrderInParent < script.Actor.Scripts.Length - 1;
|
||||
// TODO: copy script
|
||||
// TODO: paste script values
|
||||
// TODO: paste script as new
|
||||
// TODO: copy script reference
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Copy type name", OnClickCopyTypeName);
|
||||
cm.AddButton("Edit script", OnClickEditScript).Enabled = item != null;
|
||||
var showButton = cm.AddButton("Show in content window", OnClickShowScript);
|
||||
showButton.Enabled = item != null;
|
||||
showButton.Icon = Editor.Instance.Icons.Search12;
|
||||
cm.Show(image, image.Size);
|
||||
}
|
||||
|
||||
private void OnClickRemove(ContextMenuButton button)
|
||||
{
|
||||
var script = (Script)button.ParentContextMenu.Tag;
|
||||
var action = AddRemoveScript.Remove(script);
|
||||
action.Do();
|
||||
Presenter.Undo?.AddAction(action);
|
||||
}
|
||||
|
||||
private void OnClickMoveUp(ContextMenuButton button)
|
||||
{
|
||||
var script = (Script)button.ParentContextMenu.Tag;
|
||||
var action = ChangeScriptAction.ChangeOrder(script, script.OrderInParent - 1);
|
||||
action.Do();
|
||||
Presenter.Undo?.AddAction(action);
|
||||
}
|
||||
|
||||
private void OnClickMoveDown(ContextMenuButton button)
|
||||
{
|
||||
var script = (Script)button.ParentContextMenu.Tag;
|
||||
var action = ChangeScriptAction.ChangeOrder(script, script.OrderInParent + 1);
|
||||
action.Do();
|
||||
Presenter.Undo?.AddAction(action);
|
||||
}
|
||||
|
||||
private void OnClickCopyTypeName(ContextMenuButton button)
|
||||
{
|
||||
var script = (Script)button.ParentContextMenu.Tag;
|
||||
Clipboard.Text = script.TypeName;
|
||||
}
|
||||
|
||||
private void OnClickEditScript(ContextMenuButton button)
|
||||
{
|
||||
var script = (Script)button.ParentContextMenu.Tag;
|
||||
var scriptType = TypeUtils.GetType(script.TypeName);
|
||||
var item = scriptType.ContentItem;
|
||||
if (item != null)
|
||||
Editor.Instance.ContentEditing.Open(item);
|
||||
}
|
||||
|
||||
private void OnClickShowScript(ContextMenuButton button)
|
||||
{
|
||||
var script = (Script)button.ParentContextMenu.Tag;
|
||||
var scriptType = TypeUtils.GetType(script.TypeName);
|
||||
var item = scriptType.ContentItem;
|
||||
if (item != null)
|
||||
Editor.Instance.Windows.ContentWin.Select(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
if (Values.Count == 1)
|
||||
{
|
||||
var scripts = ((Actor)ParentEditor.Values[0]).Scripts;
|
||||
if (!Utils.ArraysEqual(scripts, _scripts))
|
||||
{
|
||||
ParentEditor.RebuildLayout();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _scriptToggles.Length; i++)
|
||||
{
|
||||
if (_scriptToggles[i] != null)
|
||||
_scriptToggles[i].Checked = scripts[i].Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
base.Refresh();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
_scriptToggles = null;
|
||||
|
||||
base.Deinitialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Source/Editor/CustomEditors/Dedicated/SkyLightEditor.cs
Normal file
40
Source/Editor/CustomEditors/Dedicated/SkyLightEditor.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="SkyLight"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(SkyLight)), DefaultEditor]
|
||||
public class SkyLightEditor : ActorEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
if (Values.HasDifferentTypes == false)
|
||||
{
|
||||
// Add 'Bake' button
|
||||
layout.Space(10);
|
||||
var button = layout.Button("Bake");
|
||||
button.Button.Clicked += BakeButtonClicked;
|
||||
}
|
||||
}
|
||||
|
||||
private void BakeButtonClicked()
|
||||
{
|
||||
for (int i = 0; i < Values.Count; i++)
|
||||
{
|
||||
if (Values[i] is SkyLight skyLight)
|
||||
{
|
||||
skyLight.Bake();
|
||||
Editor.Instance.Scene.MarkSceneEdited(skyLight.Scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs
Normal file
40
Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="Terrain"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(Terrain)), DefaultEditor]
|
||||
public class TerrainEditor : ActorEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
// Add info box
|
||||
if (IsSingleObject && Values[0] is Terrain terrain)
|
||||
{
|
||||
var patchesCount = terrain.PatchesCount;
|
||||
var chunkSize = terrain.ChunkSize;
|
||||
var resolution = terrain.Scale;
|
||||
var totalSize = terrain.Box.Size;
|
||||
string text = string.Format("Patches: {0}\nTotal Chunks: {1}\nChunk Size: {2}\nResolution: {3}m x {4}m\nTotal size: {5}km x {6}km",
|
||||
patchesCount,
|
||||
patchesCount * 16,
|
||||
chunkSize,
|
||||
1.0f / (resolution.X + 1e-9f),
|
||||
1.0f / (resolution.Z + 1e-9f),
|
||||
totalSize.X * 0.00001f,
|
||||
totalSize.Z * 0.00001f
|
||||
);
|
||||
var label = layout.Label(text);
|
||||
label.Label.AutoHeight = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
424
Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
Normal file
424
Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
Normal file
@@ -0,0 +1,424 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Dedicated custom editor for <see cref="AnchorPresets"/> enum.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.CustomEditor" />
|
||||
[CustomEditor(typeof(AnchorPresets)), DefaultEditor]
|
||||
public sealed class AnchorPresetsEditor : CustomEditor
|
||||
{
|
||||
class AnchorButton : Button
|
||||
{
|
||||
private AnchorPresets _presets = (AnchorPresets)((int)AnchorPresets.StretchAll + 1);
|
||||
|
||||
public AnchorPresets Presets
|
||||
{
|
||||
get => _presets;
|
||||
set
|
||||
{
|
||||
if (_presets != value)
|
||||
{
|
||||
_presets = value;
|
||||
OnPresetsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected;
|
||||
|
||||
private void OnPresetsChanged()
|
||||
{
|
||||
TooltipText = CustomEditorsUtil.GetPropertyNameUI(_presets.ToString());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
// Cache data
|
||||
var rect = new Rectangle(Vector2.Zero, Size);
|
||||
if (rect.Width >= rect.Height)
|
||||
{
|
||||
rect.X = (rect.Width - rect.Height) * 0.5f;
|
||||
rect.Width = rect.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.Y = (rect.Height - rect.Width) * 0.5f;
|
||||
rect.Height = rect.Width;
|
||||
}
|
||||
var enabled = EnabledInHierarchy;
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
var backgroundColor = BackgroundColor;
|
||||
var borderColor = BorderColor;
|
||||
if (!enabled)
|
||||
{
|
||||
backgroundColor *= 0.7f;
|
||||
borderColor *= 0.7f;
|
||||
}
|
||||
else if (_isPressed)
|
||||
{
|
||||
backgroundColor = BackgroundColorSelected;
|
||||
borderColor = BorderColorSelected;
|
||||
}
|
||||
else if (IsMouseOver)
|
||||
{
|
||||
backgroundColor = BackgroundColorHighlighted;
|
||||
borderColor = BorderColorHighlighted;
|
||||
}
|
||||
|
||||
// Calculate fill area
|
||||
float fillSize = rect.Width / 3;
|
||||
Rectangle fillArea;
|
||||
switch (_presets)
|
||||
{
|
||||
case AnchorPresets.Custom:
|
||||
fillArea = Rectangle.Empty;
|
||||
break;
|
||||
case AnchorPresets.TopLeft:
|
||||
fillArea = new Rectangle(0, 0, fillSize, fillSize);
|
||||
break;
|
||||
case AnchorPresets.TopCenter:
|
||||
fillArea = new Rectangle(fillSize, 0, fillSize, fillSize);
|
||||
break;
|
||||
case AnchorPresets.TopRight:
|
||||
fillArea = new Rectangle(fillSize * 2, 0, fillSize, fillSize);
|
||||
break;
|
||||
case AnchorPresets.MiddleLeft:
|
||||
fillArea = new Rectangle(0, fillSize, fillSize, fillSize);
|
||||
break;
|
||||
case AnchorPresets.MiddleCenter:
|
||||
fillArea = new Rectangle(fillSize, fillSize, fillSize, fillSize);
|
||||
break;
|
||||
case AnchorPresets.MiddleRight:
|
||||
fillArea = new Rectangle(fillSize * 2, fillSize, fillSize, fillSize);
|
||||
break;
|
||||
case AnchorPresets.BottomLeft:
|
||||
fillArea = new Rectangle(0, fillSize * 2, fillSize, fillSize);
|
||||
break;
|
||||
case AnchorPresets.BottomCenter:
|
||||
fillArea = new Rectangle(fillSize, fillSize * 2, fillSize, fillSize);
|
||||
break;
|
||||
case AnchorPresets.BottomRight:
|
||||
fillArea = new Rectangle(fillSize * 2, fillSize * 2, fillSize, fillSize);
|
||||
break;
|
||||
case AnchorPresets.VerticalStretchLeft:
|
||||
fillArea = new Rectangle(0, 0, fillSize, fillSize * 3);
|
||||
break;
|
||||
case AnchorPresets.VerticalStretchRight:
|
||||
fillArea = new Rectangle(fillSize * 2, 0, fillSize, fillSize * 3);
|
||||
break;
|
||||
case AnchorPresets.VerticalStretchCenter:
|
||||
fillArea = new Rectangle(fillSize, 0, fillSize, fillSize * 3);
|
||||
break;
|
||||
case AnchorPresets.HorizontalStretchTop:
|
||||
fillArea = new Rectangle(0, 0, fillSize * 3, fillSize);
|
||||
break;
|
||||
case AnchorPresets.HorizontalStretchMiddle:
|
||||
fillArea = new Rectangle(0, fillSize, fillSize * 3, fillSize);
|
||||
break;
|
||||
case AnchorPresets.HorizontalStretchBottom:
|
||||
fillArea = new Rectangle(0, fillSize * 2, fillSize * 3, fillSize);
|
||||
break;
|
||||
case AnchorPresets.StretchAll:
|
||||
fillArea = new Rectangle(0, 0, fillSize * 3, fillSize * 3);
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
// Draw background
|
||||
//Render2D.FillRectangle(rect, backgroundColor);
|
||||
Render2D.DrawRectangle(rect, borderColor, 1.1f);
|
||||
|
||||
// Draw fill
|
||||
Render2D.FillRectangle(fillArea.MakeOffsetted(rect.Location), backgroundColor);
|
||||
|
||||
// Draw frame
|
||||
if (IsMouseOver)
|
||||
{
|
||||
Render2D.DrawRectangle(rect, style.ProgressNormal.AlphaMultiplied(0.8f), 1.1f);
|
||||
}
|
||||
if (IsSelected)
|
||||
{
|
||||
Render2D.DrawRectangle(rect, style.BackgroundSelected.AlphaMultiplied(0.8f), 1.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AnchorPresetsEditorPopup : ContextMenuBase
|
||||
{
|
||||
const float ButtonsMargin = 10.0f;
|
||||
const float ButtonsMarginStretch = 8.0f;
|
||||
const float ButtonsSize = 32.0f;
|
||||
const float TitleHeight = 23.0f;
|
||||
const float DialogWidth = ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch;
|
||||
const float DialogHeight = TitleHeight + ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AnchorPresetsEditorPopup"/> class.
|
||||
/// </summary>
|
||||
/// <param name="presets">The initial value.</param>
|
||||
public AnchorPresetsEditorPopup(AnchorPresets presets)
|
||||
{
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
Tag = presets;
|
||||
Size = new Vector2(DialogWidth, DialogHeight);
|
||||
|
||||
// Title
|
||||
var title = new Label(2, 2, DialogWidth - 4, TitleHeight)
|
||||
{
|
||||
Font = new FontReference(style.FontLarge),
|
||||
Text = "Anchor Presets",
|
||||
Parent = this
|
||||
};
|
||||
|
||||
// Buttons
|
||||
var buttonsX = ButtonsMargin;
|
||||
var buttonsY = title.Bottom + ButtonsMargin;
|
||||
var buttonsSpacingX = ButtonsSize + ButtonsMargin;
|
||||
var buttonsSpacingY = ButtonsSize + ButtonsMargin;
|
||||
//
|
||||
AddButton(buttonsX + buttonsSpacingX * 0, buttonsY + buttonsSpacingY * 0, AnchorPresets.TopLeft);
|
||||
AddButton(buttonsX + buttonsSpacingX * 1, buttonsY + buttonsSpacingY * 0, AnchorPresets.TopCenter);
|
||||
AddButton(buttonsX + buttonsSpacingX * 2, buttonsY + buttonsSpacingY * 0, AnchorPresets.TopRight);
|
||||
AddButton(buttonsX + buttonsSpacingX * 3 + ButtonsMarginStretch, buttonsY + buttonsSpacingY * 0, AnchorPresets.HorizontalStretchTop);
|
||||
//
|
||||
AddButton(buttonsX + buttonsSpacingX * 0, buttonsY + buttonsSpacingY * 1, AnchorPresets.MiddleLeft);
|
||||
AddButton(buttonsX + buttonsSpacingX * 1, buttonsY + buttonsSpacingY * 1, AnchorPresets.MiddleCenter);
|
||||
AddButton(buttonsX + buttonsSpacingX * 2, buttonsY + buttonsSpacingY * 1, AnchorPresets.MiddleRight);
|
||||
AddButton(buttonsX + buttonsSpacingX * 3 + ButtonsMarginStretch, buttonsY + buttonsSpacingY * 1, AnchorPresets.HorizontalStretchMiddle);
|
||||
//
|
||||
AddButton(buttonsX + buttonsSpacingX * 0, buttonsY + buttonsSpacingY * 2, AnchorPresets.BottomLeft);
|
||||
AddButton(buttonsX + buttonsSpacingX * 1, buttonsY + buttonsSpacingY * 2, AnchorPresets.BottomCenter);
|
||||
AddButton(buttonsX + buttonsSpacingX * 2, buttonsY + buttonsSpacingY * 2, AnchorPresets.BottomRight);
|
||||
AddButton(buttonsX + buttonsSpacingX * 3 + ButtonsMarginStretch, buttonsY + buttonsSpacingY * 2, AnchorPresets.HorizontalStretchBottom);
|
||||
//
|
||||
AddButton(buttonsX + buttonsSpacingX * 0, buttonsY + buttonsSpacingY * 3 + ButtonsMarginStretch, AnchorPresets.VerticalStretchLeft);
|
||||
AddButton(buttonsX + buttonsSpacingX * 1, buttonsY + buttonsSpacingY * 3 + ButtonsMarginStretch, AnchorPresets.VerticalStretchCenter);
|
||||
AddButton(buttonsX + buttonsSpacingX * 2, buttonsY + buttonsSpacingY * 3 + ButtonsMarginStretch, AnchorPresets.VerticalStretchRight);
|
||||
AddButton(buttonsX + buttonsSpacingX * 3 + ButtonsMarginStretch, buttonsY + buttonsSpacingY * 3 + ButtonsMarginStretch, AnchorPresets.StretchAll);
|
||||
}
|
||||
|
||||
private void AddButton(float x, float y, AnchorPresets presets)
|
||||
{
|
||||
var button = new AnchorButton
|
||||
{
|
||||
Bounds = new Rectangle(x, y, ButtonsSize, ButtonsSize),
|
||||
Parent = this,
|
||||
Presets = presets,
|
||||
IsSelected = presets == (AnchorPresets)Tag,
|
||||
Tag = presets,
|
||||
};
|
||||
button.ButtonClicked += OnButtonClicked;
|
||||
}
|
||||
|
||||
private void OnButtonClicked(Button button)
|
||||
{
|
||||
Tag = button.Tag;
|
||||
Hide();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnShow()
|
||||
{
|
||||
Focus();
|
||||
|
||||
base.OnShow();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Hide()
|
||||
{
|
||||
if (!Visible)
|
||||
return;
|
||||
|
||||
Focus(null);
|
||||
|
||||
base.Hide();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (key == KeyboardKeys.Escape)
|
||||
{
|
||||
Hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
}
|
||||
|
||||
private AnchorButton _button;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_button = layout.Custom<AnchorButton>().CustomControl;
|
||||
_button.Presets = (AnchorPresets)Values[0];
|
||||
_button.Clicked += OnButtonClicked;
|
||||
}
|
||||
|
||||
private void OnButtonClicked()
|
||||
{
|
||||
var location = _button.Center + new Vector2(3.0f);
|
||||
var editor = new AnchorPresetsEditorPopup(_button.Presets);
|
||||
editor.VisibleChanged += OnEditorVisibleChanged;
|
||||
editor.Show(_button.Parent, location);
|
||||
}
|
||||
|
||||
private void OnEditorVisibleChanged(Control control)
|
||||
{
|
||||
if (control.Visible)
|
||||
return;
|
||||
SetValue(control.Tag);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
_button.Presets = (AnchorPresets)Values[0];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
_button = null;
|
||||
|
||||
base.Deinitialize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dedicated custom editor for <see cref="UIControl.Control"/> object.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.Editors.GenericEditor" />
|
||||
public sealed class UIControlControlEditor : GenericEditor
|
||||
{
|
||||
private Type _cachedType;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_cachedType = null;
|
||||
|
||||
// Set control type button
|
||||
var space = layout.Space(20);
|
||||
float setTypeButtonWidth = 60.0f;
|
||||
var setTypeButton = new Button
|
||||
{
|
||||
TooltipText = "Sets the control to the given type",
|
||||
AnchorPreset = AnchorPresets.MiddleCenter,
|
||||
Text = "Set Type",
|
||||
Parent = space.Spacer,
|
||||
Bounds = new Rectangle((space.Spacer.Width - setTypeButtonWidth) / 2, 1, setTypeButtonWidth, 18),
|
||||
};
|
||||
setTypeButton.ButtonClicked += OnSetTypeButtonClicked;
|
||||
|
||||
// Don't show editor if any control is invalid
|
||||
if (Values.HasNull)
|
||||
{
|
||||
var label = layout.Label("Select control type to create", TextAlignment.Center);
|
||||
label.Label.Enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Add control type helper label
|
||||
{
|
||||
var type = Values[0].GetType();
|
||||
_cachedType = type;
|
||||
var label = layout.AddPropertyItem("Type", "The type of the created control.");
|
||||
label.Label(type.FullName);
|
||||
}
|
||||
|
||||
// Show control properties
|
||||
base.Initialize(layout);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
// Automatic layout rebuild if control type gets changed
|
||||
var type = Values.HasNull ? null : Values[0].GetType();
|
||||
if (type != _cachedType)
|
||||
{
|
||||
RebuildLayout();
|
||||
return;
|
||||
}
|
||||
|
||||
base.Refresh();
|
||||
}
|
||||
|
||||
private void OnSetTypeButtonClicked(Button button)
|
||||
{
|
||||
var controlTypes = Editor.Instance.CodeEditing.Controls.Get();
|
||||
if (controlTypes.Count == 0)
|
||||
return;
|
||||
|
||||
// Show context menu with list of controls to add
|
||||
var cm = new ItemsListContextMenu(180);
|
||||
for (int i = 0; i < controlTypes.Count; i++)
|
||||
{
|
||||
var controlType = controlTypes[i];
|
||||
var item = new ItemsListContextMenu.Item(controlType.Name, controlType)
|
||||
{
|
||||
TooltipText = controlType.TypeName,
|
||||
};
|
||||
var attributes = controlType.GetAttributes(false);
|
||||
var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute);
|
||||
if (tooltipAttribute != null)
|
||||
{
|
||||
item.TooltipText += '\n';
|
||||
item.TooltipText += tooltipAttribute.Text;
|
||||
}
|
||||
cm.AddItem(item);
|
||||
}
|
||||
|
||||
cm.ItemClicked += controlType => SetType((ScriptType)controlType.Tag);
|
||||
cm.SortChildren();
|
||||
cm.Show(button.Parent, button.BottomLeft);
|
||||
}
|
||||
|
||||
private void SetType(ScriptType controlType)
|
||||
{
|
||||
var uiControls = ParentEditor.Values;
|
||||
if (Presenter.Undo != null)
|
||||
{
|
||||
using (new UndoMultiBlock(Presenter.Undo, uiControls, "Set Control Type"))
|
||||
{
|
||||
for (int i = 0; i < uiControls.Count; i++)
|
||||
{
|
||||
var uiControl = (UIControl)uiControls[i];
|
||||
uiControl.Control = (Control)controlType.CreateInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < uiControls.Count; i++)
|
||||
{
|
||||
var uiControl = (UIControl)uiControls[i];
|
||||
uiControl.Control = (Control)controlType.CreateInstance();
|
||||
}
|
||||
}
|
||||
|
||||
ParentEditor.RebuildLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
105
Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
Normal file
105
Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Content.Settings;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for picking actor layer. Instead of choosing bit mask or layer index it shows a combo box with simple layer picking by name.
|
||||
/// </summary>
|
||||
public sealed class ActorLayerEditor : CustomEditor
|
||||
{
|
||||
private ComboBoxElement element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
element = layout.ComboBox();
|
||||
element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
|
||||
// Set layer names
|
||||
element.ComboBox.SetItems(LayersAndTagsSettings.GetCurrentLayers());
|
||||
}
|
||||
|
||||
private void GetActorsTree(List<Actor> list, Actor a)
|
||||
{
|
||||
list.Add(a);
|
||||
int cnt = a.ChildrenCount;
|
||||
for (int i = 0; i < cnt; i++)
|
||||
{
|
||||
GetActorsTree(list, a.GetChild(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(ComboBox comboBox)
|
||||
{
|
||||
int value = comboBox.SelectedIndex;
|
||||
if (value == -1)
|
||||
value = 0;
|
||||
|
||||
// If selected is single actor that has children, ask if apply layer to the sub objects as well
|
||||
if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren)
|
||||
{
|
||||
var valueText = comboBox.SelectedItem;
|
||||
|
||||
// Ask user
|
||||
var result = MessageBox.Show(
|
||||
string.Format("Do you want to change layer to \"{0}\" for all child actors as well?", valueText),
|
||||
"Change actor layer", MessageBoxButtons.YesNoCancel);
|
||||
if (result == DialogResult.Cancel)
|
||||
return;
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
// Note: this possibly breaks the design a little bit
|
||||
// But it's the easiest way to set value for selected actor and its children with one undo action
|
||||
List<Actor> actors = new List<Actor>(32);
|
||||
GetActorsTree(actors, actor);
|
||||
if (Presenter.Undo != null)
|
||||
{
|
||||
using (new UndoMultiBlock(Presenter.Undo, actors.ToArray(), "Change layer"))
|
||||
{
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
actors[i].Layer = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
actors[i].Layer = value;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values on many actor selected
|
||||
}
|
||||
else
|
||||
{
|
||||
element.ComboBox.SelectedIndex = (int)Values[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for picking actor static flags. Overrides the default enum editor logic to provide more useful functionalities.
|
||||
/// </summary>
|
||||
public sealed class ActorStaticFlagsEditor : EnumEditor
|
||||
{
|
||||
private void GetActorsTree(List<Actor> list, Actor a)
|
||||
{
|
||||
list.Add(a);
|
||||
int cnt = a.ChildrenCount;
|
||||
for (int i = 0; i < cnt; i++)
|
||||
{
|
||||
GetActorsTree(list, a.GetChild(i));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged()
|
||||
{
|
||||
var value = (StaticFlags)element.EnumComboBox.EnumTypeValue;
|
||||
|
||||
// If selected is single actor that has children, ask if apply flags to the sub objects as well
|
||||
if (Values.IsSingleObject && (StaticFlags)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren)
|
||||
{
|
||||
// Ask user
|
||||
var result = MessageBox.Show(
|
||||
"Do you want to set static flags to all child actors as well?",
|
||||
"Change actor static flags", MessageBoxButtons.YesNoCancel);
|
||||
if (result == DialogResult.Cancel)
|
||||
return;
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
// Note: this possibly breaks the design a little bit
|
||||
// But it's the easiest way to set value for selected actor and its children with one undo action
|
||||
List<Actor> actors = new List<Actor>(32);
|
||||
GetActorsTree(actors, actor);
|
||||
if (Presenter.Undo != null && Presenter.Undo.Enabled)
|
||||
{
|
||||
using (new UndoMultiBlock(Presenter.Undo, actors.ToArray(), "Change static flags"))
|
||||
{
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
actors[i].StaticFlags = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
actors[i].StaticFlags = value;
|
||||
}
|
||||
}
|
||||
|
||||
OnUnDirty();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
base.OnValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Source/Editor/CustomEditors/Editors/ActorTagEditor.cs
Normal file
57
Source/Editor/CustomEditors/Editors/ActorTagEditor.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Content.Settings;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for picking actor tag. Instead of choosing tag index or entering tag text it shows a combo box with simple tag picking by name.
|
||||
/// </summary>
|
||||
public sealed class ActorTagEditor : CustomEditor
|
||||
{
|
||||
private ComboBoxElement element;
|
||||
private const string NoTagText = "Untagged";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
element = layout.ComboBox();
|
||||
element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
|
||||
// Set tag names
|
||||
element.ComboBox.AddItem(NoTagText);
|
||||
element.ComboBox.AddItems(LayersAndTagsSettings.GetCurrentTags());
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(ComboBox comboBox)
|
||||
{
|
||||
string value = comboBox.SelectedItem;
|
||||
if (value == NoTagText)
|
||||
value = string.Empty;
|
||||
SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values on many actor selected
|
||||
}
|
||||
else
|
||||
{
|
||||
string value = (string)Values[0];
|
||||
if (string.IsNullOrEmpty(value))
|
||||
value = NoTagText;
|
||||
element.ComboBox.SelectedItem = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
Normal file
74
Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Actor transform editor.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public static class ActorTransformEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The X axis color.
|
||||
/// </summary>
|
||||
public static Color AxisColorX = new Color(1.0f, 0.0f, 0.02745f, 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// The Y axis color.
|
||||
/// </summary>
|
||||
public static Color AxisColorY = new Color(0.239215f, 1.0f, 0.047058f, 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// The Z axis color.
|
||||
/// </summary>
|
||||
public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// Custom editor for actor position/scale property.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.Editors.Vector3Editor" />
|
||||
public class PositionScaleEditor : Vector3Editor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
// Override colors
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
var grayOutFactor = 0.6f;
|
||||
XElement.FloatValue.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
|
||||
XElement.FloatValue.BorderSelectedColor = AxisColorX;
|
||||
YElement.FloatValue.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor);
|
||||
YElement.FloatValue.BorderSelectedColor = AxisColorY;
|
||||
ZElement.FloatValue.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
|
||||
ZElement.FloatValue.BorderSelectedColor = AxisColorZ;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom editor for actor orientation property.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.Editors.QuaternionEditor" />
|
||||
public class OrientationEditor : QuaternionEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
// Override colors
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
var grayOutFactor = 0.6f;
|
||||
XElement.FloatValue.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
|
||||
XElement.FloatValue.BorderSelectedColor = AxisColorX;
|
||||
YElement.FloatValue.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor);
|
||||
YElement.FloatValue.BorderSelectedColor = AxisColorY;
|
||||
ZElement.FloatValue.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
|
||||
ZElement.FloatValue.BorderSelectedColor = AxisColorZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Source/Editor/CustomEditors/Editors/ArrayEditor.cs
Normal file
73
Source/Editor/CustomEditors/Editors/ArrayEditor.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit arrays.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Array)), DefaultEditor]
|
||||
public class ArrayEditor : CollectionEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override int Count => (Values[0] as Array)?.Length ?? 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IList Allocate(int size)
|
||||
{
|
||||
var arrayType = Values.Type;
|
||||
var elementType = arrayType.GetElementType();
|
||||
return Array.CreateInstance(elementType, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Resize(int newSize)
|
||||
{
|
||||
var array = Values[0] as Array;
|
||||
var oldSize = array?.Length ?? 0;
|
||||
|
||||
if (oldSize != newSize)
|
||||
{
|
||||
// Allocate new array
|
||||
var arrayType = Values.Type;
|
||||
var elementType = arrayType.GetElementType();
|
||||
var newValues = Array.CreateInstance(elementType, newSize);
|
||||
|
||||
var sharedCount = Mathf.Min(oldSize, newSize);
|
||||
if (array != null && sharedCount > 0)
|
||||
{
|
||||
// Copy old values
|
||||
Array.Copy(array, 0, newValues, 0, sharedCount);
|
||||
|
||||
// Fill new entries with the last value
|
||||
for (int i = oldSize; i < newSize; i++)
|
||||
{
|
||||
Array.Copy(array, oldSize - 1, newValues, i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
SetValue(newValues);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IList CloneValues()
|
||||
{
|
||||
var array = Values[0] as Array;
|
||||
if (array == null)
|
||||
return null;
|
||||
|
||||
var size = array.Length;
|
||||
var arrayType = Values.Type;
|
||||
var elementType = arrayType.GetElementType();
|
||||
var cloned = Array.CreateInstance(elementType, size);
|
||||
|
||||
Array.Copy(array, 0, cloned, 0, size);
|
||||
|
||||
return cloned;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
Normal file
104
Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit reference to the <see cref="AssetItem"/>.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(AssetItem)), DefaultEditor]
|
||||
public sealed class AssetItemRefEditor : AssetRefEditor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit reference to the <see cref="SceneReference"/>.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(SceneReference)), DefaultEditor]
|
||||
public sealed class SceneRefEditor : AssetRefEditor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit reference to the <see cref="FlaxEngine.Asset"/>.
|
||||
/// </summary>
|
||||
/// <remarks>Supports editing reference to the asset using various containers: <see cref="Asset"/> or <see cref="AssetItem"/> or <see cref="Guid"/>.</remarks>
|
||||
[CustomEditor(typeof(Asset)), DefaultEditor]
|
||||
public class AssetRefEditor : CustomEditor
|
||||
{
|
||||
private CustomElement<AssetPicker> _element;
|
||||
private ScriptType _type;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
if (!HasDifferentTypes)
|
||||
{
|
||||
_type = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
|
||||
|
||||
float height = 48;
|
||||
var attributes = Values.GetAttributes();
|
||||
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
|
||||
if (assetReference != null)
|
||||
{
|
||||
if (assetReference.UseSmallPicker)
|
||||
height = 32;
|
||||
|
||||
if (!string.IsNullOrEmpty(assetReference.TypeName))
|
||||
{
|
||||
var customType = TypeUtils.GetType(assetReference.TypeName);
|
||||
if (customType != ScriptType.Null)
|
||||
_type = customType;
|
||||
else
|
||||
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
|
||||
}
|
||||
}
|
||||
|
||||
_element = layout.Custom<AssetPicker>();
|
||||
_element.CustomControl.AssetType = _type;
|
||||
_element.CustomControl.Height = height;
|
||||
_element.CustomControl.SelectedItemChanged += OnSelectedItemChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectedItemChanged()
|
||||
{
|
||||
if (typeof(AssetItem).IsAssignableFrom(_type.Type))
|
||||
SetValue(_element.CustomControl.SelectedItem);
|
||||
else if (_type.Type == typeof(Guid))
|
||||
SetValue(_element.CustomControl.SelectedID);
|
||||
else if (_type.Type == typeof(SceneReference))
|
||||
SetValue(new SceneReference(_element.CustomControl.SelectedID));
|
||||
else
|
||||
SetValue(_element.CustomControl.SelectedAsset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (!HasDifferentValues)
|
||||
{
|
||||
if (Values[0] is AssetItem assetItem)
|
||||
_element.CustomControl.SelectedItem = assetItem;
|
||||
else if (Values[0] is Guid guid)
|
||||
_element.CustomControl.SelectedID = guid;
|
||||
else if (Values[0] is SceneReference sceneAsset)
|
||||
_element.CustomControl.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
|
||||
else
|
||||
_element.CustomControl.SelectedAsset = Values[0] as Asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Source/Editor/CustomEditors/Editors/BooleanEditor.cs
Normal file
41
Source/Editor/CustomEditors/Editors/BooleanEditor.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit bool value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(bool)), DefaultEditor]
|
||||
public sealed class BooleanEditor : CustomEditor
|
||||
{
|
||||
private CheckBoxElement element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
element = layout.Checkbox();
|
||||
element.CheckBox.StateChanged += (box) => SetValue(box.Checked);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
element.CheckBox.Intermediate = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
element.CheckBox.Checked = (bool)Values[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
307
Source/Editor/CustomEditors/Editors/CollectionEditor.cs
Normal file
307
Source/Editor/CustomEditors/Editors/CollectionEditor.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit arrays/list.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public abstract class CollectionEditor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The custom implementation of the collection items labels that can be used to reorder items.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.GUI.PropertyNameLabel" />
|
||||
private class CollectionItemLabel : PropertyNameLabel
|
||||
{
|
||||
/// <summary>
|
||||
/// The editor.
|
||||
/// </summary>
|
||||
public CollectionEditor Editor;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the item (zero-based).
|
||||
/// </summary>
|
||||
public readonly int Index;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CollectionItemLabel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="editor">The editor.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
public CollectionItemLabel(CollectionEditor editor, int index)
|
||||
: base("Element " + index)
|
||||
{
|
||||
Editor = editor;
|
||||
Index = index;
|
||||
|
||||
SetupContextMenu += OnSetupContextMenu;
|
||||
}
|
||||
|
||||
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
|
||||
{
|
||||
menu.AddSeparator();
|
||||
|
||||
var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked);
|
||||
moveUpButton.Enabled = Index > 0;
|
||||
|
||||
var moveDownButton = menu.AddButton("Move down", OnMoveDownClicked);
|
||||
moveDownButton.Enabled = Index + 1 < Editor.Count;
|
||||
|
||||
menu.AddButton("Remove", OnRemoveClicked);
|
||||
}
|
||||
|
||||
private void OnMoveUpClicked(ContextMenuButton button)
|
||||
{
|
||||
Editor.Move(Index, Index - 1);
|
||||
}
|
||||
|
||||
private void OnMoveDownClicked(ContextMenuButton button)
|
||||
{
|
||||
Editor.Move(Index, Index + 1);
|
||||
}
|
||||
|
||||
private void OnRemoveClicked(ContextMenuButton button)
|
||||
{
|
||||
Editor.Remove(Index);
|
||||
}
|
||||
}
|
||||
|
||||
private IntegerValueElement _size;
|
||||
private int _elementsCount;
|
||||
private bool _readOnly;
|
||||
private bool _canReorderItems;
|
||||
private bool _notNullItems;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the collection.
|
||||
/// </summary>
|
||||
public abstract int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the collection elements.
|
||||
/// </summary>
|
||||
public ScriptType ElementType
|
||||
{
|
||||
get
|
||||
{
|
||||
var type = Values.Type;
|
||||
return new ScriptType(type.IsGenericType ? type.GetGenericArguments()[0] : type.GetElementType());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_readOnly = false;
|
||||
_canReorderItems = true;
|
||||
_notNullItems = false;
|
||||
|
||||
// No support for different collections for now
|
||||
if (HasDifferentValues || HasDifferentTypes)
|
||||
return;
|
||||
|
||||
var size = Count;
|
||||
|
||||
// Try get CollectionAttribute for collection editor meta
|
||||
var attributes = Values.GetAttributes();
|
||||
Type overrideEditorType = null;
|
||||
float spacing = 0.0f;
|
||||
var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
|
||||
if (collection != null)
|
||||
{
|
||||
// TODO: handle NotNullItems by filtering child editors SetValue
|
||||
|
||||
_readOnly = collection.ReadOnly;
|
||||
_canReorderItems = collection.CanReorderItems;
|
||||
_notNullItems = collection.NotNullItems;
|
||||
overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
|
||||
spacing = collection.Spacing;
|
||||
}
|
||||
|
||||
// Size
|
||||
if (_readOnly)
|
||||
{
|
||||
layout.Label("Size", size.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
_size = layout.IntegerValue("Size");
|
||||
_size.IntValue.MinValue = 0;
|
||||
_size.IntValue.MaxValue = ushort.MaxValue;
|
||||
_size.IntValue.Value = size;
|
||||
_size.IntValue.ValueChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
// Elements
|
||||
if (size > 0)
|
||||
{
|
||||
var elementType = ElementType;
|
||||
if (_canReorderItems)
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
if (i != 0 && spacing > 0f)
|
||||
{
|
||||
if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
||||
propertiesListElement.Space(spacing);
|
||||
else
|
||||
layout.Space(spacing);
|
||||
}
|
||||
|
||||
var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null;
|
||||
layout.Object(new CollectionItemLabel(this, i), new ListValueContainer(elementType, i, Values), overrideEditor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
if (i != 0 && spacing > 0f)
|
||||
{
|
||||
if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
||||
propertiesListElement.Space(spacing);
|
||||
else
|
||||
layout.Space(spacing);
|
||||
}
|
||||
|
||||
var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null;
|
||||
layout.Object("Element " + i, new ListValueContainer(elementType, i, Values), overrideEditor);
|
||||
}
|
||||
}
|
||||
}
|
||||
_elementsCount = size;
|
||||
|
||||
// Add/Remove buttons
|
||||
if (!_readOnly)
|
||||
{
|
||||
var area = layout.Space(20);
|
||||
var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16)
|
||||
{
|
||||
Text = "+",
|
||||
TooltipText = "Add new item",
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Parent = area.ContainerControl
|
||||
};
|
||||
addButton.Clicked += () =>
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
Resize(Count + 1);
|
||||
};
|
||||
var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16)
|
||||
{
|
||||
Text = "-",
|
||||
TooltipText = "Remove last item",
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Parent = area.ContainerControl,
|
||||
Enabled = size > 0
|
||||
};
|
||||
removeButton.Clicked += () =>
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
Resize(Count - 1);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSizeChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
Resize(_size.IntValue.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the specified item at the given index and swaps it with the other item. It supports undo.
|
||||
/// </summary>
|
||||
/// <param name="srcIndex">Index of the source item.</param>
|
||||
/// <param name="dstIndex">Index of the destination item to swap with.</param>
|
||||
private void Move(int srcIndex, int dstIndex)
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var cloned = CloneValues();
|
||||
|
||||
var tmp = cloned[dstIndex];
|
||||
cloned[dstIndex] = cloned[srcIndex];
|
||||
cloned[srcIndex] = tmp;
|
||||
|
||||
SetValue(cloned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the item at the specified index. It supports undo.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the item to remove.</param>
|
||||
private void Remove(int index)
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var newValues = Allocate(Count - 1);
|
||||
var oldValues = (IList)Values[0];
|
||||
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
newValues[i] = oldValues[i];
|
||||
}
|
||||
|
||||
for (int i = index; i < newValues.Count; i++)
|
||||
{
|
||||
newValues[i] = oldValues[i + 1];
|
||||
}
|
||||
|
||||
SetValue(newValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates the collection of the specified size.
|
||||
/// </summary>
|
||||
/// <param name="size">The size.</param>
|
||||
/// <returns>The collection.</returns>
|
||||
protected abstract IList Allocate(int size);
|
||||
|
||||
/// <summary>
|
||||
/// Resizes collection to the specified new size.
|
||||
/// </summary>
|
||||
/// <param name="newSize">The new size.</param>
|
||||
protected abstract void Resize(int newSize);
|
||||
|
||||
/// <summary>
|
||||
/// Clones the collection values.
|
||||
/// </summary>
|
||||
protected abstract IList CloneValues();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
// No support for different collections for now
|
||||
if (HasDifferentValues || HasDifferentTypes)
|
||||
return;
|
||||
|
||||
// Check if collection has been resized (by UI or from external source)
|
||||
if (Count != _elementsCount)
|
||||
{
|
||||
RebuildLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Source/Editor/CustomEditors/Editors/ColorEditor.cs
Normal file
48
Source/Editor/CustomEditors/Editors/ColorEditor.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Color value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Color)), DefaultEditor]
|
||||
public sealed class ColorEditor : CustomEditor
|
||||
{
|
||||
private CustomElement<ColorValueBox> element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
element = layout.Custom<ColorValueBox>();
|
||||
element.CustomControl.ValueChanged += OnValueChanged;
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
var token = element.CustomControl.IsSliding ? this : null;
|
||||
SetValue(element.CustomControl.Value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values on ColorValueBox
|
||||
}
|
||||
else
|
||||
{
|
||||
element.CustomControl.Value = (Color)Values[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Source/Editor/CustomEditors/Editors/ColorTrackball.cs
Normal file
133
Source/Editor/CustomEditors/Editors/ColorTrackball.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI.Dialogs;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom implementation of the inspector used to edit Vector4 color value type properties with color grading trackball.
|
||||
/// </summary>
|
||||
public sealed class ColorTrackball : CustomEditor
|
||||
{
|
||||
private FloatValueElement _xElement;
|
||||
private FloatValueElement _yElement;
|
||||
private FloatValueElement _zElement;
|
||||
private FloatValueElement _wElement;
|
||||
private CustomElement<ColorSelector> _trackball;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
float trackBallSize = 80.0f;
|
||||
float margin = 4.0f;
|
||||
|
||||
// Panel
|
||||
var masterPanel = layout.CustomContainer<GridPanel>();
|
||||
var masterPanelControl = masterPanel.CustomControl;
|
||||
masterPanelControl.ClipChildren = false;
|
||||
masterPanelControl.SlotPadding = new Margin(0, 0, margin, margin);
|
||||
masterPanelControl.Height = trackBallSize + margin + margin;
|
||||
masterPanelControl.ColumnFill = new[]
|
||||
{
|
||||
-trackBallSize,
|
||||
1.0f
|
||||
};
|
||||
masterPanelControl.RowFill = new[] { 1.0f };
|
||||
|
||||
// Trackball
|
||||
_trackball = masterPanel.Custom<ColorSelector>();
|
||||
_trackball.CustomControl.ColorChanged += OnColorWheelChanged;
|
||||
|
||||
// Scale editor
|
||||
{
|
||||
var grid = masterPanel.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.SlotPadding = new Margin(4, 2, 2, 2);
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.SlotsHorizontally = 1;
|
||||
gridControl.SlotsVertically = 4;
|
||||
|
||||
LimitAttribute limit = null;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
}
|
||||
|
||||
_xElement = CreateFloatEditor(grid, limit, Color.Red);
|
||||
_yElement = CreateFloatEditor(grid, limit, Color.Green);
|
||||
_zElement = CreateFloatEditor(grid, limit, Color.Blue);
|
||||
_wElement = CreateFloatEditor(grid, limit, Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
private FloatValueElement CreateFloatEditor(LayoutElementsContainer layout, LimitAttribute limit, Color borderColor)
|
||||
{
|
||||
var element = layout.FloatValue();
|
||||
element.SetLimits(limit);
|
||||
element.FloatValue.ValueChanged += OnValueChanged;
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
var grayOutFactor = 0.6f;
|
||||
element.FloatValue.BorderColor = Color.Lerp(borderColor, back, grayOutFactor);
|
||||
element.FloatValue.BorderSelectedColor = borderColor;
|
||||
return element;
|
||||
}
|
||||
|
||||
private void OnColorWheelChanged(Color color)
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
SetValue(new Vector4(color.R, color.G, color.B, _wElement.FloatValue.Value));
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
SetValue(new Vector4(
|
||||
_xElement.FloatValue.Value,
|
||||
_yElement.FloatValue.Value,
|
||||
_zElement.FloatValue.Value,
|
||||
_wElement.FloatValue.Value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (!HasDifferentValues)
|
||||
{
|
||||
var value = (Vector4)Values[0];
|
||||
var color = new Vector3(value);
|
||||
var scale = value.W;
|
||||
float min = color.MinValue;
|
||||
float max = color.MaxValue;
|
||||
if (min < 0.0f)
|
||||
{
|
||||
// Negative color case (e.g. offset)
|
||||
}
|
||||
else if (max > 1.0f)
|
||||
{
|
||||
// HDR color case
|
||||
color /= max;
|
||||
scale *= max;
|
||||
}
|
||||
_xElement.Value = color.X;
|
||||
_yElement.Value = color.Y;
|
||||
_zElement.Value = color.Z;
|
||||
_wElement.Value = scale;
|
||||
_trackball.CustomControl.Color = Vector3.Abs(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
449
Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
Normal file
449
Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
Normal file
@@ -0,0 +1,449 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Json;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit key-value dictionaries.
|
||||
/// </summary>
|
||||
public class DictionaryEditor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The custom implementation of the dictionary items labels that can be used to remove items or edit keys.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.GUI.PropertyNameLabel" />
|
||||
private class DictionaryItemLabel : PropertyNameLabel
|
||||
{
|
||||
private DictionaryEditor _editor;
|
||||
private object _key;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DictionaryItemLabel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="editor">The editor.</param>
|
||||
/// <param name="key">The key.</param>
|
||||
public DictionaryItemLabel(DictionaryEditor editor, object key)
|
||||
: base(key?.ToString() ?? "<null>")
|
||||
{
|
||||
_editor = editor;
|
||||
_key = key;
|
||||
|
||||
SetupContextMenu += OnSetupContextMenu;
|
||||
}
|
||||
|
||||
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
|
||||
{
|
||||
menu.AddSeparator();
|
||||
|
||||
menu.AddButton("Remove", OnRemoveClicked).Enabled = !_editor._readOnly;
|
||||
menu.AddButton("Edit", OnEditClicked).Enabled = _editor._canEditKeys;
|
||||
}
|
||||
|
||||
private void OnRemoveClicked(ContextMenuButton button)
|
||||
{
|
||||
_editor.Remove(_key);
|
||||
}
|
||||
|
||||
private void OnEditClicked(ContextMenuButton button)
|
||||
{
|
||||
var keyType = _editor.Values.Type.GetGenericArguments()[0];
|
||||
if (keyType == typeof(string) || keyType.IsPrimitive)
|
||||
{
|
||||
var popup = RenamePopup.Show(Parent, Bounds, Text, false);
|
||||
popup.Validate += (renamePopup, value) =>
|
||||
{
|
||||
object newKey;
|
||||
if (keyType.IsPrimitive)
|
||||
newKey = JsonSerializer.Deserialize(value, keyType);
|
||||
else
|
||||
newKey = value;
|
||||
return !((IDictionary)_editor.Values[0]).Contains(newKey);
|
||||
};
|
||||
popup.Renamed += renamePopup =>
|
||||
{
|
||||
object newKey;
|
||||
if (keyType.IsPrimitive)
|
||||
newKey = JsonSerializer.Deserialize(renamePopup.Text, keyType);
|
||||
else
|
||||
newKey = renamePopup.Text;
|
||||
|
||||
_editor.ChangeKey(_key, newKey);
|
||||
_key = newKey;
|
||||
Text = _key.ToString();
|
||||
};
|
||||
}
|
||||
else if (keyType.IsEnum)
|
||||
{
|
||||
var popup = RenamePopup.Show(Parent, Bounds, Text, false);
|
||||
var picker = new EnumComboBox(keyType)
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = Margin.Zero,
|
||||
Parent = popup,
|
||||
EnumTypeValue = _key,
|
||||
};
|
||||
picker.ValueChanged += () =>
|
||||
{
|
||||
popup.Hide();
|
||||
object newKey = picker.EnumTypeValue;
|
||||
if (!((IDictionary)_editor.Values[0]).Contains(newKey))
|
||||
{
|
||||
_editor.ChangeKey(_key, newKey);
|
||||
_key = newKey;
|
||||
Text = _key.ToString();
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Missing editing for dictionary key type " + keyType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
OnEditClicked(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_editor = null;
|
||||
_key = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
private IntegerValueElement _size;
|
||||
private int _elementsCount;
|
||||
private bool _readOnly;
|
||||
private bool _notNullItems;
|
||||
private bool _canEditKeys;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this editor[can edit the specified dictionary type.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of the dictionary.</param>
|
||||
/// <returns>True if can edit, otherwise false.</returns>
|
||||
public static bool CanEditType(Type type)
|
||||
{
|
||||
// Ensure it's a generic dictionary type
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the collection.
|
||||
/// </summary>
|
||||
public int Count => (Values[0] as IDictionary)?.Count ?? 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_readOnly = false;
|
||||
_notNullItems = false;
|
||||
|
||||
// No support for different collections for now
|
||||
if (HasDifferentValues || HasDifferentTypes)
|
||||
return;
|
||||
|
||||
var type = Values.Type;
|
||||
var size = Count;
|
||||
var argTypes = type.GetGenericArguments();
|
||||
var keyType = argTypes[0];
|
||||
var valueType = argTypes[1];
|
||||
_canEditKeys = keyType == typeof(string) || keyType.IsPrimitive || keyType.IsEnum;
|
||||
|
||||
// Try get CollectionAttribute for collection editor meta
|
||||
var attributes = Values.GetAttributes();
|
||||
Type overrideEditorType = null;
|
||||
float spacing = 0.0f;
|
||||
if (attributes != null)
|
||||
{
|
||||
var collection = (CollectionAttribute)attributes.FirstOrDefault(x => x is CollectionAttribute);
|
||||
if (collection != null)
|
||||
{
|
||||
// TODO: handle ReadOnly and NotNullItems by filtering child editors SetValue
|
||||
|
||||
_readOnly = collection.ReadOnly;
|
||||
_notNullItems = collection.NotNullItems;
|
||||
overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
|
||||
spacing = collection.Spacing;
|
||||
}
|
||||
}
|
||||
|
||||
// Size
|
||||
if (_readOnly || !_canEditKeys)
|
||||
{
|
||||
layout.Label("Size", size.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
_size = layout.IntegerValue("Size");
|
||||
_size.IntValue.MinValue = 0;
|
||||
_size.IntValue.MaxValue = ushort.MaxValue;
|
||||
_size.IntValue.Value = size;
|
||||
_size.IntValue.ValueChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
// Elements
|
||||
if (size > 0)
|
||||
{
|
||||
var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType<object>();
|
||||
var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray();
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
if (i != 0 && spacing > 0f)
|
||||
{
|
||||
if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
||||
propertiesListElement.Space(spacing);
|
||||
else
|
||||
layout.Space(spacing);
|
||||
}
|
||||
|
||||
var key = keys.ElementAt(i);
|
||||
var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null;
|
||||
layout.Object(new DictionaryItemLabel(this, key), new DictionaryValueContainer(new ScriptType(valueType), key, Values), overrideEditor);
|
||||
}
|
||||
}
|
||||
_elementsCount = size;
|
||||
|
||||
// Add/Remove buttons
|
||||
if (!_readOnly && _canEditKeys)
|
||||
{
|
||||
var area = layout.Space(20);
|
||||
var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16)
|
||||
{
|
||||
Text = "+",
|
||||
TooltipText = "Add new item",
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Parent = area.ContainerControl
|
||||
};
|
||||
addButton.Clicked += () =>
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
Resize(Count + 1);
|
||||
};
|
||||
var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16)
|
||||
{
|
||||
Text = "-",
|
||||
TooltipText = "Remove last item",
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Parent = area.ContainerControl,
|
||||
Enabled = size > 0
|
||||
};
|
||||
removeButton.Clicked += () =>
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
Resize(Count - 1);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSizeChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
Resize(_size.IntValue.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the item of the specified key. It supports undo.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the item to remove.</param>
|
||||
private void Remove(object key)
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
// Allocate new collection
|
||||
var dictionary = Values[0] as IDictionary;
|
||||
var type = Values.Type;
|
||||
var newValues = (IDictionary)type.CreateInstance();
|
||||
|
||||
// Copy all keys/values except the specified one
|
||||
if (dictionary != null)
|
||||
{
|
||||
foreach (var e in dictionary.Keys)
|
||||
{
|
||||
if (e == key)
|
||||
continue;
|
||||
newValues[e] = dictionary[e];
|
||||
}
|
||||
}
|
||||
|
||||
SetValue(newValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the key of the item.
|
||||
/// </summary>
|
||||
/// <param name="oldKey">The old key value.</param>
|
||||
/// <param name="newKey">The new key value.</param>
|
||||
protected void ChangeKey(object oldKey, object newKey)
|
||||
{
|
||||
var dictionary = (IDictionary)Values[0];
|
||||
var newValues = (IDictionary)Values.Type.CreateInstance();
|
||||
foreach (var e in dictionary.Keys)
|
||||
{
|
||||
if (Equals(e, oldKey))
|
||||
newValues[newKey] = dictionary[e];
|
||||
else
|
||||
newValues[e] = dictionary[e];
|
||||
}
|
||||
SetValue(newValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes collection to the specified new size.
|
||||
/// </summary>
|
||||
/// <param name="newSize">The new size.</param>
|
||||
protected void Resize(int newSize)
|
||||
{
|
||||
var dictionary = Values[0] as IDictionary;
|
||||
var oldSize = dictionary?.Count ?? 0;
|
||||
|
||||
if (oldSize == newSize)
|
||||
return;
|
||||
|
||||
// Allocate new collection
|
||||
var type = Values.Type;
|
||||
var argTypes = type.GetGenericArguments();
|
||||
var keyType = argTypes[0];
|
||||
var valueType = argTypes[1];
|
||||
var newValues = (IDictionary)type.CreateInstance();
|
||||
|
||||
// Copy all keys/values
|
||||
int itemsLeft = newSize;
|
||||
if (dictionary != null)
|
||||
{
|
||||
foreach (var e in dictionary.Keys)
|
||||
{
|
||||
if (itemsLeft == 0)
|
||||
break;
|
||||
newValues[e] = dictionary[e];
|
||||
itemsLeft--;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert new items (find unique keys)
|
||||
int newItemsLeft = newSize - oldSize;
|
||||
while (newItemsLeft-- > 0)
|
||||
{
|
||||
if (keyType.IsPrimitive)
|
||||
{
|
||||
long uniqueKey = 0;
|
||||
bool isUnique;
|
||||
do
|
||||
{
|
||||
isUnique = true;
|
||||
foreach (var e in newValues.Keys)
|
||||
{
|
||||
var asLong = Convert.ToInt64(e);
|
||||
if (asLong == uniqueKey)
|
||||
{
|
||||
uniqueKey++;
|
||||
isUnique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!isUnique);
|
||||
|
||||
newValues[Convert.ChangeType(uniqueKey, keyType)] = TypeUtils.GetDefaultValue(new ScriptType(valueType));
|
||||
}
|
||||
else if (keyType.IsEnum)
|
||||
{
|
||||
var enumValues = Enum.GetValues(keyType);
|
||||
int uniqueKeyIndex = 0;
|
||||
bool isUnique;
|
||||
do
|
||||
{
|
||||
isUnique = true;
|
||||
foreach (var e in newValues.Keys)
|
||||
{
|
||||
if (Equals(e, enumValues.GetValue(uniqueKeyIndex)))
|
||||
{
|
||||
uniqueKeyIndex++;
|
||||
isUnique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!isUnique && uniqueKeyIndex < enumValues.Length);
|
||||
|
||||
newValues[enumValues.GetValue(uniqueKeyIndex)] = TypeUtils.GetDefaultValue(new ScriptType(valueType));
|
||||
}
|
||||
else if (keyType == typeof(string))
|
||||
{
|
||||
string uniqueKey = "Key";
|
||||
bool isUnique;
|
||||
do
|
||||
{
|
||||
isUnique = true;
|
||||
foreach (var e in newValues.Keys)
|
||||
{
|
||||
if ((string)e == uniqueKey)
|
||||
{
|
||||
uniqueKey += "*";
|
||||
isUnique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!isUnique);
|
||||
|
||||
newValues[uniqueKey] = TypeUtils.GetDefaultValue(new ScriptType(valueType));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
SetValue(newValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
// No support for different collections for now
|
||||
if (HasDifferentValues || HasDifferentTypes)
|
||||
return;
|
||||
|
||||
// Check if collection has been resized (by UI or from external source)
|
||||
if (Count != _elementsCount)
|
||||
{
|
||||
RebuildLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Source/Editor/CustomEditors/Editors/DoubleEditor.cs
Normal file
73
Source/Editor/CustomEditors/Editors/DoubleEditor.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit double value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(double)), DefaultEditor]
|
||||
public sealed class DoubleEditor : CustomEditor
|
||||
{
|
||||
private DoubleValueElement _element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_element = null;
|
||||
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
// Use double value editor with limit
|
||||
var doubleValue = layout.DoubleValue();
|
||||
doubleValue.SetLimits((LimitAttribute)limit);
|
||||
doubleValue.DoubleValue.ValueChanged += OnValueChanged;
|
||||
doubleValue.DoubleValue.SlidingEnd += ClearToken;
|
||||
_element = doubleValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_element == null)
|
||||
{
|
||||
// Use double value editor
|
||||
var doubleValue = layout.DoubleValue();
|
||||
doubleValue.DoubleValue.ValueChanged += OnValueChanged;
|
||||
doubleValue.DoubleValue.SlidingEnd += ClearToken;
|
||||
_element = doubleValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
var isSliding = _element.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
SetValue(_element.Value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
_element.Value = (double)Values[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Source/Editor/CustomEditors/Editors/EnumEditor.cs
Normal file
70
Source/Editor/CustomEditors/Editors/EnumEditor.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit float value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Enum)), DefaultEditor]
|
||||
public class EnumEditor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The enum element.
|
||||
/// </summary>
|
||||
protected EnumElement element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var mode = EnumDisplayAttribute.FormatMode.Default;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes?.FirstOrDefault(x => x is EnumDisplayAttribute) is EnumDisplayAttribute enumDisplay)
|
||||
{
|
||||
mode = enumDisplay.Mode;
|
||||
}
|
||||
|
||||
if (HasDifferentTypes)
|
||||
{
|
||||
// No support for different enum types
|
||||
}
|
||||
else
|
||||
{
|
||||
var enumType = Values.Type.Type != typeof(object) || Values[0] == null ? TypeUtils.GetType(Values.Type) : Values[0].GetType();
|
||||
element = layout.Enum(enumType, null, mode);
|
||||
element.EnumComboBox.ValueChanged += OnValueChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when value get changed. Allows to override default value setter logic.
|
||||
/// </summary>
|
||||
protected virtual void OnValueChanged()
|
||||
{
|
||||
SetValue(element.EnumComboBox.EnumTypeValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// No support for different enum values
|
||||
}
|
||||
else
|
||||
{
|
||||
element.EnumComboBox.EnumTypeValue = Values[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
499
Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
Normal file
499
Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
Normal file
@@ -0,0 +1,499 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom control type used to pick reference to <see cref="FlaxEngine.Object"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Control" />
|
||||
public class FlaxObjectRefPickerControl : Control
|
||||
{
|
||||
private ScriptType _type;
|
||||
private Object _value;
|
||||
private string _valueName;
|
||||
private bool _supportsPickDropDown;
|
||||
|
||||
private bool _isMouseDown;
|
||||
private Vector2 _mouseDownPos;
|
||||
private Vector2 _mousePos;
|
||||
|
||||
private bool _hasValidDragOver;
|
||||
private DragActors _dragActors;
|
||||
private DragActors _dragActorsWithScript;
|
||||
private DragAssets _dragAssets;
|
||||
private DragScripts _dragScripts;
|
||||
private DragHandlers _dragHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the allowed objects type (given type and all sub classes). Must be <see cref="Object"/> type of any subclass.
|
||||
/// </summary>
|
||||
public ScriptType Type
|
||||
{
|
||||
get => _type;
|
||||
set
|
||||
{
|
||||
if (_type == value)
|
||||
return;
|
||||
if (value == ScriptType.Null || (value.Type != typeof(Object) && !value.IsSubclassOf(new ScriptType(typeof(Object)))))
|
||||
throw new ArgumentException(string.Format("Invalid type for FlaxObjectRefEditor. Input type: {0}", value != ScriptType.Null ? value.TypeName : "null"));
|
||||
|
||||
_type = value;
|
||||
_supportsPickDropDown = new ScriptType(typeof(Actor)).IsAssignableFrom(value) || new ScriptType(typeof(Script)).IsAssignableFrom(value);
|
||||
|
||||
// Deselect value if it's not valid now
|
||||
if (!IsValid(_value))
|
||||
Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected object value.
|
||||
/// </summary>
|
||||
public Object Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
if (_value == value)
|
||||
return;
|
||||
if (!IsValid(value))
|
||||
throw new ArgumentException("Invalid object type.");
|
||||
|
||||
// Special case for missing objects (eg. referenced actor in script that is deleted in editor)
|
||||
if (value != null && (Object.GetUnmanagedPtr(value) == IntPtr.Zero || value.ID == Guid.Empty))
|
||||
value = null;
|
||||
|
||||
_value = value;
|
||||
var type = TypeUtils.GetObjectType(_value);
|
||||
|
||||
// Get name to display
|
||||
if (_value is Script script)
|
||||
{
|
||||
_valueName = script.Actor ? $"{type.Name} ({script.Actor.Name})" : type.Name;
|
||||
}
|
||||
else if (_value != null)
|
||||
{
|
||||
_valueName = _value.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueName = string.Empty;
|
||||
}
|
||||
|
||||
// Update tooltip
|
||||
if (_value is SceneObject sceneObject)
|
||||
{
|
||||
var str = sceneObject is Actor actor ? actor.Name : type.Name;
|
||||
var o = sceneObject.Parent;
|
||||
while (o)
|
||||
{
|
||||
str = o.Name + " -> " + str;
|
||||
o = o.Parent;
|
||||
}
|
||||
TooltipText = str;
|
||||
}
|
||||
else
|
||||
{
|
||||
TooltipText = string.Empty;
|
||||
}
|
||||
|
||||
OnValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected object value by identifier.
|
||||
/// </summary>
|
||||
public Guid ValueID
|
||||
{
|
||||
get => _value ? _value.ID : Guid.Empty;
|
||||
set => Value = Object.Find<Object>(ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when value gets changed.
|
||||
/// </summary>
|
||||
public event Action ValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The custom callback for objects validation. Cane be used to implement a rule for objects to pick.
|
||||
/// </summary>
|
||||
public Func<Object, ScriptType, bool> CheckValid;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlaxObjectRefPickerControl"/> class.
|
||||
/// </summary>
|
||||
public FlaxObjectRefPickerControl()
|
||||
: base(0, 0, 50, 16)
|
||||
{
|
||||
_type = new ScriptType(typeof(Object));
|
||||
}
|
||||
|
||||
private bool IsValid(Object obj)
|
||||
{
|
||||
var type = TypeUtils.GetObjectType(obj);
|
||||
return obj == null || _type.IsAssignableFrom(type) && (CheckValid == null || CheckValid(obj, type));
|
||||
}
|
||||
|
||||
private void ShowDropDownMenu()
|
||||
{
|
||||
Focus();
|
||||
if (new ScriptType(typeof(Actor)).IsAssignableFrom(_type))
|
||||
{
|
||||
ActorSearchPopup.Show(this, new Vector2(0, Height), IsValid, actor =>
|
||||
{
|
||||
Value = actor;
|
||||
RootWindow.Focus();
|
||||
Focus();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptSearchPopup.Show(this, new Vector2(0, Height), IsValid, script =>
|
||||
{
|
||||
Value = script;
|
||||
RootWindow.Focus();
|
||||
Focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when value gets changed.
|
||||
/// </summary>
|
||||
protected virtual void OnValueChanged()
|
||||
{
|
||||
ValueChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Cache data
|
||||
var style = Style.Current;
|
||||
bool isSelected = _value != null;
|
||||
bool isEnabled = EnabledInHierarchy;
|
||||
var frameRect = new Rectangle(0, 0, Width, 16);
|
||||
if (isSelected)
|
||||
frameRect.Width -= 16;
|
||||
if (_supportsPickDropDown)
|
||||
frameRect.Width -= 16;
|
||||
var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14);
|
||||
var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14);
|
||||
var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14);
|
||||
|
||||
// Draw frame
|
||||
Render2D.DrawRectangle(frameRect, isEnabled && IsMouseOver ? style.BorderHighlighted : style.BorderNormal);
|
||||
|
||||
// Check if has item selected
|
||||
if (isSelected)
|
||||
{
|
||||
// Draw name
|
||||
Render2D.PushClip(nameRect);
|
||||
Render2D.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center);
|
||||
Render2D.PopClip();
|
||||
|
||||
// Draw deselect button
|
||||
Render2D.DrawSprite(style.Cross, button1Rect, isEnabled && button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw info
|
||||
Render2D.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center);
|
||||
}
|
||||
|
||||
// Draw picker button
|
||||
if (_supportsPickDropDown)
|
||||
{
|
||||
var pickerRect = isSelected ? button2Rect : button1Rect;
|
||||
Render2D.DrawSprite(style.ArrowDown, pickerRect, isEnabled && pickerRect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
// Check if drag is over
|
||||
if (IsDragOver && _hasValidDragOver)
|
||||
Render2D.FillRectangle(new Rectangle(Vector2.Zero, Size), style.BackgroundSelected * 0.4f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Vector2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
_mouseDownPos = Vector2.Minimum;
|
||||
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
_mousePos = Vector2.Minimum;
|
||||
|
||||
// Check if start drag drop
|
||||
if (_isMouseDown)
|
||||
{
|
||||
// Do the drag
|
||||
DoDrag();
|
||||
|
||||
// Clear flag
|
||||
_isMouseDown = false;
|
||||
}
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Vector2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
// Check if start drag drop
|
||||
if (_isMouseDown && Vector2.Distance(location, _mouseDownPos) > 10.0f)
|
||||
{
|
||||
// Do the drag
|
||||
DoDrag();
|
||||
|
||||
// Clear flag
|
||||
_isMouseDown = false;
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
// Clear flag
|
||||
_isMouseDown = false;
|
||||
}
|
||||
|
||||
// Cache data
|
||||
bool isSelected = _value != null;
|
||||
var frameRect = new Rectangle(0, 0, Width, 16);
|
||||
if (isSelected)
|
||||
frameRect.Width -= 16;
|
||||
if (_supportsPickDropDown)
|
||||
frameRect.Width -= 16;
|
||||
var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14);
|
||||
var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14);
|
||||
var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14);
|
||||
|
||||
// Deselect
|
||||
if (_value != null && button1Rect.Contains(ref location))
|
||||
Value = null;
|
||||
|
||||
// Picker dropdown menu
|
||||
if (_supportsPickDropDown && (isSelected ? button2Rect : button1Rect).Contains(ref location))
|
||||
ShowDropDownMenu();
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
// Set flag
|
||||
_isMouseDown = true;
|
||||
_mouseDownPos = location;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Vector2 location, MouseButton button)
|
||||
{
|
||||
Focus();
|
||||
|
||||
// Check if has object selected
|
||||
if (_value != null)
|
||||
{
|
||||
// Select object
|
||||
if (_value is Actor actor)
|
||||
Editor.Instance.SceneEditing.Select(actor);
|
||||
else if (_value is Script script && script.Actor)
|
||||
Editor.Instance.SceneEditing.Select(script.Actor);
|
||||
else if (_value is Asset asset)
|
||||
Editor.Instance.Windows.ContentWin.Select(asset);
|
||||
}
|
||||
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
private void DoDrag()
|
||||
{
|
||||
// Do the drag drop operation if has selected element
|
||||
if (_value != null)
|
||||
{
|
||||
if (_value is Actor actor)
|
||||
DoDragDrop(DragActors.GetDragData(actor));
|
||||
else if (_value is Asset asset)
|
||||
DoDragDrop(DragAssets.GetDragData(asset));
|
||||
else if (_value is Script script)
|
||||
DoDragDrop(DragScripts.GetDragData(script));
|
||||
}
|
||||
}
|
||||
|
||||
private DragDropEffect DragEffect => _hasValidDragOver ? DragDropEffect.Move : DragDropEffect.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragEnter(ref Vector2 location, DragData data)
|
||||
{
|
||||
base.OnDragEnter(ref location, data);
|
||||
|
||||
// Ensure to have valid drag helpers (uses lazy init)
|
||||
if (_dragActors == null)
|
||||
_dragActors = new DragActors(x => IsValid(x.Actor));
|
||||
if (_dragActorsWithScript == null)
|
||||
_dragActorsWithScript = new DragActors(ValidateDragActorWithScript);
|
||||
if (_dragAssets == null)
|
||||
_dragAssets = new DragAssets(ValidateDragAsset);
|
||||
if (_dragScripts == null)
|
||||
_dragScripts = new DragScripts(IsValid);
|
||||
if (_dragHandlers == null)
|
||||
{
|
||||
_dragHandlers = new DragHandlers
|
||||
{
|
||||
_dragActors,
|
||||
_dragActorsWithScript,
|
||||
_dragAssets,
|
||||
_dragScripts,
|
||||
};
|
||||
}
|
||||
|
||||
_hasValidDragOver = _dragHandlers.OnDragEnter(data) != DragDropEffect.None;
|
||||
|
||||
// Special case when dragging the actor with script to link script reference
|
||||
if (_dragActorsWithScript.HasValidDrag)
|
||||
{
|
||||
var script = _dragActorsWithScript.Objects[0].Actor.Scripts.First(IsValid);
|
||||
_dragActorsWithScript.Objects.Clear();
|
||||
_dragScripts.Objects.Add(script);
|
||||
}
|
||||
|
||||
return DragEffect;
|
||||
}
|
||||
|
||||
private bool ValidateDragAsset(AssetItem assetItem)
|
||||
{
|
||||
// Check if can accept assets
|
||||
if (!new ScriptType(typeof(Asset)).IsAssignableFrom(_type))
|
||||
return false;
|
||||
|
||||
// Load or get asset
|
||||
var id = assetItem.ID;
|
||||
var obj = Object.Find<Asset>(ref id);
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
// Check it
|
||||
return IsValid(obj);
|
||||
}
|
||||
|
||||
private bool ValidateDragActorWithScript(ActorNode node)
|
||||
{
|
||||
return node.Actor.Scripts.Any(IsValid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragMove(ref Vector2 location, DragData data)
|
||||
{
|
||||
base.OnDragMove(ref location, data);
|
||||
|
||||
return DragEffect;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_hasValidDragOver = false;
|
||||
_dragHandlers.OnDragLeave();
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragDrop(ref Vector2 location, DragData data)
|
||||
{
|
||||
var result = DragEffect;
|
||||
|
||||
base.OnDragDrop(ref location, data);
|
||||
|
||||
if (_dragActors.HasValidDrag)
|
||||
{
|
||||
Value = _dragActors.Objects[0].Actor;
|
||||
}
|
||||
else if (_dragAssets.HasValidDrag)
|
||||
{
|
||||
ValueID = _dragAssets.Objects[0].ID;
|
||||
}
|
||||
else if (_dragScripts.HasValidDrag)
|
||||
{
|
||||
ValueID = _dragScripts.Objects[0].ID;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_value = null;
|
||||
_type = ScriptType.Null;
|
||||
_valueName = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit reference to the <see cref="FlaxEngine.Object"/>.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Object)), DefaultEditor]
|
||||
public sealed class FlaxObjectRefEditor : CustomEditor
|
||||
{
|
||||
private CustomElement<FlaxObjectRefPickerControl> _element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
if (!HasDifferentTypes)
|
||||
{
|
||||
_element = layout.Custom<FlaxObjectRefPickerControl>();
|
||||
_element.CustomControl.Type = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
|
||||
_element.CustomControl.ValueChanged += () => SetValue(_element.CustomControl.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (!HasDifferentValues)
|
||||
{
|
||||
_element.CustomControl.Value = Values[0] as Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Source/Editor/CustomEditors/Editors/FloatEditor.cs
Normal file
91
Source/Editor/CustomEditors/Editors/FloatEditor.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit float value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(float)), DefaultEditor]
|
||||
public sealed class FloatEditor : CustomEditor
|
||||
{
|
||||
private IFloatValueEditor _element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_element = null;
|
||||
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
var range = attributes.FirstOrDefault(x => x is RangeAttribute);
|
||||
if (range != null)
|
||||
{
|
||||
// Use slider
|
||||
var slider = layout.Slider();
|
||||
slider.SetLimits((RangeAttribute)range);
|
||||
slider.Slider.ValueChanged += OnValueChanged;
|
||||
slider.Slider.SlidingEnd += ClearToken;
|
||||
_element = slider;
|
||||
return;
|
||||
}
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
// Use float value editor with limit
|
||||
var floatValue = layout.FloatValue();
|
||||
floatValue.SetLimits((LimitAttribute)limit);
|
||||
floatValue.FloatValue.ValueChanged += OnValueChanged;
|
||||
floatValue.FloatValue.SlidingEnd += ClearToken;
|
||||
_element = floatValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_element == null)
|
||||
{
|
||||
// Use float value editor
|
||||
var floatValue = layout.FloatValue();
|
||||
floatValue.FloatValue.ValueChanged += OnValueChanged;
|
||||
floatValue.FloatValue.SlidingEnd += ClearToken;
|
||||
_element = floatValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
var isSliding = _element.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
SetValue(_element.Value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = Values[0];
|
||||
if (value is float asFloat)
|
||||
_element.Value = asFloat;
|
||||
else if (value is double asDouble)
|
||||
_element.Value = (float)asDouble;
|
||||
else
|
||||
throw new Exception("Invalid value.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
607
Source/Editor/CustomEditors/Editors/GenericEditor.cs
Normal file
607
Source/Editor/CustomEditors/Editors/GenericEditor.cs
Normal file
@@ -0,0 +1,607 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used when no specified inspector is provided for the type. Inspector
|
||||
/// displays GUI for all the inspectable fields in the object.
|
||||
/// </summary>
|
||||
public class GenericEditor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes object property/field information for custom editors pipeline.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IComparable" />
|
||||
protected class ItemInfo : IComparable
|
||||
{
|
||||
/// <summary>
|
||||
/// The member information from reflection.
|
||||
/// </summary>
|
||||
public ScriptMemberInfo Info;
|
||||
|
||||
/// <summary>
|
||||
/// The order attribute.
|
||||
/// </summary>
|
||||
public EditorOrderAttribute Order;
|
||||
|
||||
/// <summary>
|
||||
/// The display attribute.
|
||||
/// </summary>
|
||||
public EditorDisplayAttribute Display;
|
||||
|
||||
/// <summary>
|
||||
/// The tooltip attribute.
|
||||
/// </summary>
|
||||
public TooltipAttribute Tooltip;
|
||||
|
||||
/// <summary>
|
||||
/// The custom editor attribute.
|
||||
/// </summary>
|
||||
public CustomEditorAttribute CustomEditor;
|
||||
|
||||
/// <summary>
|
||||
/// The custom editor alias attribute.
|
||||
/// </summary>
|
||||
public CustomEditorAliasAttribute CustomEditorAlias;
|
||||
|
||||
/// <summary>
|
||||
/// The space attribute.
|
||||
/// </summary>
|
||||
public SpaceAttribute Space;
|
||||
|
||||
/// <summary>
|
||||
/// The header attribute.
|
||||
/// </summary>
|
||||
public HeaderAttribute Header;
|
||||
|
||||
/// <summary>
|
||||
/// The visible if attribute.
|
||||
/// </summary>
|
||||
public VisibleIfAttribute VisibleIf;
|
||||
|
||||
/// <summary>
|
||||
/// The read-only attribute usage flag.
|
||||
/// </summary>
|
||||
public bool IsReadOnly;
|
||||
|
||||
/// <summary>
|
||||
/// The expand groups flag.
|
||||
/// </summary>
|
||||
public bool ExpandGroups;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name.
|
||||
/// </summary>
|
||||
public string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether use dedicated group.
|
||||
/// </summary>
|
||||
public bool UseGroup => Display?.Group != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the overridden custom editor for item editing.
|
||||
/// </summary>
|
||||
public CustomEditor OverrideEditor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CustomEditor != null)
|
||||
return (CustomEditor)Activator.CreateInstance(CustomEditor.Type);
|
||||
if (CustomEditorAlias != null)
|
||||
return (CustomEditor)TypeUtils.CreateInstance(CustomEditorAlias.TypeName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tooltip text (may be null if not provided).
|
||||
/// </summary>
|
||||
public string TooltipText => Tooltip?.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="info">The reflection information.</param>
|
||||
public ItemInfo(ScriptMemberInfo info)
|
||||
: this(info, info.GetAttributes(true))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="info">The reflection information.</param>
|
||||
/// <param name="attributes">The attributes.</param>
|
||||
public ItemInfo(ScriptMemberInfo info, object[] attributes)
|
||||
{
|
||||
Info = info;
|
||||
Order = (EditorOrderAttribute)attributes.FirstOrDefault(x => x is EditorOrderAttribute);
|
||||
Display = (EditorDisplayAttribute)attributes.FirstOrDefault(x => x is EditorDisplayAttribute);
|
||||
Tooltip = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute);
|
||||
CustomEditor = (CustomEditorAttribute)attributes.FirstOrDefault(x => x is CustomEditorAttribute);
|
||||
CustomEditorAlias = (CustomEditorAliasAttribute)attributes.FirstOrDefault(x => x is CustomEditorAliasAttribute);
|
||||
Space = (SpaceAttribute)attributes.FirstOrDefault(x => x is SpaceAttribute);
|
||||
Header = (HeaderAttribute)attributes.FirstOrDefault(x => x is HeaderAttribute);
|
||||
VisibleIf = (VisibleIfAttribute)attributes.FirstOrDefault(x => x is VisibleIfAttribute);
|
||||
IsReadOnly = attributes.FirstOrDefault(x => x is ReadOnlyAttribute) != null;
|
||||
ExpandGroups = attributes.FirstOrDefault(x => x is ExpandGroupsAttribute) != null;
|
||||
|
||||
IsReadOnly |= !info.HasSet;
|
||||
DisplayName = Display?.Name ?? CustomEditorsUtil.GetPropertyNameUI(info.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values.
|
||||
/// </summary>
|
||||
/// <param name="instanceValues">The instance values.</param>
|
||||
/// <returns>The values container.</returns>
|
||||
public ValueContainer GetValues(ValueContainer instanceValues)
|
||||
{
|
||||
return new ValueContainer(Info, instanceValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is ItemInfo other)
|
||||
{
|
||||
// By order
|
||||
if (Order != null)
|
||||
{
|
||||
if (other.Order != null)
|
||||
return Order.Order - other.Order.Order;
|
||||
return -1;
|
||||
}
|
||||
if (other.Order != null)
|
||||
return 1;
|
||||
|
||||
// By group name
|
||||
if (Display?.Group != null)
|
||||
{
|
||||
if (other.Display?.Group != null)
|
||||
return string.Compare(Display.Group, other.Display.Group, StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
// By name
|
||||
return string.Compare(Info.Name, other.Info.Name, StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Info.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether can merge two item infos to present them at once.
|
||||
/// </summary>
|
||||
/// <param name="a">The a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns><c>true</c> if can merge two item infos to present them at once; otherwise, <c>false</c>.</returns>
|
||||
public static bool CanMerge(ItemInfo a, ItemInfo b)
|
||||
{
|
||||
if (a.Info.DeclaringType != b.Info.DeclaringType)
|
||||
return false;
|
||||
return a.Info.Name == b.Info.Name;
|
||||
}
|
||||
}
|
||||
|
||||
private struct VisibleIfCache
|
||||
{
|
||||
public ScriptMemberInfo Target;
|
||||
public ScriptMemberInfo Source;
|
||||
public PropertiesListElement PropertiesList;
|
||||
public bool Invert;
|
||||
public int LabelIndex;
|
||||
|
||||
public bool GetValue(object instance)
|
||||
{
|
||||
var value = (bool)Source.GetValue(instance);
|
||||
if (Invert)
|
||||
value = !value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private VisibleIfCache[] _visibleIfCaches;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items for the type
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>The items.</returns>
|
||||
protected virtual List<ItemInfo> GetItemsForType(ScriptType type)
|
||||
{
|
||||
return GetItemsForType(type, type.IsClass, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items for the type
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="useProperties">True if use type properties.</param>
|
||||
/// <param name="useFields">True if use type fields.</param>
|
||||
/// <returns>The items.</returns>
|
||||
protected List<ItemInfo> GetItemsForType(ScriptType type, bool useProperties, bool useFields)
|
||||
{
|
||||
var items = new List<ItemInfo>();
|
||||
|
||||
if (useProperties)
|
||||
{
|
||||
// Process properties
|
||||
var properties = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
items.Capacity = Math.Max(items.Capacity, items.Count + properties.Length);
|
||||
for (int i = 0; i < properties.Length; i++)
|
||||
{
|
||||
var p = properties[i];
|
||||
|
||||
// Skip properties without getter or setter
|
||||
if (!p.HasGet || !p.HasSet)
|
||||
continue;
|
||||
|
||||
var attributes = p.GetAttributes(true);
|
||||
|
||||
// Skip hidden fields, handle special attributes
|
||||
if ((!p.IsPublic && !attributes.Any(x => x is ShowInEditorAttribute)) || attributes.Any(x => x is HideInEditorAttribute))
|
||||
continue;
|
||||
|
||||
items.Add(new ItemInfo(p, attributes));
|
||||
}
|
||||
}
|
||||
|
||||
if (useFields)
|
||||
{
|
||||
// Process fields
|
||||
var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
items.Capacity = Math.Max(items.Capacity, items.Count + fields.Length);
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var f = fields[i];
|
||||
|
||||
var attributes = f.GetAttributes(true);
|
||||
|
||||
// Skip hidden fields, handle special attributes
|
||||
if ((!f.IsPublic && !attributes.Any(x => x is ShowInEditorAttribute)) || attributes.Any(x => x is HideInEditorAttribute))
|
||||
continue;
|
||||
|
||||
items.Add(new ItemInfo(f, attributes));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private static ScriptMemberInfo GetVisibleIfSource(ScriptType type, VisibleIfAttribute visibleIf)
|
||||
{
|
||||
var property = type.GetProperty(visibleIf.MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
if (property != ScriptMemberInfo.Null)
|
||||
{
|
||||
if (!property.HasGet)
|
||||
{
|
||||
Debug.LogError("Invalid VisibleIf rule. Property has missing getter " + visibleIf.MemberName);
|
||||
return ScriptMemberInfo.Null;
|
||||
}
|
||||
|
||||
if (property.ValueType.Type != typeof(bool))
|
||||
{
|
||||
Debug.LogError("Invalid VisibleIf rule. Property has to return bool type " + visibleIf.MemberName);
|
||||
return ScriptMemberInfo.Null;
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
var field = type.GetField(visibleIf.MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
if (field != ScriptMemberInfo.Null)
|
||||
{
|
||||
if (field.ValueType.Type != typeof(bool))
|
||||
{
|
||||
Debug.LogError("Invalid VisibleIf rule. Field has to be bool type " + visibleIf.MemberName);
|
||||
return ScriptMemberInfo.Null;
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
Debug.LogError("Invalid VisibleIf rule. Cannot find member " + visibleIf.MemberName);
|
||||
return ScriptMemberInfo.Null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the property for the given item.
|
||||
/// </summary>
|
||||
/// <param name="itemLayout">The item layout.</param>
|
||||
/// <param name="itemValues">The item values.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
|
||||
{
|
||||
int labelIndex = 0;
|
||||
if ((item.IsReadOnly || item.VisibleIf != null) &&
|
||||
itemLayout.Children.Count > 0 &&
|
||||
itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
||||
{
|
||||
labelIndex = propertiesListElement.Labels.Count;
|
||||
}
|
||||
|
||||
itemLayout.Property(item.DisplayName, itemValues, item.OverrideEditor, item.TooltipText);
|
||||
|
||||
if (item.IsReadOnly && itemLayout.Children.Count > 0)
|
||||
{
|
||||
PropertiesListElement list = null;
|
||||
int firstChildControlIndex = 0;
|
||||
bool disableSingle = true;
|
||||
var control = itemLayout.Children[itemLayout.Children.Count - 1];
|
||||
if (control is GroupElement group && group.Children.Count > 0)
|
||||
{
|
||||
list = group.Children[0] as PropertiesListElement;
|
||||
disableSingle = false; // Disable all nested editors
|
||||
}
|
||||
else if (control is PropertiesListElement list1)
|
||||
{
|
||||
list = list1;
|
||||
firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex;
|
||||
}
|
||||
|
||||
if (list != null)
|
||||
{
|
||||
// Disable controls added to the editor
|
||||
var count = list.Properties.Children.Count;
|
||||
for (int j = firstChildControlIndex; j < count; j++)
|
||||
{
|
||||
var child = list.Properties.Children[j];
|
||||
if (disableSingle && child is PropertyNameLabel)
|
||||
break;
|
||||
|
||||
child.Enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.VisibleIf != null)
|
||||
{
|
||||
PropertiesListElement list;
|
||||
if (itemLayout.Children.Count > 0 && itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement list1)
|
||||
{
|
||||
list = list1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: support inlined objects hiding?
|
||||
return;
|
||||
}
|
||||
|
||||
// Get source member used to check rule
|
||||
var sourceMember = GetVisibleIfSource(item.Info.DeclaringType, item.VisibleIf);
|
||||
if (sourceMember == ScriptType.Null)
|
||||
return;
|
||||
|
||||
// Find the target control to show/hide
|
||||
|
||||
// Resize cache
|
||||
if (_visibleIfCaches == null)
|
||||
_visibleIfCaches = new VisibleIfCache[8];
|
||||
int count = 0;
|
||||
while (count < _visibleIfCaches.Length && _visibleIfCaches[count].Target != ScriptType.Null)
|
||||
count++;
|
||||
if (count >= _visibleIfCaches.Length)
|
||||
Array.Resize(ref _visibleIfCaches, count * 2);
|
||||
|
||||
// Add item
|
||||
_visibleIfCaches[count] = new VisibleIfCache
|
||||
{
|
||||
Target = item.Info,
|
||||
Source = sourceMember,
|
||||
PropertiesList = list,
|
||||
LabelIndex = labelIndex,
|
||||
Invert = item.VisibleIf.Invert,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_visibleIfCaches = null;
|
||||
|
||||
// Collect items to edit
|
||||
List<ItemInfo> items;
|
||||
if (!HasDifferentTypes)
|
||||
{
|
||||
var value = Values[0];
|
||||
if (value == null)
|
||||
{
|
||||
// Check if it's an object type that can be created in editor
|
||||
var type = Values.Type;
|
||||
if (type != ScriptMemberInfo.Null && type.CanCreateInstance)
|
||||
{
|
||||
layout = layout.Space(20);
|
||||
|
||||
const float ButtonSize = 14.0f;
|
||||
var button = new Button
|
||||
{
|
||||
Text = "+",
|
||||
TooltipText = "Create a new instance of the object",
|
||||
Height = ButtonSize,
|
||||
Width = ButtonSize,
|
||||
X = layout.ContainerControl.Width - ButtonSize - 4,
|
||||
AnchorPreset = AnchorPresets.MiddleRight,
|
||||
Parent = layout.ContainerControl
|
||||
};
|
||||
button.Clicked += () =>
|
||||
{
|
||||
var newType = Values.Type;
|
||||
SetValue(newType.CreateInstance());
|
||||
RebuildLayoutOnRefresh();
|
||||
};
|
||||
}
|
||||
|
||||
layout.Label("<null>");
|
||||
return;
|
||||
}
|
||||
|
||||
items = GetItemsForType(TypeUtils.GetObjectType(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
var types = ValuesTypes;
|
||||
items = new List<ItemInfo>(GetItemsForType(types[0]));
|
||||
for (int i = 1; i < types.Length && items.Count > 0; i++)
|
||||
{
|
||||
var otherItems = GetItemsForType(types[i]);
|
||||
|
||||
// Merge items
|
||||
for (int j = 0; j < items.Count && items.Count > 0; j++)
|
||||
{
|
||||
bool isInvalid = true;
|
||||
for (int k = 0; k < otherItems.Count; k++)
|
||||
{
|
||||
var a = items[j];
|
||||
var b = otherItems[k];
|
||||
|
||||
if (ItemInfo.CanMerge(a, b))
|
||||
{
|
||||
isInvalid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isInvalid)
|
||||
{
|
||||
items.RemoveAt(j--);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort items
|
||||
items.Sort();
|
||||
|
||||
// Add items
|
||||
GroupElement lastGroup = null;
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
|
||||
// Check if use group
|
||||
LayoutElementsContainer itemLayout;
|
||||
if (item.UseGroup)
|
||||
{
|
||||
if (lastGroup == null || lastGroup.Panel.HeaderText != item.Display.Group)
|
||||
lastGroup = layout.Group(item.Display.Group);
|
||||
itemLayout = lastGroup;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastGroup = null;
|
||||
itemLayout = layout;
|
||||
}
|
||||
|
||||
// Space
|
||||
if (item.Space != null)
|
||||
itemLayout.Space(item.Space.Height);
|
||||
|
||||
// Header
|
||||
if (item.Header != null)
|
||||
itemLayout.Header(item.Header.Text);
|
||||
|
||||
// Peek values
|
||||
ValueContainer itemValues;
|
||||
try
|
||||
{
|
||||
itemValues = item.GetValues(Values);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogWarning("Failed to get object values for item " + item);
|
||||
Editor.LogWarning(ex.Message);
|
||||
Editor.LogWarning(ex.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn property editor
|
||||
SpawnProperty(itemLayout, itemValues, item);
|
||||
|
||||
// Expand all parent groups if need to
|
||||
if (item.ExpandGroups)
|
||||
{
|
||||
var c = itemLayout.ContainerControl;
|
||||
do
|
||||
{
|
||||
if (c is DropPanel dropPanel)
|
||||
dropPanel.Open(false);
|
||||
else if (c is CustomEditorPresenter.PresenterPanel)
|
||||
break;
|
||||
c = c.Parent;
|
||||
} while (c != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
if (_visibleIfCaches != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < _visibleIfCaches.Length; i++)
|
||||
{
|
||||
var c = _visibleIfCaches[i];
|
||||
|
||||
if (c.Target == ScriptMemberInfo.Null)
|
||||
break;
|
||||
|
||||
// Check rule (all objects must allow to show this property)
|
||||
bool visible = true;
|
||||
for (int j = 0; j < Values.Count; j++)
|
||||
{
|
||||
if (Values[j] != null && !c.GetValue(Values[j]))
|
||||
{
|
||||
visible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the visibility (note: there may be no label)
|
||||
if (c.LabelIndex != -1 && c.PropertiesList.Labels.Count > c.LabelIndex)
|
||||
{
|
||||
var label = c.PropertiesList.Labels[c.LabelIndex];
|
||||
label.Visible = visible;
|
||||
for (int j = label.FirstChildControlIndex; j < c.PropertiesList.Properties.Children.Count; j++)
|
||||
{
|
||||
var child = c.PropertiesList.Properties.Children[j];
|
||||
if (child is PropertyNameLabel)
|
||||
break;
|
||||
|
||||
child.Visible = visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogWarning(ex);
|
||||
Editor.LogError("Failed to update VisibleIf rules. " + ex.Message);
|
||||
|
||||
// Remove rules to prevent error in loop
|
||||
_visibleIfCaches = null;
|
||||
}
|
||||
}
|
||||
|
||||
base.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Source/Editor/CustomEditors/Editors/GuidEditor.cs
Normal file
53
Source/Editor/CustomEditors/Editors/GuidEditor.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Guid properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Guid)), DefaultEditor]
|
||||
public sealed class GuidEditor : CustomEditor
|
||||
{
|
||||
private TextBoxElement _element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_element = layout.TextBox();
|
||||
_element.TextBox.EditEnd += OnEditEnd;
|
||||
}
|
||||
|
||||
private void OnEditEnd()
|
||||
{
|
||||
Guid value;
|
||||
if (Guid.TryParse(_element.Text, out value))
|
||||
{
|
||||
SetValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
_element.TextBox.Text = string.Empty;
|
||||
_element.TextBox.WatermarkText = "Different values";
|
||||
}
|
||||
else
|
||||
{
|
||||
_element.TextBox.Text = ((Guid)Values[0]).ToString("D");
|
||||
_element.TextBox.WatermarkText = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Source/Editor/CustomEditors/Editors/IBrushEditor.cs
Normal file
27
Source/Editor/CustomEditors/Editors/IBrushEditor.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit <see cref="IBrush"/> type properties.
|
||||
/// </summary>
|
||||
/// <seealso cref="IBrush"/>
|
||||
/// <seealso cref="ObjectSwitcherEditor"/>
|
||||
[CustomEditor(typeof(IBrush)), DefaultEditor]
|
||||
public sealed class IBrushEditor : ObjectSwitcherEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override OptionType[] Options => new[]
|
||||
{
|
||||
new OptionType("Texture", typeof(TextureBrush)),
|
||||
new OptionType("Sprite", typeof(SpriteBrush)),
|
||||
new OptionType("GPU Texture", typeof(GPUTextureBrush)),
|
||||
new OptionType("Material", typeof(MaterialBrush)),
|
||||
new OptionType("Solid Color", typeof(SolidColorBrush)),
|
||||
new OptionType("Linear Gradient", typeof(LinearGradientBrush)),
|
||||
};
|
||||
}
|
||||
}
|
||||
87
Source/Editor/CustomEditors/Editors/Int2Editor.cs
Normal file
87
Source/Editor/CustomEditors/Editors/Int2Editor.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Int2 value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Int2)), DefaultEditor]
|
||||
public class Int2Editor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The X component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement XElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Y component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement YElement;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var grid = layout.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = TextBox.DefaultHeight;
|
||||
gridControl.SlotsHorizontally = 2;
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
LimitAttribute limit = null;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
}
|
||||
|
||||
XElement = grid.IntegerValue();
|
||||
XElement.SetLimits(limit);
|
||||
XElement.IntValue.ValueChanged += OnValueChanged;
|
||||
XElement.IntValue.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.IntegerValue();
|
||||
YElement.SetLimits(limit);
|
||||
YElement.IntValue.ValueChanged += OnValueChanged;
|
||||
YElement.IntValue.SlidingEnd += ClearToken;
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var isSliding = XElement.IsSliding || YElement.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
var value = new Int2(
|
||||
XElement.IntValue.Value,
|
||||
YElement.IntValue.Value);
|
||||
SetValue(value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = (Int2)Values[0];
|
||||
XElement.IntValue.Value = value.X;
|
||||
YElement.IntValue.Value = value.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
Source/Editor/CustomEditors/Editors/Int3Editor.cs
Normal file
99
Source/Editor/CustomEditors/Editors/Int3Editor.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Int3 value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Int3)), DefaultEditor]
|
||||
public class Int3Editor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The X component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement XElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Y component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement YElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Z component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement ZElement;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var grid = layout.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = TextBox.DefaultHeight;
|
||||
gridControl.SlotsHorizontally = 3;
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
LimitAttribute limit = null;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
}
|
||||
|
||||
XElement = grid.IntegerValue();
|
||||
XElement.SetLimits(limit);
|
||||
XElement.IntValue.ValueChanged += OnValueChanged;
|
||||
XElement.IntValue.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.IntegerValue();
|
||||
YElement.SetLimits(limit);
|
||||
YElement.IntValue.ValueChanged += OnValueChanged;
|
||||
YElement.IntValue.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.IntegerValue();
|
||||
ZElement.SetLimits(limit);
|
||||
ZElement.IntValue.ValueChanged += OnValueChanged;
|
||||
ZElement.IntValue.SlidingEnd += ClearToken;
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
var value = new Int3(
|
||||
XElement.IntValue.Value,
|
||||
YElement.IntValue.Value,
|
||||
ZElement.IntValue.Value);
|
||||
SetValue(value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = (Int3)Values[0];
|
||||
XElement.IntValue.Value = value.X;
|
||||
YElement.IntValue.Value = value.Y;
|
||||
ZElement.IntValue.Value = value.Z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Source/Editor/CustomEditors/Editors/Int4Editor.cs
Normal file
111
Source/Editor/CustomEditors/Editors/Int4Editor.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Int4 value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Int4)), DefaultEditor]
|
||||
public class Int4Editor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The X component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement XElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Y component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement YElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Z component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement ZElement;
|
||||
|
||||
/// <summary>
|
||||
/// The W component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement WElement;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var grid = layout.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = TextBox.DefaultHeight;
|
||||
gridControl.SlotsHorizontally = 4;
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
LimitAttribute limit = null;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
}
|
||||
|
||||
XElement = grid.IntegerValue();
|
||||
XElement.SetLimits(limit);
|
||||
XElement.IntValue.ValueChanged += OnValueChanged;
|
||||
XElement.IntValue.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.IntegerValue();
|
||||
YElement.SetLimits(limit);
|
||||
YElement.IntValue.ValueChanged += OnValueChanged;
|
||||
YElement.IntValue.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.IntegerValue();
|
||||
ZElement.SetLimits(limit);
|
||||
ZElement.IntValue.ValueChanged += OnValueChanged;
|
||||
ZElement.IntValue.SlidingEnd += ClearToken;
|
||||
|
||||
WElement = grid.IntegerValue();
|
||||
WElement.SetLimits(limit);
|
||||
WElement.IntValue.ValueChanged += OnValueChanged;
|
||||
WElement.IntValue.SlidingEnd += ClearToken;
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding || WElement.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
var value = new Int4(
|
||||
XElement.IntValue.Value,
|
||||
YElement.IntValue.Value,
|
||||
ZElement.IntValue.Value,
|
||||
WElement.IntValue.Value);
|
||||
SetValue(value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = (Int4)Values[0];
|
||||
XElement.IntValue.Value = value.X;
|
||||
YElement.IntValue.Value = value.Y;
|
||||
ZElement.IntValue.Value = value.Z;
|
||||
WElement.IntValue.Value = value.W;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
474
Source/Editor/CustomEditors/Editors/IntegerEditor.cs
Normal file
474
Source/Editor/CustomEditors/Editors/IntegerEditor.cs
Normal file
@@ -0,0 +1,474 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit integer value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(int)), DefaultEditor]
|
||||
public sealed class IntegerEditor : CustomEditor
|
||||
{
|
||||
private IIntegerValueEditor _element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_element = null;
|
||||
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
var range = attributes.FirstOrDefault(x => x is RangeAttribute);
|
||||
if (range != null)
|
||||
{
|
||||
// Use slider
|
||||
var element = layout.Slider();
|
||||
element.SetLimits((RangeAttribute)range);
|
||||
element.Slider.ValueChanged += OnValueChanged;
|
||||
element.Slider.SlidingEnd += ClearToken;
|
||||
_element = element;
|
||||
return;
|
||||
}
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
// Use int value editor with limit
|
||||
var element = layout.IntegerValue();
|
||||
element.SetLimits((LimitAttribute)limit);
|
||||
element.IntValue.ValueChanged += OnValueChanged;
|
||||
element.IntValue.SlidingEnd += ClearToken;
|
||||
_element = element;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_element == null)
|
||||
{
|
||||
// Use int value editor
|
||||
var element = layout.IntegerValue();
|
||||
element.IntValue.ValueChanged += OnValueChanged;
|
||||
element.IntValue.SlidingEnd += ClearToken;
|
||||
_element = element;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
var isSliding = _element.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
SetValue(_element.Value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
_element.Value = (int)Values[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit signed integer value type properties (maps to the full range of long type).
|
||||
/// </summary>
|
||||
public abstract class SignedIntegerValueEditor : CustomEditor
|
||||
{
|
||||
private SignedIntegerValueElement _element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_element = null;
|
||||
|
||||
GetLimits(out var min, out var max);
|
||||
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
// Use int value editor with limit
|
||||
var element = layout.SignedIntegerValue();
|
||||
element.LongValue.SetLimits((LimitAttribute)limit);
|
||||
element.LongValue.MinValue = Mathf.Max(element.LongValue.MinValue, min);
|
||||
element.LongValue.MaxValue = Mathf.Min(element.LongValue.MaxValue, max);
|
||||
element.LongValue.ValueChanged += OnValueChanged;
|
||||
element.LongValue.SlidingEnd += ClearToken;
|
||||
_element = element;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_element == null)
|
||||
{
|
||||
// Use int value editor
|
||||
var element = layout.SignedIntegerValue();
|
||||
element.LongValue.MinValue = Mathf.Max(element.LongValue.MinValue, min);
|
||||
element.LongValue.MaxValue = Mathf.Min(element.LongValue.MaxValue, max);
|
||||
element.LongValue.ValueChanged += OnValueChanged;
|
||||
element.LongValue.SlidingEnd += ClearToken;
|
||||
_element = element;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
var isSliding = _element.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
SetValue(SetValue(_element.Value), token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
_element.Value = GetValue(Values[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value limits.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum value.</param>
|
||||
/// <param name="max">The maximum value.</param>
|
||||
protected abstract void GetLimits(out long min, out long max);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value as long.
|
||||
/// </summary>
|
||||
/// <param name="value">The value from object.</param>
|
||||
/// <returns>The value for editor.</returns>
|
||||
protected abstract long GetValue(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value from long.
|
||||
/// </summary>
|
||||
/// <param name="value">The value from editor.</param>
|
||||
/// <returns>The value to object.</returns>
|
||||
protected abstract object SetValue(long value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit sbyte value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(sbyte)), DefaultEditor]
|
||||
public sealed class SByteEditor : SignedIntegerValueEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void GetLimits(out long min, out long max)
|
||||
{
|
||||
min = sbyte.MinValue;
|
||||
max = sbyte.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override long GetValue(object value)
|
||||
{
|
||||
return (sbyte)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object SetValue(long value)
|
||||
{
|
||||
return (sbyte)value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit short value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(short)), DefaultEditor]
|
||||
public sealed class ShortEditor : SignedIntegerValueEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void GetLimits(out long min, out long max)
|
||||
{
|
||||
min = short.MinValue;
|
||||
max = short.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override long GetValue(object value)
|
||||
{
|
||||
return (short)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object SetValue(long value)
|
||||
{
|
||||
return (short)value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit long value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(long)), DefaultEditor]
|
||||
public sealed class LongEditor : SignedIntegerValueEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void GetLimits(out long min, out long max)
|
||||
{
|
||||
min = long.MinValue;
|
||||
max = long.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override long GetValue(object value)
|
||||
{
|
||||
return (long)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object SetValue(long value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit unsigned integer value type properties (maps to the full range of ulong type).
|
||||
/// </summary>
|
||||
public abstract class UnsignedIntegerValueEditor : CustomEditor
|
||||
{
|
||||
private UnsignedIntegerValueElement _element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_element = null;
|
||||
|
||||
GetLimits(out var min, out var max);
|
||||
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
// Use int value editor with limit
|
||||
var element = layout.UnsignedIntegerValue();
|
||||
element.ULongValue.SetLimits((LimitAttribute)limit);
|
||||
element.ULongValue.MinValue = Mathf.Max(element.ULongValue.MinValue, min);
|
||||
element.ULongValue.MaxValue = Mathf.Min(element.ULongValue.MaxValue, max);
|
||||
element.ULongValue.ValueChanged += OnValueChanged;
|
||||
element.ULongValue.SlidingEnd += ClearToken;
|
||||
_element = element;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_element == null)
|
||||
{
|
||||
// Use int value editor
|
||||
var element = layout.UnsignedIntegerValue();
|
||||
element.ULongValue.MinValue = Mathf.Max(element.ULongValue.MinValue, min);
|
||||
element.ULongValue.MaxValue = Mathf.Min(element.ULongValue.MaxValue, max);
|
||||
element.ULongValue.ValueChanged += OnValueChanged;
|
||||
element.ULongValue.SlidingEnd += ClearToken;
|
||||
_element = element;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
var isSliding = _element.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
SetValue(SetValue(_element.Value), token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
_element.Value = GetValue(Values[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value limits.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum value.</param>
|
||||
/// <param name="max">The maximum value.</param>
|
||||
protected abstract void GetLimits(out ulong min, out ulong max);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value as long.
|
||||
/// </summary>
|
||||
/// <param name="value">The value from object.</param>
|
||||
/// <returns>The value for editor.</returns>
|
||||
protected abstract ulong GetValue(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value from long.
|
||||
/// </summary>
|
||||
/// <param name="value">The value from editor.</param>
|
||||
/// <returns>The value to object.</returns>
|
||||
protected abstract object SetValue(ulong value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit byte value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(byte)), DefaultEditor]
|
||||
public sealed class ByteEditor : UnsignedIntegerValueEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void GetLimits(out ulong min, out ulong max)
|
||||
{
|
||||
min = byte.MinValue;
|
||||
max = byte.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ulong GetValue(object value)
|
||||
{
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object SetValue(ulong value)
|
||||
{
|
||||
return (byte)value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit char value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(char)), DefaultEditor]
|
||||
public sealed class CharEditor : UnsignedIntegerValueEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void GetLimits(out ulong min, out ulong max)
|
||||
{
|
||||
min = char.MinValue;
|
||||
max = char.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ulong GetValue(object value)
|
||||
{
|
||||
return (char)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object SetValue(ulong value)
|
||||
{
|
||||
return (char)value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit ushort value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(ushort)), DefaultEditor]
|
||||
public sealed class UShortEditor : UnsignedIntegerValueEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void GetLimits(out ulong min, out ulong max)
|
||||
{
|
||||
min = ushort.MinValue;
|
||||
max = ushort.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ulong GetValue(object value)
|
||||
{
|
||||
return (ushort)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object SetValue(ulong value)
|
||||
{
|
||||
return (ushort)value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit uint value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(uint)), DefaultEditor]
|
||||
public sealed class UintEditor : UnsignedIntegerValueEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void GetLimits(out ulong min, out ulong max)
|
||||
{
|
||||
min = uint.MinValue;
|
||||
max = uint.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ulong GetValue(object value)
|
||||
{
|
||||
return (uint)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object SetValue(ulong value)
|
||||
{
|
||||
return (uint)value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit ulong value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(ulong)), DefaultEditor]
|
||||
public sealed class ULongEditor : UnsignedIntegerValueEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void GetLimits(out ulong min, out ulong max)
|
||||
{
|
||||
min = ulong.MinValue;
|
||||
max = ulong.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ulong GetValue(object value)
|
||||
{
|
||||
return (ulong)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object SetValue(ulong value)
|
||||
{
|
||||
return (ulong)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Source/Editor/CustomEditors/Editors/ListEditor.cs
Normal file
92
Source/Editor/CustomEditors/Editors/ListEditor.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit lists.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(List<>)), DefaultEditor]
|
||||
public class ListEditor : CollectionEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override int Count => (Values[0] as IList)?.Count ?? 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IList Allocate(int size)
|
||||
{
|
||||
var listType = Values.Type;
|
||||
var list = (IList)listType.CreateInstance();
|
||||
var defaultValue = Scripting.TypeUtils.GetDefaultValue(ElementType);
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
list.Add(defaultValue);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Resize(int newSize)
|
||||
{
|
||||
var list = Values[0] as IList;
|
||||
var oldSize = list?.Count ?? 0;
|
||||
|
||||
if (oldSize != newSize)
|
||||
{
|
||||
// Allocate new list
|
||||
var listType = Values.Type;
|
||||
var newValues = (IList)listType.CreateInstance();
|
||||
|
||||
var sharedCount = Mathf.Min(oldSize, newSize);
|
||||
if (list != null && sharedCount > 0)
|
||||
{
|
||||
// Copy old values
|
||||
for (int i = 0; i < sharedCount; i++)
|
||||
{
|
||||
newValues.Add(list[i]);
|
||||
}
|
||||
|
||||
// Fill new entries with the last value
|
||||
for (int i = oldSize; i < newSize; i++)
|
||||
{
|
||||
newValues.Add(list[oldSize - 1]);
|
||||
}
|
||||
}
|
||||
else if (newSize > 0)
|
||||
{
|
||||
// Fill new entries
|
||||
var defaultValue = Scripting.TypeUtils.GetDefaultValue(ElementType);
|
||||
for (int i = oldSize; i < newSize; i++)
|
||||
{
|
||||
newValues.Add(defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
SetValue(newValues);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IList CloneValues()
|
||||
{
|
||||
var list = Values[0] as IList;
|
||||
if (list == null)
|
||||
return null;
|
||||
|
||||
var size = list.Count;
|
||||
var listType = Values.Type;
|
||||
var cloned = (IList)listType.CreateInstance();
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
cloned.Add(list[i]);
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Source/Editor/CustomEditors/Editors/MatrixEditor.cs
Normal file
89
Source/Editor/CustomEditors/Editors/MatrixEditor.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Matrix value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Matrix)), DefaultEditor]
|
||||
public class MatrixEditor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The 16 components editors.
|
||||
/// </summary>
|
||||
protected readonly FloatValueElement[] Elements = new FloatValueElement[16];
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var grid = layout.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = TextBox.DefaultHeight * 4;
|
||||
gridControl.SlotsHorizontally = 4;
|
||||
gridControl.SlotsVertically = 4;
|
||||
|
||||
LimitAttribute limit = null;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
var elemnt = grid.FloatValue();
|
||||
elemnt.SetLimits(limit);
|
||||
elemnt.FloatValue.ValueChanged += OnValueChanged;
|
||||
elemnt.FloatValue.SlidingEnd += ClearToken;
|
||||
Elements[i] = elemnt;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var isSliding = false;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
isSliding = Elements[i].IsSliding;
|
||||
}
|
||||
var token = isSliding ? this : null;
|
||||
var value = new Matrix();
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
value[i] = Elements[i].FloatValue.Value;
|
||||
}
|
||||
SetValue(value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = (Matrix)Values[0];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
Elements[i].FloatValue.Value = value[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit <see cref="ModelInstanceEntry"/> value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(ModelInstanceEntry)), DefaultEditor]
|
||||
public sealed class ModelInstanceEntryEditor : GenericEditor
|
||||
{
|
||||
private GroupElement _group;
|
||||
private bool _updateName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_updateName = true;
|
||||
var group = layout.Group("Entry");
|
||||
_group = group;
|
||||
|
||||
base.Initialize(group);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
if (_updateName &&
|
||||
_group != null &&
|
||||
ParentEditor?.ParentEditor != null &&
|
||||
ParentEditor.ParentEditor.Values.Count > 0)
|
||||
{
|
||||
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
|
||||
if (ParentEditor.ParentEditor.Values[0] is StaticModel staticModel)
|
||||
{
|
||||
var model = staticModel.Model;
|
||||
if (model && model.IsLoaded)
|
||||
{
|
||||
_group.Panel.HeaderText = "Entry " + model.MaterialSlots[entryIndex].Name;
|
||||
_updateName = false;
|
||||
}
|
||||
}
|
||||
else if (ParentEditor.ParentEditor.Values[0] is AnimatedModel animatedModel)
|
||||
{
|
||||
var model = animatedModel.SkinnedModel;
|
||||
if (model && model.IsLoaded)
|
||||
{
|
||||
_group.Panel.HeaderText = "Entry " + model.MaterialSlots[entryIndex].Name;
|
||||
_updateName = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
182
Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
Normal file
182
Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Base implementation of the inspector used to edit properties of the given abstract or interface type that contain a setter to assign a derived implementation type.
|
||||
/// </summary>
|
||||
public abstract class ObjectSwitcherEditor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines type that can be assigned to the modified property.
|
||||
/// </summary>
|
||||
public struct OptionType
|
||||
{
|
||||
/// <summary>
|
||||
/// The type name used to show in the type selector dropdown menu (for user interface).
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// The type.
|
||||
/// </summary>
|
||||
public Type Type;
|
||||
|
||||
/// <summary>
|
||||
/// The creator function that spawns the object of the given type.
|
||||
/// </summary>
|
||||
public Func<Type, object> Creator;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OptionType"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
public OptionType(Type type)
|
||||
{
|
||||
Name = type.Name;
|
||||
Type = type;
|
||||
Creator = Activator.CreateInstance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OptionType"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
public OptionType(string name, Type type)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
Creator = Activator.CreateInstance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OptionType"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="creator">The instance creation function.</param>
|
||||
public OptionType(string name, Type type, Func<Type, object> creator)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
Creator = creator;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the options collection for the property value assignment.
|
||||
/// </summary>
|
||||
protected abstract OptionType[] Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the type ComboBox property name for the object type picking.
|
||||
/// </summary>
|
||||
protected virtual string TypeComboBoxName => "Type";
|
||||
|
||||
private OptionType[] _options;
|
||||
private ScriptType _type;
|
||||
|
||||
private ScriptType Type => Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
// Get the target options
|
||||
_options = Options;
|
||||
if (_options == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
int selectedIndex = -1;
|
||||
bool hasDifferentTypes = Values.HasDifferentTypes;
|
||||
var type = Type;
|
||||
_type = type;
|
||||
|
||||
// Type
|
||||
var typeEditor = layout.ComboBox(TypeComboBoxName, "Type of the object value. Use it to change the object.");
|
||||
for (int i = 0; i < _options.Length; i++)
|
||||
{
|
||||
typeEditor.ComboBox.AddItem(_options[i].Name);
|
||||
selectedIndex = _options[i].Type == type.Type ? i : selectedIndex;
|
||||
}
|
||||
typeEditor.ComboBox.SupportMultiSelect = false;
|
||||
typeEditor.ComboBox.SelectedIndex = hasDifferentTypes ? -1 : selectedIndex;
|
||||
typeEditor.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
|
||||
// Editing different types is not supported
|
||||
if (Values.HasDifferentTypes)
|
||||
{
|
||||
var property = layout.AddPropertyItem("Value");
|
||||
property.Label("Different Values");
|
||||
return;
|
||||
}
|
||||
|
||||
// Nothing to edit
|
||||
if (Values.HasNull)
|
||||
{
|
||||
var property = layout.AddPropertyItem("Value");
|
||||
property.Label("<null>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Value
|
||||
var values = new CustomValueContainer(type, (instance, index) => instance, (instance, index, value) => { });
|
||||
values.AddRange(Values);
|
||||
var editor = CustomEditorsUtil.CreateEditor(type);
|
||||
var style = editor.Style;
|
||||
if (style == DisplayStyle.InlineIntoParent)
|
||||
{
|
||||
layout.Object(values, editor);
|
||||
}
|
||||
else if (style == DisplayStyle.Group)
|
||||
{
|
||||
var group = layout.Group("Value", true);
|
||||
group.Panel.Open(false);
|
||||
group.Object(values, editor);
|
||||
|
||||
// Remove empty group
|
||||
if (group.ContainerControl.ChildrenCount == 0)
|
||||
{
|
||||
layout.Children.Remove(group);
|
||||
group.Panel.Dispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
layout.AddPropertyItem("Value").Object(values, editor);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(ComboBox comboBox)
|
||||
{
|
||||
object value = null;
|
||||
if (comboBox.SelectedIndex != -1)
|
||||
{
|
||||
var option = _options[comboBox.SelectedIndex];
|
||||
value = option.Creator(option.Type);
|
||||
}
|
||||
SetValue(value);
|
||||
RebuildLayoutOnRefresh();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
// Check if type has been modified outside the editor (eg. from code)
|
||||
if (Type != _type)
|
||||
{
|
||||
if (ParentEditor != null)
|
||||
ParentEditor.RebuildLayout();
|
||||
else
|
||||
RebuildLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
Source/Editor/CustomEditors/Editors/QuaternionEditor.cs
Normal file
90
Source/Editor/CustomEditors/Editors/QuaternionEditor.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Quaternion value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Quaternion)), DefaultEditor]
|
||||
public class QuaternionEditor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The X component element
|
||||
/// </summary>
|
||||
protected FloatValueElement XElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Y component element
|
||||
/// </summary>
|
||||
protected FloatValueElement YElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Z component element
|
||||
/// </summary>
|
||||
protected FloatValueElement ZElement;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var grid = layout.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = TextBox.DefaultHeight;
|
||||
gridControl.SlotsHorizontally = 3;
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
XElement = grid.FloatValue();
|
||||
XElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
XElement.FloatValue.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.FloatValue();
|
||||
YElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
YElement.FloatValue.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.FloatValue();
|
||||
ZElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
ZElement.FloatValue.SlidingEnd += ClearToken;
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
float x = XElement.FloatValue.Value;
|
||||
float y = YElement.FloatValue.Value;
|
||||
float z = ZElement.FloatValue.Value;
|
||||
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
Quaternion value;
|
||||
Quaternion.Euler(x, y, z, out value);
|
||||
SetValue(value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = (Quaternion)Values[0];
|
||||
var euler = value.EulerAngles;
|
||||
XElement.FloatValue.Value = euler.X;
|
||||
YElement.FloatValue.Value = euler.Y;
|
||||
ZElement.FloatValue.Value = euler.Z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Source/Editor/CustomEditors/Editors/SkeletonNodeEditor.cs
Normal file
59
Source/Editor/CustomEditors/Editors/SkeletonNodeEditor.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for picking skeleton node. Instead of choosing node index or entering node text it shows a combo box with simple tag picking by name.
|
||||
/// </summary>
|
||||
public sealed class SkeletonNodeEditor : CustomEditor
|
||||
{
|
||||
private ComboBoxElement element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
element = layout.ComboBox();
|
||||
element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
|
||||
// Set node names
|
||||
if (ParentEditor != null
|
||||
&& ParentEditor.Values.Count == 1 && ParentEditor.Values[0] is BoneSocket boneSocket
|
||||
&& boneSocket.Parent is AnimatedModel animatedModel && animatedModel.SkinnedModel
|
||||
&& !animatedModel.SkinnedModel.WaitForLoaded())
|
||||
{
|
||||
var nodes = animatedModel.SkinnedModel.Nodes;
|
||||
for (int nodeIndex = 0; nodeIndex < nodes.Length; nodeIndex++)
|
||||
element.ComboBox.AddItem(nodes[nodeIndex].Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(ComboBox comboBox)
|
||||
{
|
||||
string value = comboBox.SelectedItem;
|
||||
SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values on many actor selected
|
||||
}
|
||||
else
|
||||
{
|
||||
string value = (string)Values[0];
|
||||
element.ComboBox.SelectedItem = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
113
Source/Editor/CustomEditors/Editors/SpriteHandleEditor.cs
Normal file
113
Source/Editor/CustomEditors/Editors/SpriteHandleEditor.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit <see cref="SpriteHandle"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.CustomEditor" />
|
||||
[CustomEditor(typeof(SpriteHandle)), DefaultEditor]
|
||||
public class SpriteHandleEditor : CustomEditor
|
||||
{
|
||||
private ComboBox _spritePicker;
|
||||
private ValueContainer _atlasValues;
|
||||
private ValueContainer _indexValues;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
// Atlas
|
||||
var atlasField = typeof(SpriteHandle).GetField("Atlas");
|
||||
var atlasValues = new ValueContainer(new ScriptMemberInfo(atlasField), Values);
|
||||
layout.Property("Atlas", atlasValues, null, "The target atlas texture used as a sprite image source.");
|
||||
_atlasValues = atlasValues;
|
||||
|
||||
// Sprite
|
||||
var spriteIndexField = typeof(SpriteHandle).GetField("Index");
|
||||
_indexValues = new ValueContainer(new ScriptMemberInfo(spriteIndexField), Values);
|
||||
var spriteLabel = layout.AddPropertyItem("Sprite", "The selected sprite from the atlas.");
|
||||
|
||||
// Check state
|
||||
if (atlasValues.HasDifferentValues)
|
||||
{
|
||||
spriteLabel.Label("Different values");
|
||||
return;
|
||||
}
|
||||
var value = (SpriteAtlas)atlasValues[0];
|
||||
if (value == null)
|
||||
{
|
||||
spriteLabel.Label("Pick atlas first");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: don't stall use Refresh to rebuild UI when sprite atlas gets loaded
|
||||
if (value.WaitForLoaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// List all sprites from the atlas asset
|
||||
var spritesCount = value.SpritesCount;
|
||||
var spritePicker = spriteLabel.ComboBox();
|
||||
spritePicker.ComboBox.Items.Capacity = spritesCount;
|
||||
for (int i = 0; i < spritesCount; i++)
|
||||
{
|
||||
spritePicker.ComboBox.AddItem(value.GetSprite(i).Name);
|
||||
}
|
||||
spritePicker.ComboBox.SupportMultiSelect = false;
|
||||
spritePicker.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
_spritePicker = spritePicker.ComboBox;
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(ComboBox obj)
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
SpriteHandle value;
|
||||
value.Atlas = (SpriteAtlas)_atlasValues[0];
|
||||
value.Index = _spritePicker.SelectedIndex;
|
||||
SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues || _spritePicker == null)
|
||||
return;
|
||||
|
||||
// Fetch the instance values
|
||||
_atlasValues.Refresh(Values);
|
||||
_indexValues.Refresh(Values);
|
||||
|
||||
// Update selection
|
||||
int selectedIndex = -1;
|
||||
for (int i = 0; i < Values.Count; i++)
|
||||
{
|
||||
var idx = (int)_indexValues[i];
|
||||
if (idx != -1 && idx < _spritePicker.Items.Count)
|
||||
selectedIndex = idx;
|
||||
}
|
||||
_spritePicker.SelectedIndex = selectedIndex;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool OnDirty(CustomEditor editor, object value, object token = null)
|
||||
{
|
||||
// Check if Atlas has been changed
|
||||
if (editor is AssetRefEditor)
|
||||
{
|
||||
// Rebuild layout to update the dropdown menu with sprites list
|
||||
RebuildLayoutOnRefresh();
|
||||
}
|
||||
|
||||
return base.OnDirty(editor, value, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Source/Editor/CustomEditors/Editors/StringEditor.cs
Normal file
53
Source/Editor/CustomEditors/Editors/StringEditor.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit string properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(string)), DefaultEditor]
|
||||
public sealed class StringEditor : CustomEditor
|
||||
{
|
||||
private TextBoxElement _element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
bool isMultiLine = false;
|
||||
|
||||
var attributes = Values.GetAttributes();
|
||||
var multiLine = attributes?.FirstOrDefault(x => x is MultilineTextAttribute);
|
||||
if (multiLine != null)
|
||||
{
|
||||
isMultiLine = true;
|
||||
}
|
||||
|
||||
_element = layout.TextBox(isMultiLine);
|
||||
_element.TextBox.EditEnd += () => SetValue(_element.Text);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
_element.TextBox.Text = string.Empty;
|
||||
_element.TextBox.WatermarkText = "Different values";
|
||||
}
|
||||
else
|
||||
{
|
||||
_element.TextBox.Text = (string)Values[0];
|
||||
_element.TextBox.WatermarkText = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Source/Editor/CustomEditors/Editors/StyleEditor.cs
Normal file
53
Source/Editor/CustomEditors/Editors/StyleEditor.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit styles.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Style)), DefaultEditor]
|
||||
public class StyleEditor : CustomEditor
|
||||
{
|
||||
private CustomElement<StyleValueEditor> _element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this editor.
|
||||
/// </summary>
|
||||
/// <param name="layout">The layout builder.</param>
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var style = (Style)Values[0];
|
||||
|
||||
_element = layout.Custom<StyleValueEditor>();
|
||||
_element.CustomControl.Value = style;
|
||||
_element.CustomControl.ValueChanged += OnValueChanged;
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
SetValue(_element.CustomControl.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
_element.CustomControl.Value = (Style)Values[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
451
Source/Editor/CustomEditors/Editors/TypeEditor.cs
Normal file
451
Source/Editor/CustomEditors/Editors/TypeEditor.cs
Normal file
@@ -0,0 +1,451 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom control type used to pick reference to <see cref="System.Type"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Control" />
|
||||
[HideInEditor]
|
||||
public class TypePickerControl : Control
|
||||
{
|
||||
private ScriptType _type;
|
||||
private ScriptType _value;
|
||||
private string _valueName;
|
||||
|
||||
private Vector2 _mousePos;
|
||||
|
||||
private bool _hasValidDragOver;
|
||||
private DragActors _dragActors;
|
||||
private DragScripts _dragScripts;
|
||||
private DragHandlers _dragHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the allowed type (given type and all sub classes). Must be <see cref="System.Type"/> type of any subclass.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The allowed type.
|
||||
/// </value>
|
||||
public ScriptType Type
|
||||
{
|
||||
get => _type;
|
||||
set
|
||||
{
|
||||
if (_type == value)
|
||||
return;
|
||||
if (value == ScriptType.Null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
_type = value;
|
||||
|
||||
// Deselect value if it's not valid now
|
||||
if (!IsValid(_value))
|
||||
Value = ScriptType.Null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected types value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The value.
|
||||
/// </value>
|
||||
public ScriptType Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
if (_value == value)
|
||||
return;
|
||||
if (value != ScriptType.Null && !IsValid(value))
|
||||
throw new ArgumentException(string.Format("Invalid type {0}. Checked base type {1}.", value.TypeName ?? "<null>", _type.TypeName ?? "<null>"));
|
||||
|
||||
_value = value;
|
||||
|
||||
// Get name to display
|
||||
if (_value)
|
||||
{
|
||||
_valueName = _value.Name;
|
||||
TooltipText = _value.TypeName;
|
||||
|
||||
var attributes = _value.GetAttributes(false);
|
||||
var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute);
|
||||
if (tooltipAttribute != null)
|
||||
{
|
||||
TooltipText += "\n" + tooltipAttribute.Text;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueName = string.Empty;
|
||||
TooltipText = string.Empty;
|
||||
}
|
||||
|
||||
ValueChanged?.Invoke();
|
||||
TypePickerValueChanged?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected type fullname (namespace and class).
|
||||
/// </summary>
|
||||
public string ValueTypeName
|
||||
{
|
||||
get => _value.TypeName ?? string.Empty;
|
||||
set => Value = TypeUtils.GetType(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when value gets changed.
|
||||
/// </summary>
|
||||
public event Action ValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when value gets changed.
|
||||
/// </summary>
|
||||
public event Action<TypePickerControl> TypePickerValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The custom callback for types validation. Cane be used to implement a rule for types to pick.
|
||||
/// </summary>
|
||||
public Func<ScriptType, bool> CheckValid;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypePickerControl"/> class.
|
||||
/// </summary>
|
||||
public TypePickerControl()
|
||||
: base(0, 0, 50, 16)
|
||||
{
|
||||
_type = new ScriptType(typeof(object));
|
||||
}
|
||||
|
||||
private bool IsValid(ScriptType obj)
|
||||
{
|
||||
return _type.IsAssignableFrom(obj) && (CheckValid == null || CheckValid(obj));
|
||||
}
|
||||
|
||||
private void ShowDropDownMenu()
|
||||
{
|
||||
Focus();
|
||||
TypeSearchPopup.Show(this, new Vector2(0, Height), IsValid, scriptType =>
|
||||
{
|
||||
Value = scriptType;
|
||||
RootWindow.Focus();
|
||||
Focus();
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Cache data
|
||||
var style = Style.Current;
|
||||
bool isSelected = _value != ScriptType.Null;
|
||||
var frameRect = new Rectangle(0, 0, Width - 16, 16);
|
||||
if (isSelected && _type == ScriptType.Null)
|
||||
frameRect.Width -= 16;
|
||||
var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14);
|
||||
var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14);
|
||||
var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14);
|
||||
|
||||
// Draw frame
|
||||
Render2D.DrawRectangle(frameRect, IsMouseOver ? style.BorderHighlighted : style.BorderNormal);
|
||||
|
||||
// Check if has item selected
|
||||
if (isSelected)
|
||||
{
|
||||
// Draw deselect button
|
||||
if (_type == ScriptType.Null)
|
||||
Render2D.DrawSprite(style.Cross, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
|
||||
|
||||
// Draw name
|
||||
Render2D.PushClip(nameRect);
|
||||
Render2D.DrawText(style.FontMedium, _valueName, nameRect, style.Foreground, TextAlignment.Near, TextAlignment.Center);
|
||||
Render2D.PopClip();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw info
|
||||
Render2D.DrawText(style.FontMedium, "-", nameRect, Color.OrangeRed, TextAlignment.Near, TextAlignment.Center);
|
||||
}
|
||||
|
||||
// Draw picker button
|
||||
var pickerRect = isSelected && _type == ScriptType.Null ? button2Rect : button1Rect;
|
||||
Render2D.DrawSprite(style.ArrowDown, pickerRect, pickerRect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
|
||||
|
||||
// Check if drag is over
|
||||
if (IsDragOver && _hasValidDragOver)
|
||||
Render2D.FillRectangle(new Rectangle(Vector2.Zero, Size), style.BackgroundSelected * 0.4f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Vector2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Vector2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
_mousePos = Vector2.Minimum;
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
// Cache data
|
||||
bool isSelected = _value != ScriptType.Null;
|
||||
var frameRect = new Rectangle(0, 0, Width - 16, 16);
|
||||
if (isSelected && _type == ScriptType.Null)
|
||||
frameRect.Width -= 16;
|
||||
var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14);
|
||||
var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14);
|
||||
var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14);
|
||||
|
||||
// Deselect
|
||||
if (_value && button1Rect.Contains(ref location) && _type == ScriptType.Null)
|
||||
Value = ScriptType.Null;
|
||||
|
||||
// Picker dropdown menu
|
||||
if ((isSelected && _type == ScriptType.Null ? button2Rect : button1Rect).Contains(ref location))
|
||||
ShowDropDownMenu();
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Vector2 location, MouseButton button)
|
||||
{
|
||||
// Navigate to types from game project
|
||||
if (button == MouseButton.Left && _value != ScriptType.Null)
|
||||
{
|
||||
var item = _value.ContentItem;
|
||||
if (item != null)
|
||||
Editor.Instance.ContentEditing.Open(item);
|
||||
}
|
||||
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
private DragDropEffect DragEffect => _hasValidDragOver ? DragDropEffect.Move : DragDropEffect.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragEnter(ref Vector2 location, DragData data)
|
||||
{
|
||||
base.OnDragEnter(ref location, data);
|
||||
|
||||
// Ensure to have valid drag helpers (uses lazy init)
|
||||
if (_dragActors == null)
|
||||
_dragActors = new DragActors(x => IsValid(TypeUtils.GetObjectType(x.Actor)));
|
||||
if (_dragScripts == null)
|
||||
_dragScripts = new DragScripts(x => IsValid(TypeUtils.GetObjectType(x)));
|
||||
if (_dragHandlers == null)
|
||||
{
|
||||
_dragHandlers = new DragHandlers();
|
||||
_dragHandlers.Add(_dragActors);
|
||||
_dragHandlers.Add(_dragScripts);
|
||||
}
|
||||
|
||||
_hasValidDragOver = _dragHandlers.OnDragEnter(data) != DragDropEffect.None;
|
||||
|
||||
return DragEffect;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragMove(ref Vector2 location, DragData data)
|
||||
{
|
||||
base.OnDragMove(ref location, data);
|
||||
|
||||
return DragEffect;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_hasValidDragOver = false;
|
||||
_dragHandlers.OnDragLeave();
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragDrop(ref Vector2 location, DragData data)
|
||||
{
|
||||
var result = DragEffect;
|
||||
|
||||
base.OnDragDrop(ref location, data);
|
||||
|
||||
if (_dragActors.HasValidDrag)
|
||||
{
|
||||
Value = TypeUtils.GetObjectType(_dragActors.Objects[0].Actor);
|
||||
}
|
||||
else if (_dragScripts.HasValidDrag)
|
||||
{
|
||||
Value = TypeUtils.GetObjectType(_dragScripts.Objects[0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_value = ScriptType.Null;
|
||||
_type = ScriptType.Null;
|
||||
_valueName = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for type reference editors.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.CustomEditor" />
|
||||
public class TypeEditorBase : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The element.
|
||||
/// </summary>
|
||||
protected CustomElement<TypePickerControl> _element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
if (!HasDifferentTypes)
|
||||
{
|
||||
_element = layout.Custom<TypePickerControl>();
|
||||
|
||||
var attributes = Values.GetAttributes();
|
||||
var typeReference = (TypeReferenceAttribute)attributes?.FirstOrDefault(x => x is TypeReferenceAttribute);
|
||||
if (typeReference != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(typeReference.TypeName))
|
||||
{
|
||||
var customType = TypeUtils.GetType(typeReference.TypeName);
|
||||
if (customType)
|
||||
_element.CustomControl.Type = customType;
|
||||
else
|
||||
Editor.LogError(string.Format("Unknown type '{0}' to use for type picker filter.", typeReference.TypeName));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(typeReference.CheckMethod))
|
||||
{
|
||||
var parentType = ParentEditor.Values[0].GetType();
|
||||
var method = parentType.GetMethod(typeReference.CheckMethod, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (method != null)
|
||||
{
|
||||
if (method.ReturnType == typeof(bool))
|
||||
{
|
||||
var methodParameters = method.GetParameters();
|
||||
if (methodParameters.Length == 1 && methodParameters[0].ParameterType == typeof(ScriptType))
|
||||
_element.CustomControl.CheckValid += type => { return (bool)method.Invoke(ParentEditor.Values[0], new object[] { type }); };
|
||||
else if (methodParameters.Length == 1 && methodParameters[0].ParameterType == typeof(Type))
|
||||
_element.CustomControl.CheckValid += type => { return type.Type != null && (bool)method.Invoke(ParentEditor.Values[0], new object[] { type.Type }); };
|
||||
else
|
||||
Editor.LogError(string.Format("Unknown method '{0}' to use for type picker filter check function (object type: {1}). It must contain a single parameter of type System.Type.", typeReference.CheckMethod, parentType.FullName));
|
||||
}
|
||||
else
|
||||
Editor.LogError(string.Format("Invalid method '{0}' to use for type picker filter check function (object type: {1}). It must return boolean value.", typeReference.CheckMethod, parentType.FullName));
|
||||
}
|
||||
else
|
||||
Editor.LogError(string.Format("Unknown method '{0}' to use for type picker filter check function (object type: {1}).", typeReference.CheckMethod, parentType.FullName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
_element = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit reference to the <see cref="System.Type"/>. Used to pick classes.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Type)), DefaultEditor]
|
||||
public class TypeEditor : TypeEditorBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
if (_element != null)
|
||||
{
|
||||
_element.CustomControl.ValueChanged += () => SetValue(_element.CustomControl.Value.Type);
|
||||
|
||||
if (_element.CustomControl.Type == new ScriptType(typeof(object)))
|
||||
{
|
||||
_element.CustomControl.Type = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
if (!HasDifferentValues)
|
||||
{
|
||||
_element.CustomControl.Value = new ScriptType(Values[0] as Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit reference to the <see cref="FlaxEditor.Scripting.ScriptType"/>. Used to pick classes.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(ScriptType)), DefaultEditor]
|
||||
public class ScriptTypeEditor : TypeEditorBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
if (_element != null)
|
||||
{
|
||||
_element.CustomControl.ValueChanged += () => SetValue(_element.CustomControl.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
if (!HasDifferentValues)
|
||||
{
|
||||
_element.CustomControl.Value = (ScriptType)Values[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
Source/Editor/CustomEditors/Editors/Vector2Editor.cs
Normal file
87
Source/Editor/CustomEditors/Editors/Vector2Editor.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Vector2 value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Vector2)), DefaultEditor]
|
||||
public class Vector2Editor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The X component editor.
|
||||
/// </summary>
|
||||
protected FloatValueElement XElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Y component editor.
|
||||
/// </summary>
|
||||
protected FloatValueElement YElement;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var grid = layout.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = TextBox.DefaultHeight;
|
||||
gridControl.SlotsHorizontally = 2;
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
LimitAttribute limit = null;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
}
|
||||
|
||||
XElement = grid.FloatValue();
|
||||
XElement.SetLimits(limit);
|
||||
XElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
XElement.FloatValue.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.FloatValue();
|
||||
YElement.SetLimits(limit);
|
||||
YElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
YElement.FloatValue.SlidingEnd += ClearToken;
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var isSliding = XElement.IsSliding || YElement.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
var value = new Vector2(
|
||||
XElement.FloatValue.Value,
|
||||
YElement.FloatValue.Value);
|
||||
SetValue(value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = (Vector2)Values[0];
|
||||
XElement.FloatValue.Value = value.X;
|
||||
YElement.FloatValue.Value = value.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
Source/Editor/CustomEditors/Editors/Vector3Editor.cs
Normal file
99
Source/Editor/CustomEditors/Editors/Vector3Editor.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Vector3 value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Vector3)), DefaultEditor]
|
||||
public class Vector3Editor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The X component editor.
|
||||
/// </summary>
|
||||
protected FloatValueElement XElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Y component editor.
|
||||
/// </summary>
|
||||
protected FloatValueElement YElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Z component editor.
|
||||
/// </summary>
|
||||
protected FloatValueElement ZElement;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var grid = layout.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = TextBox.DefaultHeight;
|
||||
gridControl.SlotsHorizontally = 3;
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
LimitAttribute limit = null;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
}
|
||||
|
||||
XElement = grid.FloatValue();
|
||||
XElement.SetLimits(limit);
|
||||
XElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
XElement.FloatValue.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.FloatValue();
|
||||
YElement.SetLimits(limit);
|
||||
YElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
YElement.FloatValue.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.FloatValue();
|
||||
ZElement.SetLimits(limit);
|
||||
ZElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
ZElement.FloatValue.SlidingEnd += ClearToken;
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
var value = new Vector3(
|
||||
XElement.FloatValue.Value,
|
||||
YElement.FloatValue.Value,
|
||||
ZElement.FloatValue.Value);
|
||||
SetValue(value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = (Vector3)Values[0];
|
||||
XElement.FloatValue.Value = value.X;
|
||||
YElement.FloatValue.Value = value.Y;
|
||||
ZElement.FloatValue.Value = value.Z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Source/Editor/CustomEditors/Editors/Vector4Editor.cs
Normal file
111
Source/Editor/CustomEditors/Editors/Vector4Editor.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Vector4 value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Vector4)), DefaultEditor]
|
||||
public class Vector4Editor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The X component editor.
|
||||
/// </summary>
|
||||
protected FloatValueElement XElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Y component editor.
|
||||
/// </summary>
|
||||
protected FloatValueElement YElement;
|
||||
|
||||
/// <summary>
|
||||
/// The Z component editor.
|
||||
/// </summary>
|
||||
protected FloatValueElement ZElement;
|
||||
|
||||
/// <summary>
|
||||
/// The W component editor.
|
||||
/// </summary>
|
||||
protected FloatValueElement WElement;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var grid = layout.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = TextBox.DefaultHeight;
|
||||
gridControl.SlotsHorizontally = 4;
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
LimitAttribute limit = null;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
}
|
||||
|
||||
XElement = grid.FloatValue();
|
||||
XElement.SetLimits(limit);
|
||||
XElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
XElement.FloatValue.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.FloatValue();
|
||||
YElement.SetLimits(limit);
|
||||
YElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
YElement.FloatValue.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.FloatValue();
|
||||
ZElement.SetLimits(limit);
|
||||
ZElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
ZElement.FloatValue.SlidingEnd += ClearToken;
|
||||
|
||||
WElement = grid.FloatValue();
|
||||
WElement.SetLimits(limit);
|
||||
WElement.FloatValue.ValueChanged += OnValueChanged;
|
||||
WElement.FloatValue.SlidingEnd += ClearToken;
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding || WElement.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
var value = new Vector4(
|
||||
XElement.FloatValue.Value,
|
||||
YElement.FloatValue.Value,
|
||||
ZElement.FloatValue.Value,
|
||||
WElement.FloatValue.Value);
|
||||
SetValue(value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = (Vector4)Values[0];
|
||||
XElement.FloatValue.Value = value.X;
|
||||
YElement.FloatValue.Value = value.Y;
|
||||
ZElement.FloatValue.Value = value.Z;
|
||||
WElement.FloatValue.Value = value.W;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
114
Source/Editor/CustomEditors/Editors/VersionEditor.cs
Normal file
114
Source/Editor/CustomEditors/Editors/VersionEditor.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit Version value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Version)), DefaultEditor]
|
||||
public class VersionEditor : CustomEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The Version.Major component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement Major;
|
||||
|
||||
/// <summary>
|
||||
/// The Version.Minor component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement Minor;
|
||||
|
||||
/// <summary>
|
||||
/// The Version.Build component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement Build;
|
||||
|
||||
/// <summary>
|
||||
/// The Version.Revision component editor.
|
||||
/// </summary>
|
||||
protected IntegerValueElement Revision;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var grid = layout.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = TextBox.DefaultHeight;
|
||||
gridControl.SlotsHorizontally = 4;
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
Major = grid.IntegerValue();
|
||||
Major.IntValue.SetLimits(0, 100000000);
|
||||
Major.IntValue.ValueChanged += OnValueChanged;
|
||||
Major.IntValue.SlidingEnd += ClearToken;
|
||||
|
||||
Minor = grid.IntegerValue();
|
||||
Minor.IntValue.SetLimits(0, 100000000);
|
||||
Minor.IntValue.ValueChanged += OnValueChanged;
|
||||
Minor.IntValue.SlidingEnd += ClearToken;
|
||||
|
||||
Build = grid.IntegerValue();
|
||||
Build.IntValue.SetLimits(-1, 100000000);
|
||||
Build.IntValue.ValueChanged += OnValueChanged;
|
||||
Build.IntValue.SlidingEnd += ClearToken;
|
||||
|
||||
Revision = grid.IntegerValue();
|
||||
Revision.IntValue.SetLimits(-1, 100000000);
|
||||
Revision.IntValue.ValueChanged += OnValueChanged;
|
||||
Revision.IntValue.SlidingEnd += ClearToken;
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var isSliding = Major.IsSliding || Minor.IsSliding || Build.IsSliding || Revision.IsSliding;
|
||||
var token = isSliding ? this : null;
|
||||
Version value;
|
||||
var major = Major.IntValue.Value;
|
||||
var minor = Minor.IntValue.Value;
|
||||
var build = Build.IntValue.Value;
|
||||
var revision = Revision.IntValue.Value;
|
||||
if (build > -1)
|
||||
{
|
||||
if (revision > 0)
|
||||
value = new Version(major, minor, build, revision);
|
||||
else
|
||||
value = new Version(major, minor, build);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = new Version(major, minor);
|
||||
}
|
||||
SetValue(value, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
// TODO: support different values for ValueBox<T>
|
||||
}
|
||||
else if (Values[0] is Version value)
|
||||
{
|
||||
Major.IntValue.Value = value.Major;
|
||||
Minor.IntValue.Value = value.Minor;
|
||||
Build.IntValue.Value = value.Build;
|
||||
Revision.IntValue.Value = value.Revision;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Source/Editor/CustomEditors/Elements/ButtonElement.cs
Normal file
42
Source/Editor/CustomEditors/Elements/ButtonElement.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The button element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class ButtonElement : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The button.
|
||||
/// </summary>
|
||||
public readonly Button Button = new Button();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the element.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
public void Init(string text)
|
||||
{
|
||||
Button.Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the element.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="color">The color.</param>
|
||||
public void Init(string text, Color color)
|
||||
{
|
||||
Button.Text = text;
|
||||
Button.SetColors(color);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => Button;
|
||||
}
|
||||
}
|
||||
29
Source/Editor/CustomEditors/Elements/CheckBoxElement.cs
Normal file
29
Source/Editor/CustomEditors/Elements/CheckBoxElement.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The checkbox element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class CheckBoxElement : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The check box.
|
||||
/// </summary>
|
||||
public readonly CheckBox CheckBox;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CheckBoxElement"/> class.
|
||||
/// </summary>
|
||||
public CheckBoxElement()
|
||||
{
|
||||
CheckBox = new CheckBox(0, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => CheckBox;
|
||||
}
|
||||
}
|
||||
30
Source/Editor/CustomEditors/Elements/ComboBoxElement.cs
Normal file
30
Source/Editor/CustomEditors/Elements/ComboBoxElement.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The combobx element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class ComboBoxElement : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The combo box.
|
||||
/// </summary>
|
||||
public readonly ComboBox ComboBox;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ComboBoxElement"/> class.
|
||||
/// </summary>
|
||||
public ComboBoxElement()
|
||||
{
|
||||
ComboBox = new ComboBox();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => ComboBox;
|
||||
}
|
||||
}
|
||||
38
Source/Editor/CustomEditors/Elements/CustomElement.cs
Normal file
38
Source/Editor/CustomEditors/Elements/CustomElement.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The custom layout element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElementsContainer" />
|
||||
public class CustomElementsContainer<T> : LayoutElementsContainer
|
||||
where T : ContainerControl, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// The custom control.
|
||||
/// </summary>
|
||||
public readonly T CustomControl = new T();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ContainerControl ContainerControl => CustomControl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The custom layout element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class CustomElement<T> : LayoutElement
|
||||
where T : Control, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// The custom control.
|
||||
/// </summary>
|
||||
public readonly T CustomControl = new T();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => CustomControl;
|
||||
}
|
||||
}
|
||||
89
Source/Editor/CustomEditors/Elements/DoubleValueElement.cs
Normal file
89
Source/Editor/CustomEditors/Elements/DoubleValueElement.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The double precision floating point value element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class DoubleValueElement : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The double value box.
|
||||
/// </summary>
|
||||
public readonly DoubleValueBox DoubleValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FloatValueElement"/> class.
|
||||
/// </summary>
|
||||
public DoubleValueElement()
|
||||
{
|
||||
DoubleValue = new DoubleValueBox(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="LimitAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="member">The member.</param>
|
||||
public void SetLimits(MemberInfo member)
|
||||
{
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
if (member != null)
|
||||
{
|
||||
var attributes = member.GetCustomAttributes(true);
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
DoubleValue.SetLimits((LimitAttribute)limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="LimitAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="limit">The limit.</param>
|
||||
public void SetLimits(LimitAttribute limit)
|
||||
{
|
||||
if (limit != null)
|
||||
{
|
||||
DoubleValue.SetLimits(limit);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from the other <see cref="DoubleValueElement"/>.
|
||||
/// </summary>
|
||||
/// <param name="other">The other.</param>
|
||||
public void SetLimits(DoubleValueElement other)
|
||||
{
|
||||
if (other != null)
|
||||
{
|
||||
DoubleValue.SetLimits(other.DoubleValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => DoubleValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
public double Value
|
||||
{
|
||||
get => DoubleValue.Value;
|
||||
set => DoubleValue.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user is using a slider.
|
||||
/// </summary>
|
||||
public bool IsSliding => DoubleValue.IsSliding;
|
||||
}
|
||||
}
|
||||
35
Source/Editor/CustomEditors/Elements/EnumElement.cs
Normal file
35
Source/Editor/CustomEditors/Elements/EnumElement.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The enum editor element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class EnumElement : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The combo box used to show enum values.
|
||||
/// </summary>
|
||||
public EnumComboBox EnumComboBox;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnumElement"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The enum type.</param>
|
||||
/// <param name="customBuildEntriesDelegate">The custom entries layout builder. Allows to hide existing or add different enum values to editor.</param>
|
||||
/// <param name="formatMode">The formatting mode.</param>
|
||||
public EnumElement(Type type, EnumComboBox.BuildEntriesDelegate customBuildEntriesDelegate = null, EnumDisplayAttribute.FormatMode formatMode = EnumDisplayAttribute.FormatMode.Default)
|
||||
{
|
||||
EnumComboBox = new EnumComboBox(type, customBuildEntriesDelegate, formatMode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => EnumComboBox;
|
||||
}
|
||||
}
|
||||
85
Source/Editor/CustomEditors/Elements/FloatValueElement.cs
Normal file
85
Source/Editor/CustomEditors/Elements/FloatValueElement.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The floating point value element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class FloatValueElement : LayoutElement, IFloatValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The float value box.
|
||||
/// </summary>
|
||||
public readonly FloatValueBox FloatValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FloatValueElement"/> class.
|
||||
/// </summary>
|
||||
public FloatValueElement()
|
||||
{
|
||||
FloatValue = new FloatValueBox(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="LimitAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="member">The member.</param>
|
||||
public void SetLimits(MemberInfo member)
|
||||
{
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
if (member != null)
|
||||
{
|
||||
var attributes = member.GetCustomAttributes(true);
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
FloatValue.SetLimits((LimitAttribute)limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="LimitAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="limit">The limit.</param>
|
||||
public void SetLimits(LimitAttribute limit)
|
||||
{
|
||||
if (limit != null)
|
||||
{
|
||||
FloatValue.SetLimits(limit);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from the other <see cref="FloatValueElement"/>.
|
||||
/// </summary>
|
||||
/// <param name="other">The other.</param>
|
||||
public void SetLimits(FloatValueElement other)
|
||||
{
|
||||
if (other != null)
|
||||
{
|
||||
FloatValue.SetLimits(other.FloatValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => FloatValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Value
|
||||
{
|
||||
get => FloatValue.Value;
|
||||
set => FloatValue.Value = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSliding => FloatValue.IsSliding;
|
||||
}
|
||||
}
|
||||
21
Source/Editor/CustomEditors/Elements/GroupElement.cs
Normal file
21
Source/Editor/CustomEditors/Elements/GroupElement.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The layout group element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class GroupElement : LayoutElementsContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The drop panel.
|
||||
/// </summary>
|
||||
public readonly DropPanel Panel = new DropPanel();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ContainerControl ContainerControl => Panel;
|
||||
}
|
||||
}
|
||||
20
Source/Editor/CustomEditors/Elements/IFloatValueEditor.cs
Normal file
20
Source/Editor/CustomEditors/Elements/IFloatValueEditor.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The floating point value editor element.
|
||||
/// </summary>
|
||||
public interface IFloatValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
float Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user is using a slider.
|
||||
/// </summary>
|
||||
bool IsSliding { get; }
|
||||
}
|
||||
}
|
||||
20
Source/Editor/CustomEditors/Elements/IIntegerValueEditor.cs
Normal file
20
Source/Editor/CustomEditors/Elements/IIntegerValueEditor.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The integer value editor element.
|
||||
/// </summary>
|
||||
public interface IIntegerValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
int Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user is using a slider.
|
||||
/// </summary>
|
||||
bool IsSliding { get; }
|
||||
}
|
||||
}
|
||||
21
Source/Editor/CustomEditors/Elements/ImageElement.cs
Normal file
21
Source/Editor/CustomEditors/Elements/ImageElement.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The image element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class ImageElement : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The image.
|
||||
/// </summary>
|
||||
public readonly Image Image = new Image();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => Image;
|
||||
}
|
||||
}
|
||||
159
Source/Editor/CustomEditors/Elements/IntegerValueElement.cs
Normal file
159
Source/Editor/CustomEditors/Elements/IntegerValueElement.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The integer value element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class IntegerValueElement : LayoutElement, IIntegerValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The integer value box.
|
||||
/// </summary>
|
||||
public readonly IntValueBox IntValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IntegerValueElement"/> class.
|
||||
/// </summary>
|
||||
public IntegerValueElement()
|
||||
{
|
||||
IntValue = new IntValueBox(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="LimitAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="member">The member.</param>
|
||||
public void SetLimits(MemberInfo member)
|
||||
{
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
if (member != null)
|
||||
{
|
||||
var attributes = member.GetCustomAttributes(true);
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
IntValue.SetLimits((LimitAttribute)limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="LimitAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="limit">The limit.</param>
|
||||
public void SetLimits(LimitAttribute limit)
|
||||
{
|
||||
if (limit != null)
|
||||
{
|
||||
IntValue.SetLimits(limit);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from the other <see cref="IntegerValueElement"/>.
|
||||
/// </summary>
|
||||
/// <param name="other">The other.</param>
|
||||
public void SetLimits(IntegerValueElement other)
|
||||
{
|
||||
if (other != null)
|
||||
{
|
||||
IntValue.SetLimits(other.IntValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => IntValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Value
|
||||
{
|
||||
get => IntValue.Value;
|
||||
set => IntValue.Value = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSliding => IntValue.IsSliding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The signed integer value element (maps to the full range of long type).
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class SignedIntegerValueElement : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The signed integer (long) value box.
|
||||
/// </summary>
|
||||
public readonly LongValueBox LongValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SignedIntegerValueElement"/> class.
|
||||
/// </summary>
|
||||
public SignedIntegerValueElement()
|
||||
{
|
||||
LongValue = new LongValueBox(0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => LongValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
public long Value
|
||||
{
|
||||
get => LongValue.Value;
|
||||
set => LongValue.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user is using a slider.
|
||||
/// </summary>
|
||||
public bool IsSliding => LongValue.IsSliding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The unsigned integer value element (maps to the full range of ulong type).
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class UnsignedIntegerValueElement : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The unsigned integer (ulong) value box.
|
||||
/// </summary>
|
||||
public readonly ULongValueBox ULongValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UnsignedIntegerValueElement"/> class.
|
||||
/// </summary>
|
||||
public UnsignedIntegerValueElement()
|
||||
{
|
||||
ULongValue = new ULongValueBox(0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => ULongValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
public ulong Value
|
||||
{
|
||||
get => ULongValue.Value;
|
||||
set => ULongValue.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user is using a slider.
|
||||
/// </summary>
|
||||
public bool IsSliding => ULongValue.IsSliding;
|
||||
}
|
||||
}
|
||||
32
Source/Editor/CustomEditors/Elements/LabelElement.cs
Normal file
32
Source/Editor/CustomEditors/Elements/LabelElement.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The label element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class LabelElement : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The label.
|
||||
/// </summary>
|
||||
public readonly Label Label;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CheckBoxElement"/> class.
|
||||
/// </summary>
|
||||
public LabelElement()
|
||||
{
|
||||
Label = new Label(0, 0, 100, 18);
|
||||
Label.HorizontalAlignment = TextAlignment.Near;
|
||||
// TODO: auto height for label
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => Label;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="CustomEditor"/> properties list element.
|
||||
/// </summary>
|
||||
/// <seealso cref="LayoutElementsContainer"/>
|
||||
public class PropertiesListElement : LayoutElementsContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The list.
|
||||
/// </summary>
|
||||
public readonly PropertiesList Properties;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ContainerControl ContainerControl => Properties;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PropertiesListElement"/> class.
|
||||
/// </summary>
|
||||
public PropertiesListElement()
|
||||
{
|
||||
Properties = new PropertiesList(this);
|
||||
}
|
||||
|
||||
internal readonly List<PropertyNameLabel> Labels = new List<PropertyNameLabel>();
|
||||
|
||||
internal void OnAddProperty(string name, string tooltip)
|
||||
{
|
||||
var label = new PropertyNameLabel(name)
|
||||
{
|
||||
Parent = Properties,
|
||||
TooltipText = tooltip,
|
||||
FirstChildControlIndex = Properties.Children.Count
|
||||
};
|
||||
Labels.Add(label);
|
||||
}
|
||||
|
||||
internal void OnAddProperty(PropertyNameLabel label, string tooltip)
|
||||
{
|
||||
if (label == null)
|
||||
throw new ArgumentNullException();
|
||||
label.Parent = Properties;
|
||||
label.TooltipText = tooltip;
|
||||
label.FirstChildControlIndex = Properties.Children.Count;
|
||||
Labels.Add(label);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnAddEditor(CustomEditor editor)
|
||||
{
|
||||
// Link to the last label
|
||||
if (Labels.Count > 0)
|
||||
{
|
||||
Labels[Labels.Count - 1].LinkEditor(editor);
|
||||
}
|
||||
|
||||
base.OnAddEditor(editor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ClearLayout()
|
||||
{
|
||||
base.ClearLayout();
|
||||
Labels.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Source/Editor/CustomEditors/Elements/SliderElement.cs
Normal file
80
Source/Editor/CustomEditors/Elements/SliderElement.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The slider element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class SliderElement : LayoutElement, IFloatValueEditor, IIntegerValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The slider control.
|
||||
/// </summary>
|
||||
public readonly SliderControl Slider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SliderElement"/> class.
|
||||
/// </summary>
|
||||
public SliderElement()
|
||||
{
|
||||
Slider = new SliderControl(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="RangeAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="member">The member.</param>
|
||||
public void SetLimits(MemberInfo member)
|
||||
{
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
if (member != null)
|
||||
{
|
||||
var attributes = member.GetCustomAttributes(true);
|
||||
var limit = attributes.FirstOrDefault(x => x is RangeAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
Slider.SetLimits((RangeAttribute)limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="RangeAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="limit">The limit.</param>
|
||||
public void SetLimits(RangeAttribute limit)
|
||||
{
|
||||
if (limit != null)
|
||||
{
|
||||
Slider.SetLimits(limit);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => Slider;
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Value
|
||||
{
|
||||
get => Slider.Value;
|
||||
set => Slider.Value = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int IIntegerValueEditor.Value
|
||||
{
|
||||
get => (int)Slider.Value;
|
||||
set => Slider.Value = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IFloatValueEditor.IsSliding" />
|
||||
public bool IsSliding => Slider.IsSliding;
|
||||
}
|
||||
}
|
||||
30
Source/Editor/CustomEditors/Elements/SpaceElement.cs
Normal file
30
Source/Editor/CustomEditors/Elements/SpaceElement.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The spacer element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElementsContainer" />
|
||||
public class SpaceElement : LayoutElementsContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The spacer.
|
||||
/// </summary>
|
||||
public readonly Spacer Spacer = new Spacer(0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the element.
|
||||
/// </summary>
|
||||
/// <param name="height">The height.</param>
|
||||
public void Init(float height)
|
||||
{
|
||||
Spacer.Height = height;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ContainerControl ContainerControl => Spacer;
|
||||
}
|
||||
}
|
||||
41
Source/Editor/CustomEditors/Elements/TextBoxElement.cs
Normal file
41
Source/Editor/CustomEditors/Elements/TextBoxElement.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The textbox element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class TextBoxElement : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The text box.
|
||||
/// </summary>
|
||||
public readonly TextBox TextBox;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text.
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get => TextBox.Text;
|
||||
set => TextBox.Text = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextBoxElement"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isMultiline">Enable/disable multiline text input support</param>
|
||||
public TextBoxElement(bool isMultiline = false)
|
||||
{
|
||||
TextBox = new TextBox(isMultiline, 0, 0);
|
||||
if (isMultiline)
|
||||
TextBox.Height = TextBox.DefaultHeight * 4;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => TextBox;
|
||||
}
|
||||
}
|
||||
31
Source/Editor/CustomEditors/Elements/TreeElement.cs
Normal file
31
Source/Editor/CustomEditors/Elements/TreeElement.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The tree structure element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElementsContainer" />
|
||||
public class TreeElement : LayoutElementsContainer, ITreeElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The tree control.
|
||||
/// </summary>
|
||||
public readonly Tree TreeControl = new Tree(false);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ContainerControl ContainerControl => TreeControl;
|
||||
|
||||
/// <inheritdoc />
|
||||
public TreeNodeElement Node(string text)
|
||||
{
|
||||
TreeNodeElement element = new TreeNodeElement();
|
||||
element.TreeNode.Text = text;
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Source/Editor/CustomEditors/Elements/TreeNodeElement.cs
Normal file
44
Source/Editor/CustomEditors/Elements/TreeNodeElement.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// Tree nodes elements.
|
||||
/// </summary>
|
||||
public interface ITreeElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds new tree node element.
|
||||
/// </summary>
|
||||
/// <param name="text">The node name (title text).</param>
|
||||
/// <returns>The created element.</returns>
|
||||
TreeNodeElement Node(string text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The tree structure node element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElementsContainer" />
|
||||
public class TreeNodeElement : LayoutElementsContainer, ITreeElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The tree node control.
|
||||
/// </summary>
|
||||
public readonly TreeNode TreeNode = new TreeNode(false);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ContainerControl ContainerControl => TreeNode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public TreeNodeElement Node(string text)
|
||||
{
|
||||
TreeNodeElement element = new TreeNodeElement();
|
||||
element.TreeNode.Text = text;
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Source/Editor/CustomEditors/Elements/VerticalPanelElement.cs
Normal file
21
Source/Editor/CustomEditors/Elements/VerticalPanelElement.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// The vertical panel element.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
public class VerticalPanelElement : LayoutElementsContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The panel.
|
||||
/// </summary>
|
||||
public readonly VerticalPanel Panel = new VerticalPanel();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ContainerControl ContainerControl => Panel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Assertions;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom property name label that contains a checkbox used to enable/disable a property.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.GUI.PropertyNameLabel" />
|
||||
[HideInEditor]
|
||||
public class CheckablePropertyNameLabel : PropertyNameLabel
|
||||
{
|
||||
/// <summary>
|
||||
/// The check box.
|
||||
/// </summary>
|
||||
public readonly CheckBox CheckBox;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when 'checked' state gets changed.
|
||||
/// </summary>
|
||||
public event Action<CheckablePropertyNameLabel> CheckChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CheckablePropertyNameLabel(string name)
|
||||
: base(name)
|
||||
{
|
||||
CheckBox = new CheckBox(2, 2)
|
||||
{
|
||||
Checked = true,
|
||||
Size = new Vector2(14),
|
||||
Parent = this
|
||||
};
|
||||
CheckBox.StateChanged += OnCheckChanged;
|
||||
Margin = new Margin(CheckBox.Right + 4, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void OnCheckChanged(CheckBox box)
|
||||
{
|
||||
CheckChanged?.Invoke(this);
|
||||
UpdateStyle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the label style.
|
||||
/// </summary>
|
||||
public virtual void UpdateStyle()
|
||||
{
|
||||
var style = Style.Current;
|
||||
bool check = CheckBox.Checked;
|
||||
|
||||
// Update label text color
|
||||
TextColor = check ? style.Foreground : style.ForegroundGrey;
|
||||
|
||||
// Update child controls enabled state
|
||||
if (FirstChildControlIndex >= 0 && Parent is PropertiesList propertiesList)
|
||||
{
|
||||
var controls = propertiesList.Children;
|
||||
var labels = propertiesList.Element.Labels;
|
||||
var thisIndex = labels.IndexOf(this);
|
||||
Assert.AreNotEqual(-1, thisIndex, "Invalid label linkage.");
|
||||
int childControlsCount = 0;
|
||||
if (thisIndex + 1 < labels.Count)
|
||||
childControlsCount = labels[thisIndex + 1].FirstChildControlIndex - FirstChildControlIndex - 1;
|
||||
else
|
||||
childControlsCount = controls.Count;
|
||||
int lastControl = Mathf.Min(FirstChildControlIndex + childControlsCount, controls.Count);
|
||||
for (int i = FirstChildControlIndex; i < lastControl; i++)
|
||||
{
|
||||
controls[i].Enabled = check;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
base.PerformLayoutBeforeChildren();
|
||||
|
||||
// Center checkbox
|
||||
CheckBox.Y = (Height - CheckBox.Height) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
Source/Editor/CustomEditors/GUI/ClickablePropertyNameLabel.cs
Normal file
107
Source/Editor/CustomEditors/GUI/ClickablePropertyNameLabel.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom property name label that fires mouse events for label.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.GUI.PropertyNameLabel" />
|
||||
[HideInEditor]
|
||||
public class ClickablePropertyNameLabel : PropertyNameLabel
|
||||
{
|
||||
/// <summary>
|
||||
/// Mouse action delegate.
|
||||
/// </summary>
|
||||
/// <param name="label">The label.</param>
|
||||
/// <param name="location">The mouse location.</param>
|
||||
public delegate void MouseDelegate(ClickablePropertyNameLabel label, Vector2 location);
|
||||
|
||||
/// <summary>
|
||||
/// The mouse left button clicks on the label.
|
||||
/// </summary>
|
||||
public MouseDelegate MouseLeftClick;
|
||||
|
||||
/// <summary>
|
||||
/// The mouse right button clicks on the label.
|
||||
/// </summary>
|
||||
public MouseDelegate MouseRightClick;
|
||||
|
||||
/// <summary>
|
||||
/// The mouse left button double clicks on the label.
|
||||
/// </summary>
|
||||
public MouseDelegate MouseLeftDoubleClick;
|
||||
|
||||
/// <summary>
|
||||
/// The mouse right button double clicks on the label.
|
||||
/// </summary>
|
||||
public MouseDelegate MouseRightDoubleClick;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ClickablePropertyNameLabel(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
// Fire events
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
if (MouseLeftClick != null)
|
||||
{
|
||||
MouseLeftClick.Invoke(this, location);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (button == MouseButton.Right)
|
||||
{
|
||||
if (MouseRightClick != null)
|
||||
{
|
||||
MouseRightClick.Invoke(this, location);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Vector2 location, MouseButton button)
|
||||
{
|
||||
// Fire events
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
if (MouseLeftDoubleClick != null)
|
||||
{
|
||||
MouseLeftDoubleClick.Invoke(this, location);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (button == MouseButton.Right)
|
||||
{
|
||||
if (MouseRightDoubleClick != null)
|
||||
{
|
||||
MouseRightDoubleClick.Invoke(this, location);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Unlink events
|
||||
MouseLeftClick = null;
|
||||
MouseRightClick = null;
|
||||
MouseLeftDoubleClick = null;
|
||||
MouseRightDoubleClick = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom property name label that fires mouse events for label and supports dragging.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.GUI.PropertyNameLabel" />
|
||||
[HideInEditor]
|
||||
public class DraggablePropertyNameLabel : ClickablePropertyNameLabel
|
||||
{
|
||||
private bool _isLeftMouseButtonDown;
|
||||
|
||||
/// <summary>
|
||||
/// Mouse drag action delegate.
|
||||
/// </summary>
|
||||
/// <param name="label">The label.</param>
|
||||
/// <returns>The drag data or null if not use drag.</returns>
|
||||
public delegate DragData DragDelegate(DraggablePropertyNameLabel label);
|
||||
|
||||
/// <summary>
|
||||
/// The mouse starts the drag. Callbacks gets the drag data.
|
||||
/// </summary>
|
||||
public DragDelegate Drag;
|
||||
|
||||
/// <inheritdoc />
|
||||
public DraggablePropertyNameLabel(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
_isLeftMouseButtonDown = false;
|
||||
}
|
||||
|
||||
base.OnMouseUp(location, button);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
_isLeftMouseButtonDown = true;
|
||||
}
|
||||
|
||||
base.OnMouseDown(location, button);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
base.OnMouseLeave();
|
||||
|
||||
if (_isLeftMouseButtonDown)
|
||||
{
|
||||
_isLeftMouseButtonDown = false;
|
||||
|
||||
var data = Drag?.Invoke(this);
|
||||
if (data != null)
|
||||
{
|
||||
DoDragDrop(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Unlink event
|
||||
Drag = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
251
Source/Editor/CustomEditors/GUI/PropertiesList.cs
Normal file
251
Source/Editor/CustomEditors/GUI/PropertiesList.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="CustomEditor"/> properties list control.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.PanelWithMargins" />
|
||||
[HideInEditor]
|
||||
public class PropertiesList : PanelWithMargins
|
||||
{
|
||||
// TODO: sync splitter for whole presenter
|
||||
|
||||
/// <summary>
|
||||
/// The splitter size (in pixels).
|
||||
/// </summary>
|
||||
public const int SplitterSize = 2;
|
||||
|
||||
/// <summary>
|
||||
/// The splitter margin (in pixels).
|
||||
/// </summary>
|
||||
public const int SplitterMargin = 4;
|
||||
|
||||
private const int SplitterSizeHalf = SplitterSize / 2;
|
||||
|
||||
private PropertiesListElement _element;
|
||||
private float _splitterValue;
|
||||
private Rectangle _splitterRect;
|
||||
private bool _splitterClicked, _mouseOverSplitter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the splitter value (always in range [0; 1]).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The splitter value (always in range [0; 1]).
|
||||
/// </value>
|
||||
public float SplitterValue
|
||||
{
|
||||
get => _splitterValue;
|
||||
set
|
||||
{
|
||||
value = Mathf.Clamp(value, 0.05f, 0.95f);
|
||||
if (!Mathf.NearEqual(_splitterValue, value))
|
||||
{
|
||||
_splitterValue = value;
|
||||
UpdateSplitRect();
|
||||
PerformLayout(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the properties list element. It's a parent object for this control.
|
||||
/// </summary>
|
||||
public PropertiesListElement Element => _element;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PropertiesList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="element">The element.</param>
|
||||
public PropertiesList(PropertiesListElement element)
|
||||
{
|
||||
_element = element;
|
||||
_splitterValue = 0.4f;
|
||||
BottomMargin = TopMargin = RightMargin = SplitterMargin;
|
||||
UpdateSplitRect();
|
||||
}
|
||||
|
||||
private void UpdateSplitRect()
|
||||
{
|
||||
_splitterRect = new Rectangle(Mathf.Clamp(_splitterValue * Width - SplitterSizeHalf, 0.0f, Width), 0, SplitterSize, Height);
|
||||
LeftMargin = _splitterValue * Width + SplitterMargin;
|
||||
}
|
||||
|
||||
private void StartTracking()
|
||||
{
|
||||
// Start move
|
||||
_splitterClicked = true;
|
||||
|
||||
// Start capturing mouse
|
||||
StartMouseCapture();
|
||||
}
|
||||
|
||||
private void EndTracking()
|
||||
{
|
||||
if (_splitterClicked)
|
||||
{
|
||||
// Clear flag
|
||||
_splitterClicked = false;
|
||||
|
||||
// End capturing mouse
|
||||
EndMouseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
|
||||
// Draw splitter
|
||||
Render2D.FillRectangle(_splitterRect, _splitterClicked ? style.BackgroundSelected : _mouseOverSplitter ? style.BackgroundHighlighted : style.Background * 0.8f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
EndTracking();
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Vector2 location)
|
||||
{
|
||||
_mouseOverSplitter = _splitterRect.Contains(location);
|
||||
|
||||
if (_splitterClicked)
|
||||
{
|
||||
SplitterValue = location.X / Width;
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
if (_splitterRect.Contains(location))
|
||||
{
|
||||
// Start moving splitter
|
||||
StartTracking();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (_splitterClicked)
|
||||
{
|
||||
EndTracking();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
// Clear flag
|
||||
_mouseOverSplitter = false;
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
// Clear flag
|
||||
_splitterClicked = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnSizeChanged()
|
||||
{
|
||||
base.OnSizeChanged();
|
||||
|
||||
// Refresh
|
||||
UpdateSplitRect();
|
||||
PerformLayout(true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
base.PerformLayoutBeforeChildren();
|
||||
|
||||
// Pre-set width of all controls
|
||||
float w = Width - _margin.Width;
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
{
|
||||
Control c = _children[i];
|
||||
if (!(c is PropertyNameLabel))
|
||||
{
|
||||
c.Width = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutAfterChildren()
|
||||
{
|
||||
// Sort controls from up to down into two columns: one for labels and one for the rest of the stuff
|
||||
|
||||
float y = _margin.Top;
|
||||
float w = Width - _margin.Width;
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
{
|
||||
Control c = _children[i];
|
||||
if (!(c is PropertyNameLabel))
|
||||
{
|
||||
var h = c.Height;
|
||||
c.Bounds = new Rectangle(_margin.Left, y + _spacing, w, h);
|
||||
if (c.Visible)
|
||||
y = c.Bottom;
|
||||
}
|
||||
}
|
||||
y += _margin.Bottom;
|
||||
|
||||
float namesWidth = _splitterValue * Width;
|
||||
int count = _element.Labels.Count;
|
||||
float[] yStarts = new float[count + 1];
|
||||
for (int i = 1; i < count; i++)
|
||||
{
|
||||
var label = _element.Labels[i];
|
||||
|
||||
if (label.FirstChildControlIndex < 0)
|
||||
yStarts[i] = 0;
|
||||
else if (_children.Count <= label.FirstChildControlIndex)
|
||||
yStarts[i] = y;
|
||||
else
|
||||
yStarts[i] = _children[label.FirstChildControlIndex].Top;
|
||||
}
|
||||
yStarts[count] = y;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var label = _element.Labels[i];
|
||||
|
||||
var rect = new Rectangle(0, yStarts[i] + 1, namesWidth, yStarts[i + 1] - yStarts[i] - 2);
|
||||
//label.Parent = this;
|
||||
label.Bounds = rect;
|
||||
}
|
||||
|
||||
if (_autoSize)
|
||||
Height = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
157
Source/Editor/CustomEditors/GUI/PropertyNameLabel.cs
Normal file
157
Source/Editor/CustomEditors/GUI/PropertyNameLabel.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays custom editor property name.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Label" />
|
||||
[HideInEditor]
|
||||
public class PropertyNameLabel : Label
|
||||
{
|
||||
// TODO: if name is too long to show -> use tooltip to show it
|
||||
|
||||
/// <summary>
|
||||
/// Custom event delegate that can be used to extend the property name label with an additional functionality.
|
||||
/// </summary>
|
||||
/// <param name="label">The label.</param>
|
||||
/// <param name="menu">The menu.</param>
|
||||
/// <param name="linkedEditor">The linked editor. Can be null.</param>
|
||||
public delegate void SetupContextMenuDelegate(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor);
|
||||
|
||||
private bool _mouseDown;
|
||||
|
||||
/// <summary>
|
||||
/// Helper value used by the <see cref="PropertiesList"/> to draw property names in a proper area.
|
||||
/// </summary>
|
||||
internal int FirstChildControlIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The linked custom editor (shows the label property).
|
||||
/// </summary>
|
||||
internal CustomEditor LinkedEditor;
|
||||
|
||||
/// <summary>
|
||||
/// The highlight strip color drawn on a side (transparent if skip rendering).
|
||||
/// </summary>
|
||||
public Color HighlightStripColor;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when label creates the context menu popup for th property. Can be used to add some custom logic per property editor.
|
||||
/// </summary>
|
||||
public event SetupContextMenuDelegate SetupContextMenu;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PropertyNameLabel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
public PropertyNameLabel(string name)
|
||||
{
|
||||
Text = name;
|
||||
HorizontalAlignment = TextAlignment.Near;
|
||||
VerticalAlignment = TextAlignment.Center;
|
||||
Margin = new Margin(4, 0, 0, 0);
|
||||
ClipText = true;
|
||||
|
||||
HighlightStripColor = Color.Transparent;
|
||||
}
|
||||
|
||||
internal void LinkEditor(CustomEditor editor)
|
||||
{
|
||||
if (LinkedEditor == null)
|
||||
{
|
||||
LinkedEditor = editor;
|
||||
editor.LinkLabel(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
if (HighlightStripColor.A > 0.0f)
|
||||
{
|
||||
Render2D.FillRectangle(new Rectangle(0, 0, 2, Height), HighlightStripColor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
_mouseDown = false;
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Right)
|
||||
{
|
||||
_mouseDown = true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseUp(location, button))
|
||||
return true;
|
||||
|
||||
if (_mouseDown && button == MouseButton.Right)
|
||||
{
|
||||
_mouseDown = false;
|
||||
|
||||
// Skip if is not extended
|
||||
var linkedEditor = LinkedEditor;
|
||||
if (linkedEditor == null && SetupContextMenu == null)
|
||||
return false;
|
||||
|
||||
var menu = new ContextMenu();
|
||||
|
||||
if (linkedEditor != null)
|
||||
{
|
||||
var revertToPrefab = menu.AddButton("Revert to Prefab", linkedEditor.RevertToReferenceValue);
|
||||
revertToPrefab.Enabled = linkedEditor.CanRevertReferenceValue;
|
||||
var resetToDefault = menu.AddButton("Reset to default", linkedEditor.RevertToDefaultValue);
|
||||
resetToDefault.Enabled = linkedEditor.CanRevertDefaultValue;
|
||||
menu.AddSeparator();
|
||||
menu.AddButton("Copy", linkedEditor.Copy);
|
||||
var paste = menu.AddButton("Paste", linkedEditor.Paste);
|
||||
paste.Enabled = linkedEditor.CanPaste;
|
||||
}
|
||||
|
||||
SetupContextMenu?.Invoke(this, menu, linkedEditor);
|
||||
|
||||
menu.Show(this, location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
_mouseDown = false;
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
SetupContextMenu = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Source/Editor/CustomEditors/LayoutElement.cs
Normal file
19
Source/Editor/CustomEditors/LayoutElement.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents single element of the Custom Editor layout.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public abstract class LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the control represented by this element.
|
||||
/// </summary>
|
||||
public abstract Control Control { get; }
|
||||
}
|
||||
}
|
||||
671
Source/Editor/CustomEditors/LayoutElementsContainer.cs
Normal file
671
Source/Editor/CustomEditors/LayoutElementsContainer.cs
Normal file
@@ -0,0 +1,671 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Assertions;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a container control for <see cref="LayoutElement"/>. Can contain child elements.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
|
||||
[HideInEditor]
|
||||
public abstract class LayoutElementsContainer : LayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper flag that is set to true if this container is in root presenter area, otherwise it's one of child groups.
|
||||
/// It's used to collapse all child groups and open the root ones by auto.
|
||||
/// </summary>
|
||||
internal bool isRootGroup = true;
|
||||
|
||||
/// <summary>
|
||||
/// The children.
|
||||
/// </summary>
|
||||
public readonly List<LayoutElement> Children = new List<LayoutElement>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control represented by this element.
|
||||
/// </summary>
|
||||
public abstract ContainerControl ContainerControl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds new group element.
|
||||
/// </summary>
|
||||
/// <param name="title">The title.</param>
|
||||
/// <param name="useTransparentHeader">True if use drop down icon and transparent group header, otherwise use normal style.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public GroupElement Group(string title, bool useTransparentHeader = false)
|
||||
{
|
||||
var element = new GroupElement();
|
||||
if (!isRootGroup)
|
||||
{
|
||||
element.Panel.Close(false);
|
||||
}
|
||||
else if (this is CustomEditorPresenter presenter && presenter.CacheExpandedGroups)
|
||||
{
|
||||
if (Editor.Instance.ProjectCache.IsCollapsedGroup(title))
|
||||
element.Panel.Close(false);
|
||||
element.Panel.IsClosedChanged += OnPanelIsClosedChanged;
|
||||
}
|
||||
element.isRootGroup = false;
|
||||
element.Panel.HeaderText = title;
|
||||
if (useTransparentHeader)
|
||||
{
|
||||
element.Panel.EnableDropDownIcon = true;
|
||||
element.Panel.HeaderColor = element.Panel.HeaderColorMouseOver = Color.Transparent;
|
||||
}
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
private void OnPanelIsClosedChanged(DropPanel panel)
|
||||
{
|
||||
Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new button element.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public ButtonElement Button(string text)
|
||||
{
|
||||
var element = new ButtonElement();
|
||||
element.Init(text);
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new button element with custom color.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="color">The color.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public ButtonElement Button(string text, Color color)
|
||||
{
|
||||
ButtonElement element = new ButtonElement();
|
||||
element.Init(text, color);
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new custom element.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The custom control.</typeparam>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomElement<T> Custom<T>()
|
||||
where T : Control, new()
|
||||
{
|
||||
var element = new CustomElement<T>();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new custom element with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <typeparam name="T">The custom control.</typeparam>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomElement<T> Custom<T>(string name, string tooltip = null)
|
||||
where T : Control, new()
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.Custom<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new custom elements container.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The custom control.</typeparam>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomElementsContainer<T> CustomContainer<T>()
|
||||
where T : ContainerControl, new()
|
||||
{
|
||||
var element = new CustomElementsContainer<T>();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new custom elements container with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <typeparam name="T">The custom control.</typeparam>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomElementsContainer<T> CustomContainer<T>(string name, string tooltip = null)
|
||||
where T : ContainerControl, new()
|
||||
{
|
||||
var property = AddPropertyItem(name);
|
||||
return property.CustomContainer<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new space.
|
||||
/// </summary>
|
||||
/// <param name="height">The space height.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public SpaceElement Space(float height)
|
||||
{
|
||||
var element = new SpaceElement();
|
||||
element.Init(height);
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds sprite image to the layout.
|
||||
/// </summary>
|
||||
/// <param name="sprite">The sprite.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public ImageElement Image(SpriteHandle sprite)
|
||||
{
|
||||
var element = new ImageElement();
|
||||
element.Image.Brush = new SpriteBrush(sprite);
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds texture image to the layout.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public ImageElement Image(Texture texture)
|
||||
{
|
||||
var element = new ImageElement();
|
||||
element.Image.Brush = new TextureBrush(texture);
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds GPU texture image to the layout.
|
||||
/// </summary>
|
||||
/// <param name="texture">The GPU texture.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public ImageElement Image(GPUTexture texture)
|
||||
{
|
||||
var element = new ImageElement();
|
||||
element.Image.Brush = new GPUTextureBrush(texture);
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new header control.
|
||||
/// </summary>
|
||||
/// <param name="text">The header text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public LabelElement Header(string text)
|
||||
{
|
||||
var element = Label(text);
|
||||
element.Label.Font = new FontReference(Style.Current.FontLarge);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new text box element.
|
||||
/// </summary>
|
||||
/// <param name="isMultiline">Enable/disable multiline text input support</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public TextBoxElement TextBox(bool isMultiline = false)
|
||||
{
|
||||
var element = new TextBoxElement(isMultiline);
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new check box element.
|
||||
/// </summary>
|
||||
/// <returns>The created element.</returns>
|
||||
public CheckBoxElement Checkbox()
|
||||
{
|
||||
var element = new CheckBoxElement();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new check box element with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public CheckBoxElement Checkbox(string name, string tooltip = null)
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.Checkbox();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new tree element.
|
||||
/// </summary>
|
||||
/// <returns>The created element.</returns>
|
||||
public TreeElement Tree()
|
||||
{
|
||||
var element = new TreeElement();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new label element.
|
||||
/// </summary>
|
||||
/// <param name="text">The label text.</param>
|
||||
/// <param name="horizontalAlignment">The label text horizontal alignment.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public LabelElement Label(string text, TextAlignment horizontalAlignment = TextAlignment.Near)
|
||||
{
|
||||
var element = new LabelElement();
|
||||
element.Label.Text = text;
|
||||
element.Label.HorizontalAlignment = horizontalAlignment;
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new label element with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="text">The label text.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public LabelElement Label(string name, string text, string tooltip = null)
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.Label(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new label element.
|
||||
/// </summary>
|
||||
/// <param name="text">The label text.</param>
|
||||
/// <param name="horizontalAlignment">The label text horizontal alignment.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomElement<ClickableLabel> ClickableLabel(string text, TextAlignment horizontalAlignment = TextAlignment.Near)
|
||||
{
|
||||
var element = new CustomElement<ClickableLabel>();
|
||||
element.CustomControl.Height = 18.0f;
|
||||
element.CustomControl.Text = text;
|
||||
element.CustomControl.HorizontalAlignment = horizontalAlignment;
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new label element with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="text">The label text.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomElement<ClickableLabel> ClickableLabel(string name, string text, string tooltip = null)
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.ClickableLabel(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new float value element.
|
||||
/// </summary>
|
||||
/// <returns>The created element.</returns>
|
||||
public FloatValueElement FloatValue()
|
||||
{
|
||||
var element = new FloatValueElement();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new float value element with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public FloatValueElement FloatValue(string name, string tooltip = null)
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.FloatValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new double value element.
|
||||
/// </summary>
|
||||
/// <returns>The created element.</returns>
|
||||
public DoubleValueElement DoubleValue()
|
||||
{
|
||||
var element = new DoubleValueElement();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new double value element with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public DoubleValueElement DoubleValue(string name, string tooltip = null)
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.DoubleValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new slider element.
|
||||
/// </summary>
|
||||
/// <returns>The created element.</returns>
|
||||
public SliderElement Slider()
|
||||
{
|
||||
var element = new SliderElement();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new slider element with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public SliderElement Slider(string name, string tooltip = null)
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.Slider();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new signed integer (up to long range) value element.
|
||||
/// </summary>
|
||||
/// <returns>The created element.</returns>
|
||||
public SignedIntegerValueElement SignedIntegerValue()
|
||||
{
|
||||
var element = new SignedIntegerValueElement();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new unsigned signed integer (up to ulong range) value element.
|
||||
/// </summary>
|
||||
/// <returns>The created element.</returns>
|
||||
public UnsignedIntegerValueElement UnsignedIntegerValue()
|
||||
{
|
||||
var element = new UnsignedIntegerValueElement();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new integer value element.
|
||||
/// </summary>
|
||||
/// <returns>The created element.</returns>
|
||||
public IntegerValueElement IntegerValue()
|
||||
{
|
||||
var element = new IntegerValueElement();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new integer value element with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public IntegerValueElement IntegerValue(string name, string tooltip = null)
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.IntegerValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new combobox element.
|
||||
/// </summary>
|
||||
/// <returns>The created element.</returns>
|
||||
public ComboBoxElement ComboBox()
|
||||
{
|
||||
var element = new ComboBoxElement();
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new combobox element with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public ComboBoxElement ComboBox(string name, string tooltip = null)
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.ComboBox();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new enum value element.
|
||||
/// </summary>
|
||||
/// <param name="type">The enum type.</param>
|
||||
/// <param name="customBuildEntriesDelegate">The custom entries layout builder. Allows to hide existing or add different enum values to editor.</param>
|
||||
/// <param name="formatMode">The formatting mode.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public EnumElement Enum(Type type, EnumComboBox.BuildEntriesDelegate customBuildEntriesDelegate = null, EnumDisplayAttribute.FormatMode formatMode = EnumDisplayAttribute.FormatMode.Default)
|
||||
{
|
||||
var element = new EnumElement(type, customBuildEntriesDelegate, formatMode);
|
||||
OnAddElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new enum value element with name label.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="type">The enum type.</param>
|
||||
/// <param name="customBuildEntriesDelegate">The custom entries layout builder. Allows to hide existing or add different enum values to editor.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <param name="formatMode">The formatting mode.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public EnumElement Enum(string name, Type type, EnumComboBox.BuildEntriesDelegate customBuildEntriesDelegate = null, string tooltip = null, EnumDisplayAttribute.FormatMode formatMode = EnumDisplayAttribute.FormatMode.Default)
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.Enum(type, customBuildEntriesDelegate, formatMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds object(s) editor. Selects proper <see cref="CustomEditor"/> based on overrides.
|
||||
/// </summary>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <param name="overrideEditor">The custom editor to use. If null will detect it by auto.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomEditor Object(ValueContainer values, CustomEditor overrideEditor = null)
|
||||
{
|
||||
if (values == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
var editor = CustomEditorsUtil.CreateEditor(values, overrideEditor);
|
||||
|
||||
OnAddEditor(editor);
|
||||
editor.Initialize(CustomEditor.CurrentCustomEditor.Presenter, this, values);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds object(s) editor with name label. Selects proper <see cref="CustomEditor"/> based on overrides.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <param name="overrideEditor">The custom editor to use. If null will detect it by auto.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomEditor Object(string name, ValueContainer values, CustomEditor overrideEditor = null, string tooltip = null)
|
||||
{
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.Object(values, overrideEditor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds object(s) editor with name label. Selects proper <see cref="CustomEditor"/> based on overrides.
|
||||
/// </summary>
|
||||
/// <param name="label">The property label.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <param name="overrideEditor">The custom editor to use. If null will detect it by auto.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomEditor Object(PropertyNameLabel label, ValueContainer values, CustomEditor overrideEditor = null, string tooltip = null)
|
||||
{
|
||||
var property = AddPropertyItem(label, tooltip);
|
||||
return property.Object(values, overrideEditor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds object property editor. Selects proper <see cref="CustomEditor"/> based on overrides.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <param name="overrideEditor">The custom editor to use. If null will detect it by auto.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomEditor Property(string name, ValueContainer values, CustomEditor overrideEditor = null, string tooltip = null)
|
||||
{
|
||||
var editor = CustomEditorsUtil.CreateEditor(values, overrideEditor);
|
||||
var style = editor.Style;
|
||||
|
||||
if (style == DisplayStyle.InlineIntoParent || name == EditorDisplayAttribute.InlineStyle)
|
||||
{
|
||||
return Object(values, editor);
|
||||
}
|
||||
|
||||
if (style == DisplayStyle.Group)
|
||||
{
|
||||
var group = Group(name, true);
|
||||
group.Panel.Close(false);
|
||||
group.Panel.TooltipText = tooltip;
|
||||
return group.Object(values, editor);
|
||||
}
|
||||
|
||||
var property = AddPropertyItem(name, tooltip);
|
||||
return property.Object(values, editor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds object property editor. Selects proper <see cref="CustomEditor"/> based on overrides.
|
||||
/// </summary>
|
||||
/// <param name="label">The property label.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <param name="overrideEditor">The custom editor to use. If null will detect it by auto.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The created element.</returns>
|
||||
public CustomEditor Property(PropertyNameLabel label, ValueContainer values, CustomEditor overrideEditor = null, string tooltip = null)
|
||||
{
|
||||
var editor = CustomEditorsUtil.CreateEditor(values, overrideEditor);
|
||||
var style = editor.Style;
|
||||
|
||||
if (style == DisplayStyle.InlineIntoParent)
|
||||
{
|
||||
return Object(values, editor);
|
||||
}
|
||||
|
||||
if (style == DisplayStyle.Group)
|
||||
{
|
||||
var group = Group(label.Text, true);
|
||||
group.Panel.Close(false);
|
||||
return group.Object(values, editor);
|
||||
}
|
||||
|
||||
var property = AddPropertyItem(label, tooltip);
|
||||
return property.Object(values, editor);
|
||||
}
|
||||
|
||||
private PropertiesListElement AddPropertyItem()
|
||||
{
|
||||
// Try to reuse previous control
|
||||
PropertiesListElement element;
|
||||
if (Children.Count > 0 && Children[Children.Count - 1] is PropertiesListElement propertiesListElement)
|
||||
{
|
||||
element = propertiesListElement;
|
||||
}
|
||||
else
|
||||
{
|
||||
element = new PropertiesListElement();
|
||||
OnAddElement(element);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the <see cref="PropertiesListElement"/> to the current layout or reuses the previous one. Used to inject properties.
|
||||
/// </summary>
|
||||
/// <param name="name">The property label name.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The element.</returns>
|
||||
public PropertiesListElement AddPropertyItem(string name, string tooltip = null)
|
||||
{
|
||||
var element = AddPropertyItem();
|
||||
element.OnAddProperty(name, tooltip);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the <see cref="PropertiesListElement"/> to the current layout or reuses the previous one. Used to inject properties.
|
||||
/// </summary>
|
||||
/// <param name="label">The property label.</param>
|
||||
/// <param name="tooltip">The property label tooltip text.</param>
|
||||
/// <returns>The element.</returns>
|
||||
public PropertiesListElement AddPropertyItem(PropertyNameLabel label, string tooltip = null)
|
||||
{
|
||||
if (label == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
var element = AddPropertyItem();
|
||||
element.OnAddProperty(label, tooltip);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when element is added to the layout.
|
||||
/// </summary>
|
||||
/// <param name="element">The element.</param>
|
||||
protected virtual void OnAddElement(LayoutElement element)
|
||||
{
|
||||
element.Control.Parent = ContainerControl;
|
||||
Children.Add(element);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when editor is added.
|
||||
/// </summary>
|
||||
/// <param name="editor">The editor.</param>
|
||||
protected virtual void OnAddEditor(CustomEditor editor)
|
||||
{
|
||||
// This could be passed by the calling code but it's easier to hide it from the user
|
||||
// Note: we need that custom editor to link generated editor into the parent
|
||||
var customEditor = CustomEditor.CurrentCustomEditor;
|
||||
Assert.IsNotNull(customEditor);
|
||||
customEditor.OnChildCreated(editor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the layout.
|
||||
/// </summary>
|
||||
public virtual void ClearLayout()
|
||||
{
|
||||
Children.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Control Control => ContainerControl;
|
||||
}
|
||||
}
|
||||
141
Source/Editor/CustomEditors/SyncPointEditor.cs
Normal file
141
Source/Editor/CustomEditors/SyncPointEditor.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronizes objects modifications and records undo operations.
|
||||
/// Allows to override undo action target objects for the part of the custom editors hierarchy.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.CustomEditor" />
|
||||
[HideInEditor]
|
||||
public class SyncPointEditor : CustomEditor
|
||||
{
|
||||
private object[] _snapshotUndoInternal;
|
||||
|
||||
/// <summary>
|
||||
/// The 'is dirty' flag.
|
||||
/// </summary>
|
||||
protected bool _isDirty;
|
||||
|
||||
/// <summary>
|
||||
/// The cached token used by the value setter to support batching Undo actions (eg. for sliders or color pickers).
|
||||
/// </summary>
|
||||
protected object _setValueToken;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the undo objects used to record undo operation changes.
|
||||
/// </summary>
|
||||
public virtual IEnumerable<object> UndoObjects => Presenter.GetUndoObjects(Presenter);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the undo.
|
||||
/// </summary>
|
||||
public virtual Undo Undo => Presenter.Undo;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
EndUndoRecord();
|
||||
|
||||
base.Deinitialize();
|
||||
}
|
||||
|
||||
internal override void RefreshInternal()
|
||||
{
|
||||
bool isDirty = _isDirty;
|
||||
_isDirty = false;
|
||||
|
||||
// If any UI control has been modified we should try to record selected objects change
|
||||
if (isDirty && Undo != null && Undo.Enabled)
|
||||
{
|
||||
string actionName = "Edit object(s)";
|
||||
|
||||
// Check if use token
|
||||
if (_setValueToken != null)
|
||||
{
|
||||
// Check if record start
|
||||
if (_snapshotUndoInternal == null)
|
||||
{
|
||||
// Record undo action start only (end called in EndUndoRecord)
|
||||
_snapshotUndoInternal = UndoObjects.ToArray();
|
||||
Undo.RecordMultiBegin(_snapshotUndoInternal, actionName);
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal undo action recording
|
||||
using (new UndoMultiBlock(Undo, UndoObjects, actionName))
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
if (isDirty)
|
||||
OnModified();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
RefreshRoot();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when data gets modified by the custom editors.
|
||||
/// </summary>
|
||||
protected virtual void OnModified()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool OnDirty(CustomEditor editor, object value, object token = null)
|
||||
{
|
||||
// End any active Undo action batching
|
||||
if (token != _setValueToken)
|
||||
EndUndoRecord();
|
||||
_setValueToken = token;
|
||||
|
||||
// Mark as modified and don't pass event further
|
||||
_isDirty = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ClearToken()
|
||||
{
|
||||
EndUndoRecord();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the undo recording if started with custom token (eg. by value slider).
|
||||
/// </summary>
|
||||
protected void EndUndoRecord()
|
||||
{
|
||||
if (_snapshotUndoInternal != null)
|
||||
{
|
||||
Undo.RecordMultiEnd(_snapshotUndoInternal);
|
||||
_snapshotUndoInternal = null;
|
||||
}
|
||||
|
||||
// Clear token
|
||||
_setValueToken = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
137
Source/Editor/CustomEditors/Values/CustomValueContainer.cs
Normal file
137
Source/Editor/CustomEditors/Values/CustomValueContainer.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom <see cref="ValueContainer"/> for any type of storage and data management logic.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.ValueContainer" />
|
||||
[HideInEditor]
|
||||
public class CustomValueContainer : ValueContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Get value delegate.
|
||||
/// </summary>
|
||||
/// <param name="instance">The parent object instance.</param>
|
||||
/// <param name="index">The index (for multi selected objects).</param>
|
||||
/// <returns>The value.</returns>
|
||||
public delegate object GetDelegate(object instance, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Set value delegate.
|
||||
/// </summary>
|
||||
/// <param name="instance">The parent object instance.</param>
|
||||
/// <param name="index">The index (for multi selected objects).</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public delegate void SetDelegate(object instance, int index, object value);
|
||||
|
||||
private readonly GetDelegate _getter;
|
||||
private readonly SetDelegate _setter;
|
||||
private readonly object[] _attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="valueType">Type of the value.</param>
|
||||
/// <param name="getter">The value getter.</param>
|
||||
/// <param name="setter">The value setter.</param>
|
||||
/// <param name="attributes">The custom type attributes used to override the value editor logic or appearance (eg. instance of <see cref="LimitAttribute"/>).</param>
|
||||
public CustomValueContainer(ScriptType valueType, GetDelegate getter, SetDelegate setter, object[] attributes = null)
|
||||
: base(ScriptMemberInfo.Null, valueType)
|
||||
{
|
||||
if (getter == null || setter == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
_getter = getter;
|
||||
_setter = setter;
|
||||
_attributes = attributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="valueType">Type of the value.</param>
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
/// <param name="getter">The value getter.</param>
|
||||
/// <param name="setter">The value setter.</param>
|
||||
/// <param name="attributes">The custom type attributes used to override the value editor logic or appearance (eg. instance of <see cref="LimitAttribute"/>).</param>
|
||||
public CustomValueContainer(ScriptType valueType, object initialValue, GetDelegate getter, SetDelegate setter, object[] attributes = null)
|
||||
: this(valueType, getter, setter, attributes)
|
||||
{
|
||||
Add(initialValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object[] GetAttributes()
|
||||
{
|
||||
return _attributes ?? base.GetAttributes();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh(ValueContainer instanceValues)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = instanceValues[i];
|
||||
this[i] = _getter(v, i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues, object value)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = instanceValues[i];
|
||||
_setter(v, i, value);
|
||||
this[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues, ValueContainer values)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
if (values == null || values.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = instanceValues[i];
|
||||
var value = ((CustomValueContainer)values)[i];
|
||||
_setter(v, i, value);
|
||||
this[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = instanceValues[i];
|
||||
_setter(v, i, _getter(v, i));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RefreshReferenceValue(object instanceValue)
|
||||
{
|
||||
// Not supported
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs
Normal file
127
Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom <see cref="ValueContainer"/> for <see cref="IDictionary"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.ValueContainer" />
|
||||
[HideInEditor]
|
||||
public class DictionaryValueContainer : ValueContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The key in the collection.
|
||||
/// </summary>
|
||||
public readonly object Key;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DictionaryValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="elementType">Type of the collection elements.</param>
|
||||
/// <param name="key">The key.</param>
|
||||
public DictionaryValueContainer(ScriptType elementType, object key)
|
||||
: base(ScriptMemberInfo.Null, elementType)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DictionaryValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="elementType">Type of the collection elements.</param>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="values">The collection values.</param>
|
||||
public DictionaryValueContainer(ScriptType elementType, object key, ValueContainer values)
|
||||
: this(elementType, key)
|
||||
{
|
||||
Capacity = values.Count;
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
{
|
||||
var v = (IDictionary)values[i];
|
||||
Add(v[Key]);
|
||||
}
|
||||
|
||||
if (values.HasReferenceValue)
|
||||
{
|
||||
var v = (IDictionary)values.ReferenceValue;
|
||||
if (v.Contains(key))
|
||||
{
|
||||
_referenceValue = v[key];
|
||||
_hasReferenceValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh(ValueContainer instanceValues)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = (IDictionary)instanceValues[i];
|
||||
this[i] = v[Key];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues, object value)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = (IDictionary)instanceValues[i];
|
||||
v[Key] = value;
|
||||
this[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues, ValueContainer values)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
if (values == null || values.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = (IDictionary)instanceValues[i];
|
||||
var value = ((DictionaryValueContainer)values)[i];
|
||||
v[Key] = value;
|
||||
this[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = (IDictionary)instanceValues[i];
|
||||
v[Key] = this[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RefreshReferenceValue(object instanceValue)
|
||||
{
|
||||
if (instanceValue is IDictionary v)
|
||||
{
|
||||
_referenceValue = v[Key];
|
||||
_hasReferenceValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Source/Editor/CustomEditors/Values/ListValueContainer.cs
Normal file
127
Source/Editor/CustomEditors/Values/ListValueContainer.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using FlaxEditor.Scripting;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom <see cref="ValueContainer"/> for <see cref="IList"/> (used for <see cref="Array"/> and <see cref="System.Collections.Generic.List{T}"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.ValueContainer" />
|
||||
public class ListValueContainer : ValueContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The index in the collection.
|
||||
/// </summary>
|
||||
public readonly int Index;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ListValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="elementType">Type of the collection elements.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
public ListValueContainer(ScriptType elementType, int index)
|
||||
: base(ScriptMemberInfo.Null, elementType)
|
||||
{
|
||||
Index = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ListValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="elementType">Type of the collection elements.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <param name="values">The collection values.</param>
|
||||
public ListValueContainer(ScriptType elementType, int index, ValueContainer values)
|
||||
: this(elementType, index)
|
||||
{
|
||||
Capacity = values.Count;
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
{
|
||||
var v = (IList)values[i];
|
||||
Add(v[index]);
|
||||
}
|
||||
|
||||
if (values.HasReferenceValue)
|
||||
{
|
||||
var v = (IList)values.ReferenceValue;
|
||||
|
||||
// Get the reference value if collections are the same size
|
||||
if (v != null && values.Count == v.Count)
|
||||
{
|
||||
_referenceValue = v[index];
|
||||
_hasReferenceValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh(ValueContainer instanceValues)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = (IList)instanceValues[i];
|
||||
this[i] = v[Index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues, object value)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = (IList)instanceValues[i];
|
||||
v[Index] = value;
|
||||
this[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues, ValueContainer values)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
if (values == null || values.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = (IList)instanceValues[i];
|
||||
var value = ((ListValueContainer)values)[i];
|
||||
v[Index] = value;
|
||||
this[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues)
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = (IList)instanceValues[i];
|
||||
v[Index] = this[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RefreshReferenceValue(object instanceValue)
|
||||
{
|
||||
if (instanceValue is IList v)
|
||||
{
|
||||
_referenceValue = v[Index];
|
||||
_hasReferenceValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Source/Editor/CustomEditors/Values/ReadOnlyValueContainer.cs
Normal file
66
Source/Editor/CustomEditors/Values/ReadOnlyValueContainer.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom <see cref="ValueContainer"/> for read-only values.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.ValueContainer" />
|
||||
[HideInEditor]
|
||||
public sealed class ReadOnlyValueContainer : ValueContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The initial value.</param>
|
||||
public ReadOnlyValueContainer(object value)
|
||||
: base(ScriptMemberInfo.Null, new ScriptType(typeof(object)))
|
||||
{
|
||||
Add(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The values type.</param>
|
||||
/// <param name="value">The initial value.</param>
|
||||
public ReadOnlyValueContainer(ScriptType type, object value)
|
||||
: base(ScriptMemberInfo.Null, type)
|
||||
{
|
||||
Add(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh(ValueContainer instanceValues)
|
||||
{
|
||||
// Not supported
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues, object value)
|
||||
{
|
||||
// Not supported
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues, ValueContainer values)
|
||||
{
|
||||
// Not supported
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Set(ValueContainer instanceValues)
|
||||
{
|
||||
// Not supported
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RefreshReferenceValue(object instanceValue)
|
||||
{
|
||||
// Not supported
|
||||
}
|
||||
}
|
||||
}
|
||||
420
Source/Editor/CustomEditors/Values/ValueContainer.cs
Normal file
420
Source/Editor/CustomEditors/Values/ValueContainer.cs
Normal file
@@ -0,0 +1,420 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Editable object values.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public class ValueContainer : List<object>
|
||||
{
|
||||
/// <summary>
|
||||
/// The has default value flag. Set if <see cref="_defaultValue"/> is valid and assigned.
|
||||
/// </summary>
|
||||
protected bool _hasDefaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// The default value used to show difference in the UI compared to the default object values. Used to revert modified properties.
|
||||
/// </summary>
|
||||
protected object _defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// The has reference value flag. Set if <see cref="_referenceValue"/> is valid and assigned.
|
||||
/// </summary>
|
||||
protected bool _hasReferenceValue;
|
||||
|
||||
/// <summary>
|
||||
/// The reference value used to show difference in the UI compared to the other object. Used by the prefabs system.
|
||||
/// </summary>
|
||||
protected object _referenceValue;
|
||||
|
||||
/// <summary>
|
||||
/// The values source information from reflection. Used to update values.
|
||||
/// </summary>
|
||||
public readonly ScriptMemberInfo Info;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values type.
|
||||
/// </summary>
|
||||
public ScriptType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether single object is selected.
|
||||
/// </summary>
|
||||
public bool IsSingleObject => Count == 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether selected objects are different values.
|
||||
/// </summary>
|
||||
public bool HasDifferentValues
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 1; i < Count; i++)
|
||||
{
|
||||
if (!Equals(this[0], this[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether selected objects are different types.
|
||||
/// </summary>
|
||||
public bool HasDifferentTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Count < 2)
|
||||
return false;
|
||||
var theFirstType = TypeUtils.GetObjectType(this[0]);
|
||||
for (int i = 1; i < Count; i++)
|
||||
{
|
||||
if (theFirstType != TypeUtils.GetObjectType(this[1]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether any value in the collection is null. Returns false if collection is empty.
|
||||
/// </summary>
|
||||
public bool HasNull
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (this[i] == null)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this any value in the collection is of value type (eg. a structure, not a class type). Returns false if collection is empty.
|
||||
/// </summary>
|
||||
public bool HasValueType
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (this[i] != null && TypeUtils.GetObjectType(this[i]).IsValueType)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this values container type is array.
|
||||
/// </summary>
|
||||
public bool IsArray => Type != ScriptType.Null && Type.IsArray;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values types array (without duplicates).
|
||||
/// </summary>
|
||||
public ScriptType[] ValuesTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Count == 1)
|
||||
return new[] { TypeUtils.GetObjectType(this[0]) };
|
||||
return ConvertAll(TypeUtils.GetObjectType).Distinct().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="info">The member info.</param>
|
||||
public ValueContainer(ScriptMemberInfo info)
|
||||
{
|
||||
Info = info;
|
||||
Type = Info.ValueType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has reference value assigned (see <see cref="ReferenceValue"/>).
|
||||
/// </summary>
|
||||
public bool HasReferenceValue => _hasReferenceValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reference value used to show difference in the UI compared to the other object. Used by the prefabs system.
|
||||
/// </summary>
|
||||
public object ReferenceValue => _referenceValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has reference value and the any of the values in the contains is modified (compared to the reference).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For prefabs system it means that object property has been modified compared to the prefab value.
|
||||
/// </remarks>
|
||||
public bool IsReferenceValueModified
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hasReferenceValue)
|
||||
{
|
||||
if (_referenceValue is ISceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink)
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (this[i] == referenceSceneObject)
|
||||
continue;
|
||||
|
||||
if (this[i] == null || (this[i] is ISceneObject valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (!Equals(this[i], _referenceValue))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has default value assigned (see <see cref="DefaultValue"/>).
|
||||
/// </summary>
|
||||
public bool HasDefaultValue => _hasDefaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default value used to show difference in the UI compared to the default value object. Used to revert modified properties.
|
||||
/// </summary>
|
||||
public object DefaultValue => _defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has default value and the any of the values in the contains is modified (compared to the reference).
|
||||
/// </summary>
|
||||
public bool IsDefaultValueModified
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hasDefaultValue)
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (!Equals(this[i], _defaultValue))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="info">The member info.</param>
|
||||
/// <param name="instanceValues">The parent values.</param>
|
||||
public ValueContainer(ScriptMemberInfo info, ValueContainer instanceValues)
|
||||
: this(info)
|
||||
{
|
||||
Capacity = instanceValues.Count;
|
||||
|
||||
for (int i = 0; i < instanceValues.Count; i++)
|
||||
{
|
||||
Add(Info.GetValue(instanceValues[i]));
|
||||
}
|
||||
|
||||
if (instanceValues._hasDefaultValue)
|
||||
{
|
||||
_defaultValue = Info.GetValue(instanceValues._defaultValue);
|
||||
_hasDefaultValue = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultValueAttribute = Info.GetAttribute<DefaultValueAttribute>();
|
||||
if (defaultValueAttribute != null)
|
||||
{
|
||||
_defaultValue = defaultValueAttribute.Value;
|
||||
_hasDefaultValue = true;
|
||||
}
|
||||
}
|
||||
if (instanceValues._hasReferenceValue)
|
||||
{
|
||||
_referenceValue = Info.GetValue(instanceValues._referenceValue);
|
||||
_hasReferenceValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="customType">The target custom type of the container values. Used to override the data.</param>
|
||||
/// <param name="other">The other values container to clone.</param>
|
||||
public ValueContainer(ScriptType customType, ValueContainer other)
|
||||
{
|
||||
Capacity = other.Capacity;
|
||||
AddRange(other);
|
||||
Info = other.Info;
|
||||
Type = customType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValueContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="info">The member info.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
protected ValueContainer(ScriptMemberInfo info, ScriptType type)
|
||||
{
|
||||
Info = info;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the custom attributes defined for the values source member.
|
||||
/// </summary>
|
||||
/// <returns>The attributes objects array.</returns>
|
||||
public virtual object[] GetAttributes()
|
||||
{
|
||||
return Info.GetAttributes(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the specified instance values.
|
||||
/// </summary>
|
||||
/// <param name="instanceValues">The parent values.</param>
|
||||
public virtual void Refresh(ValueContainer instanceValues)
|
||||
{
|
||||
if (instanceValues == null)
|
||||
throw new ArgumentNullException();
|
||||
if (instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
this[i] = Info.GetValue(instanceValues[i]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified instance values. Refreshes this values container.
|
||||
/// </summary>
|
||||
/// <param name="instanceValues">The parent values.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public virtual void Set(ValueContainer instanceValues, object value)
|
||||
{
|
||||
if (instanceValues == null)
|
||||
throw new ArgumentNullException();
|
||||
if (instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
Info.SetValue(instanceValues[i], value);
|
||||
this[i] = Info.GetValue(instanceValues[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified instance values. Refreshes this values container.
|
||||
/// </summary>
|
||||
/// <param name="instanceValues">The parent values.</param>
|
||||
/// <param name="values">The other values to set this container to.</param>
|
||||
public virtual void Set(ValueContainer instanceValues, ValueContainer values)
|
||||
{
|
||||
if (instanceValues == null || values == null)
|
||||
throw new ArgumentNullException();
|
||||
if (instanceValues.Count != Count || values.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
Info.SetValue(instanceValues[i], values[i]);
|
||||
this[i] = Info.GetValue(instanceValues[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified instance values with the container values.
|
||||
/// </summary>
|
||||
/// <param name="instanceValues">The parent values.</param>
|
||||
public virtual void Set(ValueContainer instanceValues)
|
||||
{
|
||||
if (instanceValues == null)
|
||||
throw new ArgumentNullException();
|
||||
if (instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
Info.SetValue(instanceValues[i], this[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default value of the container.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
public virtual void SetDefaultValue(object value)
|
||||
{
|
||||
_defaultValue = value;
|
||||
_hasDefaultValue = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the default value of the container.
|
||||
/// </summary>
|
||||
/// <param name="instanceValue">The parent value.</param>
|
||||
public virtual void RefreshDefaultValue(object instanceValue)
|
||||
{
|
||||
_defaultValue = Info.GetValue(instanceValue);
|
||||
_hasDefaultValue = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the default value of the container.
|
||||
/// </summary>
|
||||
public virtual void ClearDefaultValue()
|
||||
{
|
||||
_defaultValue = null;
|
||||
_hasDefaultValue = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the reference value of the container.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
public virtual void SetReferenceValue(object value)
|
||||
{
|
||||
_referenceValue = value;
|
||||
_hasReferenceValue = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the reference value of the container.
|
||||
/// </summary>
|
||||
/// <param name="instanceValue">The parent value.</param>
|
||||
public virtual void RefreshReferenceValue(object instanceValue)
|
||||
{
|
||||
_referenceValue = Info.GetValue(instanceValue);
|
||||
_hasReferenceValue = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the reference value of the container.
|
||||
/// </summary>
|
||||
public virtual void ClearReferenceValue()
|
||||
{
|
||||
_referenceValue = null;
|
||||
_hasReferenceValue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user