From e6450bfc7a46f205672e0132929a8756d98e9ad3 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 30 Dec 2024 16:36:10 -0600 Subject: [PATCH 1/9] Add `ControlReference` for easier UI referencing. --- .../Editors/ControlReferenceEditor.cs | 472 ++++++++++++++++++ Source/Engine/UI/ControlReference.cs | 91 ++++ 2 files changed, 563 insertions(+) create mode 100644 Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs create mode 100644 Source/Engine/UI/ControlReference.cs diff --git a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs new file mode 100644 index 000000000..06bcb4b41 --- /dev/null +++ b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs @@ -0,0 +1,472 @@ +using System; +using System.Collections.Generic; +using FlaxEditor.CustomEditors; +using FlaxEditor.CustomEditors.Elements; +using FlaxEditor.GUI; +using FlaxEditor.GUI.Drag; +using FlaxEditor.SceneGraph.GUI; +using FlaxEngine; +using FlaxEngine.GUI; +using FlaxEngine.Utilities; +using Object = FlaxEngine.Object; + +namespace FlaxEditor.CustomEditors.Editors; + +/// +/// The reference picker control used for UIControls using ControlReference. +/// +public class UIControlObjectRefPickerControl : Control +{ + private Type _controlType; + public UIControl _value; + private ActorTreeNode _linkedTreeNode; + private string _valueName; + private bool _supportsPickDropDown; + + private bool _isMouseDown; + private Float2 _mouseDownPos; + private Float2 _mousePos; + + private bool _hasValidDragOver; + private DragActors _dragActors; + private DragHandlers _dragHandlers; + + /// + /// Occurs when value gets changed. + /// + public event Action ValueChanged; + + /// + /// The type of the Control + /// + /// Throws exception if value is not a subclass of control. + public Type ControlType + { + get => _controlType; + set + { + if (_controlType == value) + return; + if (!value.IsSubclassOf(typeof(Control))) + throw new ArgumentException(string.Format("Invalid type for UIControlObjectRefPicker. Input type: {0}", value)); + _controlType = value; + _supportsPickDropDown = typeof(Control).IsAssignableFrom(value); + + // Deselect value if it's not valid now + if (!IsValid(_value)) + Value = null; + } + } + + /// + /// The UIControl value. + /// + public UIControl Value + { + get => _value; + set + { + if (_value == value) + return; + if (!IsValid(value)) + 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)) + value = null; + + _value = value; + + // Get name to display + if (_value != null) + _valueName = _value.Name; + else + _valueName = string.Empty; + + // Update tooltip + if (_value is SceneObject sceneObject) + TooltipText = FlaxEditor.Utilities.Utils.GetTooltip(sceneObject); + else + TooltipText = string.Empty; + + OnValueChanged(); + } + } + + /// + /// Initializes a new instance of the class. + /// + public UIControlObjectRefPickerControl() + : base(0, 0, 50, 16) + { + + } + + private void OnValueChanged() + { + ValueChanged?.Invoke(); + } + + private void ShowDropDownMenu() + { + Focus(); + if (typeof(Control).IsAssignableFrom(_controlType)) + { + ActorSearchPopup.Show(this, new Float2(0, Height), IsValid, actor => + { + Value = actor as UIControl; + RootWindow.Focus(); + Focus(); + }); + } + } + + private bool IsValid(Actor actor) + { + return actor is UIControl a && a.Control.GetType() == _controlType; + } + + /// + 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 || IsNavFocused) ? style.BorderHighlighted : style.BorderNormal); + + // Check if has item selected + if (isSelected) + { + // Draw name + Render2D.PushClip(nameRect); + Render2D.DrawText(style.FontMedium, $"{_valueName} ({Utilities.Utils.GetPropertyNameUI(ControlType.GetTypeDisplayName())})", 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.PushClip(nameRect); + Render2D.DrawText(style.FontMedium, ControlType != null ? $"None ({Utilities.Utils.GetPropertyNameUI(ControlType.GetTypeDisplayName())})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center); + Render2D.PopClip(); + } + + // 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) + { + var bounds = new Rectangle(Float2.Zero, Size); + Render2D.FillRectangle(bounds, style.Selection); + Render2D.DrawRectangle(bounds, style.SelectionBorder); + } + } + + /// + public override void OnMouseEnter(Float2 location) + { + _mousePos = location; + _mouseDownPos = Float2.Minimum; + + base.OnMouseEnter(location); + } + + /// + public override void OnMouseLeave() + { + _mousePos = Float2.Minimum; + + // Check if start drag drop + if (_isMouseDown) + { + // Do the drag + DoDrag(); + + // Clear flag + _isMouseDown = false; + } + + base.OnMouseLeave(); + } + + /// + public override void OnMouseMove(Float2 location) + { + _mousePos = location; + + // Check if start drag drop + if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f) + { + // Do the drag + DoDrag(); + + // Clear flag + _isMouseDown = false; + } + + base.OnMouseMove(location); + } + + /// + public override bool OnMouseUp(Float2 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 true; + } + + if (button == MouseButton.Left) + { + _isMouseDown = false; + + // Highlight actor or script reference + if (!_hasValidDragOver && !IsDragOver) + { + Actor actor = _value; + if (actor != null) + { + if (_linkedTreeNode != null && _linkedTreeNode.Actor == actor) + { + _linkedTreeNode.ExpandAllParents(); + _linkedTreeNode.StartHighlight(); + } + else + { + _linkedTreeNode = FlaxEditor.Editor.Instance.Scene.GetActorNode(actor).TreeNode; + _linkedTreeNode.ExpandAllParents(); + Editor.Instance.Windows.SceneWin.SceneTreePanel.ScrollViewTo(_linkedTreeNode, true); + _linkedTreeNode.StartHighlight(); + } + return true; + } + } + + // Reset valid drag over if still true at this point + if (_hasValidDragOver) + _hasValidDragOver = false; + } + + return base.OnMouseUp(location, button); + } + + /// + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (button == MouseButton.Left) + { + // Set flag + _isMouseDown = true; + _mouseDownPos = location; + } + + return base.OnMouseDown(location, button); + } + + /// + public override bool OnMouseDoubleClick(Float2 location, MouseButton button) + { + Focus(); + + // Check if has object selected + if (_value != null) + { + if (_linkedTreeNode != null) + { + _linkedTreeNode.StopHighlight(); + _linkedTreeNode = null; + } + + // Select object + if (_value is Actor actor) + Editor.Instance.SceneEditing.Select(actor); + } + + return base.OnMouseDoubleClick(location, button); + } + + /// + public override void OnSubmit() + { + base.OnSubmit(); + + // Picker dropdown menu + if (_supportsPickDropDown) + ShowDropDownMenu(); + } + + private void DoDrag() + { + // Do the drag drop operation if has selected element + if (_value != null) + { + if (_value is Actor actor) + DoDragDrop(DragActors.GetDragData(actor)); + } + } + + private DragDropEffect DragEffect => _hasValidDragOver ? DragDropEffect.Move : DragDropEffect.None; + + /// + public override DragDropEffect OnDragEnter(ref Float2 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 (_dragHandlers == null) + { + _dragHandlers = new DragHandlers + { + _dragActors, + }; + } + + _hasValidDragOver = _dragHandlers.OnDragEnter(data) != DragDropEffect.None; + + return DragEffect; + } + + /// + public override DragDropEffect OnDragMove(ref Float2 location, DragData data) + { + base.OnDragMove(ref location, data); + + return DragEffect; + } + + /// + public override void OnDragLeave() + { + _hasValidDragOver = false; + _dragHandlers.OnDragLeave(); + + base.OnDragLeave(); + } + + /// + public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) + { + var result = DragEffect; + + base.OnDragDrop(ref location, data); + + if (_dragActors.HasValidDrag) + { + Value = _dragActors.Objects[0].Actor as UIControl; + } + + return result; + } + + /// + public override void OnDestroy() + { + _value = null; + _controlType = null; + _valueName = null; + _linkedTreeNode = null; + + base.OnDestroy(); + } +} + +/// +/// ControlReferenceEditor class. +/// +[CustomEditor(typeof(ControlReference<>)), DefaultEditor] +public 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.ControlType = genType; + } + _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); + (instance as IControlReference).Set(_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/Engine/UI/ControlReference.cs b/Source/Engine/UI/ControlReference.cs new file mode 100644 index 000000000..84d79f3c0 --- /dev/null +++ b/Source/Engine/UI/ControlReference.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEngine; + +/// +/// The control reference interface. +/// +public interface IControlReference +{ + /// + /// The UIControl. + /// + public UIControl UIControl { get; } + + /// + /// The Control type + /// + /// The Control Type + public Type GetControlType(); + + /// + /// A safe set of the UI Control. Will warn if Control is of the wrong type. + /// + /// + public void Set(UIControl uiControl); +} + +/// +/// ControlReference class. +/// +[Serializable] +public struct ControlReference : IControlReference where T : Control +{ + [Serialize, ShowInEditor] + private UIControl _uiControl; + + /// + public Type GetControlType() + { + return typeof(T); + } + + /// + /// The Control attached to the UI Control. + /// + public T Control + { + get + { + if (_uiControl.Control is T t) + return t; + else + { + Debug.LogWarning("Trying to get Control from ControlReference but UIControl.Control is null or UIControl.Control is not the correct type."); + return null; + } + } + } + + /// + public UIControl UIControl => _uiControl; + + /// + public void Set(UIControl uiControl) + { + if (uiControl == null) + { + Clear(); + return; + } + + if (uiControl.Control is T castedControl) + _uiControl = uiControl; + else + Debug.LogWarning("Trying to set ControlReference but UIControl.Control is null or UIControl.Control is not the correct type."); + } + + /// + /// Clears the UIControl reference. + /// + public void Clear() + { + _uiControl = null; + } + + public static implicit operator T(ControlReference reference) => reference.Control; + public static implicit operator UIControl(ControlReference reference) => reference.UIControl; +} From 9219b34dc359b805f1bdcb1661cc3cdf62252621 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 30 Dec 2024 16:43:06 -0600 Subject: [PATCH 2/9] Shorten picker control name. --- .../CustomEditors/Editors/ControlReferenceEditor.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs index 06bcb4b41..c93a2aff3 100644 --- a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs @@ -15,7 +15,7 @@ namespace FlaxEditor.CustomEditors.Editors; /// /// The reference picker control used for UIControls using ControlReference. /// -public class UIControlObjectRefPickerControl : Control +public class UIControlRefPickerControl : Control { private Type _controlType; public UIControl _value; @@ -94,9 +94,9 @@ public class UIControlObjectRefPickerControl : Control } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public UIControlObjectRefPickerControl() + public UIControlRefPickerControl() : base(0, 0, 50, 16) { @@ -420,7 +420,7 @@ public class UIControlObjectRefPickerControl : Control [CustomEditor(typeof(ControlReference<>)), DefaultEditor] public class ControlReferenceEditor : CustomEditor { - private CustomElement _element; + private CustomElement _element; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -430,7 +430,7 @@ public class ControlReferenceEditor : CustomEditor { if (!HasDifferentTypes) { - _element = layout.Custom(); + _element = layout.Custom(); if (ValuesTypes == null || ValuesTypes[0] == null) { Editor.LogWarning("ControlReference needs to be assigned in code."); From 711fc80d8cf1f9bb4ffab59bd762e512c2085837 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 30 Dec 2024 16:46:42 -0600 Subject: [PATCH 3/9] Fix comment. --- Source/Engine/UI/ControlReference.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/UI/ControlReference.cs b/Source/Engine/UI/ControlReference.cs index 84d79f3c0..b05f73682 100644 --- a/Source/Engine/UI/ControlReference.cs +++ b/Source/Engine/UI/ControlReference.cs @@ -24,7 +24,7 @@ public interface IControlReference /// /// A safe set of the UI Control. Will warn if Control is of the wrong type. /// - /// + /// The UIControl to set. public void Set(UIControl uiControl); } From c79cd82fd4070836d19658cab8477620cb52d564 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 30 Dec 2024 18:59:45 -0600 Subject: [PATCH 4/9] Fix code warnings. --- .../CustomEditors/Editors/ControlReferenceEditor.cs | 2 +- Source/Engine/UI/ControlReference.cs | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs index c93a2aff3..8a0595a0f 100644 --- a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs @@ -18,7 +18,7 @@ namespace FlaxEditor.CustomEditors.Editors; public class UIControlRefPickerControl : Control { private Type _controlType; - public UIControl _value; + private UIControl _value; private ActorTreeNode _linkedTreeNode; private string _valueName; private bool _supportsPickDropDown; diff --git a/Source/Engine/UI/ControlReference.cs b/Source/Engine/UI/ControlReference.cs index b05f73682..a708e48fa 100644 --- a/Source/Engine/UI/ControlReference.cs +++ b/Source/Engine/UI/ControlReference.cs @@ -86,6 +86,17 @@ public struct ControlReference : IControlReference where T : Control _uiControl = null; } + /// + /// The implicit operator for the Control. + /// + /// The ControlReference + /// The Control. public static implicit operator T(ControlReference reference) => reference.Control; + + /// + /// The implicit operator for the UIControl + /// + /// The ControlReference + /// The UIControl. public static implicit operator UIControl(ControlReference reference) => reference.UIControl; } From 04fc118ddfcd803807e2ed898bf12ca92415c643 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 4 Jan 2025 11:45:08 -0600 Subject: [PATCH 5/9] Small fixes. --- .../Editors/ControlReferenceEditor.cs | 21 ++++++++++++------- Source/Engine/UI/ControlReference.cs | 21 +++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs index 8a0595a0f..d7bbf1502 100644 --- a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs @@ -85,7 +85,7 @@ public class UIControlRefPickerControl : Control // Update tooltip if (_value is SceneObject sceneObject) - TooltipText = FlaxEditor.Utilities.Utils.GetTooltip(sceneObject); + TooltipText = Utilities.Utils.GetTooltip(sceneObject); else TooltipText = string.Empty; @@ -93,6 +93,15 @@ public class UIControlRefPickerControl : Control } } + /// + /// Gets or sets the selected object value by identifier. + /// + public Guid ValueID + { + get => _value ? _value.ID : Guid.Empty; + set => Value = Object.Find(ref value); + } + /// /// Initializes a new instance of the class. /// @@ -123,7 +132,7 @@ public class UIControlRefPickerControl : Control private bool IsValid(Actor actor) { - return actor is UIControl a && a.Control.GetType() == _controlType; + return actor == null || actor is UIControl a && a.Control.GetType() == _controlType; } /// @@ -263,7 +272,7 @@ public class UIControlRefPickerControl : Control _isMouseDown = false; // Highlight actor or script reference - if (!_hasValidDragOver && !IsDragOver) + if (!_hasValidDragOver && !IsDragOver && nameRect.Contains(location)) { Actor actor = _value; if (actor != null) @@ -275,7 +284,7 @@ public class UIControlRefPickerControl : Control } else { - _linkedTreeNode = FlaxEditor.Editor.Instance.Scene.GetActorNode(actor).TreeNode; + _linkedTreeNode = Editor.Instance.Scene.GetActorNode(actor).TreeNode; _linkedTreeNode.ExpandAllParents(); Editor.Instance.Windows.SceneWin.SceneTreePanel.ScrollViewTo(_linkedTreeNode, true); _linkedTreeNode.StartHighlight(); @@ -455,7 +464,7 @@ public class ControlReferenceEditor : CustomEditor }; } } - + /// public override void Refresh() { @@ -464,9 +473,7 @@ public class ControlReferenceEditor : CustomEditor if (!HasDifferentValues) { if (Values[0] is IControlReference cr) - { _element.CustomControl.Value = cr.UIControl; - } } } } diff --git a/Source/Engine/UI/ControlReference.cs b/Source/Engine/UI/ControlReference.cs index a708e48fa..efcc42362 100644 --- a/Source/Engine/UI/ControlReference.cs +++ b/Source/Engine/UI/ControlReference.cs @@ -36,25 +36,34 @@ public struct ControlReference : IControlReference where T : Control { [Serialize, ShowInEditor] private UIControl _uiControl; - + + /// + /// Default constructor for ControlReference; + /// + public ControlReference() + { + _uiControl = null; + } + /// public Type GetControlType() { return typeof(T); } - + /// /// The Control attached to the UI Control. /// + [HideInEditor] public T Control { get { - if (_uiControl.Control is T t) + if (_uiControl != null && _uiControl.Control is T t) return t; else { - Debug.LogWarning("Trying to get Control from ControlReference but UIControl.Control is null or UIControl.Control is not the correct type."); + Debug.LogWarning("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; } } @@ -85,14 +94,14 @@ public struct ControlReference : IControlReference where T : Control { _uiControl = null; } - + /// /// The implicit operator for the Control. /// /// The ControlReference /// The Control. public static implicit operator T(ControlReference reference) => reference.Control; - + /// /// The implicit operator for the UIControl /// From 25b8939aff946168151bebb57538077085cc4fa2 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 16 Feb 2025 18:05:25 -0600 Subject: [PATCH 6/9] Update to include presenter context. --- .../Editors/ControlReferenceEditor.cs | 31 +++++++++++++++++-- Source/Engine/UI/ControlReference.cs | 4 +-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs index d7bbf1502..700accabb 100644 --- a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs @@ -4,7 +4,10 @@ using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.GUI; using FlaxEditor.GUI.Drag; +using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.GUI; +using FlaxEditor.Windows; +using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; @@ -31,6 +34,11 @@ public class UIControlRefPickerControl : Control private DragActors _dragActors; private DragHandlers _dragHandlers; + /// + /// The presenter using this control. + /// + public IPresenterOwner PresenterContext; + /// /// Occurs when value gets changed. /// @@ -126,7 +134,7 @@ public class UIControlRefPickerControl : Control Value = actor as UIControl; RootWindow.Focus(); Focus(); - }); + }, PresenterContext); } } @@ -134,6 +142,24 @@ public class UIControlRefPickerControl : Control { return actor == null || actor is UIControl a && a.Control.GetType() == _controlType; } + + private bool ValidateDragActor(ActorNode a) + { + if (!IsValid(a.Actor)) + return false; + + if (PresenterContext is PrefabWindow prefabWindow) + { + if (prefabWindow.Tree == a.TreeNode.ParentTree) + return true; + } + else if (PresenterContext is PropertiesWindow || PresenterContext == null) + { + if (a.ParentScene != null) + return true; + } + return false; + } /// public override void Draw() @@ -365,7 +391,7 @@ public class UIControlRefPickerControl : Control // Ensure to have valid drag helpers (uses lazy init) if (_dragActors == null) - _dragActors = new DragActors(x => IsValid(x.Actor)); + _dragActors = new DragActors(ValidateDragActor); if (_dragHandlers == null) { _dragHandlers = new DragHandlers @@ -448,6 +474,7 @@ public class ControlReferenceEditor : CustomEditor Type genType = ValuesTypes[0].GetGenericArguments()[0]; if (typeof(Control).IsAssignableFrom(genType)) { + _element.CustomControl.PresenterContext = Presenter.Owner; _element.CustomControl.ControlType = genType; } _element.CustomControl.ValueChanged += () => diff --git a/Source/Engine/UI/ControlReference.cs b/Source/Engine/UI/ControlReference.cs index efcc42362..75b5d57a8 100644 --- a/Source/Engine/UI/ControlReference.cs +++ b/Source/Engine/UI/ControlReference.cs @@ -63,7 +63,7 @@ public struct ControlReference : IControlReference where T : Control return t; else { - Debug.LogWarning("Trying to get Control from ControlReference but UIControl is null, or UIControl.Control is null, or UIControl.Control is not the correct type."); + 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; } } @@ -84,7 +84,7 @@ public struct ControlReference : IControlReference where T : Control if (uiControl.Control is T castedControl) _uiControl = uiControl; else - Debug.LogWarning("Trying to set ControlReference but UIControl.Control is null or UIControl.Control is not the correct type."); + Debug.Write(LogType.Warning, "Trying to set ControlReference but UIControl.Control is null or UIControl.Control is not the correct type."); } /// From 35fa63852c533bda6e96050e53009b3ba9b4890d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Mar 2025 16:06:48 +0100 Subject: [PATCH 7/9] Format code and simplify `IControlReference` a little bit #3123 --- .../Editors/ControlReferenceEditor.cs | 45 +++++---- Source/Engine/UI/ControlReference.cs | 91 +++++++------------ 2 files changed, 54 insertions(+), 82 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs index 700accabb..174dc9b20 100644 --- a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs @@ -1,6 +1,6 @@ -using System; -using System.Collections.Generic; -using FlaxEditor.CustomEditors; +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.GUI; using FlaxEditor.GUI.Drag; @@ -25,7 +25,7 @@ public class UIControlRefPickerControl : Control private ActorTreeNode _linkedTreeNode; private string _valueName; private bool _supportsPickDropDown; - + private bool _isMouseDown; private Float2 _mouseDownPos; private Float2 _mousePos; @@ -33,17 +33,17 @@ public class UIControlRefPickerControl : Control private bool _hasValidDragOver; private DragActors _dragActors; private DragHandlers _dragHandlers; - + /// /// The presenter using this control. /// public IPresenterOwner PresenterContext; - + /// /// Occurs when value gets changed. /// public event Action ValueChanged; - + /// /// The type of the Control /// @@ -100,7 +100,7 @@ public class UIControlRefPickerControl : Control OnValueChanged(); } } - + /// /// Gets or sets the selected object value by identifier. /// @@ -109,14 +109,13 @@ public class UIControlRefPickerControl : Control get => _value ? _value.ID : Guid.Empty; set => Value = Object.Find(ref value); } - + /// /// Initializes a new instance of the class. /// public UIControlRefPickerControl() - : base(0, 0, 50, 16) + : base(0, 0, 50, 16) { - } private void OnValueChanged() @@ -142,12 +141,12 @@ public class UIControlRefPickerControl : Control { return actor == null || actor is UIControl a && a.Control.GetType() == _controlType; } - + private bool ValidateDragActor(ActorNode a) { if (!IsValid(a.Actor)) return false; - + if (PresenterContext is PrefabWindow prefabWindow) { if (prefabWindow.Tree == a.TreeNode.ParentTree) @@ -216,7 +215,7 @@ public class UIControlRefPickerControl : Control Render2D.DrawRectangle(bounds, style.SelectionBorder); } } - + /// public override void OnMouseEnter(Float2 location) { @@ -225,8 +224,8 @@ public class UIControlRefPickerControl : Control base.OnMouseEnter(location); } - - /// + + /// public override void OnMouseLeave() { _mousePos = Float2.Minimum; @@ -326,7 +325,7 @@ public class UIControlRefPickerControl : Control return base.OnMouseUp(location, button); } - + /// public override bool OnMouseDown(Float2 location, MouseButton button) { @@ -339,7 +338,7 @@ public class UIControlRefPickerControl : Control return base.OnMouseDown(location, button); } - + /// public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { @@ -361,7 +360,7 @@ public class UIControlRefPickerControl : Control return base.OnMouseDoubleClick(location, button); } - + /// public override void OnSubmit() { @@ -404,7 +403,7 @@ public class UIControlRefPickerControl : Control return DragEffect; } - + /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { @@ -456,10 +455,10 @@ public class UIControlRefPickerControl : Control public class ControlReferenceEditor : CustomEditor { private CustomElement _element; - + /// public override DisplayStyle Style => DisplayStyle.Inline; - + /// public override void Initialize(LayoutElementsContainer layout) { @@ -485,7 +484,7 @@ public class ControlReferenceEditor : CustomEditor Type t = typeof(ControlReference<>); Type tw = t.MakeGenericType(new Type[] { genericType }); var instance = Activator.CreateInstance(tw); - (instance as IControlReference).Set(_element.CustomControl.Value); + ((IControlReference)instance).UIControl = _element.CustomControl.Value; SetValue(instance); } }; diff --git a/Source/Engine/UI/ControlReference.cs b/Source/Engine/UI/ControlReference.cs index 75b5d57a8..dba3a3397 100644 --- a/Source/Engine/UI/ControlReference.cs +++ b/Source/Engine/UI/ControlReference.cs @@ -1,58 +1,36 @@ -using System; -using System.Collections.Generic; -using FlaxEngine; +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System; using FlaxEngine.GUI; namespace FlaxEngine; /// -/// The control reference interface. +/// Interface for control references access. /// public interface IControlReference { /// - /// The UIControl. + /// Gets or sets the reference to actor. /// - public UIControl UIControl { get; } - + public UIControl UIControl { get; set; } + /// - /// The Control type + /// Gets the type of the control the interface uses. /// - /// The Control Type - public Type GetControlType(); - - /// - /// A safe set of the UI Control. Will warn if Control is of the wrong type. - /// - /// The UIControl to set. - public void Set(UIControl uiControl); + public Type ControlType { get; } } /// -/// ControlReference class. +/// UI Control reference utility. References UI Control actor with a typed control type. /// [Serializable] public struct ControlReference : IControlReference where T : Control { - [Serialize, ShowInEditor] private UIControl _uiControl; /// - /// Default constructor for ControlReference; - /// - public ControlReference() - { - _uiControl = null; - } - - /// - public Type GetControlType() - { - return typeof(T); - } - - /// - /// The Control attached to the UI Control. + /// Gets the typed UI control object owned by the referenced actor. /// [HideInEditor] public T Control @@ -61,39 +39,34 @@ public struct ControlReference : IControlReference where T : Control { 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 get Control from ControlReference but UIControl is null, or UIControl.Control is null, or UIControl.Control is not the correct type."); - return null; + Debug.Write(LogType.Warning, "Trying to set ControlReference but UIControl.Control is null or UIControl.Control is not the correct type."); } } } /// - public UIControl UIControl => _uiControl; - - /// - public void Set(UIControl uiControl) - { - if (uiControl == null) - { - Clear(); - return; - } - - if (uiControl.Control is T castedControl) - _uiControl = uiControl; - else - Debug.Write(LogType.Warning, "Trying to set ControlReference but UIControl.Control is null or UIControl.Control is not the correct type."); - } - - /// - /// Clears the UIControl reference. - /// - public void Clear() - { - _uiControl = null; - } + public Type ControlType => typeof(T); /// /// The implicit operator for the Control. From 520bab7463f6c86b76ac15e89dbc73a25a8fd283 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Mar 2025 17:14:21 +0100 Subject: [PATCH 8/9] Refactor serialziation of `ControlReference` into a string with ID of actor for simplicity #3123 --- Source/Engine/Serialization/JsonConverters.cs | 30 ++- Source/Engine/Serialization/JsonSerializer.cs | 1 + Source/Engine/UI/ControlReference.cs | 229 +++++++++++++----- 3 files changed, 194 insertions(+), 66 deletions(-) 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 index dba3a3397..0bcd54374 100644 --- a/Source/Engine/UI/ControlReference.cs +++ b/Source/Engine/UI/ControlReference.cs @@ -1,84 +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 +namespace FlaxEngine { /// - /// Gets or sets the reference to actor. + /// Interface for control references access. /// - public UIControl UIControl { get; set; } + 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; } + } /// - /// Gets the type of the control the interface uses. + /// UI Control reference utility. References UI Control actor with a typed control type. /// - public Type ControlType { get; } + /// 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; + } } -/// -/// UI Control reference utility. References UI Control actor with a typed control type. -/// -[Serializable] -public struct ControlReference : IControlReference where T : Control +#if FLAX_EDITOR +namespace FlaxEngine.TypeConverters { - private UIControl _uiControl; - - /// - /// Gets the typed UI control object owned by the referenced actor. - /// - [HideInEditor] - public T Control + internal class ControlReferenceConverter : TypeConverter { - get + /// + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { - 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; + 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); } } - - /// - 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); - - /// - /// The implicit operator for the Control. - /// - /// The ControlReference - /// The Control. - public static implicit operator T(ControlReference reference) => reference.Control; - - /// - /// The implicit operator for the UIControl - /// - /// The ControlReference - /// The UIControl. - public static implicit operator UIControl(ControlReference reference) => reference.UIControl; } +#endif From 93442ec8fa2023e584a4aa0ebc66ca4b7d0d5cb2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Mar 2025 20:32:06 +0100 Subject: [PATCH 9/9] Refactor `ControlReference` to use `FlaxObjectRefPickerControl` as a base class #3123 --- .../Editors/ControlReferenceEditor.cs | 439 +----------------- .../Editors/FlaxObjectRefEditor.cs | 33 +- 2 files changed, 28 insertions(+), 444 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs index 174dc9b20..65a6dc1a1 100644 --- a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs @@ -2,15 +2,9 @@ using System; using FlaxEditor.CustomEditors.Elements; -using FlaxEditor.GUI; -using FlaxEditor.GUI.Drag; -using FlaxEditor.SceneGraph; -using FlaxEditor.SceneGraph.GUI; -using FlaxEditor.Windows; -using FlaxEditor.Windows.Assets; +using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; -using FlaxEngine.Utilities; using Object = FlaxEngine.Object; namespace FlaxEditor.CustomEditors.Editors; @@ -18,433 +12,17 @@ namespace FlaxEditor.CustomEditors.Editors; /// /// The reference picker control used for UIControls using ControlReference. /// -public class UIControlRefPickerControl : Control +internal class UIControlRefPickerControl : FlaxObjectRefPickerControl { - private Type _controlType; - private UIControl _value; - private ActorTreeNode _linkedTreeNode; - private string _valueName; - private bool _supportsPickDropDown; - - private bool _isMouseDown; - private Float2 _mouseDownPos; - private Float2 _mousePos; - - private bool _hasValidDragOver; - private DragActors _dragActors; - private DragHandlers _dragHandlers; - /// - /// The presenter using this control. + /// Type of the control to pick. /// - public IPresenterOwner PresenterContext; - - /// - /// Occurs when value gets changed. - /// - public event Action ValueChanged; - - /// - /// The type of the Control - /// - /// Throws exception if value is not a subclass of control. - public Type ControlType - { - get => _controlType; - set - { - if (_controlType == value) - return; - if (!value.IsSubclassOf(typeof(Control))) - throw new ArgumentException(string.Format("Invalid type for UIControlObjectRefPicker. Input type: {0}", value)); - _controlType = value; - _supportsPickDropDown = typeof(Control).IsAssignableFrom(value); - - // Deselect value if it's not valid now - if (!IsValid(_value)) - Value = null; - } - } - - /// - /// The UIControl value. - /// - public UIControl Value - { - get => _value; - set - { - if (_value == value) - return; - if (!IsValid(value)) - 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)) - value = null; - - _value = value; - - // Get name to display - if (_value != null) - _valueName = _value.Name; - else - _valueName = string.Empty; - - // Update tooltip - if (_value is SceneObject sceneObject) - TooltipText = Utilities.Utils.GetTooltip(sceneObject); - else - TooltipText = string.Empty; - - OnValueChanged(); - } - } - - /// - /// Gets or sets the selected object value by identifier. - /// - public Guid ValueID - { - get => _value ? _value.ID : Guid.Empty; - set => Value = Object.Find(ref value); - } - - /// - /// Initializes a new instance of the class. - /// - public UIControlRefPickerControl() - : base(0, 0, 50, 16) - { - } - - private void OnValueChanged() - { - ValueChanged?.Invoke(); - } - - private void ShowDropDownMenu() - { - Focus(); - if (typeof(Control).IsAssignableFrom(_controlType)) - { - ActorSearchPopup.Show(this, new Float2(0, Height), IsValid, actor => - { - Value = actor as UIControl; - RootWindow.Focus(); - Focus(); - }, PresenterContext); - } - } - - private bool IsValid(Actor actor) - { - return actor == null || actor is UIControl a && a.Control.GetType() == _controlType; - } - - private bool ValidateDragActor(ActorNode a) - { - if (!IsValid(a.Actor)) - return false; - - if (PresenterContext is PrefabWindow prefabWindow) - { - if (prefabWindow.Tree == a.TreeNode.ParentTree) - return true; - } - else if (PresenterContext is PropertiesWindow || PresenterContext == null) - { - if (a.ParentScene != null) - return true; - } - return false; - } + public Type ControlType; /// - public override void Draw() + protected override bool IsValid(Object obj) { - 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 || IsNavFocused) ? style.BorderHighlighted : style.BorderNormal); - - // Check if has item selected - if (isSelected) - { - // Draw name - Render2D.PushClip(nameRect); - Render2D.DrawText(style.FontMedium, $"{_valueName} ({Utilities.Utils.GetPropertyNameUI(ControlType.GetTypeDisplayName())})", 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.PushClip(nameRect); - Render2D.DrawText(style.FontMedium, ControlType != null ? $"None ({Utilities.Utils.GetPropertyNameUI(ControlType.GetTypeDisplayName())})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center); - Render2D.PopClip(); - } - - // 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) - { - var bounds = new Rectangle(Float2.Zero, Size); - Render2D.FillRectangle(bounds, style.Selection); - Render2D.DrawRectangle(bounds, style.SelectionBorder); - } - } - - /// - public override void OnMouseEnter(Float2 location) - { - _mousePos = location; - _mouseDownPos = Float2.Minimum; - - base.OnMouseEnter(location); - } - - /// - public override void OnMouseLeave() - { - _mousePos = Float2.Minimum; - - // Check if start drag drop - if (_isMouseDown) - { - // Do the drag - DoDrag(); - - // Clear flag - _isMouseDown = false; - } - - base.OnMouseLeave(); - } - - /// - public override void OnMouseMove(Float2 location) - { - _mousePos = location; - - // Check if start drag drop - if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f) - { - // Do the drag - DoDrag(); - - // Clear flag - _isMouseDown = false; - } - - base.OnMouseMove(location); - } - - /// - public override bool OnMouseUp(Float2 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 true; - } - - if (button == MouseButton.Left) - { - _isMouseDown = false; - - // Highlight actor or script reference - if (!_hasValidDragOver && !IsDragOver && nameRect.Contains(location)) - { - Actor actor = _value; - if (actor != null) - { - if (_linkedTreeNode != null && _linkedTreeNode.Actor == actor) - { - _linkedTreeNode.ExpandAllParents(); - _linkedTreeNode.StartHighlight(); - } - else - { - _linkedTreeNode = Editor.Instance.Scene.GetActorNode(actor).TreeNode; - _linkedTreeNode.ExpandAllParents(); - Editor.Instance.Windows.SceneWin.SceneTreePanel.ScrollViewTo(_linkedTreeNode, true); - _linkedTreeNode.StartHighlight(); - } - return true; - } - } - - // Reset valid drag over if still true at this point - if (_hasValidDragOver) - _hasValidDragOver = false; - } - - return base.OnMouseUp(location, button); - } - - /// - public override bool OnMouseDown(Float2 location, MouseButton button) - { - if (button == MouseButton.Left) - { - // Set flag - _isMouseDown = true; - _mouseDownPos = location; - } - - return base.OnMouseDown(location, button); - } - - /// - public override bool OnMouseDoubleClick(Float2 location, MouseButton button) - { - Focus(); - - // Check if has object selected - if (_value != null) - { - if (_linkedTreeNode != null) - { - _linkedTreeNode.StopHighlight(); - _linkedTreeNode = null; - } - - // Select object - if (_value is Actor actor) - Editor.Instance.SceneEditing.Select(actor); - } - - return base.OnMouseDoubleClick(location, button); - } - - /// - public override void OnSubmit() - { - base.OnSubmit(); - - // Picker dropdown menu - if (_supportsPickDropDown) - ShowDropDownMenu(); - } - - private void DoDrag() - { - // Do the drag drop operation if has selected element - if (_value != null) - { - if (_value is Actor actor) - DoDragDrop(DragActors.GetDragData(actor)); - } - } - - private DragDropEffect DragEffect => _hasValidDragOver ? DragDropEffect.Move : DragDropEffect.None; - - /// - public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) - { - base.OnDragEnter(ref location, data); - - // Ensure to have valid drag helpers (uses lazy init) - if (_dragActors == null) - _dragActors = new DragActors(ValidateDragActor); - if (_dragHandlers == null) - { - _dragHandlers = new DragHandlers - { - _dragActors, - }; - } - - _hasValidDragOver = _dragHandlers.OnDragEnter(data) != DragDropEffect.None; - - return DragEffect; - } - - /// - public override DragDropEffect OnDragMove(ref Float2 location, DragData data) - { - base.OnDragMove(ref location, data); - - return DragEffect; - } - - /// - public override void OnDragLeave() - { - _hasValidDragOver = false; - _dragHandlers.OnDragLeave(); - - base.OnDragLeave(); - } - - /// - public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) - { - var result = DragEffect; - - base.OnDragDrop(ref location, data); - - if (_dragActors.HasValidDrag) - { - Value = _dragActors.Objects[0].Actor as UIControl; - } - - return result; - } - - /// - public override void OnDestroy() - { - _value = null; - _controlType = null; - _valueName = null; - _linkedTreeNode = null; - - base.OnDestroy(); + return obj == null || (obj is UIControl control && control.Control.GetType() == ControlType); } } @@ -452,7 +30,7 @@ public class UIControlRefPickerControl : Control /// ControlReferenceEditor class. /// [CustomEditor(typeof(ControlReference<>)), DefaultEditor] -public class ControlReferenceEditor : CustomEditor +internal class ControlReferenceEditor : CustomEditor { private CustomElement _element; @@ -475,6 +53,7 @@ public class ControlReferenceEditor : CustomEditor { _element.CustomControl.PresenterContext = Presenter.Owner; _element.CustomControl.ControlType = genType; + _element.CustomControl.Type = new ScriptType(typeof(UIControl)); } _element.CustomControl.ValueChanged += () => { @@ -484,7 +63,7 @@ public class ControlReferenceEditor : CustomEditor Type t = typeof(ControlReference<>); Type tw = t.MakeGenericType(new Type[] { genericType }); var instance = Activator.CreateInstance(tw); - ((IControlReference)instance).UIControl = _element.CustomControl.Value; + ((IControlReference)instance).UIControl = (UIControl)_element.CustomControl.Value; SetValue(instance); } }; 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 =>