diff --git a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs
new file mode 100644
index 000000000..65a6dc1a1
--- /dev/null
+++ b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs
@@ -0,0 +1,84 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using System;
+using FlaxEditor.CustomEditors.Elements;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using Object = FlaxEngine.Object;
+
+namespace FlaxEditor.CustomEditors.Editors;
+
+///
+/// The reference picker control used for UIControls using ControlReference.
+///
+internal class UIControlRefPickerControl : FlaxObjectRefPickerControl
+{
+ ///
+ /// Type of the control to pick.
+ ///
+ public Type ControlType;
+
+ ///
+ protected override bool IsValid(Object obj)
+ {
+ return obj == null || (obj is UIControl control && control.Control.GetType() == ControlType);
+ }
+}
+
+///
+/// ControlReferenceEditor class.
+///
+[CustomEditor(typeof(ControlReference<>)), DefaultEditor]
+internal class ControlReferenceEditor : CustomEditor
+{
+ private CustomElement _element;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ if (!HasDifferentTypes)
+ {
+ _element = layout.Custom();
+ if (ValuesTypes == null || ValuesTypes[0] == null)
+ {
+ Editor.LogWarning("ControlReference needs to be assigned in code.");
+ return;
+ }
+ Type genType = ValuesTypes[0].GetGenericArguments()[0];
+ if (typeof(Control).IsAssignableFrom(genType))
+ {
+ _element.CustomControl.PresenterContext = Presenter.Owner;
+ _element.CustomControl.ControlType = genType;
+ _element.CustomControl.Type = new ScriptType(typeof(UIControl));
+ }
+ _element.CustomControl.ValueChanged += () =>
+ {
+ Type genericType = ValuesTypes[0].GetGenericArguments()[0];
+ if (typeof(Control).IsAssignableFrom(genericType))
+ {
+ Type t = typeof(ControlReference<>);
+ Type tw = t.MakeGenericType(new Type[] { genericType });
+ var instance = Activator.CreateInstance(tw);
+ ((IControlReference)instance).UIControl = (UIControl)_element.CustomControl.Value;
+ SetValue(instance);
+ }
+ };
+ }
+ }
+
+ ///
+ public override void Refresh()
+ {
+ base.Refresh();
+
+ if (!HasDifferentValues)
+ {
+ if (Values[0] is IControlReference cr)
+ _element.CustomControl.Value = cr.UIControl;
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
index bab748dcc..9a8b80d40 100644
--- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
@@ -48,7 +48,7 @@ namespace FlaxEditor.CustomEditors.Editors
public IPresenterOwner PresenterContext;
///
- /// Gets or sets the allowed objects type (given type and all sub classes). Must be type of any subclass.
+ /// Gets or sets the allowed objects type (given type and all subclasses). Must be type of any subclass.
///
public ScriptType Type
{
@@ -61,7 +61,8 @@ namespace FlaxEditor.CustomEditors.Editors
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);
+ _supportsPickDropDown = new ScriptType(typeof(Actor)).IsAssignableFrom(value) ||
+ new ScriptType(typeof(Script)).IsAssignableFrom(value);
// Deselect value if it's not valid now
if (!IsValid(_value))
@@ -80,7 +81,7 @@ namespace FlaxEditor.CustomEditors.Editors
if (_value == value)
return;
if (!IsValid(value))
- throw new ArgumentException("Invalid object type.");
+ value = null;
// 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))
@@ -91,27 +92,17 @@ namespace FlaxEditor.CustomEditors.Editors
// 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)
- {
TooltipText = Utilities.Utils.GetTooltip(sceneObject);
- }
else
- {
TooltipText = string.Empty;
- }
OnValueChanged();
}
@@ -150,7 +141,12 @@ namespace FlaxEditor.CustomEditors.Editors
_type = ScriptType.Object;
}
- private bool IsValid(Object obj)
+ ///
+ /// Object validation check routine.
+ ///
+ /// Input object to check.
+ /// True if it can be assigned, otherwise false.
+ protected virtual bool IsValid(Object obj)
{
var type = TypeUtils.GetObjectType(obj);
return obj == null || _type.IsAssignableFrom(type) && (CheckValid == null || CheckValid(obj, type));
@@ -168,6 +164,15 @@ namespace FlaxEditor.CustomEditors.Editors
Focus();
}, PresenterContext);
}
+ else if (new ScriptType(typeof(Control)).IsAssignableFrom(_type))
+ {
+ ActorSearchPopup.Show(this, new Float2(0, Height), IsValid, actor =>
+ {
+ Value = actor as UIControl;
+ RootWindow.Focus();
+ Focus();
+ }, PresenterContext);
+ }
else
{
ScriptSearchPopup.Show(this, new Float2(0, Height), IsValid, script =>
diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs
index 04a20fdb4..f225c29d8 100644
--- a/Source/Engine/Serialization/JsonConverters.cs
+++ b/Source/Engine/Serialization/JsonConverters.cs
@@ -442,7 +442,7 @@ namespace FlaxEngine.Json
///
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
- var result = Activator.CreateInstance(objectType);
+ var result = existingValue ?? Activator.CreateInstance(objectType);
if (reader.TokenType == JsonToken.String)
{
JsonSerializer.ParseID((string)reader.Value, out var id);
@@ -483,6 +483,34 @@ namespace FlaxEngine.Json
}
}
+ internal class ControlReferenceConverter : JsonConverter
+ {
+ ///
+ public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ var id = (value as IControlReference)?.UIControl?.ID ?? Guid.Empty;
+ writer.WriteValue(JsonSerializer.GetStringID(&id));
+ }
+
+ ///
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ var result = existingValue ?? Activator.CreateInstance(objectType);
+ if (reader.TokenType == JsonToken.String && result is IControlReference controlReference)
+ {
+ JsonSerializer.ParseID((string)reader.Value, out var id);
+ controlReference.UIControl = Object.Find(ref id);
+ }
+ return result;
+ }
+
+ ///
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType.Name.StartsWith("ControlReference", StringComparison.Ordinal);
+ }
+ }
+
/*
///
/// Serialize Guid values using `N` format
diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs
index f13be1e8f..59b13c549 100644
--- a/Source/Engine/Serialization/JsonSerializer.cs
+++ b/Source/Engine/Serialization/JsonSerializer.cs
@@ -194,6 +194,7 @@ namespace FlaxEngine.Json
settings.Converters.Add(new SoftObjectReferenceConverter());
settings.Converters.Add(new SoftTypeReferenceConverter());
settings.Converters.Add(new BehaviorKnowledgeSelectorAnyConverter());
+ settings.Converters.Add(new ControlReferenceConverter());
settings.Converters.Add(new MarginConverter());
settings.Converters.Add(new VersionConverter());
settings.Converters.Add(new LocalizedStringConverter());
diff --git a/Source/Engine/UI/ControlReference.cs b/Source/Engine/UI/ControlReference.cs
new file mode 100644
index 000000000..0bcd54374
--- /dev/null
+++ b/Source/Engine/UI/ControlReference.cs
@@ -0,0 +1,183 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using FlaxEngine.GUI;
+
+namespace FlaxEngine
+{
+ ///
+ /// Interface for control references access.
+ ///
+ public interface IControlReference
+ {
+ ///
+ /// Gets or sets the reference to actor.
+ ///
+ public UIControl UIControl { get; set; }
+
+ ///
+ /// Gets the type of the control the interface uses.
+ ///
+ public Type ControlType { get; }
+ }
+
+ ///
+ /// UI Control reference utility. References UI Control actor with a typed control type.
+ ///
+ /// Type of the UI control object.
+#if FLAX_EDITOR
+ [TypeConverter(typeof(TypeConverters.ControlReferenceConverter))]
+#endif
+ public struct ControlReference : IControlReference, IComparable, IComparable>, IEquatable> where T : Control
+ {
+ private UIControl _uiControl;
+
+ ///
+ /// Gets the typed UI control object owned by the referenced actor.
+ ///
+ [HideInEditor]
+ public T Control
+ {
+ get
+ {
+ if (_uiControl != null && _uiControl.Control is T t)
+ return t;
+ Debug.Write(LogType.Warning, "Trying to get Control from ControlReference but UIControl is null, or UIControl.Control is null, or UIControl.Control is not the correct type.");
+ return null;
+ }
+ }
+
+ ///
+ public UIControl UIControl
+ {
+ get => _uiControl;
+ set
+ {
+ if (value == null)
+ {
+ _uiControl = null;
+ }
+ else if (value.Control is T t)
+ {
+ _uiControl = value;
+ }
+ else
+ {
+ Debug.Write(LogType.Warning, "Trying to set ControlReference but UIControl.Control is null or UIControl.Control is not the correct type.");
+ }
+ }
+ }
+
+ ///
+ public Type ControlType => typeof(T);
+
+ ///
+ public override string ToString()
+ {
+ return _uiControl?.ToString() ?? "null";
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return _uiControl?.GetHashCode() ?? 0;
+ }
+
+ ///
+ public int CompareTo(object obj)
+ {
+ if (obj is IControlReference other)
+ return CompareTo(other);
+ return 0;
+ }
+
+ ///
+ public int CompareTo(ControlReference other)
+ {
+ return _uiControl == other._uiControl ? 0 : 1;
+ }
+
+ ///
+ public bool Equals(ControlReference other)
+ {
+ return _uiControl == other._uiControl;
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ return obj is ControlReference other && _uiControl == other._uiControl;
+ }
+
+ ///
+ /// The implicit operator for the Control.
+ ///
+ /// The control reference.
+ /// The control object.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator T(ControlReference reference) => reference.Control;
+
+ ///
+ /// The implicit operator for the UIControl.
+ ///
+ /// The control reference.
+ /// The control actor.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator UIControl(ControlReference reference) => reference.UIControl;
+
+ ///
+ /// Checks if the object exists (reference is not null and the unmanaged object pointer is valid).
+ ///
+ /// The object to check.
+ /// True if object is valid, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator bool(ControlReference obj) => obj._uiControl;
+
+ ///
+ /// Checks whether the two objects are equal.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(ControlReference left, ControlReference right) => left._uiControl == right._uiControl;
+
+ ///
+ /// Checks whether the two objects are not equal.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(ControlReference left, ControlReference right) => left._uiControl != right._uiControl;
+ }
+}
+
+#if FLAX_EDITOR
+namespace FlaxEngine.TypeConverters
+{
+ internal class ControlReferenceConverter : TypeConverter
+ {
+ ///
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (value is string valueStr)
+ {
+ var result = Activator.CreateInstance(destinationType);
+ if (result is IControlReference control)
+ {
+ Json.JsonSerializer.ParseID(valueStr, out var id);
+ control.UIControl = Object.Find(ref id);
+ }
+ return result;
+ }
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType.Name.StartsWith("ControlReference", StringComparison.Ordinal))
+ return true;
+ return base.CanConvertTo(context, destinationType);
+ }
+ }
+}
+#endif