From 7e1d6679ce1547afd8d682bbd1cda68eeba4526c Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Tue, 15 Oct 2024 19:43:08 +0200 Subject: [PATCH 01/35] add debug draw clear to game and editor panel --- Source/Editor/Viewport/EditorViewport.cs | 9 +++++++++ Source/Editor/Windows/GameWindow.cs | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index edb3e0e39..619338fe7 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -973,6 +973,15 @@ namespace FlaxEditor.Viewport ViewWidgetButtonMenu.AddSeparator(); + // Clear Debug Draw + { + var button = ViewWidgetButtonMenu.AddButton("Clear Debug Draw"); + button.CloseMenuOnClick = false; + button.Clicked += () => DebugDraw.Clear(); + } + + ViewWidgetButtonMenu.AddSeparator(); + // Brightness { var brightness = ViewWidgetButtonMenu.AddButton("Brightness"); diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 56e4c4380..ea6889cb7 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -596,6 +596,13 @@ namespace FlaxEditor.Windows checkbox.StateChanged += x => ShowDebugDraw = x.Checked; } + // Debug Draw Clear + { + var button = menu.AddButton("Clear Debug Draw"); + button.CloseMenuOnClick = false; + button.Clicked += () => DebugDraw.Clear(); + } + menu.MinimumWidth = 200; menu.AddSeparator(); } From e6450bfc7a46f205672e0132929a8756d98e9ad3 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 30 Dec 2024 16:36:10 -0600 Subject: [PATCH 02/35] 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 03/35] 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 04/35] 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 05/35] 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 97184c87af69a7e25145c865ec37871f37cb73e8 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 1 Jan 2025 23:05:15 -0600 Subject: [PATCH 06/35] Add android emulation options to game cooker android tab. --- Source/Editor/Editor.Build.cs | 1 + Source/Editor/Windows/GameCookerWindow.cs | 223 ++++++++++++++++++++++ 2 files changed, 224 insertions(+) diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index e76cb5dfe..84dfa82dd 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -41,6 +41,7 @@ public class Editor : EditorModule options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter"); options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions"); options.ScriptingAPI.SystemReferences.Add("System.IO.Compression.ZipFile"); + options.ScriptingAPI.SystemReferences.Add("System.Diagnostics.Process"); // Enable optimizations for Editor, disable this for debugging the editor if (options.Configuration == TargetConfiguration.Development) diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index cbe211eca..a44cae6a5 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -8,10 +8,12 @@ using FlaxEditor.Content.Settings; using FlaxEditor.CustomEditors; using FlaxEditor.GUI; using FlaxEditor.GUI.Tabs; +using FlaxEditor.GUI.Tree; using FlaxEditor.Utilities; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; +using PlatformType = FlaxEngine.PlatformType; // ReSharper disable InconsistentNaming // ReSharper disable MemberCanBePrivate.Local @@ -343,6 +345,227 @@ namespace FlaxEditor.Windows _buildButton = layout.Button("Build").Button; _buildButton.Clicked += OnBuildClicked; + + // Add emulation options to android tab. + if (_platform == PlatformType.Android) + { + layout.Space(2); + var emulatorGroup = layout.Group("Emulator"); + var sdkPath = Environment.GetEnvironmentVariable("ANDROID_HOME"); + if (string.IsNullOrEmpty(sdkPath)) + sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK"); + emulatorGroup.Label($"SDK path: {sdkPath}"); + + // AVD and starting emulator + var avdGroup = emulatorGroup.Group("AVD"); + avdGroup.Label("Note: Create AVDs using Android Studio."); + avdGroup.Panel.IsClosed = false; + var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button; + var avdListGroup = avdGroup.Group("AVD List"); + var avdListTree = new Tree(false) + { + Parent = avdListGroup.Panel, + }; + refreshAVDListButton.Clicked += () => + { + if (avdListTree.Children.Count > 0) + avdListTree.DisposeChildren(); + + var processStartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"), + Arguments = "-list-avds", + RedirectStandardOutput = true, + }; + + var process = new System.Diagnostics.Process + { + StartInfo = processStartInfo + }; + process.Start(); + var output = new string(process.StandardOutput.ReadToEnd()); + /* + CreateProcessSettings processSettings = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"), + Arguments = "-list-avds", + HiddenWindow = false, + SaveOutput = true, + WaitForEnd = true, + }; + //processSettings.ShellExecute = true; + FlaxEngine.Platform.CreateProcess(ref processSettings); + + var output = new string(processSettings.Output);*/ + if (output.Length == 0) + { + Debug.LogWarning("No AVDs detected."); + return; + } + var splitOutput = output.Split('\n'); + foreach (var line in splitOutput) + { + if (string.IsNullOrEmpty(line.Trim())) + continue; + var item = new TreeNode + { + Text = line.Trim(), + Parent = avdListTree, + }; + } + avdListGroup.Panel.IsClosed = false; + }; + + avdGroup.Label("Emulator AVD Commands:"); + var commandsTextBox = avdGroup.TextBox().TextBox; + commandsTextBox.IsMultiline = false; + commandsTextBox.Text = "-no-snapshot-load -no-boot-anim"; // TODO: save user changes + + var startEmulatorButton = avdGroup.Button("Start AVD Emulator").Button; + startEmulatorButton.TooltipText = "Starts selected AVD from list."; + startEmulatorButton.Clicked += () => + { + if (avdListTree.Selection.Count == 0) + return; + + CreateProcessSettings processSettings = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"), + Arguments = $"-avd {avdListTree.Selection[0].Text} {commandsTextBox.Text}", + HiddenWindow = true, + SaveOutput = false, + WaitForEnd = false, + }; + processSettings.ShellExecute = true; + FlaxEngine.Platform.CreateProcess(ref processSettings); + }; + + emulatorGroup.Space(2); + + // Device + var installGroup = emulatorGroup.Group("Install"); + installGroup.Panel.IsClosed = false; + installGroup.Label("Note: used to install to AVD or physical devices."); + var refreshDeviceListButton = installGroup.Button("Refresh device list").Button; + var deviceListGroup = installGroup.Group("List of devices"); + var deviceListTree = new Tree(false) + { + Parent = deviceListGroup.Panel, + }; + refreshDeviceListButton.Clicked += () => + { + if (deviceListTree.Children.Count > 0) + deviceListTree.DisposeChildren(); + + var processStartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = "devices -l", + RedirectStandardOutput = true, + }; + + var process = new System.Diagnostics.Process + { + StartInfo = processStartInfo + }; + process.Start(); + var output = new string(process.StandardOutput.ReadToEnd()); + /* + CreateProcessSettings processSettings = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = "devices -l", + HiddenWindow = false, + //SaveOutput = true, + WaitForEnd = true, + }; + processSettings.SaveOutput = true; + processSettings.ShellExecute = false; + FlaxEngine.Platform.CreateProcess(ref processSettings); + + var output = new string(processSettings.Output); + */ + if (output.Length > 0 && !output.Equals("List of devices attached", StringComparison.Ordinal)) + { + var splitLines = output.Split('\n'); + foreach (var line in splitLines) + { + if (line.Trim().Equals("List of devices attached", StringComparison.Ordinal) || string.IsNullOrEmpty(line.Trim())) + continue; + + var tab = line.Split("device "); + if (tab.Length < 2) + continue; + var item = new TreeNode + { + Text = $"{tab[0].Trim()} - {tab[1].Trim()}", + Tag = tab[0].Trim(), + Parent = deviceListTree, + }; + } + } + + deviceListGroup.Panel.IsClosed = false; + }; + + var autoStart = installGroup.Checkbox("Try to auto start activity on device."); + var installButton = installGroup.Button("Install APK to Device").Button; + installButton.TooltipText = "Installs APK from the output folder to the selected device."; + installButton.Clicked += () => + { + if (deviceListTree.Selection.Count == 0) + return; + + // Get built APK at output path + string output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(proxy.PerPlatformOptions[_platform].Output)); + if (!Directory.Exists(output)) + { + FlaxEditor.Editor.LogWarning("Can not copy APK because output folder does not exist."); + return; + } + + var apkFiles = Directory.GetFiles(output, "*.apk"); + if (apkFiles.Length == 0) + { + FlaxEditor.Editor.LogWarning("Can not copy APK because no .apk files were found in output folder."); + return; + } + + string apkFilesString = string.Empty; + for (int i = 0; i < apkFiles.Length; i++) + { + var file = apkFiles[i]; + if (i == 0) + { + apkFilesString = $"\"{file}\""; + continue; + } + apkFilesString += $" \"{file}\""; + } + + CreateProcessSettings processSettings = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = $"-s {deviceListTree.Selection[0].Tag} {(apkFiles.Length > 1 ? "install-multiple" : "install")} {apkFilesString}", + LogOutput = true, + }; + FlaxEngine.Platform.CreateProcess(ref processSettings); + + if (autoStart.CheckBox.Checked) + { + var gameSettings = GameSettings.Load(); + var productName = gameSettings.ProductName.Replace(" ", "").ToLower(); + var companyName = gameSettings.CompanyName.Replace(" ", "").ToLower(); + CreateProcessSettings processSettings1 = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = $"shell am start -n com.{companyName}.{productName}/com.flaxengine.GameActivity", + LogOutput = true, + }; + FlaxEngine.Platform.CreateProcess(ref processSettings1); + } + }; + } } else { From 04fc118ddfcd803807e2ed898bf12ca92415c643 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 4 Jan 2025 11:45:08 -0600 Subject: [PATCH 07/35] 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 fa17c49eb1713d7966318b1be53e41d76afe3925 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 7 Jan 2025 21:11:37 -0600 Subject: [PATCH 08/35] Add starting adb log collecting button. --- Source/Editor/Windows/GameCookerWindow.cs | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index a44cae6a5..81b120937 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using FlaxEditor.Content.Settings; @@ -13,6 +14,7 @@ using FlaxEditor.Utilities; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; +using Debug = FlaxEngine.Debug; using PlatformType = FlaxEngine.PlatformType; // ReSharper disable InconsistentNaming @@ -376,6 +378,7 @@ namespace FlaxEditor.Windows FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"), Arguments = "-list-avds", RedirectStandardOutput = true, + CreateNoWindow = true, }; var process = new System.Diagnostics.Process @@ -462,6 +465,7 @@ namespace FlaxEditor.Windows FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), Arguments = "devices -l", RedirectStandardOutput = true, + CreateNoWindow = true, }; var process = new System.Diagnostics.Process @@ -565,6 +569,35 @@ namespace FlaxEditor.Windows FlaxEngine.Platform.CreateProcess(ref processSettings1); } }; + + var adbLogButton = emulatorGroup.Button("Start adb log collecting").Button; + adbLogButton.TooltipText = "In debug and development builds the engine and game logs can be output directly to the adb."; + adbLogButton.Clicked += () => + { + + var processStartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = "logcat Flax:I *:S", + }; + processStartInfo.CreateNoWindow = false; + processStartInfo.WindowStyle = ProcessWindowStyle.Normal; + + var process = new System.Diagnostics.Process + { + StartInfo = processStartInfo + }; + process.Start(); + /* + CreateProcessSettings processSettings = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = $"logcat Flax:I *:S", + //LogOutput = true, + }; + FlaxEngine.Platform.CreateProcess(ref processSettings); + */ + }; } } else From 6fbb5548b90d4a512a8fe18115580a5d8903b6dd Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 7 Jan 2025 21:15:35 -0600 Subject: [PATCH 09/35] Small code clean up. --- Source/Editor/Windows/GameCookerWindow.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 81b120937..694e86e5c 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -574,14 +574,13 @@ namespace FlaxEditor.Windows adbLogButton.TooltipText = "In debug and development builds the engine and game logs can be output directly to the adb."; adbLogButton.Clicked += () => { - var processStartInfo = new System.Diagnostics.ProcessStartInfo { FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), Arguments = "logcat Flax:I *:S", + CreateNoWindow = false, + WindowStyle = ProcessWindowStyle.Normal, }; - processStartInfo.CreateNoWindow = false; - processStartInfo.WindowStyle = ProcessWindowStyle.Normal; var process = new System.Diagnostics.Process { @@ -593,7 +592,7 @@ namespace FlaxEditor.Windows { FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), Arguments = $"logcat Flax:I *:S", - //LogOutput = true, + WaitForEnd = false, }; FlaxEngine.Platform.CreateProcess(ref processSettings); */ From 21f1a46c05992cd2b49076cdcff980fe32b6882b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 7 Jan 2025 21:29:27 -0600 Subject: [PATCH 10/35] Small wording changes --- Source/Editor/Windows/GameCookerWindow.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 694e86e5c..8bd3523de 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -351,15 +351,15 @@ namespace FlaxEditor.Windows // Add emulation options to android tab. if (_platform == PlatformType.Android) { - layout.Space(2); - var emulatorGroup = layout.Group("Emulator"); + layout.Space(5); + var emulatorGroup = layout.Group("Tools"); var sdkPath = Environment.GetEnvironmentVariable("ANDROID_HOME"); if (string.IsNullOrEmpty(sdkPath)) sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK"); emulatorGroup.Label($"SDK path: {sdkPath}"); // AVD and starting emulator - var avdGroup = emulatorGroup.Group("AVD"); + var avdGroup = emulatorGroup.Group("AVD Emulator"); avdGroup.Label("Note: Create AVDs using Android Studio."); avdGroup.Panel.IsClosed = false; var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button; @@ -448,7 +448,7 @@ namespace FlaxEditor.Windows // Device var installGroup = emulatorGroup.Group("Install"); installGroup.Panel.IsClosed = false; - installGroup.Label("Note: used to install to AVD or physical devices."); + installGroup.Label("Note: Used to install to AVD or physical devices."); var refreshDeviceListButton = installGroup.Button("Refresh device list").Button; var deviceListGroup = installGroup.Group("List of devices"); var deviceListTree = new Tree(false) From e1dbaebe8ec0933b2d3969e7b0527992f11bf28b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 7 Jan 2025 21:40:05 -0600 Subject: [PATCH 11/35] Add instructions for list populating. --- Source/Editor/Windows/GameCookerWindow.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 8bd3523de..0088cdf86 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -364,6 +364,8 @@ namespace FlaxEditor.Windows avdGroup.Panel.IsClosed = false; var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button; var avdListGroup = avdGroup.Group("AVD List"); + avdListGroup.Panel.IsClosed = false; + var noAvdLabel = avdListGroup.Label("No AVDs detected. Click Refresh.", TextAlignment.Center).Label; var avdListTree = new Tree(false) { Parent = avdListGroup.Panel, @@ -402,9 +404,11 @@ namespace FlaxEditor.Windows var output = new string(processSettings.Output);*/ if (output.Length == 0) { - Debug.LogWarning("No AVDs detected."); + noAvdLabel.Visible = true; + FlaxEditor.Editor.LogWarning("No AVDs detected."); return; } + noAvdLabel.Visible = false; var splitOutput = output.Split('\n'); foreach (var line in splitOutput) { @@ -451,6 +455,8 @@ namespace FlaxEditor.Windows installGroup.Label("Note: Used to install to AVD or physical devices."); var refreshDeviceListButton = installGroup.Button("Refresh device list").Button; var deviceListGroup = installGroup.Group("List of devices"); + deviceListGroup.Panel.IsClosed = false; + var noDevicesLabel = deviceListGroup.Label("No devices found. Click Refresh.", TextAlignment.Center).Label; var deviceListTree = new Tree(false) { Parent = deviceListGroup.Panel, @@ -489,8 +495,10 @@ namespace FlaxEditor.Windows var output = new string(processSettings.Output); */ + if (output.Length > 0 && !output.Equals("List of devices attached", StringComparison.Ordinal)) { + noDevicesLabel.Visible = false; var splitLines = output.Split('\n'); foreach (var line in splitLines) { @@ -508,6 +516,10 @@ namespace FlaxEditor.Windows }; } } + else + { + noDevicesLabel.Visible = true; + } deviceListGroup.Panel.IsClosed = false; }; From be6a2588460a7c5cae778a7663e984ccd56aa498 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 3 Feb 2025 21:57:14 -0600 Subject: [PATCH 12/35] Add runtime input mapping helpers. --- Source/Engine/Input/Input.cpp | 204 ++++++++++++++++++++++++++++++++++ Source/Engine/Input/Input.h | 111 ++++++++++++++++++ 2 files changed, 315 insertions(+) diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index d1ccd367e..e708be2c9 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -614,6 +614,210 @@ float Input::GetAxisRaw(const StringView& name) return e ? e->ValueRaw : false; } +void Input::SetInputMappingFromSettings(const JsonAssetReference& settings) +{ + ActionMappings.Clear(); + auto actionMappings = settings.GetInstance()->ActionMappings; + ActionMappings.Resize(actionMappings.Count(), false); + for (int i = 0; i < actionMappings.Count(); i++) + { + ActionMappings[i] = actionMappings.At(i); + } + + auto axisMappings = settings.GetInstance()->AxisMappings; + AxisMappings.Resize(axisMappings.Count(), false); + for (int i = 0; i < axisMappings.Count(); i++) + { + AxisMappings[i] = axisMappings.At(i); + } + Axes.Clear(); + Actions.Clear(); +} + +void Input::SetInputMappingToDefaultSettings() +{ + InputSettings* settings = InputSettings::Get(); + if (settings) + { + ActionMappings = settings->ActionMappings; + AxisMappings = settings->AxisMappings; + Axes.Clear(); + Actions.Clear(); + } +} + +ActionConfig Input::GetActionConfigByName(const StringView& name) +{ + ActionConfig config = {}; + for (const auto& a : ActionMappings) + { + if (a.Name == name) + { + config = a; + return config; + } + } + return config; +} + +Array Input::GetAllActionConfigsByName(const StringView& name) +{ + Array actionConfigs; + for (const auto& a : ActionMappings) + { + if (a.Name == name) + actionConfigs.Add(a); + } + return actionConfigs; +} + +AxisConfig Input::GetAxisConfigByName(const StringView& name) +{ + AxisConfig config = {}; + for (const auto& a : AxisMappings) + { + if (a.Name == name) + { + config = a; + return config; + } + } + return config; +} + +Array Input::GetAllAxisConfigsByName(const StringView& name) +{ + Array actionConfigs; + for (const auto& a : AxisMappings) + { + if (a.Name == name) + actionConfigs.Add(a); + } + return actionConfigs; +} + +void Input::SetAxisConfigByName(const StringView& name, AxisConfig& config, bool all) +{ + for (int i = 0; i < AxisMappings.Count(); ++i) + { + auto& mapping = AxisMappings.At(i); + if (mapping.Name.Compare(name.ToString()) == 0) + { + if (config.Name.IsEmpty()) + config.Name = name; + mapping = config; + if (!all) + break; + } + } +} + +void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const KeyboardKeys positiveButton, const KeyboardKeys negativeButton, bool all) +{ + for (int i = 0; i < AxisMappings.Count(); ++i) + { + auto& mapping = AxisMappings.At(i); + if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Axis == inputType) + { + mapping.PositiveButton = positiveButton; + mapping.NegativeButton = negativeButton; + if (!all) + break; + } + } +} + +void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const GamepadButton positiveButton, const GamepadButton negativeButton, InputGamepadIndex gamepadIndex, bool all) +{ + for (int i = 0; i < AxisMappings.Count(); ++i) + { + auto& mapping = AxisMappings.At(i); + if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Gamepad == gamepadIndex && mapping.Axis == inputType) + { + mapping.GamepadPositiveButton = positiveButton; + mapping.GamepadNegativeButton = negativeButton; + if (!all) + break; + } + } +} + +void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const float gravity, const float deadZone, const float sensitivity, const float scale, const bool snap, bool all) +{ + for (int i = 0; i < AxisMappings.Count(); ++i) + { + auto& mapping = AxisMappings.At(i); + if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Axis == inputType) + { + mapping.Gravity = gravity; + mapping.DeadZone = deadZone; + mapping.Sensitivity = sensitivity; + mapping.Scale = scale; + mapping.Snap = snap; + if (!all) + break; + } + } +} + +void Input::SetActionConfigByName(const StringView& name, const KeyboardKeys key, bool all) +{ + for (int i = 0; i < ActionMappings.Count(); ++i) + { + auto& mapping = ActionMappings.At(i); + if (mapping.Name.Compare(name.ToString()) == 0) + { + mapping.Key = key; + if (!all) + break; + } + } +} + +void Input::SetActionConfigByName(const StringView& name, const MouseButton mouseButton, bool all) +{ + for (int i = 0; i < ActionMappings.Count(); ++i) + { + auto& mapping = ActionMappings.At(i); + if (mapping.Name.Compare(name.ToString()) == 0) + { + mapping.MouseButton = mouseButton; + if (!all) + break; + } + } +} + +void Input::SetActionConfigByName(const StringView& name, const GamepadButton gamepadButton, InputGamepadIndex gamepadIndex, bool all) +{ + for (int i = 0; i < ActionMappings.Count(); ++i) + { + auto& mapping = ActionMappings.At(i); + if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Gamepad == gamepadIndex) + { + mapping.GamepadButton = gamepadButton; + if (!all) + break; + } + } +} + +void Input::SetActionConfigByName(const StringView& name, ActionConfig& config, bool all) +{ + for (int i = 0; i < ActionMappings.Count(); ++i) + { + auto& mapping = ActionMappings.At(i); + if (mapping.Name.Compare(name.ToString()) == 0) + { + if (config.Name.IsEmpty()) + config.Name = name; + mapping = config; + if (!all) + break; + } + } +} + void InputService::Update() { PROFILE_CPU(); diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h index 2f8c09b38..a937f4f2e 100644 --- a/Source/Engine/Input/Input.h +++ b/Source/Engine/Input/Input.h @@ -9,6 +9,7 @@ #include "KeyboardKeys.h" #include "Enums.h" #include "VirtualInput.h" +#include "Engine/Content/JsonAssetReference.h" class Mouse; class Keyboard; @@ -332,4 +333,114 @@ public: /// The current axis value (e.g for gamepads it's in the range -1..1). No smoothing applied. /// API_FUNCTION() static float GetAxisRaw(const StringView& name); + + /// + /// Sets and overwrites the Action and Axis mappings with the values from a new InputSettings. + /// + /// The input settings. + API_FUNCTION() static void SetInputMappingFromSettings(const JsonAssetReference& settings); + + /// + /// Sets and overwrites the Action and Axis mappings with the values from the InputSettings in GameSettings. + /// + API_FUNCTION() static void SetInputMappingToDefaultSettings(); + + /// + /// Gets the first action configuration by name. + /// + /// The name of the action config. + API_FUNCTION() static ActionConfig GetActionConfigByName(const StringView& name); + + /// + /// Gets all the action configurations by name. + /// + /// The name of the action config. + API_FUNCTION() static Array GetAllActionConfigsByName(const StringView& name); + + /// + /// Sets the action configuration keyboard key by name. + /// + /// The name of the action config. + /// The key to set. + /// Whether to set only the first config found or all of them. + API_FUNCTION() static void SetActionConfigByName(const StringView& name, const KeyboardKeys key, bool all = false); + + /// + /// Sets the action configuration mouse button by name. + /// + /// The name of the action config. + /// The mouse button to set. + /// Whether to set only the first config found or all of them. + API_FUNCTION() static void SetActionConfigByName(const StringView& name, const MouseButton mouseButton, bool all = false); + + /// + /// Sets the action configuration gamepad button by name and index. + /// + /// The name of the action config. + /// The gamepad button to set. + /// The gamepad index used to find the correct config. + /// Whether to set only the first config found or all of them. + API_FUNCTION() static void SetActionConfigByName(const StringView& name, const GamepadButton gamepadButton, InputGamepadIndex gamepadIndex, bool all = false); + + /// + /// Sets the action configuration by name. + /// + /// The name of the action config. + /// The action configuration to set. Leave the config name empty to use set name. + /// Whether to set only the first config found or all of them. + API_FUNCTION() static void SetActionConfigByName(const StringView& name, ActionConfig& config, bool all = false); + + /// + /// Gets the first axis configurations by name. + /// + /// The name of the axis config. + API_FUNCTION() static AxisConfig GetAxisConfigByName(const StringView& name); + + /// + /// Gets all the axis configurations by name. + /// + /// The name of the axis config. + API_FUNCTION() static Array GetAllAxisConfigsByName(const StringView& name); + + /// + /// Sets the axis configuration keyboard key by name and type. + /// + /// The name of the action config. + /// The configuration to set. Leave the config name empty to use set name. + /// Whether to set only the first config found or all of them. + API_FUNCTION() static void SetAxisConfigByName(const StringView& name, AxisConfig& config, bool all = false); + + /// + /// Sets the axis configuration keyboard key buttons by name and type. + /// + /// The name of the action config. + /// The type to sort by. + /// The positive key button. + /// The negative key button. + /// Whether to set only the first config found or all of them. + API_FUNCTION() static void SetAxisConfigByName(const StringView& name, InputAxisType inputType, const KeyboardKeys positiveButton, const KeyboardKeys negativeButton, bool all = false); + + /// + /// Sets the axis configuration gamepad buttons by name, type, and index. + /// + /// The name of the action config. + /// The type to sort by. + /// The positive gamepad button. + /// The negative gamepad button. + /// The gamepad index to sort by. + /// Whether to set only the first config found or all of them. + API_FUNCTION() static void SetAxisConfigByName(const StringView& name, InputAxisType inputType, const GamepadButton positiveButton, const GamepadButton negativeButton, InputGamepadIndex gamepadIndex, bool all = false); + + /// + /// Sets the axis configuration accessories by name, and type.. + /// + /// The name of the action config. + /// The type to sort by. + /// The gravity to set. + /// The dead zone to set. + /// The sensitivity to set. + /// The scale to set. + /// The snap to set. + /// Whether to set only the first config found or all of them. + API_FUNCTION() static void SetAxisConfigByName(const StringView& name, InputAxisType inputType, const float gravity, const float deadZone, const float sensitivity, const float scale, const bool snap, bool all = false); }; From a17c15ae96ca9470c647788a56ff37e27d0fa45a Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 3 Feb 2025 22:11:07 -0600 Subject: [PATCH 13/35] Add missing return comments for get methods. --- Source/Engine/Input/Input.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h index a937f4f2e..dec381ca9 100644 --- a/Source/Engine/Input/Input.h +++ b/Source/Engine/Input/Input.h @@ -349,12 +349,14 @@ public: /// Gets the first action configuration by name. /// /// The name of the action config. + /// The first Action configuration with the name. Empty configuration if not found. API_FUNCTION() static ActionConfig GetActionConfigByName(const StringView& name); /// /// Gets all the action configurations by name. /// /// The name of the action config. + /// The Action configurations with the name. API_FUNCTION() static Array GetAllActionConfigsByName(const StringView& name); /// @@ -394,12 +396,14 @@ public: /// Gets the first axis configurations by name. /// /// The name of the axis config. + /// The first Axis configuration with the name. Empty configuration if not found. API_FUNCTION() static AxisConfig GetAxisConfigByName(const StringView& name); /// /// Gets all the axis configurations by name. /// /// The name of the axis config. + /// The axis configurations with the name. API_FUNCTION() static Array GetAllAxisConfigsByName(const StringView& name); /// @@ -432,7 +436,7 @@ public: API_FUNCTION() static void SetAxisConfigByName(const StringView& name, InputAxisType inputType, const GamepadButton positiveButton, const GamepadButton negativeButton, InputGamepadIndex gamepadIndex, bool all = false); /// - /// Sets the axis configuration accessories by name, and type.. + /// Sets the axis configuration accessories by name, and type. /// /// The name of the action config. /// The type to sort by. From 898295e9d421e51b7b4fd4146a222d8bd6f510a4 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 3 Feb 2025 22:20:51 -0600 Subject: [PATCH 14/35] Small code cleanup. --- Source/Engine/Input/Input.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index e708be2c9..de956a3ce 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -616,7 +616,6 @@ float Input::GetAxisRaw(const StringView& name) void Input::SetInputMappingFromSettings(const JsonAssetReference& settings) { - ActionMappings.Clear(); auto actionMappings = settings.GetInstance()->ActionMappings; ActionMappings.Resize(actionMappings.Count(), false); for (int i = 0; i < actionMappings.Count(); i++) From 9e25714338f1afee1b2ce16c66fb1834190086fb Mon Sep 17 00:00:00 2001 From: Anton Makarenko Date: Wed, 5 Feb 2025 16:24:55 +0200 Subject: [PATCH 15/35] Changes text of documentation above static Log method from warning to info to better reflect method's purpose and avoid possible confusion --- Source/Engine/Debug/DebugLog.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Debug/DebugLog.h b/Source/Engine/Debug/DebugLog.h index a70fc3d3b..fe3708b46 100644 --- a/Source/Engine/Debug/DebugLog.h +++ b/Source/Engine/Debug/DebugLog.h @@ -19,7 +19,7 @@ public: static void Log(LogType type, const StringView& message); /// - /// A variant of Debug.Log that logs a warning message to the console. + /// A variant of Debug.Log that logs an info message to the console. /// /// The text message to display. FORCE_INLINE static void Log(const StringView& message) From 054d06f49e1c61a0a90586a2ad2c446b6b8d165e Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sat, 15 Feb 2025 17:38:03 +0100 Subject: [PATCH 16/35] add rmb menu entry to show item in Content Panel --- Source/Editor/Windows/ContentWindow.ContextMenu.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 182467566..292ec79c5 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -76,6 +76,15 @@ namespace FlaxEditor.Windows }); cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path))); + + if (!String.IsNullOrEmpty(Editor.Instance.Windows.ContentWin._itemsSearchBox.Text)) + { + cm.AddButton("Show in Content Panel", () => + { + Editor.Instance.Windows.ContentWin.ClearItemsSearch(); + Editor.Instance.Windows.ContentWin.Select(item); + }); + } if (item.HasDefaultThumbnail == false) { From 25b8939aff946168151bebb57538077085cc4fa2 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 16 Feb 2025 18:05:25 -0600 Subject: [PATCH 17/35] 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 0d5534c445475ee8422ccdbe85d91f488c149d30 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Mon, 24 Feb 2025 17:43:32 +0100 Subject: [PATCH 18/35] decrease grid panel fill slider sensitivity --- Source/Engine/UI/GUI/Panels/GridPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/UI/GUI/Panels/GridPanel.cs b/Source/Engine/UI/GUI/Panels/GridPanel.cs index 2b1a0a123..24a7e0090 100644 --- a/Source/Engine/UI/GUI/Panels/GridPanel.cs +++ b/Source/Engine/UI/GUI/Panels/GridPanel.cs @@ -35,7 +35,7 @@ namespace FlaxEngine.GUI /// /// The cells heights in container height percentage (from top to bottom). Use negative values to set fixed widths for the cells. /// - [EditorOrder(10), Tooltip("The cells heights in container height percentage (from top to bottom). Use negative values to set fixed height for the cells.")] + [EditorOrder(10), Limit(float.MinValue, float.MaxValue, 0.001f), Tooltip("The cells heights in container height percentage (from top to bottom). Use negative values to set fixed height for the cells.")] public float[] RowFill { get => _cellsV; @@ -49,7 +49,7 @@ namespace FlaxEngine.GUI /// /// The cells heights in container width percentage (from left to right). Use negative values to set fixed heights for the cells. /// - [EditorOrder(20), Tooltip("The cells heights in container width percentage (from left to right). Use negative values to set fixed width for the cells.")] + [EditorOrder(20), Limit(float.MinValue, float.MaxValue, 0.001f), Tooltip("The cells heights in container width percentage (from left to right). Use negative values to set fixed width for the cells.")] public float[] ColumnFill { get => _cellsH; From 484685276246831e4d209612110333269226df5e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 2 Mar 2025 10:45:51 +0100 Subject: [PATCH 19/35] Fix crash when spline actor gets removed (speculative fix) #3180 --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 411bf29a3..8e62347b2 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -306,6 +306,13 @@ namespace FlaxEditor.SceneGraph.Actors private void OnUpdate() { + // Prevent update event called when actor got deleted (incorrect state) + if (!Actor) + { + FlaxEngine.Scripting.Update -= OnUpdate; + return; + } + // If this node's point is selected var selection = Editor.Instance.SceneEditing.Selection; if (selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this) From cbe96252f6080e587e83ed0c6a793ac741126356 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 2 Mar 2025 11:38:25 +0100 Subject: [PATCH 20/35] Update assets --- Content/Shaders/Editor/Grid.flax | 4 ++-- Content/Shaders/Editor/LightmapUVsDensity.flax | 4 ++-- Content/Shaders/GI/GlobalSurfaceAtlas.flax | 4 ++-- Content/Shaders/Quad.flax | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Content/Shaders/Editor/Grid.flax b/Content/Shaders/Editor/Grid.flax index 440d37512..5fea3f4a2 100644 --- a/Content/Shaders/Editor/Grid.flax +++ b/Content/Shaders/Editor/Grid.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9aa0cb389296afe4ee474395f188e61f8a1e428aac1f09fc7d54825b1d6e58df -size 4744 +oid sha256:489711fab457331f74da08325ce7129237fbcb815d0d8c8a4ffe52e85a2672f7 +size 4743 diff --git a/Content/Shaders/Editor/LightmapUVsDensity.flax b/Content/Shaders/Editor/LightmapUVsDensity.flax index d7f265055..3ef5f9091 100644 --- a/Content/Shaders/Editor/LightmapUVsDensity.flax +++ b/Content/Shaders/Editor/LightmapUVsDensity.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0ed25e40158253b2fdd565e7ad0e745308eadc5091a89502cbc1fa22288039f -size 4515 +oid sha256:eb3a36137b2c7ca358be2496ee60913446215218bd39e539ee4db8c5daea82f5 +size 4511 diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax index fa75716d9..59d6caed5 100644 --- a/Content/Shaders/GI/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c87a431fc7230f3d345ce131fdb1727ffe45874b55ed062feff73aabed514f59 -size 13194 +oid sha256:2948a652d488effda7d5f14c3f603c0ae266c318aaa15cf8543cda8a10d21c0e +size 13163 diff --git a/Content/Shaders/Quad.flax b/Content/Shaders/Quad.flax index 6bdf6ce8e..69f7c4f31 100644 --- a/Content/Shaders/Quad.flax +++ b/Content/Shaders/Quad.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60680a4ce8deee4de81d8aafed454fb5aace3a6d18a11eda3b96e81ccaf801a9 -size 4443 +oid sha256:32bb06520034006149c4036a8e2ac90b483da07f5e9e7f01568b5e6696bbca59 +size 4397 From 47b22b305d084cf9a25bab563dc365b8378d9d65 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Mar 2025 09:51:05 +0100 Subject: [PATCH 21/35] Add conditional showing of `Clear Debug Draw` buttons only if there is anything to clear #2989 --- Source/Editor/Viewport/EditorViewport.cs | 3 +-- Source/Editor/Windows/GameWindow.cs | 16 ++++++++-------- Source/Engine/Debug/DebugDraw.cpp | 12 ++++++++++++ Source/Engine/Debug/DebugDraw.h | 7 +++++++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 3ca66c343..e30e242e9 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -968,13 +968,12 @@ namespace FlaxEditor.Viewport debugView.VisibleChanged += WidgetViewModeShowHide; } - ViewWidgetButtonMenu.AddSeparator(); - // Clear Debug Draw { var button = ViewWidgetButtonMenu.AddButton("Clear Debug Draw"); button.CloseMenuOnClick = false; button.Clicked += () => DebugDraw.Clear(); + ViewWidgetButtonMenu.VisibleChanged += (Control cm) => { button.Visible = DebugDraw.CanClear(); }; } ViewWidgetButtonMenu.AddSeparator(); diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index b7a17214b..ebc64cbd8 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -117,7 +117,6 @@ namespace FlaxEditor.Windows { if (!AudioMuted) Audio.MasterVolume = value; - _audioVolume = value; } } @@ -677,6 +676,14 @@ namespace FlaxEditor.Windows checkbox.StateChanged += x => ShowDebugDraw = x.Checked; } + // Clear Debug Draw + if (DebugDraw.CanClear()) + { + var button = menu.AddButton("Clear Debug Draw"); + button.CloseMenuOnClick = false; + button.Clicked += () => DebugDraw.Clear(); + } + menu.AddSeparator(); // Mute Audio @@ -695,13 +702,6 @@ namespace FlaxEditor.Windows slider.ValueChanged += () => AudioVolume = slider.Value; } - // Debug Draw Clear - { - var button = menu.AddButton("Clear Debug Draw"); - button.CloseMenuOnClick = false; - button.Clicked += () => DebugDraw.Clear(); - } - menu.MinimumWidth = 200; menu.AddSeparator(); } diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 4dacb95d6..6e2388ed2 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -347,6 +347,11 @@ struct DebugDrawContext DebugDrawData DebugDrawDepthTest; Float3 LastViewPos = Float3::Zero; Matrix LastViewProj = Matrix::Identity; + + inline int32 Count() const + { + return DebugDrawDefault.Count() + DebugDrawDepthTest.Count(); + } }; namespace @@ -739,6 +744,13 @@ void DebugDraw::SetContext(void* context) Context = context ? (DebugDrawContext*)context : &GlobalContext; } +bool DebugDraw::CanClear(void* context) +{ + if (!context) + context = &GlobalContext; + return ((DebugDrawContext*)context)->Count() != 0; +} + #endif Vector3 DebugDraw::GetViewPos() diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 84e223434..4bea47118 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -62,6 +62,13 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// /// The context or null. API_FUNCTION() static void SetContext(void* context); + + /// + /// Checks if can clear all debug shapes displayed on screen. Can be used to disable this functionality when not needed for the user. + /// + /// The context. + /// True fi context can be cleared (has any shapes), otherwise false. + API_FUNCTION() static bool CanClear(void* context = nullptr); #endif // Gets the last view position when rendering the current context. Can be sued for custom culling or LODing when drawing more complex shapes. From 54ad9a76298c5c29b2227e8dda6f323f22136ef2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:49:17 +0100 Subject: [PATCH 22/35] Fix error when connecting nodes in Visject --- Source/Editor/Surface/VisjectSurface.Connecting.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Surface/VisjectSurface.Connecting.cs b/Source/Editor/Surface/VisjectSurface.Connecting.cs index 8a74ef8e6..fbbfa2053 100644 --- a/Source/Editor/Surface/VisjectSurface.Connecting.cs +++ b/Source/Editor/Surface/VisjectSurface.Connecting.cs @@ -60,12 +60,12 @@ namespace FlaxEditor.Surface if (toIsReference && !fromIsReference) { var toTypeName = to.TypeName; - return from.TypeName == toTypeName.Substring(0, toTypeName.Length - 1); + return !string.IsNullOrEmpty(toTypeName) && from.TypeName == toTypeName.Substring(0, toTypeName.Length - 1); } if (!toIsReference && fromIsReference) { var fromTypeName = from.TypeName; - return to.TypeName == fromTypeName.Substring(0, fromTypeName.Length - 1); + return !string.IsNullOrEmpty(fromTypeName) && to.TypeName == fromTypeName.Substring(0, fromTypeName.Length - 1); } // Implicit casting is supported for object reference to test whenever it is valid From 3200fc1570dc31b329e3b6cac93e577a18300ad8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Mar 2025 12:03:38 +0100 Subject: [PATCH 23/35] Fix new terrain debug drawing of collision lines #3227 --- Source/Editor/Gizmo/GridGizmo.cs | 1 + Source/Engine/Terrain/TerrainPatch.cpp | 29 +++++++++++++------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Source/Editor/Gizmo/GridGizmo.cs b/Source/Editor/Gizmo/GridGizmo.cs index d063e989e..a308ca8ba 100644 --- a/Source/Editor/Gizmo/GridGizmo.cs +++ b/Source/Editor/Gizmo/GridGizmo.cs @@ -88,6 +88,7 @@ namespace FlaxEditor.Gizmo desc.CullMode = CullMode.TwoSided; desc.VS = _shader.GPU.GetVS("VS_Grid"); desc.PS = _shader.GPU.GetPS("PS_Grid"); + desc.DepthWriteEnable = false; _psGrid.Init(ref desc); } diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 5ff02cf36..ce712bada 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -74,13 +74,13 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) _physicsHeightField = nullptr; _x = x; _z = z; - const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge; + const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge; _offset = Float3(_x * size, 0.0f, _z * size); _yOffset = 0.0f; _yHeight = 1.0f; for (int32 i = 0; i < Terrain::ChunksCount; i++) { - Chunks[i].Init(this, i % Terrain::Terrain::ChunksCountEdge, i / Terrain::Terrain::ChunksCountEdge); + Chunks[i].Init(this, i % Terrain::ChunksCountEdge, i / Terrain::ChunksCountEdge); } Heightmap = nullptr; for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) @@ -99,7 +99,7 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) } #endif #if TERRAIN_USE_PHYSICS_DEBUG - SAFE_DELETE(_debugLines); + SAFE_DELETE_GPU_RESOURCE(_debugLines); _debugLinesDirty = true; #endif #if USE_EDITOR @@ -117,6 +117,9 @@ TerrainPatch::~TerrainPatch() SAFE_DELETE(_dataSplatmap[i]); } #endif +#if TERRAIN_USE_PHYSICS_DEBUG + SAFE_DELETE_GPU_RESOURCE(_debugLines); +#endif } RawDataAsset* TerrainPatch::GetHeightfield() const @@ -2324,9 +2327,9 @@ void TerrainPatch::DrawPhysicsDebug(RenderView& view) { if (!_debugLines || _debugLinesDirty) CacheDebugLines(); - const Transform terrainTransform = _terrain->_transform; - const Transform localTransform(Vector3(0, _yOffset, 0), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ)); - const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld(); + const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge; + const Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ)); + const Matrix world = localTransform.GetWorld() * _terrain->_transform.GetWorld(); DebugDraw::DrawLines(_debugLines, world); } } @@ -2352,10 +2355,9 @@ const Array& TerrainPatch::GetCollisionTriangles() #define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y) - const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge; - const Transform terrainTransform = _terrain->_transform; + const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge; Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ)); - const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld(); + const Matrix world = localTransform.GetWorld() * _terrain->_transform.GetWorld(); for (int32 row = 0; row < rows - 1; row++) { @@ -2401,7 +2403,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge; + const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge; Transform transform; transform.Translation = _offset + Vector3(0, _yOffset, 0); transform.Orientation = Quaternion::Identity; @@ -2507,10 +2509,9 @@ void TerrainPatch::ExtractCollisionGeometry(Array& vertexBuffer, Array_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge; - const Transform terrainTransform = _terrain->_transform; + const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge; const Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Float3(_collisionScaleXZ, _yHeight, _collisionScaleXZ)); - const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld(); + const Matrix world = localTransform.GetWorld() * _terrain->_transform.GetWorld(); const int32 vertexCount = rows * cols; _collisionVertices.Resize(vertexCount); @@ -2569,7 +2570,7 @@ void TerrainPatch::Serialize(SerializeStream& stream, const void* otherObj) stream.JKEY("Chunks"); stream.StartArray(); - for (int32 i = 0; i < Terrain::Terrain::ChunksCount; i++) + for (int32 i = 0; i < Terrain::ChunksCount; i++) { stream.StartObject(); Chunks[i].Serialize(stream, other ? &other->Chunks[i] : nullptr); From e82bb639edf845e86059e2ae48c08eedfd6be0ff Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Mar 2025 19:12:29 +0100 Subject: [PATCH 24/35] Fix objects creation to ensure managed object ctor is always executed #3245 --- Source/Engine/Content/Content.cpp | 2 ++ Source/Engine/Level/Level.cpp | 6 ++++-- Source/Engine/Scripting/BinaryModule.cpp | 11 +++++++++++ Source/Engine/Scripting/ScriptingObject.cpp | 22 +++++++-------------- Source/Engine/Scripting/ScriptingType.h | 6 ++++++ 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 37e77fa64..98cb1a737 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -874,6 +874,7 @@ Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type) LOG(Error, "Cannot create virtual asset object."); return nullptr; } + asset->RegisterObject(); // Call initializer function asset->InitAsVirtual(); @@ -1041,6 +1042,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) LOAD_FAILED(); } #endif + result->RegisterObject(); // Register asset AssetsLocker.Lock(); diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index ccbe7a59a..cb565f651 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -956,10 +956,12 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou objects[i] = obj; if (obj) { - obj->RegisterObject(); + if (!obj->IsRegistered()) + obj->RegisterObject(); #if USE_EDITOR // Auto-create C# objects for all actors in Editor during scene load when running in async (so main thread already has all of them) - obj->CreateManaged(); + if (!obj->GetManagedInstance()) + obj->CreateManaged(); #endif } else diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 689010116..dd493d2de 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -796,6 +796,17 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp // Mark as managed type object->Flags |= ObjectFlags::IsManagedType; + // Initialize managed instance + if (params.Managed) + { + object->SetManagedInstance((MObject*)params.Managed); + } + else + { + // Invoke managed ctor (to match C++ logic) + object->CreateManaged(); + } + return object; } diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 7f9d92369..14ce087cf 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -412,7 +412,8 @@ void ScriptingObject::DestroyManaged() void ScriptingObject::RegisterObject() { - ASSERT(!IsRegistered()); + if (IsRegistered()) + return; Flags |= ObjectFlags::IsRegistered; Scripting::RegisterObject(this); } @@ -532,10 +533,6 @@ bool ManagedScriptingObject::CreateManaged() } #endif - // Ensure to be registered - if (!IsRegistered()) - RegisterObject(); - return false; } @@ -598,8 +595,7 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_Create1(MTypeObject* type) } // Create managed object - obj->CreateManaged(); - MObject* managedInstance = obj->GetManagedInstance(); + MObject* managedInstance = obj->GetOrCreateManagedInstance(); if (managedInstance == nullptr) { LOG(Error, "Cannot create managed instance for type \'{0}\'.", String(typeClass->GetFullName())); @@ -636,8 +632,7 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_Create2(MString* typeNameObj) } // Create managed object - obj->CreateManaged(); - MObject* managedInstance = obj->GetManagedInstance(); + MObject* managedInstance = obj->GetOrCreateManagedInstance(); if (managedInstance == nullptr) { LOG(Error, "Cannot create managed instance for type \'{0}\'.", String(typeName)); @@ -667,7 +662,8 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage const ScriptingType& scriptingType = module->Types[typeIndex]; // Create unmanaged object - const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex)); + ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex)); + params.Managed = managedInstance; // Link created managed instance to the unmanaged object ScriptingObject* obj = scriptingType.Script.Spawn(params); if (obj == nullptr) { @@ -675,9 +671,6 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage return; } - // Link created managed instance to the unmanaged object - obj->SetManagedInstance(managedInstance); - // Set default name for actors if (auto* actor = dynamic_cast(obj)) { @@ -689,8 +682,7 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage MCore::ScriptingObject::SetInternalValues(klass, managedInstance, obj, &id); // Register object - if (!obj->IsRegistered()) - obj->RegisterObject(); + obj->RegisterObject(); } DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceDeleted(ScriptingObject* obj) diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index f53778401..72cbaaac9 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -355,9 +355,15 @@ struct ScriptingObjectSpawnParams /// const ScriptingTypeHandle Type; + /// + /// Optional C# object instance to use for unmanaged object. + /// + void* Managed; + FORCE_INLINE ScriptingObjectSpawnParams(const Guid& id, const ScriptingTypeHandle& typeHandle) : ID(id) , Type(typeHandle) + , Managed(nullptr) { } }; From 819ce2222d57a84acf4e79032c66fc0f71b636ee Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Mar 2025 23:32:27 +0100 Subject: [PATCH 25/35] Refactor `ISceneContextWindow` into `ISceneEditingContext` with more utilities #3257 --- Source/Editor/Modules/SceneModule.cs | 2 +- .../Editor/SceneGraph/ISceneEditingContext.cs | 47 +++++++++++++++++++ Source/Editor/SceneGraph/RootNode.cs | 9 +++- Source/Editor/Utilities/Utils.cs | 6 +-- .../Editor/Viewport/PrefabWindowViewport.cs | 1 - .../Windows/Assets/PrefabWindow.Hierarchy.cs | 2 +- .../Windows/Assets/PrefabWindow.Selection.cs | 5 +- Source/Editor/Windows/Assets/PrefabWindow.cs | 9 +++- Source/Editor/Windows/EditGameWindow.cs | 2 +- Source/Editor/Windows/SceneEditorWindow.cs | 38 ++++++++------- 10 files changed, 91 insertions(+), 30 deletions(-) create mode 100644 Source/Editor/SceneGraph/ISceneEditingContext.cs diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index a144d6f4c..850e4df3d 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.Modules public override Undo Undo => Editor.Instance.Undo; /// - public override List Selection => _editor.SceneEditing.Selection; + public override ISceneEditingContext SceneContext => _editor.Windows.EditWin; } /// diff --git a/Source/Editor/SceneGraph/ISceneEditingContext.cs b/Source/Editor/SceneGraph/ISceneEditingContext.cs new file mode 100644 index 000000000..63c2cc9c2 --- /dev/null +++ b/Source/Editor/SceneGraph/ISceneEditingContext.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System.Collections.Generic; +using FlaxEditor.SceneGraph; +using FlaxEditor.Viewport; + +namespace FlaxEditor +{ + /// + /// Shared interface for scene editing utilities. + /// + public interface ISceneEditingContext + { + /// + /// Gets the main or last editor viewport used for scene editing within this context. + /// + EditorViewport Viewport { get; } + + /// + /// Gets the list of selected scene graph nodes in the editor context. + /// + List Selection { get; } + + /// + /// Selects the specified node. + /// + /// The node. + /// if set to true will use additive mode, otherwise will clear previous selection. + void Select(SceneGraphNode node, bool additive = false); + + /// + /// Deselects node. + /// + /// The node to deselect. + void Deselect(SceneGraphNode node); + + /// + /// Opends popup for renaming selected objects. + /// + void RenameSelection(); + + /// + /// Focuses selected objects. + /// + void FocusSelection(); + } +} diff --git a/Source/Editor/SceneGraph/RootNode.cs b/Source/Editor/SceneGraph/RootNode.cs index 68044cc1a..e387520da 100644 --- a/Source/Editor/SceneGraph/RootNode.cs +++ b/Source/Editor/SceneGraph/RootNode.cs @@ -168,7 +168,14 @@ namespace FlaxEditor.SceneGraph /// /// Gets the list of selected scene graph nodes in the editor context. + /// [Deprecated in v1.10] /// - public abstract List Selection { get; } + [Obsolete("Use SceneContext.Selection instead.")] + public List Selection => SceneContext.Selection; + + /// + /// Gets the list of selected scene graph nodes in the editor context. + /// + public abstract ISceneEditingContext SceneContext { get; } } } diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index c9600b089..22aa2015e 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1545,11 +1545,11 @@ namespace FlaxEditor.Utilities return path; } - internal static ISceneContextWindow GetSceneContext(this Control c) + internal static ISceneEditingContext GetSceneContext(this Control c) { - while (c != null && !(c is ISceneContextWindow)) + while (c != null && !(c is ISceneEditingContext)) c = c.Parent; - return c as ISceneContextWindow; + return c as ISceneEditingContext; } } } diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 256b41290..f5ef95bce 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -10,7 +10,6 @@ using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Previews; -using FlaxEditor.Viewport.Widgets; using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 081573b40..da85d91ff 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.Windows.Assets public override Undo Undo => _window.Undo; /// - public override List Selection => _window.Selection; + public override ISceneEditingContext SceneContext => _window; } /// diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs index 85dfc1595..48c0c5685 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs @@ -12,10 +12,7 @@ namespace FlaxEditor.Windows.Assets { public sealed partial class PrefabWindow { - /// - /// The current selection (readonly). - /// - public readonly List Selection = new List(); + private readonly List _selection = new List(); /// /// Occurs when selection gets changed. diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 533735c93..03d666478 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using System.Xml; using FlaxEditor.Content; using FlaxEditor.CustomEditors; @@ -19,7 +20,7 @@ namespace FlaxEditor.Windows.Assets /// /// /// - public sealed partial class PrefabWindow : AssetEditorWindowBase, IPresenterOwner, ISceneContextWindow + public sealed partial class PrefabWindow : AssetEditorWindowBase, IPresenterOwner, ISceneEditingContext { private readonly SplitPanel _split1; private readonly SplitPanel _split2; @@ -54,6 +55,9 @@ namespace FlaxEditor.Windows.Assets /// public PrefabWindowViewport Viewport => _viewport; + /// + public List Selection => _selection; + /// /// Gets the prefab objects properties editor. /// @@ -557,5 +561,8 @@ namespace FlaxEditor.Windows.Assets /// public EditorViewport PresenterViewport => _viewport; + + /// + EditorViewport ISceneEditingContext.Viewport => Viewport; } } diff --git a/Source/Editor/Windows/EditGameWindow.cs b/Source/Editor/Windows/EditGameWindow.cs index 6fb3c1a08..3ff213652 100644 --- a/Source/Editor/Windows/EditGameWindow.cs +++ b/Source/Editor/Windows/EditGameWindow.cs @@ -130,7 +130,7 @@ namespace FlaxEditor.Windows /// /// The viewport control. /// - public readonly MainEditorGizmoViewport Viewport; + public new readonly MainEditorGizmoViewport Viewport; /// /// Initializes a new instance of the class. diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs index 62922ebed..4a20ab7af 100644 --- a/Source/Editor/Windows/SceneEditorWindow.cs +++ b/Source/Editor/Windows/SceneEditorWindow.cs @@ -1,32 +1,18 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +using System.Collections.Generic; using FlaxEditor.SceneGraph; +using FlaxEditor.Viewport; using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.Windows { - /// - /// Shared interface for scene editing utilities. - /// - public interface ISceneContextWindow - { - /// - /// Opends popup for renaming selected objects. - /// - void RenameSelection(); - - /// - /// Focuses selected objects. - /// - void FocusSelection(); - } - /// /// Base class for editor windows dedicated to scene editing. /// /// - public abstract class SceneEditorWindow : EditorWindow, ISceneContextWindow + public abstract class SceneEditorWindow : EditorWindow, ISceneEditingContext { /// /// Initializes a new instance of the class. @@ -46,6 +32,24 @@ namespace FlaxEditor.Windows Editor.Windows.EditWin.Viewport.FocusSelection(); } + /// + public EditorViewport Viewport => Editor.Windows.EditWin.Viewport; + + /// + public List Selection => Editor.SceneEditing.Selection; + + /// + public void Select(SceneGraphNode node, bool additive = false) + { + Editor.SceneEditing.Select(node, additive); + } + + /// + public void Deselect(SceneGraphNode node) + { + Editor.SceneEditing.Deselect(node); + } + /// public void RenameSelection() { From a3822b666952b495390b0dd6165afe4356f583d0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Mar 2025 23:38:41 +0100 Subject: [PATCH 26/35] Fix spline node to support prefab editor #3257 --- .../CustomEditors/Dedicated/SplineEditor.cs | 23 +++---- Source/Editor/SceneGraph/Actors/SplineNode.cs | 60 ++++++++++--------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs index 9a33d82af..7ec4147d8 100644 --- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -92,7 +92,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { if (IsLinearTangentMode(spline, index) || IsSmoothInTangentMode(spline, index) || IsSmoothOutTangentMode(spline, index)) { - SetPointSmooth(spline, index); + SetPointSmooth(Editor, spline, index); } } } @@ -145,7 +145,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { if (!IsAlignedTangentMode(spline, index)) { - SetPointSmooth(spline, index); + SetPointSmooth(Editor, spline, index); } } @@ -176,7 +176,7 @@ namespace FlaxEditor.CustomEditors.Dedicated /// public override void OnSetMode(Spline spline, int index) { - SetTangentSmoothIn(spline, index); + SetTangentSmoothIn(Editor, spline, index); Editor.SetSelectTangentIn(spline, index); } } @@ -190,7 +190,7 @@ namespace FlaxEditor.CustomEditors.Dedicated /// public override void OnSetMode(Spline spline, int index) { - SetTangentSmoothOut(spline, index); + SetTangentSmoothOut(Editor, spline, index); Editor.SetSelectTangentOut(spline, index); } } @@ -756,14 +756,15 @@ namespace FlaxEditor.CustomEditors.Dedicated spline.UpdateSpline(); } - private static void SetTangentSmoothIn(Spline spline, int index) + private static void SetTangentSmoothIn(SplineEditor editor, Spline spline, int index) { var keyframe = spline.GetSplineKeyframe(index); // Auto smooth tangent if's linear if (keyframe.TangentIn.Translation.Length == 0) { - var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f); + var cameraTransform = editor.Presenter.Owner.PresenterViewport.ViewTransform.Translation; + var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f, cameraTransform); var previousKeyframe = spline.GetSplineKeyframe(index - 1); var tangentDirection = keyframe.Value.WorldToLocalVector(previousKeyframe.Value.Translation - keyframe.Value.Translation); tangentDirection = tangentDirection.Normalized * smoothRange; @@ -775,14 +776,15 @@ namespace FlaxEditor.CustomEditors.Dedicated spline.UpdateSpline(); } - private static void SetTangentSmoothOut(Spline spline, int index) + private static void SetTangentSmoothOut(SplineEditor editor, Spline spline, int index) { var keyframe = spline.GetSplineKeyframe(index); // Auto smooth tangent if's linear if (keyframe.TangentOut.Translation.Length == 0) { - var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f); + var cameraTransform = editor.Presenter.Owner.PresenterViewport.ViewTransform.Translation; + var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f, cameraTransform); var nextKeyframe = spline.GetSplineKeyframe(index + 1); var tangentDirection = keyframe.Value.WorldToLocalVector(nextKeyframe.Value.Translation - keyframe.Value.Translation); tangentDirection = tangentDirection.Normalized * smoothRange; @@ -795,7 +797,7 @@ namespace FlaxEditor.CustomEditors.Dedicated spline.UpdateSpline(); } - private static void SetPointSmooth(Spline spline, int index) + private static void SetPointSmooth(SplineEditor editor, Spline spline, int index) { var keyframe = spline.GetSplineKeyframe(index); var tangentInSize = keyframe.TangentIn.Translation.Length; @@ -803,7 +805,8 @@ namespace FlaxEditor.CustomEditors.Dedicated var isLastKeyframe = index >= spline.SplinePointsCount - 1; var isFirstKeyframe = index <= 0; - var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplinePoint(index), 10f); + var cameraTransform = editor.Presenter.Owner.PresenterViewport.ViewTransform.Translation; + var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplinePoint(index), 10f, cameraTransform); // Force smooth it's linear point if (tangentInSize == 0f) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 8e62347b2..555e3cb55 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.SceneGraph.Actors public override bool CanBeSelectedDirectly => true; - public override bool CanDuplicate => !Root?.Selection.Contains(ParentNode) ?? true; + public override bool CanDuplicate => !Root?.SceneContext.Selection.Contains(ParentNode) ?? true; public override bool CanDelete => true; @@ -176,7 +176,7 @@ namespace FlaxEditor.SceneGraph.Actors normal = -ray.Ray.Direction; var actor = (Spline)_node.Actor; var pos = actor.GetSplinePoint(Index); - var nodeSize = NodeSizeByDistance(Transform.Translation, PointNodeSize); + var nodeSize = NodeSizeByDistance(Transform.Translation, PointNodeSize, ray.View.Position); return new BoundingSphere(pos, nodeSize).Intersects(ref ray.Ray, out distance); } @@ -186,9 +186,10 @@ namespace FlaxEditor.SceneGraph.Actors var pos = actor.GetSplinePoint(Index); var tangentIn = actor.GetSplineTangent(Index, true).Translation; var tangentOut = actor.GetSplineTangent(Index, false).Translation; - var pointSize = NodeSizeByDistance(pos, PointNodeSize); - var tangentInSize = NodeSizeByDistance(tangentIn, TangentNodeSize); - var tangentOutSize = NodeSizeByDistance(tangentOut, TangentNodeSize); + var cameraTransform = Root.SceneContext.Viewport.ViewTransform.Translation; + var pointSize = NodeSizeByDistance(pos, PointNodeSize, cameraTransform); + var tangentInSize = NodeSizeByDistance(tangentIn, TangentNodeSize, cameraTransform); + var tangentOutSize = NodeSizeByDistance(tangentOut, TangentNodeSize, cameraTransform); // Draw spline path ParentNode.OnDebugDraw(data); @@ -262,7 +263,7 @@ namespace FlaxEditor.SceneGraph.Actors normal = -ray.Ray.Direction; var actor = (Spline)_node.Actor; var pos = actor.GetSplineTangent(_index, _isIn).Translation; - var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize); + var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize, ray.View.Position); return new BoundingSphere(pos, tangentSize).Intersects(ref ray.Ray, out distance); } @@ -274,7 +275,8 @@ namespace FlaxEditor.SceneGraph.Actors // Draw selected tangent highlight var actor = (Spline)_node.Actor; var pos = actor.GetSplineTangent(_index, _isIn).Translation; - var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize); + var cameraTransform = Root.SceneContext.Viewport.ViewTransform.Translation; + var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize, cameraTransform); DebugDraw.DrawSphere(new BoundingSphere(pos, tangentSize), Color.YellowGreen, 0, false); } @@ -314,8 +316,8 @@ namespace FlaxEditor.SceneGraph.Actors } // If this node's point is selected - var selection = Editor.Instance.SceneEditing.Selection; - if (selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this) + var selection = Root?.SceneContext.Selection; + if (selection != null && selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this) { var mouse = Input.Mouse; var keyboard = Input.Keyboard; @@ -324,7 +326,7 @@ namespace FlaxEditor.SceneGraph.Actors EditSplineWithSnap(selectedPoint); var canAddSplinePoint = mouse.PositionDelta == Float2.Zero && mouse.Position != Float2.Zero; - var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && mouse.GetButtonDown(MouseButton.Right); + var requestAddSplinePoint = keyboard.GetKey(KeyboardKeys.Control) && mouse.GetButtonDown(MouseButton.Right); if (requestAddSplinePoint && canAddSplinePoint) AddSplinePoint(selectedPoint); } @@ -345,9 +347,7 @@ namespace FlaxEditor.SceneGraph.Actors while (srcCount > dstCount) { var node = ActorChildNodes[srcCount-- - 1]; - // TODO: support selection interface inside SceneGraph nodes (eg. on Root) so prefab editor can handle this too - if (Editor.Instance.SceneEditing.Selection.Contains(node)) - Editor.Instance.SceneEditing.Deselect(); + Root?.SceneContext.Deselect(node); node.Dispose(); } @@ -363,22 +363,23 @@ namespace FlaxEditor.SceneGraph.Actors } } - private unsafe void AddSplinePoint(SplinePointNode selectedPoint) + private void AddSplinePoint(SplinePointNode selectedPoint) { // Check mouse hit on scene + var root = Root; var spline = (Spline)Actor; - var viewport = Editor.Instance.Windows.EditWin.Viewport; + var viewport = root.SceneContext.Viewport; var mouseRay = viewport.MouseRay; var viewRay = viewport.ViewRay; var flags = RayCastData.FlagTypes.SkipColliders | RayCastData.FlagTypes.SkipEditorPrimitives; - var hit = Editor.Instance.Scene.Root.RayCast(ref mouseRay, ref viewRay, out var closest, out var normal, flags); + var hit = root.RayCast(ref mouseRay, ref viewRay, out var closest, out var normal, flags); if (hit == null) return; // Undo data var oldSpline = spline.SplineKeyframes; var editAction = new EditSplineAction(spline, oldSpline); - Root.Undo.AddAction(editAction); + root.Undo.AddAction(editAction); // Get spline point to duplicate var hitPoint = mouseRay.Position + mouseRay.Direction * closest; @@ -430,7 +431,7 @@ namespace FlaxEditor.SceneGraph.Actors // Select new point node SyncSplineKeyframeWithNodes(); - Editor.Instance.SceneEditing.Select(ChildNodes[newPointIndex]); + root.SceneContext.Select(ChildNodes[newPointIndex]); spline.UpdateSpline(); } @@ -443,6 +444,7 @@ namespace FlaxEditor.SceneGraph.Actors allSplinesInView.Remove(spline); if (allSplinesInView.Count == 0) return; + var cameraTransform = Root.SceneContext.Viewport.ViewTransform.Translation; var snappedOnSplinePoint = false; for (int i = 0; i < allSplinesInView.Count; i++) @@ -450,7 +452,7 @@ namespace FlaxEditor.SceneGraph.Actors for (int x = 0; x < allSplinesInView[i].SplineKeyframes.Length; x++) { var keyframePosition = allSplinesInView[i].GetSplinePoint(x); - var pointIndicatorSize = NodeSizeByDistance(keyframePosition, SnapPointIndicatorSize); + var pointIndicatorSize = NodeSizeByDistance(keyframePosition, SnapPointIndicatorSize, cameraTransform); var keyframeBounds = new BoundingSphere(keyframePosition, pointIndicatorSize); DebugDraw.DrawSphere(keyframeBounds, Color.Red, 0, false); @@ -466,7 +468,7 @@ namespace FlaxEditor.SceneGraph.Actors if (!snappedOnSplinePoint) { var nearSplineSnapPoint = GetNearSplineSnapPosition(selectedPoint.Transform.Translation, allSplinesInView); - var snapIndicatorSize = NodeSizeByDistance(nearSplineSnapPoint, SnapIndicatorSize); + var snapIndicatorSize = NodeSizeByDistance(nearSplineSnapPoint, SnapIndicatorSize, cameraTransform); var snapBounds = new BoundingSphere(nearSplineSnapPoint, snapIndicatorSize); if (snapBounds.Intersects(selectedPointBounds)) { @@ -558,11 +560,16 @@ namespace FlaxEditor.SceneGraph.Actors } } - private static List GetSplinesInView() + private List GetSplinesInView() { - var splines = Level.GetActors(true); + Spline[] splines; + var sceneContext = Root.SceneContext; + if (sceneContext is Windows.Assets.PrefabWindow prefabWindow) + splines = new Spline[0]; // TODO: add GetActors or similar utility to SceneContext and use Level.GetActors(..) with specific root actor + else + splines = Level.GetActors(true); var result = new List(); - var viewBounds = Editor.Instance.Windows.EditWin.Viewport.ViewFrustum; + var viewBounds = sceneContext.Viewport.ViewFrustum; foreach (var s in splines) { var contains = viewBounds.Contains(s.EditorBox); @@ -576,7 +583,6 @@ namespace FlaxEditor.SceneGraph.Actors { var nearPoint = splines[0].GetSplinePointClosestToPoint(position); var nearDistance = Vector3.Distance(nearPoint, position); - for (int i = 1; i < splines.Count; i++) { var point = splines[i].GetSplinePointClosestToPoint(position); @@ -587,14 +593,12 @@ namespace FlaxEditor.SceneGraph.Actors nearDistance = distance; } } - return nearPoint; } - internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize) + internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize, Vector3 viewPosition) { - var cameraTransform = Editor.Instance.Windows.EditWin.Viewport.ViewportCamera.Viewport.ViewTransform; - var distance = Vector3.Distance(cameraTransform.Translation, nodePosition) / 100; + var distance = Vector3.Distance(viewPosition, nodePosition) / 100; return distance * nodeSize; } From a5e8f7d405ef6d70b163e8de93bf87d69c0317a0 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 3 Mar 2025 21:42:54 -0600 Subject: [PATCH 27/35] Move to custom tool method within platform --- Source/Editor/Windows/GameCookerWindow.cs | 536 +++++++++++----------- 1 file changed, 273 insertions(+), 263 deletions(-) diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 0088cdf86..261090d4a 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -196,6 +196,12 @@ namespace FlaxEditor.Windows var label = layout.Label(text, TextAlignment.Center); label.Label.AutoHeight = true; } + + /// + /// Used to add platform specific tools if available. + /// + /// The layout to start the tools at. + public virtual void OnCustomToolsLayout(LayoutElementsContainer layout) { } public virtual void Build() { @@ -237,6 +243,272 @@ namespace FlaxEditor.Windows class Android : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.AndroidARM64; + + /// + public override void OnCustomToolsLayout(LayoutElementsContainer layout) + { + base.OnCustomToolsLayout(layout); + + // Add emulation options to android tab. + layout.Space(5); + var emulatorGroup = layout.Group("Tools"); + var sdkPath = Environment.GetEnvironmentVariable("ANDROID_HOME"); + if (string.IsNullOrEmpty(sdkPath)) + sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK"); + emulatorGroup.Label($"SDK path: {sdkPath}"); + + // AVD and starting emulator + var avdGroup = emulatorGroup.Group("AVD Emulator"); + avdGroup.Label("Note: Create AVDs using Android Studio."); + avdGroup.Panel.IsClosed = false; + var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button; + var avdListGroup = avdGroup.Group("AVD List"); + avdListGroup.Panel.IsClosed = false; + var noAvdLabel = avdListGroup.Label("No AVDs detected. Click Refresh.", TextAlignment.Center).Label; + var avdListTree = new Tree(false) + { + Parent = avdListGroup.Panel, + }; + refreshAVDListButton.Clicked += () => + { + if (avdListTree.Children.Count > 0) + avdListTree.DisposeChildren(); + + var processStartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"), + Arguments = "-list-avds", + RedirectStandardOutput = true, + CreateNoWindow = true, + }; + + var process = new System.Diagnostics.Process + { + StartInfo = processStartInfo + }; + process.Start(); + var output = new string(process.StandardOutput.ReadToEnd()); + /* + CreateProcessSettings processSettings = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"), + Arguments = "-list-avds", + HiddenWindow = false, + SaveOutput = true, + WaitForEnd = true, + }; + //processSettings.ShellExecute = true; + FlaxEngine.Platform.CreateProcess(ref processSettings); + + var output = new string(processSettings.Output);*/ + if (output.Length == 0) + { + noAvdLabel.Visible = true; + FlaxEditor.Editor.LogWarning("No AVDs detected."); + return; + } + noAvdLabel.Visible = false; + var splitOutput = output.Split('\n'); + foreach (var line in splitOutput) + { + if (string.IsNullOrEmpty(line.Trim())) + continue; + var item = new TreeNode + { + Text = line.Trim(), + Parent = avdListTree, + }; + } + avdListGroup.Panel.IsClosed = false; + }; + + avdGroup.Label("Emulator AVD Commands:"); + var commandsTextBox = avdGroup.TextBox().TextBox; + commandsTextBox.IsMultiline = false; + commandsTextBox.Text = "-no-snapshot-load -no-boot-anim"; // TODO: save user changes + + var startEmulatorButton = avdGroup.Button("Start AVD Emulator").Button; + startEmulatorButton.TooltipText = "Starts selected AVD from list."; + startEmulatorButton.Clicked += () => + { + if (avdListTree.Selection.Count == 0) + return; + + CreateProcessSettings processSettings = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"), + Arguments = $"-avd {avdListTree.Selection[0].Text} {commandsTextBox.Text}", + HiddenWindow = true, + SaveOutput = false, + WaitForEnd = false, + }; + processSettings.ShellExecute = true; + FlaxEngine.Platform.CreateProcess(ref processSettings); + }; + + emulatorGroup.Space(2); + + // Device + var installGroup = emulatorGroup.Group("Install"); + installGroup.Panel.IsClosed = false; + installGroup.Label("Note: Used to install to AVD or physical devices."); + var refreshDeviceListButton = installGroup.Button("Refresh device list").Button; + var deviceListGroup = installGroup.Group("List of devices"); + deviceListGroup.Panel.IsClosed = false; + var noDevicesLabel = deviceListGroup.Label("No devices found. Click Refresh.", TextAlignment.Center).Label; + var deviceListTree = new Tree(false) + { + Parent = deviceListGroup.Panel, + }; + refreshDeviceListButton.Clicked += () => + { + if (deviceListTree.Children.Count > 0) + deviceListTree.DisposeChildren(); + + var processStartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = "devices -l", + RedirectStandardOutput = true, + CreateNoWindow = true, + }; + + var process = new System.Diagnostics.Process + { + StartInfo = processStartInfo + }; + process.Start(); + var output = new string(process.StandardOutput.ReadToEnd()); + /* + CreateProcessSettings processSettings = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = "devices -l", + HiddenWindow = false, + //SaveOutput = true, + WaitForEnd = true, + }; + processSettings.SaveOutput = true; + processSettings.ShellExecute = false; + FlaxEngine.Platform.CreateProcess(ref processSettings); + + var output = new string(processSettings.Output); + */ + + if (output.Length > 0 && !output.Equals("List of devices attached", StringComparison.Ordinal)) + { + noDevicesLabel.Visible = false; + var splitLines = output.Split('\n'); + foreach (var line in splitLines) + { + if (line.Trim().Equals("List of devices attached", StringComparison.Ordinal) || string.IsNullOrEmpty(line.Trim())) + continue; + + var tab = line.Split("device "); + if (tab.Length < 2) + continue; + var item = new TreeNode + { + Text = $"{tab[0].Trim()} - {tab[1].Trim()}", + Tag = tab[0].Trim(), + Parent = deviceListTree, + }; + } + } + else + { + noDevicesLabel.Visible = true; + } + + deviceListGroup.Panel.IsClosed = false; + }; + + var autoStart = installGroup.Checkbox("Try to auto start activity on device."); + var installButton = installGroup.Button("Install APK to Device").Button; + installButton.TooltipText = "Installs APK from the output folder to the selected device."; + installButton.Clicked += () => + { + if (deviceListTree.Selection.Count == 0) + return; + + // Get built APK at output path + string output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(Output)); + if (!Directory.Exists(output)) + { + FlaxEditor.Editor.LogWarning("Can not copy APK because output folder does not exist."); + return; + } + + var apkFiles = Directory.GetFiles(output, "*.apk"); + if (apkFiles.Length == 0) + { + FlaxEditor.Editor.LogWarning("Can not copy APK because no .apk files were found in output folder."); + return; + } + + string apkFilesString = string.Empty; + for (int i = 0; i < apkFiles.Length; i++) + { + var file = apkFiles[i]; + if (i == 0) + { + apkFilesString = $"\"{file}\""; + continue; + } + apkFilesString += $" \"{file}\""; + } + + CreateProcessSettings processSettings = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = $"-s {deviceListTree.Selection[0].Tag} {(apkFiles.Length > 1 ? "install-multiple" : "install")} {apkFilesString}", + LogOutput = true, + }; + FlaxEngine.Platform.CreateProcess(ref processSettings); + + if (autoStart.CheckBox.Checked) + { + var gameSettings = GameSettings.Load(); + var productName = gameSettings.ProductName.Replace(" ", "").ToLower(); + var companyName = gameSettings.CompanyName.Replace(" ", "").ToLower(); + CreateProcessSettings processSettings1 = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = $"shell am start -n com.{companyName}.{productName}/com.flaxengine.GameActivity", + LogOutput = true, + }; + FlaxEngine.Platform.CreateProcess(ref processSettings1); + } + }; + + var adbLogButton = emulatorGroup.Button("Start adb log collecting").Button; + adbLogButton.TooltipText = "In debug and development builds the engine and game logs can be output directly to the adb."; + adbLogButton.Clicked += () => + { + var processStartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = "logcat Flax:I *:S", + CreateNoWindow = false, + WindowStyle = ProcessWindowStyle.Normal, + }; + + var process = new System.Diagnostics.Process + { + StartInfo = processStartInfo + }; + process.Start(); + /* + CreateProcessSettings processSettings = new CreateProcessSettings + { + FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), + Arguments = $"logcat Flax:I *:S", + WaitForEnd = false, + }; + FlaxEngine.Platform.CreateProcess(ref processSettings); + */ + }; + } } class Switch : Platform @@ -347,269 +619,7 @@ namespace FlaxEditor.Windows _buildButton = layout.Button("Build").Button; _buildButton.Clicked += OnBuildClicked; - - // Add emulation options to android tab. - if (_platform == PlatformType.Android) - { - layout.Space(5); - var emulatorGroup = layout.Group("Tools"); - var sdkPath = Environment.GetEnvironmentVariable("ANDROID_HOME"); - if (string.IsNullOrEmpty(sdkPath)) - sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK"); - emulatorGroup.Label($"SDK path: {sdkPath}"); - - // AVD and starting emulator - var avdGroup = emulatorGroup.Group("AVD Emulator"); - avdGroup.Label("Note: Create AVDs using Android Studio."); - avdGroup.Panel.IsClosed = false; - var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button; - var avdListGroup = avdGroup.Group("AVD List"); - avdListGroup.Panel.IsClosed = false; - var noAvdLabel = avdListGroup.Label("No AVDs detected. Click Refresh.", TextAlignment.Center).Label; - var avdListTree = new Tree(false) - { - Parent = avdListGroup.Panel, - }; - refreshAVDListButton.Clicked += () => - { - if (avdListTree.Children.Count > 0) - avdListTree.DisposeChildren(); - - var processStartInfo = new System.Diagnostics.ProcessStartInfo - { - FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"), - Arguments = "-list-avds", - RedirectStandardOutput = true, - CreateNoWindow = true, - }; - - var process = new System.Diagnostics.Process - { - StartInfo = processStartInfo - }; - process.Start(); - var output = new string(process.StandardOutput.ReadToEnd()); - /* - CreateProcessSettings processSettings = new CreateProcessSettings - { - FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"), - Arguments = "-list-avds", - HiddenWindow = false, - SaveOutput = true, - WaitForEnd = true, - }; - //processSettings.ShellExecute = true; - FlaxEngine.Platform.CreateProcess(ref processSettings); - - var output = new string(processSettings.Output);*/ - if (output.Length == 0) - { - noAvdLabel.Visible = true; - FlaxEditor.Editor.LogWarning("No AVDs detected."); - return; - } - noAvdLabel.Visible = false; - var splitOutput = output.Split('\n'); - foreach (var line in splitOutput) - { - if (string.IsNullOrEmpty(line.Trim())) - continue; - var item = new TreeNode - { - Text = line.Trim(), - Parent = avdListTree, - }; - } - avdListGroup.Panel.IsClosed = false; - }; - - avdGroup.Label("Emulator AVD Commands:"); - var commandsTextBox = avdGroup.TextBox().TextBox; - commandsTextBox.IsMultiline = false; - commandsTextBox.Text = "-no-snapshot-load -no-boot-anim"; // TODO: save user changes - - var startEmulatorButton = avdGroup.Button("Start AVD Emulator").Button; - startEmulatorButton.TooltipText = "Starts selected AVD from list."; - startEmulatorButton.Clicked += () => - { - if (avdListTree.Selection.Count == 0) - return; - - CreateProcessSettings processSettings = new CreateProcessSettings - { - FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"), - Arguments = $"-avd {avdListTree.Selection[0].Text} {commandsTextBox.Text}", - HiddenWindow = true, - SaveOutput = false, - WaitForEnd = false, - }; - processSettings.ShellExecute = true; - FlaxEngine.Platform.CreateProcess(ref processSettings); - }; - - emulatorGroup.Space(2); - - // Device - var installGroup = emulatorGroup.Group("Install"); - installGroup.Panel.IsClosed = false; - installGroup.Label("Note: Used to install to AVD or physical devices."); - var refreshDeviceListButton = installGroup.Button("Refresh device list").Button; - var deviceListGroup = installGroup.Group("List of devices"); - deviceListGroup.Panel.IsClosed = false; - var noDevicesLabel = deviceListGroup.Label("No devices found. Click Refresh.", TextAlignment.Center).Label; - var deviceListTree = new Tree(false) - { - Parent = deviceListGroup.Panel, - }; - refreshDeviceListButton.Clicked += () => - { - if (deviceListTree.Children.Count > 0) - deviceListTree.DisposeChildren(); - - var processStartInfo = new System.Diagnostics.ProcessStartInfo - { - FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), - Arguments = "devices -l", - RedirectStandardOutput = true, - CreateNoWindow = true, - }; - - var process = new System.Diagnostics.Process - { - StartInfo = processStartInfo - }; - process.Start(); - var output = new string(process.StandardOutput.ReadToEnd()); - /* - CreateProcessSettings processSettings = new CreateProcessSettings - { - FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), - Arguments = "devices -l", - HiddenWindow = false, - //SaveOutput = true, - WaitForEnd = true, - }; - processSettings.SaveOutput = true; - processSettings.ShellExecute = false; - FlaxEngine.Platform.CreateProcess(ref processSettings); - - var output = new string(processSettings.Output); - */ - - if (output.Length > 0 && !output.Equals("List of devices attached", StringComparison.Ordinal)) - { - noDevicesLabel.Visible = false; - var splitLines = output.Split('\n'); - foreach (var line in splitLines) - { - if (line.Trim().Equals("List of devices attached", StringComparison.Ordinal) || string.IsNullOrEmpty(line.Trim())) - continue; - - var tab = line.Split("device "); - if (tab.Length < 2) - continue; - var item = new TreeNode - { - Text = $"{tab[0].Trim()} - {tab[1].Trim()}", - Tag = tab[0].Trim(), - Parent = deviceListTree, - }; - } - } - else - { - noDevicesLabel.Visible = true; - } - - deviceListGroup.Panel.IsClosed = false; - }; - - var autoStart = installGroup.Checkbox("Try to auto start activity on device."); - var installButton = installGroup.Button("Install APK to Device").Button; - installButton.TooltipText = "Installs APK from the output folder to the selected device."; - installButton.Clicked += () => - { - if (deviceListTree.Selection.Count == 0) - return; - - // Get built APK at output path - string output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(proxy.PerPlatformOptions[_platform].Output)); - if (!Directory.Exists(output)) - { - FlaxEditor.Editor.LogWarning("Can not copy APK because output folder does not exist."); - return; - } - - var apkFiles = Directory.GetFiles(output, "*.apk"); - if (apkFiles.Length == 0) - { - FlaxEditor.Editor.LogWarning("Can not copy APK because no .apk files were found in output folder."); - return; - } - - string apkFilesString = string.Empty; - for (int i = 0; i < apkFiles.Length; i++) - { - var file = apkFiles[i]; - if (i == 0) - { - apkFilesString = $"\"{file}\""; - continue; - } - apkFilesString += $" \"{file}\""; - } - - CreateProcessSettings processSettings = new CreateProcessSettings - { - FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), - Arguments = $"-s {deviceListTree.Selection[0].Tag} {(apkFiles.Length > 1 ? "install-multiple" : "install")} {apkFilesString}", - LogOutput = true, - }; - FlaxEngine.Platform.CreateProcess(ref processSettings); - - if (autoStart.CheckBox.Checked) - { - var gameSettings = GameSettings.Load(); - var productName = gameSettings.ProductName.Replace(" ", "").ToLower(); - var companyName = gameSettings.CompanyName.Replace(" ", "").ToLower(); - CreateProcessSettings processSettings1 = new CreateProcessSettings - { - FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), - Arguments = $"shell am start -n com.{companyName}.{productName}/com.flaxengine.GameActivity", - LogOutput = true, - }; - FlaxEngine.Platform.CreateProcess(ref processSettings1); - } - }; - - var adbLogButton = emulatorGroup.Button("Start adb log collecting").Button; - adbLogButton.TooltipText = "In debug and development builds the engine and game logs can be output directly to the adb."; - adbLogButton.Clicked += () => - { - var processStartInfo = new System.Diagnostics.ProcessStartInfo - { - FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), - Arguments = "logcat Flax:I *:S", - CreateNoWindow = false, - WindowStyle = ProcessWindowStyle.Normal, - }; - - var process = new System.Diagnostics.Process - { - StartInfo = processStartInfo - }; - process.Start(); - /* - CreateProcessSettings processSettings = new CreateProcessSettings - { - FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"), - Arguments = $"logcat Flax:I *:S", - WaitForEnd = false, - }; - FlaxEngine.Platform.CreateProcess(ref processSettings); - */ - }; - } + platformObj.OnCustomToolsLayout(layout); } else { From 35fa63852c533bda6e96050e53009b3ba9b4890d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Mar 2025 16:06:48 +0100 Subject: [PATCH 28/35] 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 29/35] 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 30/35] 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 => From 5ffa3eb83731ea32d69339ff2bc2a6bf6861d414 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Mar 2025 22:12:35 +0100 Subject: [PATCH 31/35] Fix getting Control from null ControlReference #3123 --- Source/Engine/UI/ControlReference.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/Engine/UI/ControlReference.cs b/Source/Engine/UI/ControlReference.cs index 0bcd54374..68f50fa23 100644 --- a/Source/Engine/UI/ControlReference.cs +++ b/Source/Engine/UI/ControlReference.cs @@ -43,9 +43,11 @@ namespace FlaxEngine { get { - if (_uiControl != null && _uiControl.Control is T t) + if (_uiControl == null) + return null; + if (_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."); + Debug.Write(LogType.Warning, "Trying to get Control from ControlReference but UIControl.Control is null, or the correct type."); return null; } } @@ -66,7 +68,7 @@ namespace FlaxEngine } else { - Debug.Write(LogType.Warning, "Trying to set ControlReference but UIControl.Control is null or UIControl.Control is not the correct type."); + Debug.Write(LogType.Warning, "Trying to set UIControl but UIControl.Control is null or not the correct type."); } } } From 668511a72705e9dd9b243acd289082c71d4947b0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Mar 2025 22:37:05 +0100 Subject: [PATCH 32/35] Fix error in XML docs processing #2724 --- Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs index 311767caf..18233c162 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs @@ -234,7 +234,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing { if (methodGenericMap.TryGetValue(type.Name, out var methodIndex)) return "``" + methodIndex; - return "`" + typeGenericMap[type.Name]; + if (typeGenericMap.TryGetValue(type.Name, out var typeKey)) + return "`" + typeKey; + return "`"; } if (type.HasElementType) { From 06665734e730af765baa9b056d1fe665692a5b96 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Mar 2025 22:57:58 +0100 Subject: [PATCH 33/35] Fix `Platform::GetCacheLineSize` to be deprecated in favor of `CPUInfo` --- Source/Engine/Platform/Android/AndroidPlatform.cpp | 5 ----- Source/Engine/Platform/Android/AndroidPlatform.h | 1 - Source/Engine/Platform/Apple/ApplePlatform.cpp | 5 ----- Source/Engine/Platform/Apple/ApplePlatform.h | 1 - Source/Engine/Platform/Base/PlatformBase.cpp | 5 +++++ Source/Engine/Platform/Base/PlatformBase.h | 3 ++- Source/Engine/Platform/Linux/LinuxPlatform.cpp | 5 ----- Source/Engine/Platform/Linux/LinuxPlatform.h | 1 - Source/Engine/Platform/Win32/Win32Platform.cpp | 5 ----- Source/Engine/Platform/Win32/Win32Platform.h | 1 - 10 files changed, 7 insertions(+), 25 deletions(-) diff --git a/Source/Engine/Platform/Android/AndroidPlatform.cpp b/Source/Engine/Platform/Android/AndroidPlatform.cpp index c4ef0379c..591943890 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.cpp +++ b/Source/Engine/Platform/Android/AndroidPlatform.cpp @@ -709,11 +709,6 @@ CPUInfo AndroidPlatform::GetCPUInfo() return AndroidCpu; } -int32 AndroidPlatform::GetCacheLineSize() -{ - return AndroidCpu.CacheLineSize; -} - MemoryStats AndroidPlatform::GetMemoryStats() { const uint64 pageSize = getpagesize(); diff --git a/Source/Engine/Platform/Android/AndroidPlatform.h b/Source/Engine/Platform/Android/AndroidPlatform.h index 4d8955627..c300a0f2a 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.h +++ b/Source/Engine/Platform/Android/AndroidPlatform.h @@ -81,7 +81,6 @@ public: } static bool Is64BitPlatform(); static CPUInfo GetCPUInfo(); - static int32 GetCacheLineSize(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); static uint64 GetCurrentThreadID() diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index 4844c41aa..6d9b19296 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -134,11 +134,6 @@ CPUInfo ApplePlatform::GetCPUInfo() return Cpu; } -int32 ApplePlatform::GetCacheLineSize() -{ - return Cpu.CacheLineSize; -} - MemoryStats ApplePlatform::GetMemoryStats() { MemoryStats result; diff --git a/Source/Engine/Platform/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h index 9f0fddebf..6f16c07e8 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.h +++ b/Source/Engine/Platform/Apple/ApplePlatform.h @@ -67,7 +67,6 @@ public: } static bool Is64BitPlatform(); static CPUInfo GetCPUInfo(); - static int32 GetCacheLineSize(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); static uint64 GetCurrentThreadID(); diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 6efa3c8b4..93560b1e8 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -257,6 +257,11 @@ bool PlatformBase::Is64BitApp() #endif } +int32 PlatformBase::GetCacheLineSize() +{ + return (int32)Platform::GetCPUInfo().CacheLineSize; +} + void PlatformBase::Fatal(const Char* msg, void* context) { // Check if is already during fatal state diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index d5ff4c59f..e1049af70 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -357,9 +357,10 @@ public: /// /// Gets the CPU cache line size. + /// [Deprecated in v1.10] /// /// The cache line size. - API_PROPERTY() static int32 GetCacheLineSize() = delete; + API_PROPERTY() DEPRECATED("Use CacheLineSize field from CPUInfo.") static int32 GetCacheLineSize(); /// /// Gets the current memory stats. diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 2c951effa..c017a8040 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -1766,11 +1766,6 @@ CPUInfo LinuxPlatform::GetCPUInfo() return UnixCpu; } -int32 LinuxPlatform::GetCacheLineSize() -{ - return UnixCpu.CacheLineSize; -} - MemoryStats LinuxPlatform::GetMemoryStats() { // Get memory usage diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index 10c81f436..864e27812 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.h +++ b/Source/Engine/Platform/Linux/LinuxPlatform.h @@ -95,7 +95,6 @@ public: } static bool Is64BitPlatform(); static CPUInfo GetCPUInfo(); - static int32 GetCacheLineSize(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); static uint64 GetCurrentThreadID() diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index a9aa784c2..8525221b2 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -304,11 +304,6 @@ CPUInfo Win32Platform::GetCPUInfo() return CpuInfo; } -int32 Win32Platform::GetCacheLineSize() -{ - return CpuInfo.CacheLineSize; -} - MemoryStats Win32Platform::GetMemoryStats() { // Get memory stats diff --git a/Source/Engine/Platform/Win32/Win32Platform.h b/Source/Engine/Platform/Win32/Win32Platform.h index 5d020a0fd..70b44876f 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.h +++ b/Source/Engine/Platform/Win32/Win32Platform.h @@ -90,7 +90,6 @@ public: static void FreePages(void* ptr); static bool Is64BitPlatform(); static CPUInfo GetCPUInfo(); - static int32 GetCacheLineSize(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); static uint64 GetCurrentProcessId(); From bc6f4f50cbea86f2f2044460d1dd6080a545b844 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Mar 2025 18:55:03 +0100 Subject: [PATCH 34/35] Fix serialization of `ControlReference` when used in prefabs #3262 --- Source/Engine/Serialization/JsonConverters.cs | 12 +++++- Source/Engine/Serialization/JsonSerializer.cs | 17 ++++++++ Source/Engine/UI/ControlReference.cs | 40 +++++++++++++------ 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index f225c29d8..6120ba704 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -492,6 +492,16 @@ namespace FlaxEngine.Json writer.WriteValue(JsonSerializer.GetStringID(&id)); } + /// + public override void WriteJsonDiff(JsonWriter writer, object value, object other, Newtonsoft.Json.JsonSerializer serializer) + { + if (value is IControlReference valueRef && + other is IControlReference otherRef && + JsonSerializer.SceneObjectEquals(valueRef.UIControl, otherRef.UIControl)) + return; + base.WriteJsonDiff(writer, value, other, serializer); + } + /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { @@ -499,7 +509,7 @@ namespace FlaxEngine.Json if (reader.TokenType == JsonToken.String && result is IControlReference controlReference) { JsonSerializer.ParseID((string)reader.Value, out var id); - controlReference.UIControl = Object.Find(ref id); + controlReference.Load(Object.Find(ref id)); } return result; } diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 59b13c549..a87eacb9f 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -228,6 +228,23 @@ namespace FlaxEngine.Json CacheManagedOnly.Dispose(); } + /// + /// The default implementation of the values comparision function used by the serialization system. + /// + /// The object a. + /// The object b. + /// True if both objects are equal, otherwise false. + public static bool SceneObjectEquals(SceneObject objA, SceneObject objB) + { + if (objA == objB) + return true; + if (objA == null || objB == null) + return false; + if (objA.HasPrefabLink && objB.HasPrefabLink) + return objA.PrefabObjectID == objB.PrefabObjectID; + return false; + } + /// /// The default implementation of the values comparision function used by the serialization system. /// diff --git a/Source/Engine/UI/ControlReference.cs b/Source/Engine/UI/ControlReference.cs index 68f50fa23..eee093909 100644 --- a/Source/Engine/UI/ControlReference.cs +++ b/Source/Engine/UI/ControlReference.cs @@ -22,6 +22,12 @@ namespace FlaxEngine /// Gets the type of the control the interface uses. /// public Type ControlType { get; } + + /// + /// Sets control ref by force - used during loading when is not loaded yet. + /// + /// The reference. + internal void Load(UIControl control); } /// @@ -43,11 +49,12 @@ namespace FlaxEngine { get { - if (_uiControl == null) + var control = _uiControl?.Control; + if (control == null) return null; - if (_uiControl.Control is T t) + if (control is T t) return t; - Debug.Write(LogType.Warning, "Trying to get Control from ControlReference but UIControl.Control is null, or the correct type."); + Debug.Write(LogType.Warning, $"Trying to get Control from ControlReference but UIControl.Control is not correct type. It should be {typeof(T)} but is {control.GetType()}."); return null; } } @@ -58,17 +65,18 @@ namespace FlaxEngine get => _uiControl; set { + var control = value?.Control; if (value == null) { _uiControl = null; } - else if (value.Control is T t) + else if (control is T) { _uiControl = value; } else { - Debug.Write(LogType.Warning, "Trying to set UIControl but UIControl.Control is null or not the correct type."); + Debug.Write(LogType.Warning, $"Trying to set UIControl but UIControl.Control is not the correct type. It should be {typeof(T)} but is {control.GetType()}."); } } } @@ -76,6 +84,12 @@ namespace FlaxEngine /// public Type ControlType => typeof(T); + /// + public void Load(UIControl value) + { + _uiControl = value; + } + /// public override string ToString() { @@ -92,26 +106,26 @@ namespace FlaxEngine public int CompareTo(object obj) { if (obj is IControlReference other) - return CompareTo(other); - return 0; + return Json.JsonSerializer.SceneObjectEquals(_uiControl, other.UIControl) ? 0 : 1; + return 1; } /// public int CompareTo(ControlReference other) { - return _uiControl == other._uiControl ? 0 : 1; + return Json.JsonSerializer.SceneObjectEquals(_uiControl, other._uiControl) ? 0 : 1; } /// public bool Equals(ControlReference other) { - return _uiControl == other._uiControl; + return Json.JsonSerializer.SceneObjectEquals(_uiControl, other._uiControl); } /// public override bool Equals(object obj) { - return obj is ControlReference other && _uiControl == other._uiControl; + return obj is ControlReference other && Json.JsonSerializer.SceneObjectEquals(_uiControl, other._uiControl); } /// @@ -142,13 +156,13 @@ namespace FlaxEngine /// Checks whether the two objects are equal. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(ControlReference left, ControlReference right) => left._uiControl == right._uiControl; + public static bool operator ==(ControlReference left, ControlReference right) => Json.JsonSerializer.SceneObjectEquals(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; + public static bool operator !=(ControlReference left, ControlReference right) => !Json.JsonSerializer.SceneObjectEquals(left._uiControl, right._uiControl); } } @@ -166,7 +180,7 @@ namespace FlaxEngine.TypeConverters if (result is IControlReference control) { Json.JsonSerializer.ParseID(valueStr, out var id); - control.UIControl = Object.Find(ref id); + control.Load(Object.Find(ref id)); } return result; } From ca153e70ab16c3bb29bcfbc124085725954a98aa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Mar 2025 21:23:54 +0100 Subject: [PATCH 35/35] Fix crash when loading cloth that has different paint data size than mesh #3264 --- Source/Engine/Physics/Actors/Cloth.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 2781cb161..3b7bf6718 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -641,6 +641,14 @@ void Cloth::CalculateInvMasses(Array& invMasses) int32 indicesCount; if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount)) return; + if (_paint.Count() != verticesCount) + { + // Fix incorrect paint data + int32 countBefore = _paint.Count(); + _paint.Resize(verticesCount); + for (int32 i = countBefore; i < verticesCount; i++) + _paint.Get()[i] = 0.0f; + } const int32 verticesStride = verticesData.Length() / verticesCount; const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); const int32 trianglesCount = indicesCount / 3; @@ -692,12 +700,12 @@ void Cloth::CalculateInvMasses(Array& invMasses) float massSum = 0; for (int32 i = 0; i < verticesCount; i++) { - float& mass = invMasses[i]; + float& mass = invMasses.Get()[i]; #if USE_CLOTH_SANITY_CHECKS // Sanity check ASSERT(!isnan(mass) && !isinf(mass) && mass >= 0.0f); #endif - const float maxDistance = _paint[i]; + const float maxDistance = _paint.Get()[i]; if (maxDistance < 0.01f) { // Fixed @@ -717,7 +725,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) const float massScale = (float)(verticesCount - fixedCount) / massSum; for (int32 i = 0; i < verticesCount; i++) { - float& mass = invMasses[i]; + float& mass = invMasses.Get()[i]; if (mass > 0.0f) { mass *= massScale; @@ -772,6 +780,8 @@ bool Cloth::OnPreUpdate() int32 verticesCount; if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount)) return false; + if (verticesCount != _paint.Count()) + return false; PROFILE_CPU_NAMED("Skinned Pose"); auto vbStride = (uint32)verticesData.Length() / verticesCount; ASSERT(vbStride == sizeof(VB0SkinnedElementType));