Files
FlaxEngine/Source/Editor/Surface/Elements/InputBox.cs
2022-05-01 20:42:05 +02:00

1191 lines
40 KiB
C#

// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.GUI;
using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.Surface.Elements
{
/// <summary>
/// The handler for the input box default value editing. Used to display the default to the UI.
/// </summary>
[HideInEditor]
public interface IDefaultValueEditor
{
/// <summary>
/// Checks if the handles supports the given type.
/// </summary>
/// <param name="box">The input box that uses this editor.</param>
/// <param name="type">The type to check.</param>
/// <returns>True if can create UI for this type editing, otherwise false.</returns>
bool CanUse(InputBox box, ref ScriptType type);
/// <summary>
/// Creates the UI for the value editing.
/// </summary>
/// <param name="box">The input box that uses this editor.</param>
/// <param name="bounds">The control bounds (control can be smaller but cannot be bigger).</param>
/// <returns>The root control of the created UI</returns>
Control Create(InputBox box, ref Rectangle bounds);
/// <summary>
/// Returns true if given control is valid root editor for the value.
/// </summary>
/// <param name="box">The input box that uses this editor.</param>
/// <param name="control">The root control of the editor UI.</param>
/// <returns><c>true</c> if the specified control is valid; otherwise, <c>false</c>.</returns>
bool IsValid(InputBox box, Control control);
/// <summary>
/// Updates the default value editor UI value.
/// </summary>
/// <param name="box">The input box that uses this editor.</param>
/// <param name="control">The root control of the editor UI.</param>
void UpdateDefaultValue(InputBox box, Control control);
/// <summary>
/// Updates the UI after box attributes change.
/// </summary>
/// <param name="box">The input box that uses this editor.</param>
/// <param name="attributes">The custom attributes collection.</param>
/// <param name="control">The root control of the editor UI.</param>
void UpdateAttributes(InputBox box, object[] attributes, Control control);
}
class BooleanDefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.Type == typeof(bool);
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = BoolValue.Get(box.ParentNode, box.Archetype, box.Value);
var control = new CheckBox(bounds.X, bounds.Y, value, bounds.Height)
{
Parent = box.Parent,
Tag = box,
};
control.StateChanged += OnCheckboxStateChanged;
return control;
}
public bool IsValid(InputBox box, Control control)
{
return control is CheckBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is CheckBox checkBox)
checkBox.Checked = BoolValue.Get(box.ParentNode, box.Archetype, box.Value);
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private void OnCheckboxStateChanged(CheckBox control)
{
var box = (InputBox)control.Tag;
box.Value = control.Checked;
}
}
class IntegerDefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.Type == typeof(int);
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = IntegerValue.Get(box.ParentNode, box.Archetype, box.Value);
var control = new IntValueBox(value, bounds.X, bounds.Y, 40, int.MinValue, int.MaxValue, 0.01f)
{
Height = bounds.Height,
Parent = box.Parent,
Tag = box,
};
control.BoxValueChanged += OnIntValueBoxChanged;
return control;
}
public bool IsValid(InputBox box, Control control)
{
return control is IntValueBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is IntValueBox intValue)
intValue.Value = IntegerValue.Get(box.ParentNode, box.Archetype, box.Value);
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private void OnIntValueBoxChanged(ValueBox<int> control)
{
var box = (InputBox)control.Tag;
if (box.HasCustomValueAccess)
box.Value = control.Value;
else
IntegerValue.Set(box.ParentNode, box.Archetype, control.Value);
}
}
class UnsignedIntegerDefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.Type == typeof(uint);
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = UnsignedIntegerValue.Get(box.ParentNode, box.Archetype, box.Value);
var control = new UIntValueBox(value, bounds.X, bounds.Y, 40, uint.MinValue, uint.MaxValue, 0.01f)
{
Height = bounds.Height,
Parent = box.Parent,
Tag = box,
};
control.BoxValueChanged += OnValueBoxChanged;
return control;
}
public bool IsValid(InputBox box, Control control)
{
return control is UIntValueBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is UIntValueBox intValue)
intValue.Value = UnsignedIntegerValue.Get(box.ParentNode, box.Archetype, box.Value);
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private void OnValueBoxChanged(ValueBox<uint> control)
{
var box = (InputBox)control.Tag;
if (box.HasCustomValueAccess)
box.Value = control.Value;
else
UnsignedIntegerValue.Set(box.ParentNode, box.Archetype, control.Value);
}
}
class FloatingDefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.Type == typeof(float) || type.Type == typeof(double);
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = FloatValue.Get(box.ParentNode, box.Archetype, box.Value);
var control = new FloatValueBox(value, bounds.X, bounds.Y, 40, float.MinValue, float.MaxValue, 0.01f)
{
Height = bounds.Height,
Parent = box.Parent,
Tag = box,
};
control.BoxValueChanged += OnFloatValueBoxChanged;
return control;
}
public bool IsValid(InputBox box, Control control)
{
return control is FloatValueBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is FloatValueBox floatValue)
floatValue.Value = FloatValue.Get(box.ParentNode, box.Archetype, box.Value);
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private void OnFloatValueBoxChanged(ValueBox<float> control)
{
var box = (InputBox)control.Tag;
if (box.HasCustomValueAccess)
box.Value = control.Value;
else
FloatValue.Set(box.ParentNode, box.Archetype, control.Value);
}
}
class StringDefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.Type == typeof(string) && (box.Attributes == null || box.Attributes.All(x => x.GetType() != typeof(TypeReferenceAttribute)));
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = box.Value as string;
var control = new TextBox(false, bounds.X, bounds.Y, 40)
{
Text = value,
Height = bounds.Height,
Parent = box.Parent,
Tag = box,
};
control.TextBoxEditEnd += OnTextBoxTextChanged;
return control;
}
public bool IsValid(InputBox box, Control control)
{
return control is TextBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is TextBox textBox)
{
textBox.Text = box.Value as string;
}
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private void OnTextBoxTextChanged(TextBoxBase control)
{
var box = (InputBox)control.Tag;
box.Value = control.Text;
}
}
class Vector2DefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.Type == typeof(Vector2);
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var control = new ContainerControl(bounds.X, bounds.Y, 22 * 2 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnVector2ValueChanged;
var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnVector2ValueChanged;
return control;
}
public bool IsValid(InputBox box, Control control)
{
return control is ContainerControl vec2
&& vec2.ChildrenCount == 2
&& vec2.Children[0] is FloatValueBox
&& vec2.Children[1] is FloatValueBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is ContainerControl vec2
&& vec2.Tag as Type == typeof(Vector2)
&& vec2.ChildrenCount == 2
&& vec2.Children[0] is FloatValueBox x
&& vec2.Children[1] is FloatValueBox y)
{
var value = GetValue(box);
x.Value = value.X;
y.Value = value.Y;
}
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private Vector2 GetValue(InputBox box)
{
var value = Vector2.Zero;
var v = box.Value;
if (v is Vector2 vec2)
value = vec2;
else if (v is Vector3 vec3)
value = new Vector2(vec3);
else if (v is Vector4 vec4)
value = new Vector2(vec4);
else if (v is Color col)
value = new Vector2(col.R, col.G);
else if (v is float f)
value = new Vector2(f);
else if (v is int i)
value = new Vector2(i);
return value;
}
private void OnVector2ValueChanged(ValueBox<float> valueBox)
{
var control = valueBox.Parent;
var box = (InputBox)control.Tag;
var x = ((FloatValueBox)control.Children[0]).Value;
var y = ((FloatValueBox)control.Children[1]).Value;
box.Value = new Vector2(x, y);
}
}
class Vector3DefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.Type == typeof(Vector3);
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var control = new ContainerControl(bounds.X, bounds.Y, 22 * 3 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnVector3ValueChanged;
var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnVector3ValueChanged;
var floatZ = new FloatValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatZ.BoxValueChanged += OnVector3ValueChanged;
return control;
}
public bool IsValid(InputBox box, Control control)
{
return control is ContainerControl vec3
&& vec3.ChildrenCount == 3
&& vec3.Children[0] is FloatValueBox
&& vec3.Children[1] is FloatValueBox
&& vec3.Children[2] is FloatValueBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is ContainerControl vec3
&& vec3.ChildrenCount == 3
&& vec3.Children[0] is FloatValueBox x
&& vec3.Children[1] is FloatValueBox y
&& vec3.Children[2] is FloatValueBox z)
{
var value = GetValue(box);
x.Value = value.X;
y.Value = value.Y;
z.Value = value.Z;
}
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private Vector3 GetValue(InputBox box)
{
var value = Vector3.Zero;
var v = box.Value;
if (v is Vector2 vec2)
value = new Vector3(vec2, 0.0f);
else if (v is Vector3 vec3)
value = vec3;
else if (v is Vector4 vec4)
value = new Vector3(vec4);
else if (v is Color col)
value = col;
else if (v is float f)
value = new Vector3(f);
else if (v is int i)
value = new Vector3(i);
return value;
}
private void OnVector3ValueChanged(ValueBox<float> valueBox)
{
var control = valueBox.Parent;
var box = (InputBox)control.Tag;
var x = ((FloatValueBox)control.Children[0]).Value;
var y = ((FloatValueBox)control.Children[1]).Value;
var z = ((FloatValueBox)control.Children[2]).Value;
box.Value = new Vector3(x, y, z);
}
}
class Vector4DefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.Type == typeof(Vector4);
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var control = new ContainerControl(bounds.X, bounds.Y, 22 * 4 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnVector4ValueChanged;
var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnVector4ValueChanged;
var floatZ = new FloatValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatZ.BoxValueChanged += OnVector4ValueChanged;
var floatW = new FloatValueBox(value.W, 66, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatW.BoxValueChanged += OnVector4ValueChanged;
return control;
}
public bool IsValid(InputBox box, Control control)
{
return control is ContainerControl vec4
&& vec4.ChildrenCount == 4
&& vec4.Children[0] is FloatValueBox
&& vec4.Children[1] is FloatValueBox
&& vec4.Children[2] is FloatValueBox
&& vec4.Children[3] is FloatValueBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is ContainerControl vec4
&& vec4.ChildrenCount == 4
&& vec4.Children[0] is FloatValueBox x
&& vec4.Children[1] is FloatValueBox y
&& vec4.Children[2] is FloatValueBox z
&& vec4.Children[3] is FloatValueBox w)
{
var value = GetValue(box);
x.Value = value.X;
y.Value = value.Y;
z.Value = value.Z;
w.Value = value.W;
}
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private Vector4 GetValue(InputBox box)
{
var value = Vector4.Zero;
var v = box.Value;
if (v is Vector2 vec2)
value = new Vector4(vec2, 0.0f, 0.0f);
else if (v is Vector3 vec3)
value = new Vector4(vec3, 0.0f);
else if (v is Vector4 vec4)
value = vec4;
else if (v is Color col)
value = col;
else if (v is float f)
value = new Vector4(f);
else if (v is int i)
value = new Vector4(i);
return value;
}
private void OnVector4ValueChanged(ValueBox<float> valueBox)
{
var control = valueBox.Parent;
var box = (InputBox)control.Tag;
var x = ((FloatValueBox)control.Children[0]).Value;
var y = ((FloatValueBox)control.Children[1]).Value;
var z = ((FloatValueBox)control.Children[2]).Value;
var w = ((FloatValueBox)control.Children[3]).Value;
box.Value = new Vector4(x, y, z, w);
}
}
class QuaternionDefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.Type == typeof(Quaternion);
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box).EulerAngles;
var control = new ContainerControl(bounds.X, bounds.Y, 22 * 3 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnQuaternionValueChanged;
var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnQuaternionValueChanged;
var floatZ = new FloatValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatZ.BoxValueChanged += OnQuaternionValueChanged;
return control;
}
public bool IsValid(InputBox box, Control control)
{
return control is ContainerControl vec3
&& vec3.ChildrenCount == 3
&& vec3.Children[0] is FloatValueBox
&& vec3.Children[1] is FloatValueBox
&& vec3.Children[2] is FloatValueBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is ContainerControl quat
&& quat.ChildrenCount == 3
&& quat.Children[0] is FloatValueBox x
&& quat.Children[1] is FloatValueBox y
&& quat.Children[2] is FloatValueBox z)
{
var value = GetValue(box).EulerAngles;
x.Value = value.X;
y.Value = value.Y;
z.Value = value.Z;
}
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private Quaternion GetValue(InputBox box)
{
var value = Quaternion.Identity;
var v = box.Value;
if (v is Quaternion quat)
value = quat;
else if (v is Transform transform)
value = transform.Orientation;
return value;
}
private void OnQuaternionValueChanged(ValueBox<float> valueBox)
{
var control = valueBox.Parent;
var box = (InputBox)control.Tag;
var x = ((FloatValueBox)control.Children[0]).Value;
var y = ((FloatValueBox)control.Children[1]).Value;
var z = ((FloatValueBox)control.Children[2]).Value;
box.Value = Quaternion.Euler(x, y, z);
}
}
class ColorDefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.Type == typeof(Color);
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var control = new ColorValueBox(value, bounds.X, bounds.Y)
{
Height = bounds.Height,
Parent = box.Parent,
Tag = box,
};
control.ColorValueChanged += OnColorValueChanged;
return control;
}
public bool IsValid(InputBox box, Control control)
{
return control is ColorValueBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is ColorValueBox colorValueBox)
{
colorValueBox.Value = GetValue(box);
}
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private Vector4 GetValue(InputBox box)
{
var value = Color.Black;
var v = box.Value;
if (v is Vector2 vec2)
value = new Color(vec2.X, vec2.Y, 0.0f, 1.0f);
else if (v is Vector3 vec3)
value = new Color(vec3.X, vec3.Y, vec3.Z, 1.0f);
else if (v is Vector4 vec4)
value = new Color(vec4.X, vec4.Y, vec4.Z, vec4.W);
else if (v is Color col)
value = col;
else if (v is float f)
value = new Color(f);
else if (v is int i)
value = new Color(i);
return value;
}
private void OnColorValueChanged(ColorValueBox control)
{
var box = (InputBox)control.Tag;
box.Value = control.Value;
}
}
class EnumDefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
return type.IsEnum;
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var control = new EnumComboBox(box.CurrentType.Type)
{
Location = new Vector2(bounds.X, bounds.Y),
Size = new Vector2(60.0f, bounds.Height),
EnumTypeValue = value ?? box.CurrentType.CreateInstance(),
Parent = box.Parent,
Tag = box,
};
control.EnumValueChanged += OnEnumValueChanged;
return control;
}
private void OnEnumValueChanged(EnumComboBox control)
{
var box = (InputBox)control.Tag;
box.Value = control.EnumTypeValue;
}
public bool IsValid(InputBox box, Control control)
{
return control is EnumComboBox;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is EnumComboBox enumComboBox)
{
enumComboBox.EnumTypeValue = GetValue(box);
}
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
}
private object GetValue(InputBox box)
{
var value = box.CurrentType.CreateInstance();
var v = box.Value;
if (v != null && v.GetType().IsEnum)
value = v;
return value;
}
}
class TypeDefaultValueEditor : IDefaultValueEditor
{
public bool CanUse(InputBox box, ref ScriptType type)
{
if (type.Type == typeof(string) && box.Attributes != null && box.Attributes.Any(x => x.GetType() == typeof(TypeReferenceAttribute)))
return true;
return type.Type == typeof(Type) || type.Type == typeof(ScriptType);
}
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var control = new TypePickerControl
{
Location = new Vector2(bounds.X, bounds.Y),
Size = new Vector2(60.0f, bounds.Height),
ValueTypeName = value,
Parent = box.Parent,
Tag = box,
};
if (box.CurrentType.Type == typeof(Type))
control.CheckValid = type => type.Type != null;
control.TypePickerValueChanged += OnTypeValueChanged;
return control;
}
private void OnTypeValueChanged(TypePickerControl control)
{
var box = (InputBox)control.Tag;
var v = box.Value;
if (v is string)
box.Value = control.ValueTypeName;
else if (v is Type)
box.Value = TypeUtils.GetType(control.Value);
else
box.Value = control.Value;
}
public bool IsValid(InputBox box, Control control)
{
return control is TypePickerControl;
}
public void UpdateDefaultValue(InputBox box, Control control)
{
if (control is TypePickerControl typePickerControl)
{
typePickerControl.ValueTypeName = GetValue(box);
}
}
public void UpdateAttributes(InputBox box, object[] attributes, Control control)
{
var typeReference = (TypeReferenceAttribute)attributes.FirstOrDefault(x => x.GetType() == typeof(TypeReferenceAttribute));
var type = typeReference != null ? TypeUtils.GetType(typeReference.TypeName) : ScriptType.Null;
((TypePickerControl)control).Type = type ? type : ScriptType.Object;
}
private string GetValue(InputBox box)
{
var v = box.Value;
if (v is Type asType)
return asType.FullName;
if (v is ScriptType asScriptType)
return asScriptType.TypeName;
if (v is string asString)
return asString;
return string.Empty;
}
}
/// <summary>
/// Visject Surface input box element.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.Elements.Box" />
[HideInEditor]
public class InputBox : Box
{
private Control _defaultValueEditor;
private IDefaultValueEditor _editor;
private int _valueIndex;
private Func<InputBox, object> _customValueGetter;
private Action<InputBox, object> _customValueSetter;
private bool _isWatchingForValueChange;
/// <summary>
/// The handlers for the input box default value editing. Used to display the default to the UI.
/// </summary>
public static readonly List<IDefaultValueEditor> DefaultValueEditors = new List<IDefaultValueEditor>()
{
new BooleanDefaultValueEditor(),
new IntegerDefaultValueEditor(),
new UnsignedIntegerDefaultValueEditor(),
new FloatingDefaultValueEditor(),
new StringDefaultValueEditor(),
new Vector2DefaultValueEditor(),
new Vector3DefaultValueEditor(),
new Vector4DefaultValueEditor(),
new QuaternionDefaultValueEditor(),
new ColorDefaultValueEditor(),
new EnumDefaultValueEditor(),
new TypeDefaultValueEditor(),
};
/// <summary>
/// Gets the control that is used to edit default value (optional).
/// </summary>
public Control DefaultValueEditor => _defaultValueEditor;
/// <summary>
/// Gets or sets the value.
/// </summary>
public object Value
{
get => _customValueGetter != null ? _customValueGetter(this) : ParentNode.Values[_valueIndex];
set
{
if (_customValueSetter != null)
_customValueSetter(this, value);
else
ParentNode.SetValue(_valueIndex, value);
}
}
/// <summary>
/// Returns true if node has custom value access.
/// </summary>
public bool HasCustomValueAccess => _customValueGetter != null;
/// <summary>
/// Returns true if node has value.
/// </summary>
public bool HasValue => _valueIndex != -1 || _customValueGetter != null;
/// <inheritdoc />
public InputBox(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(parentNode, archetype, archetype.Position)
{
// Check if use inlined default value editor
_valueIndex = Archetype.ValueIndex;
if (_valueIndex != -1)
{
_isWatchingForValueChange = true;
ParentNode.ValuesChanged += UpdateDefaultValue;
}
}
/// <summary>
/// Updates the default value editor UI value.
/// </summary>
public void UpdateDefaultValue()
{
if (_defaultValueEditor != null && _currentType.Type != null)
{
_editor.UpdateDefaultValue(this, _defaultValueEditor);
}
}
/// <summary>
/// Sets the custom accessor callbacks for the box value.
/// </summary>
/// <param name="getter">The function to call to get value for the box.</param>
/// <param name="setter">The function to call to set value for the box.</param>
public void UseCustomValueAccess(Func<InputBox, object> getter, Action<InputBox, object> setter)
{
_customValueGetter = getter;
_customValueSetter = setter;
if (Connections.Count == 0)
CreateDefaultEditor();
if (!_isWatchingForValueChange)
{
_isWatchingForValueChange = true;
ParentNode.ValuesChanged += UpdateDefaultValue;
}
}
/// <inheritdoc />
public override bool IsOutput => false;
/// <inheritdoc />
protected override void OnLocationChanged()
{
base.OnLocationChanged();
if (_defaultValueEditor != null)
{
_defaultValueEditor.Location = new Vector2(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y);
}
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
// Box
DrawBox();
// Draw text
var style = Style.Current;
var rect = new Rectangle(Width + 4, 0, 1410, Height);
Render2D.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center);
}
/// <inheritdoc />
protected override void OnCurrentTypeChanged()
{
base.OnCurrentTypeChanged();
if (_defaultValueEditor != null && !(_editor.IsValid(this, _defaultValueEditor) && _editor.CanUse(this, ref _currentType)))
{
_defaultValueEditor.Dispose();
_defaultValueEditor = null;
_editor = null;
}
if (Connections.Count == 0)
{
CreateDefaultEditor();
}
}
/// <inheritdoc />
public override void OnConnectionsChanged()
{
bool showEditor = Connections.Count == 0 && HasValue;
if (showEditor)
{
CreateDefaultEditor();
}
if (_defaultValueEditor != null)
{
_defaultValueEditor.Enabled = showEditor;
_defaultValueEditor.Visible = showEditor;
}
}
/// <inheritdoc />
protected override void OnAttributesChanged()
{
OnCurrentTypeChanged();
_editor?.UpdateAttributes(this, _attributes ?? Utils.GetEmptyArray<object>(), _defaultValueEditor);
}
/// <inheritdoc />
public override void OnSurfaceCanEditChanged(bool canEdit)
{
base.OnSurfaceCanEditChanged(canEdit);
if (_defaultValueEditor != null)
{
_defaultValueEditor.Enabled = canEdit;
}
}
/// <inheritdoc />
public override bool OnMouseUp(Vector2 location, MouseButton button)
{
if (button == MouseButton.Right && HasValue)
{
var menu = new FlaxEditor.GUI.ContextMenu.ContextMenu();
menu.AddButton("Copy value", OnCopyValue);
var paste = menu.AddButton("Paste value", OnPasteValue);
try
{
GetClipboardValue(out _, false);
}
catch
{
paste.Enabled = false;
}
menu.Show(this, location);
return true;
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
public override void OnDestroy()
{
if (_defaultValueEditor != null)
{
_defaultValueEditor.Dispose();
_defaultValueEditor = null;
_editor = null;
}
base.OnDestroy();
}
private bool GetClipboardValue(out object result, bool deserialize)
{
result = null;
var text = Clipboard.Text;
if (string.IsNullOrEmpty(text))
return false;
object obj;
var type = CurrentType;
if (ScriptType.FlaxObject.IsAssignableFrom(type))
{
// Object reference
if (text.Length != 32)
return false;
JsonSerializer.ParseID(text, out var id);
obj = FlaxEngine.Object.Find<FlaxEngine.Object>(ref id);
}
else
{
// Default
obj = JsonConvert.DeserializeObject(text, TypeUtils.GetType(type), JsonSerializer.Settings);
}
if (obj == null || type.IsInstanceOfType(obj))
{
result = obj;
return true;
}
return false;
}
private void OnCopyValue()
{
var value = Value;
try
{
string text;
if (value == null)
{
// Missing value
var type = CurrentType;
if (type.Type == typeof(bool))
text = "false";
else if (type.Type == typeof(byte) || type.Type == typeof(sbyte) || type.Type == typeof(char) || type.Type == typeof(short) || type.Type == typeof(ushort) || type.Type == typeof(int) || type.Type == typeof(uint) || type.Type == typeof(long) || type.Type == typeof(ulong))
text = "0";
else if (type.Type == typeof(float) || type.Type == typeof(double))
text = "0.0";
else if (type.Type == typeof(Vector2) || type.Type == typeof(Vector3) || type.Type == typeof(Vector4) || type.Type == typeof(Color))
text = JsonSerializer.Serialize(TypeUtils.GetDefaultValue(type));
else if (type.Type == typeof(string))
text = "";
else
text = "null";
}
else if (value is FlaxEngine.Object asObject)
{
// Object reference
text = JsonSerializer.GetStringID(asObject);
}
else
{
// Default
text = JsonSerializer.Serialize(value);
}
Clipboard.Text = text;
}
catch (Exception ex)
{
Editor.LogWarning(ex);
Editor.LogError("Cannot copy property. See log for more info.");
}
}
private void OnPasteValue()
{
try
{
if (GetClipboardValue(out var value, true))
Value = value;
}
catch (Exception ex)
{
Editor.LogWarning(ex);
Editor.LogError("Cannot paste property value. See log for more info.");
}
}
/// <summary>
/// Creates the default value editor control.
/// </summary>
private void CreateDefaultEditor()
{
if (_defaultValueEditor != null || !HasValue)
return;
for (int i = 0; i < DefaultValueEditors.Count; i++)
{
if (DefaultValueEditors[i].CanUse(this, ref _currentType))
{
var bounds = new Rectangle(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y, 90, Height);
_editor = DefaultValueEditors[i];
try
{
_defaultValueEditor = _editor.Create(this, ref bounds);
if (_attributes != null)
_editor.UpdateAttributes(this, _attributes, _defaultValueEditor);
}
catch (Exception ex)
{
Editor.LogWarning(ex);
_editor = null;
}
break;
}
}
}
}
}