You're breathtaking!

This commit is contained in:
Wojtek Figat
2020-12-07 23:40:54 +01:00
commit 6fb9eee74c
5143 changed files with 1153594 additions and 0 deletions

View 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];
}
}
}
}

View File

@@ -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();
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View 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];
}
}
}
}

View 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();
}
}
}
}

View 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];
}
}
}
}

View 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);
}
}
}
}

View 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();
}
}
}
}

View 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];
}
}
}
}

View 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];
}
}
}
}

View 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;
}
}
}
}

View 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.");
}
}
}
}

View 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();
}
}
}

View 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;
}
}
}
}

View 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)),
};
}
}

View 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;
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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];
}
}
}
}
}

View File

@@ -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();
}
}
}

View 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();
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}
}

View 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);
}
}
}

View 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;
}
}
}
}

View 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];
}
}
}
}

View 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];
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}
}