diff --git a/Source/Editor/Content/GUI/ContentNavigation.cs b/Source/Editor/Content/GUI/ContentNavigation.cs index 1dec6f3fd..0cae5e23f 100644 --- a/Source/Editor/Content/GUI/ContentNavigation.cs +++ b/Source/Editor/Content/GUI/ContentNavigation.cs @@ -161,7 +161,7 @@ namespace FlaxEditor.Content.GUI { var style = Style.Current; var rect = new Rectangle(Float2.Zero, Size); - var color = IsDragOver ? style.BackgroundSelected * 0.6f : (_mouseDown ? style.BackgroundSelected : (IsMouseOver ? style.BackgroundHighlighted : Color.Transparent)); + var color = IsDragOver ? Color.Transparent : (_mouseDown ? style.BackgroundSelected : (IsMouseOver ? style.BackgroundHighlighted : Color.Transparent)); Render2D.FillRectangle(rect, color); Render2D.DrawSprite(Editor.Instance.Icons.ArrowRight12, new Rectangle(rect.Location.X, rect.Y + rect.Size.Y * 0.25f, rect.Size.X, rect.Size.X), EnabledInHierarchy ? style.Foreground : style.ForegroundDisabled); } diff --git a/Source/Editor/Content/GUI/ContentView.DragDrop.cs b/Source/Editor/Content/GUI/ContentView.DragDrop.cs deleted file mode 100644 index 812cb2a25..000000000 --- a/Source/Editor/Content/GUI/ContentView.DragDrop.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. - -using FlaxEditor.GUI.Drag; -using FlaxEditor.SceneGraph; -using FlaxEngine; -using FlaxEngine.GUI; - -namespace FlaxEditor.Content.GUI -{ - public partial class ContentView - { - private bool _validDragOver; - private DragActors _dragActors; - - /// - public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) - { - var result = base.OnDragEnter(ref location, data); - if (result != DragDropEffect.None) - return result; - - // Check if drop file(s) - if (data is DragDataFiles) - { - _validDragOver = true; - return DragDropEffect.Copy; - } - - // Check if drop actor(s) - if (_dragActors == null) - _dragActors = new DragActors(ValidateDragActors); - if (_dragActors.OnDragEnter(data)) - { - _validDragOver = true; - return DragDropEffect.Move; - } - - return DragDropEffect.None; - } - - private bool ValidateDragActors(ActorNode actor) - { - return actor.CanCreatePrefab && Editor.Instance.Windows.ContentWin.CurrentViewFolder.CanHaveAssets; - } - - private void ImportActors(DragActors actors, ContentFolder location) - { - foreach (var actorNode in actors.Objects) - { - var actor = actorNode.Actor; - if (actors.Objects.Contains(actorNode.ParentNode as ActorNode)) - continue; - - Editor.Instance.Prefabs.CreatePrefab(actor, false); - } - } - - /// - public override DragDropEffect OnDragMove(ref Float2 location, DragData data) - { - _validDragOver = false; - var result = base.OnDragMove(ref location, data); - if (result != DragDropEffect.None) - return result; - - if (data is DragDataFiles) - { - _validDragOver = true; - result = DragDropEffect.Copy; - } - else if (_dragActors != null && _dragActors.HasValidDrag) - { - _validDragOver = true; - result = DragDropEffect.Move; - } - - return result; - } - - /// - public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) - { - var result = base.OnDragDrop(ref location, data); - if (result != DragDropEffect.None) - return result; - - // Check if drop file(s) - if (data is DragDataFiles files) - { - // Import files - var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder; - if (currentFolder != null) - Editor.Instance.ContentImporting.Import(files.Files, currentFolder); - result = DragDropEffect.Copy; - } - // Check if drop actor(s) - else if (_dragActors != null && _dragActors.HasValidDrag) - { - // Import actors - var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder; - if (currentFolder != null) - ImportActors(_dragActors, currentFolder); - - _dragActors.OnDragDrop(); - result = DragDropEffect.Move; - } - - // Clear cache - _validDragOver = false; - - return result; - } - - /// - public override void OnDragLeave() - { - _validDragOver = false; - _dragActors?.OnDragLeave(); - - base.OnDragLeave(); - } - } -} diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 754e14d3d..105df76d4 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.GUI.Drag; using FlaxEditor.Options; +using FlaxEditor.SceneGraph; using FlaxEditor.Windows; using FlaxEngine; using FlaxEngine.GUI; @@ -52,14 +54,17 @@ namespace FlaxEditor.Content.GUI public partial class ContentView : ContainerControl, IContentItemOwner { private readonly List _items = new List(256); - private readonly List _selection = new List(16); + private readonly List _selection = new List(); private float _viewScale = 1.0f; private ContentViewType _viewType = ContentViewType.Tiles; - private bool _isRubberBandSpanning = false; - private Float2 _mousePresslocation; + private bool _isRubberBandSpanning; + private Float2 _mousePressLocation; private Rectangle _rubberBandRectangle; + private bool _validDragOver; + private DragActors _dragActors; + #region External Events /// @@ -193,6 +198,7 @@ namespace FlaxEditor.Content.GUI OnDelete?.Invoke(_selection); }), new InputActionsContainer.Binding(options => options.SelectAll, SelectAll), + new InputActionsContainer.Binding(options => options.DeselectAll, DeselectAll), new InputActionsContainer.Binding(options => options.Rename, () => { if (HasSelection && _selection[0].CanRename) @@ -397,10 +403,7 @@ namespace FlaxEditor.Content.GUI PerformLayout(); } - /// - /// Selects all the items. - /// - public void SelectAll() + private void BulkSelectUpdate(bool select = true) { // Lock layout var wasLayoutLocked = IsLayoutLocked; @@ -408,13 +411,30 @@ namespace FlaxEditor.Content.GUI // Select items _selection.Clear(); - _selection.AddRange(_items); + if (select) + _selection.AddRange(_items); // Unload and perform UI layout IsLayoutLocked = wasLayoutLocked; PerformLayout(); } + /// + /// Selects all the items. + /// + public void SelectAll() + { + BulkSelectUpdate(true); + } + + /// + /// Deselects all the items. + /// + public void DeselectAll() + { + BulkSelectUpdate(false); + } + /// /// Deselects the specified item. /// @@ -595,7 +615,9 @@ namespace FlaxEditor.Content.GUI // Check if drag is over if (IsDragOver && _validDragOver) { - Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f); + var bounds = new Rectangle(Float2.One, Size - Float2.One * 2); + Render2D.FillRectangle(bounds, style.Selection); + Render2D.DrawRectangle(bounds, style.SelectionBorder); } // Check if it's an empty thing @@ -604,10 +626,11 @@ namespace FlaxEditor.Content.GUI Render2D.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } + // Selection if (_isRubberBandSpanning) { - Render2D.FillRectangle(_rubberBandRectangle, Color.Orange * 0.4f); - Render2D.DrawRectangle(_rubberBandRectangle, Color.Orange); + Render2D.FillRectangle(_rubberBandRectangle, style.Selection); + Render2D.DrawRectangle(_rubberBandRectangle, style.SelectionBorder); } } @@ -619,8 +642,8 @@ namespace FlaxEditor.Content.GUI if (button == MouseButton.Left) { - _mousePresslocation = location; - _rubberBandRectangle = new Rectangle(_mousePresslocation, 0, 0); + _mousePressLocation = location; + _rubberBandRectangle = new Rectangle(_mousePressLocation, 0, 0); _isRubberBandSpanning = true; StartMouseCapture(); } @@ -632,8 +655,8 @@ namespace FlaxEditor.Content.GUI { if (_isRubberBandSpanning) { - _rubberBandRectangle.Width = location.X - _mousePresslocation.X; - _rubberBandRectangle.Height = location.Y - _mousePresslocation.Y; + _rubberBandRectangle.Width = location.X - _mousePressLocation.X; + _rubberBandRectangle.Height = location.Y - _mousePressLocation.Y; } base.OnMouseMove(location); @@ -768,6 +791,114 @@ namespace FlaxEditor.Content.GUI return false; } + /// + public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) + { + var result = base.OnDragEnter(ref location, data); + if (result != DragDropEffect.None) + return result; + + // Check if drop file(s) + if (data is DragDataFiles) + { + _validDragOver = true; + return DragDropEffect.Copy; + } + + // Check if drop actor(s) + if (_dragActors == null) + _dragActors = new DragActors(ValidateDragActors); + if (_dragActors.OnDragEnter(data)) + { + _validDragOver = true; + return DragDropEffect.Move; + } + + return DragDropEffect.None; + } + + private bool ValidateDragActors(ActorNode actor) + { + return actor.CanCreatePrefab && Editor.Instance.Windows.ContentWin.CurrentViewFolder.CanHaveAssets; + } + + private void ImportActors(DragActors actors, ContentFolder location) + { + foreach (var actorNode in actors.Objects) + { + var actor = actorNode.Actor; + if (actors.Objects.Contains(actorNode.ParentNode as ActorNode)) + continue; + + Editor.Instance.Prefabs.CreatePrefab(actor, false); + } + } + + /// + public override DragDropEffect OnDragMove(ref Float2 location, DragData data) + { + _validDragOver = false; + var result = base.OnDragMove(ref location, data); + if (result != DragDropEffect.None) + return result; + + if (data is DragDataFiles) + { + _validDragOver = true; + result = DragDropEffect.Copy; + } + else if (_dragActors != null && _dragActors.HasValidDrag) + { + _validDragOver = true; + result = DragDropEffect.Move; + } + + return result; + } + + /// + public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) + { + var result = base.OnDragDrop(ref location, data); + if (result != DragDropEffect.None) + return result; + + // Check if drop file(s) + if (data is DragDataFiles files) + { + // Import files + var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder; + if (currentFolder != null) + Editor.Instance.ContentImporting.Import(files.Files, currentFolder); + result = DragDropEffect.Copy; + } + // Check if drop actor(s) + else if (_dragActors != null && _dragActors.HasValidDrag) + { + // Import actors + var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder; + if (currentFolder != null) + ImportActors(_dragActors, currentFolder); + + _dragActors.OnDragDrop(); + result = DragDropEffect.Move; + } + + // Clear cache + _validDragOver = false; + + return result; + } + + /// + public override void OnDragLeave() + { + _validDragOver = false; + _dragActors?.OnDragLeave(); + + base.OnDragLeave(); + } + /// protected override void PerformLayoutBeforeChildren() { @@ -833,6 +964,9 @@ namespace FlaxEditor.Content.GUI /// public override void OnDestroy() { + if (IsDisposing) + return; + // Ensure to unlink all items ClearItems(); diff --git a/Source/Editor/Content/Items/ContentFolder.cs b/Source/Editor/Content/Items/ContentFolder.cs index 9a8e4af77..94cc8368e 100644 --- a/Source/Editor/Content/Items/ContentFolder.cs +++ b/Source/Editor/Content/Items/ContentFolder.cs @@ -241,7 +241,12 @@ namespace FlaxEditor.Content // Check if drag is over if (IsDragOver && _validDragOver) - Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), Style.Current.BackgroundSelected * 0.6f); + { + var style = Style.Current; + var bounds = new Rectangle(Float2.Zero, Size); + Render2D.FillRectangle(bounds, style.Selection); + Render2D.DrawRectangle(bounds, style.SelectionBorder); + } } private bool ValidateDragItem(ContentItem item) diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs index 0c0114fa8..ee3674daa 100644 --- a/Source/Editor/Content/Proxy/PrefabProxy.cs +++ b/Source/Editor/Content/Proxy/PrefabProxy.cs @@ -75,6 +75,8 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { + bool resetTransform = false; + var transform = Transform.Identity; if (!(arg is Actor actor)) { // Create default prefab root object @@ -86,8 +88,17 @@ namespace FlaxEditor.Content // Cleanup it after usage Object.Destroy(actor, 20.0f); } + else if (actor.Scene != null) + { + // Create prefab with identity transform so the actor instance on a level will have it customized + resetTransform = true; + transform = actor.LocalTransform; + actor.LocalTransform = Transform.Identity; + } PrefabManager.CreatePrefab(actor, outputPath, true); + if (resetTransform) + actor.LocalTransform = transform; } /// diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index ab87389de..ef6302e8e 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -747,7 +747,7 @@ namespace FlaxEditor.CustomEditors /// public void SetValueToDefault() { - SetValueCloned(Values.DefaultValue); + SetValue(Utilities.Utils.CloneValue(Values.DefaultValue)); } /// @@ -784,19 +784,7 @@ namespace FlaxEditor.CustomEditors return; } - SetValueCloned(Values.ReferenceValue); - } - - private void SetValueCloned(object value) - { - // For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor - if (value != null && !value.GetType().IsValueType) - { - var json = JsonSerializer.Serialize(value); - value = JsonSerializer.Deserialize(json, value.GetType()); - } - - SetValue(value); + SetValue(Utilities.Utils.CloneValue(Values.ReferenceValue)); } /// diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index db77e24e7..3699b8254 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -245,9 +245,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { // Special case for new Script added to actor if (editor.Values[0] is Script script && !script.HasPrefabLink) - { return CreateDiffNode(editor); - } // Skip if no change detected var isRefEdited = editor.Values.IsReferenceValueModified; @@ -258,9 +256,16 @@ namespace FlaxEditor.CustomEditors.Dedicated if (editor.ChildrenEditors.Count == 0 || (isRefEdited && editor is CollectionEditor)) result = CreateDiffNode(editor); bool isScriptEditorWithRefValue = editor is ScriptsEditor && editor.Values.HasReferenceValue; + bool isActorEditorInLevel = editor is ActorEditor && editor.Values[0] is Actor actor && actor.IsPrefabRoot && actor.Scene != null; for (int i = 0; i < editor.ChildrenEditors.Count; i++) { - var child = ProcessDiff(editor.ChildrenEditors[i], !isScriptEditorWithRefValue); + var childEditor = editor.ChildrenEditors[i]; + + // Special case for root actor transformation (can be applied only in Prefab editor, not in Level) + if (isActorEditorInLevel && childEditor.Values.Info.Name is "LocalPosition" or "LocalOrientation" or "LocalScale") + continue; + + var child = ProcessDiff(childEditor, !isScriptEditorWithRefValue); if (child != null) { if (result == null) @@ -276,7 +281,6 @@ namespace FlaxEditor.CustomEditors.Dedicated { var prefabObjectScript = prefabObjectScripts[j]; bool isRemoved = true; - for (int i = 0; i < editor.ChildrenEditors.Count; i++) { if (editor.ChildrenEditors[i].Values is ScriptsEditor.ScriptsContainer container && container.PrefabObjectId == prefabObjectScript.PrefabObjectID) @@ -286,14 +290,12 @@ namespace FlaxEditor.CustomEditors.Dedicated break; } } - if (isRemoved) { var dummy = new RemovedScriptDummy { PrefabObject = prefabObjectScript }; - var child = CreateDiffNode(dummy); if (result == null) result = CreateDiffNode(editor); diff --git a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs index bb18f6de0..81ca30361 100644 --- a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs @@ -67,25 +67,6 @@ public class MissingScriptEditor : GenericEditor base.Initialize(layout); } - private void FindActorsWithMatchingMissingScript(List missingScripts) - { - foreach (Actor actor in Level.GetActors(typeof(Actor))) - { - for (int scriptIndex = 0; scriptIndex < actor.ScriptsCount; scriptIndex++) - { - Script actorScript = actor.Scripts[scriptIndex]; - if (actorScript is not MissingScript missingActorScript) - continue; - - MissingScript currentMissing = Values[0] as MissingScript; - if (missingActorScript.MissingTypeName != currentMissing.MissingTypeName) - continue; - - missingScripts.Add(missingActorScript); - } - } - } - private void RunReplacementMultiCast(List actions) { if (actions.Count == 0) @@ -104,16 +85,54 @@ public class MissingScriptEditor : GenericEditor } } - private void ReplaceScript(ScriptType script, bool replaceAllInScene) + private void GetMissingScripts(Actor actor, string currentMissingTypeName, List missingScripts) { - var actions = new List(4); + // Iterate over the scripts of this actor + for (int i = 0; i < actor.ScriptsCount; i++) + { + if (actor.GetScript(i) is MissingScript otherMissing && + otherMissing.MissingTypeName == currentMissingTypeName) + { + missingScripts.Add(otherMissing); + } + } + // Iterate over this actor children (recursive) + for (int i = 0; i < actor.ChildrenCount; i++) + { + GetMissingScripts(actor.GetChild(i), currentMissingTypeName, missingScripts); + } + } + + private void ReplaceScript(ScriptType script) + { var missingScripts = new List(); - if (!replaceAllInScene) - missingScripts.Add((MissingScript)Values[0]); + var currentMissing = (MissingScript)Values[0]; + var currentMissingTypeName = currentMissing.MissingTypeName; + if (_shouldReplaceAllCheckbox.Checked) + { + if (currentMissing.Scene == null && currentMissing.Actor != null) + { + // Find all missing scripts in prefab instance + GetMissingScripts(currentMissing.Actor.GetPrefabRoot(), currentMissingTypeName, missingScripts); + } + else + { + // Find all missing scripts in all loaded levels that match this type + for (int i = 0; i < Level.ScenesCount; i++) + { + GetMissingScripts(Level.GetScene(i), currentMissingTypeName, missingScripts); + } + } + } else - FindActorsWithMatchingMissingScript(missingScripts); + { + // Use the current instance only + foreach (var value in Values) + missingScripts.Add((MissingScript)value); + } + var actions = new List(4); foreach (var missingScript in missingScripts) actions.Add(AddRemoveScript.Add(missingScript.Actor, script)); RunReplacementMultiCast(actions); @@ -149,7 +168,7 @@ public class MissingScriptEditor : GenericEditor var cm = new ItemsListContextMenu(180); for (int i = 0; i < scripts.Count; i++) cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); - cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag, _shouldReplaceAllCheckbox.Checked); + cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag); cm.SortItems(); cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0)); } diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs index 175312b6a..17387a202 100644 --- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs @@ -160,7 +160,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var actions = new List(); foreach (var body in bodies) { - var action = new Actions.DeleteActorsAction(new List { SceneGraphFactory.FindNode(body.ID) }); + var action = new Actions.DeleteActorsAction(body); action.Do(); actions.Add(action); } @@ -185,7 +185,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var body = bodies.FirstOrDefault(x => x.Name == name); if (body != null) { - var action = new Actions.DeleteActorsAction(new List { SceneGraphFactory.FindNode(body.ID) }); + var action = new Actions.DeleteActorsAction(body); action.Do(); Presenter.Undo?.AddAction(action); } @@ -224,7 +224,7 @@ namespace FlaxEditor.CustomEditors.Dedicated else { // Remove joint that will no longer be valid - var action = new Actions.DeleteActorsAction(new List { SceneGraphFactory.FindNode(joint.ID) }); + var action = new Actions.DeleteActorsAction(joint); action.Do(); Presenter.Undo?.AddAction(action); } @@ -233,7 +233,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Remove body { - var action = new Actions.DeleteActorsAction(new List { SceneGraphFactory.FindNode(body.ID) }); + var action = new Actions.DeleteActorsAction(body); action.Do(); Presenter.Undo?.AddAction(action); } diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index a8bcedf18..fe068e4de 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -151,8 +151,8 @@ namespace FlaxEditor.CustomEditors.Dedicated if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag) { var area = new Rectangle(Float2.Zero, size); - Render2D.FillRectangle(area, Color.Orange * 0.5f); - Render2D.DrawRectangle(area, Color.Black); + Render2D.FillRectangle(area, style.Selection); + Render2D.DrawRectangle(area, style.SelectionBorder); } base.Draw(); @@ -520,7 +520,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { base.Draw(); - var color = FlaxEngine.GUI.Style.Current.BackgroundSelected * (IsDragOver ? 0.9f : 0.1f); + var color = Style.Current.BackgroundSelected * (IsDragOver ? 0.9f : 0.1f); Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), color); } diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index cd0b534e0..27be8e538 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -589,25 +589,27 @@ namespace FlaxEditor.CustomEditors.Dedicated LayoutElementsContainer yEl; LayoutElementsContainer hEl; LayoutElementsContainer vEl; + Color axisColorX = ActorTransformEditor.AxisColorX; + Color axisColorY = ActorTransformEditor.AxisColorY; if (xEq) { - xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values)); - vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values)); + xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX); + vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX); } else { - xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values)); - vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values)); + xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX); + vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX); } if (yEq) { - yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values)); - hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values)); + yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY); + hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY); } else { - yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values)); - hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values)); + yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY); + hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY); } xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y); xEl.Control.AnchorMax = new Float2(0.5f, xEl.Control.AnchorMax.Y); @@ -624,28 +626,34 @@ namespace FlaxEditor.CustomEditors.Dedicated private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont) { - var horUp = cont.VerticalPanel(); - horUp.Panel.Margin = Margin.Zero; - return horUp; + var panel = cont.VerticalPanel(); + panel.Panel.Margin = Margin.Zero; + return panel; } private CustomElementsContainer UniformGridTwoByOne(LayoutElementsContainer cont) { - var horUp = cont.CustomContainer(); - horUp.CustomControl.SlotsHorizontally = 2; - horUp.CustomControl.SlotsVertically = 1; - horUp.CustomControl.SlotPadding = Margin.Zero; - horUp.CustomControl.ClipChildren = false; - return horUp; + var grid = cont.CustomContainer(); + grid.CustomControl.SlotsHorizontally = 2; + grid.CustomControl.SlotsVertically = 1; + grid.CustomControl.SlotPadding = Margin.Zero; + grid.CustomControl.ClipChildren = false; + return grid; } - private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values) + private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor) { - CustomElementsContainer hor = UniformGridTwoByOne(el); - hor.CustomControl.SlotPadding = new Margin(5, 5, 0, 0); - LabelElement lab = hor.Label(text); - hor.Object(values); - return hor; + var grid = UniformGridTwoByOne(el); + grid.CustomControl.SlotPadding = new Margin(5, 5, 1, 1); + var label = grid.Label(text); + var editor = grid.Object(values); + if (editor is FloatEditor floatEditor && floatEditor.Element is FloatValueElement floatEditorElement) + { + var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; + floatEditorElement.ValueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor); + floatEditorElement.ValueBox.BorderSelectedColor = borderColor; + } + return grid; } private bool _cachedXEq; diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 29a9f45e8..fb33d470c 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -26,6 +26,11 @@ namespace FlaxEditor.CustomEditors.Editors /// public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f); + /// + /// The axes colors grey out scale when input field is not focused. + /// + public static float AxisGreyOutFactor = 0.6f; + /// /// Custom editor for actor position property. /// @@ -39,12 +44,11 @@ namespace FlaxEditor.CustomEditors.Editors // Override colors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; - var grayOutFactor = 0.6f; - XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor); + XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor); XElement.ValueBox.BorderSelectedColor = AxisColorX; - YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor); + YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor); YElement.ValueBox.BorderSelectedColor = AxisColorY; - ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor); + ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor); ZElement.ValueBox.BorderSelectedColor = AxisColorZ; } } @@ -62,12 +66,11 @@ namespace FlaxEditor.CustomEditors.Editors // Override colors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; - var grayOutFactor = 0.6f; - XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor); + XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor); XElement.ValueBox.BorderSelectedColor = AxisColorX; - YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor); + YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor); YElement.ValueBox.BorderSelectedColor = AxisColorY; - ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor); + ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor); ZElement.ValueBox.BorderSelectedColor = AxisColorZ; } } diff --git a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs index 276417f96..25163febd 100644 --- a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs @@ -2,7 +2,6 @@ using System; using System.Collections; -using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.Utilities; @@ -47,8 +46,9 @@ namespace FlaxEditor.CustomEditors.Editors if (elementType.IsValueType || NotNullItems) { // Fill new entries with the last value + var lastValue = array.GetValue(oldSize - 1); for (int i = oldSize; i < newSize; i++) - Array.Copy(array, oldSize - 1, newValues, i, 1); + newValues.SetValue(Utilities.Utils.CloneValue(lastValue), i); } else { diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 211fb3980..41715d654 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -542,9 +542,10 @@ namespace FlaxEditor.CustomEditors.Editors { if (_dragHandlers is { HasValidDrag: true }) { + var style = FlaxEngine.GUI.Style.Current; var area = new Rectangle(Float2.Zero, Size); - Render2D.FillRectangle(area, Color.Orange * 0.5f); - Render2D.DrawRectangle(area, Color.Black); + Render2D.FillRectangle(area, style.Selection); + Render2D.DrawRectangle(area, style.SelectionBorder); } base.Draw(); diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs index 2035057de..97b016fba 100644 --- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs @@ -220,7 +220,11 @@ namespace FlaxEditor.CustomEditors.Editors // Check if drag is over if (IsDragOver && _hasValidDragOver) - Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f); + { + var bounds = new Rectangle(Float2.Zero, Size); + Render2D.FillRectangle(bounds, style.Selection); + Render2D.DrawRectangle(bounds, style.SelectionBorder); + } } /// diff --git a/Source/Editor/CustomEditors/Editors/TypeEditor.cs b/Source/Editor/CustomEditors/Editors/TypeEditor.cs index 3b4a6eed2..a82b1eccd 100644 --- a/Source/Editor/CustomEditors/Editors/TypeEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TypeEditor.cs @@ -175,7 +175,11 @@ namespace FlaxEditor.CustomEditors.Editors // Check if drag is over if (IsDragOver && _hasValidDragOver) - Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f); + { + var bounds = new Rectangle(Float2.Zero, Size); + Render2D.FillRectangle(bounds, style.Selection); + Render2D.DrawRectangle(bounds, style.SelectionBorder); + } } /// diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index b37f0468c..4cee6d971 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1676,6 +1676,9 @@ namespace FlaxEditor [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot); + [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetPrefabNestedObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] + internal static partial void Internal_GetPrefabNestedObject(IntPtr prefabId, IntPtr prefabObjectId, IntPtr outPrefabId, IntPtr outPrefabObjectId); + [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial float Internal_GetAnimationTime(IntPtr animatedModel); diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index 2e7116957..5c8f02a06 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -182,7 +182,11 @@ namespace FlaxEditor.GUI // Check if drag is over if (IsDragOver && _dragOverElement != null && _dragOverElement.HasValidDrag) - Render2D.FillRectangle(iconRect, style.BackgroundSelected * 0.4f); + { + var bounds = new Rectangle(Float2.Zero, Size); + Render2D.FillRectangle(bounds, style.Selection); + Render2D.DrawRectangle(bounds, style.SelectionBorder); + } } /// diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs index a0a9b1cb8..05d62d3f2 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs @@ -359,6 +359,17 @@ namespace FlaxEditor.GUI.ContextMenu ButtonClicked?.Invoke(button); } + /// + public override void Show(Control parent, Float2 location) + { + // Remove last separator to make context menu look better + int lastIndex = _panel.Children.Count - 1; + if (lastIndex >= 0 && _panel.Children[lastIndex] is ContextMenuSeparator separator) + separator.Dispose(); + + base.Show(parent, location); + } + /// public override bool ContainsPoint(ref Float2 location, bool precise) { diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index f2feed095..511cda2e5 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -168,30 +168,30 @@ namespace FlaxEditor.GUI.ContextMenu bool isUp = false, isLeft = false; if (UseAutomaticDirectionFix) { + var parentMenu = parent as ContextMenu; if (monitorBounds.Bottom < rightBottomLocationSS.Y) { - // Direction: up isUp = true; locationSS.Y -= dpiSize.Y; - - // Offset to fix sub-menu location - if (parent is ContextMenu menu && menu._childCM != null) + if (parentMenu != null && parentMenu._childCM != null) locationSS.Y += 30.0f * dpiScale; } - if (monitorBounds.Right < rightBottomLocationSS.X || _parentCM?.Direction == ContextMenuDirection.LeftDown || _parentCM?.Direction == ContextMenuDirection.LeftUp) + if (parentMenu == null) { - // Direction: left - isLeft = true; - - if (IsSubMenu && _parentCM != null) - { - locationSS.X -= _parentCM.Width + dpiSize.X; - } - else + if (monitorBounds.Right < rightBottomLocationSS.X) { + isLeft = true; locationSS.X -= dpiSize.X; } } + else if (monitorBounds.Right < rightBottomLocationSS.X || _parentCM?.Direction == ContextMenuDirection.LeftDown || _parentCM?.Direction == ContextMenuDirection.LeftUp) + { + isLeft = true; + if (IsSubMenu && _parentCM != null) + locationSS.X -= _parentCM.Width + dpiSize.X; + else + locationSS.X -= dpiSize.X; + } } // Update direction flag diff --git a/Source/Editor/GUI/CurveEditor.Contents.cs b/Source/Editor/GUI/CurveEditor.Contents.cs index 7e7fbbd32..bdb1c650b 100644 --- a/Source/Editor/GUI/CurveEditor.Contents.cs +++ b/Source/Editor/GUI/CurveEditor.Contents.cs @@ -484,6 +484,7 @@ namespace FlaxEditor.GUI cm.AddSeparator(); cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location)); cm.AddButton("Select all keyframes", _editor.SelectAll); + cm.AddButton("Deselect all keyframes", _editor.DeselectAll); cm.AddButton("Copy all keyframes", () => { _editor.SelectAll(); diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 69671b1da..141b3aa60 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using FlaxEditor.CustomEditors; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; @@ -713,15 +714,28 @@ namespace FlaxEditor.GUI } } + private void BulkSelectUpdate(bool select = true) + { + for (int i = 0; i < _points.Count; i++) + { + _points[i].IsSelected = select; + } + } + /// /// Selects all keyframes. /// public void SelectAll() { - for (int i = 0; i < _points.Count; i++) - { - _points[i].IsSelected = true; - } + BulkSelectUpdate(true); + } + + /// + /// Deselects all keyframes. + /// + public void DeselectAll() + { + BulkSelectUpdate(false); } /// @@ -899,8 +913,8 @@ namespace FlaxEditor.GUI _mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)), _mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos)) ); - Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f); - Render2D.DrawRectangle(selectionRect, Color.Orange); + Render2D.FillRectangle(selectionRect, style.Selection); + Render2D.DrawRectangle(selectionRect, style.SelectionBorder); } base.Draw(); @@ -926,34 +940,35 @@ namespace FlaxEditor.GUI if (base.OnKeyDown(key)) return true; - switch (key) + InputOptions options = Editor.Instance.Options.Options.Input; + if (options.SelectAll.Process(this)) + { + SelectAll(); + UpdateTangents(); + return true; + } + else if (options.DeselectAll.Process(this)) + { + DeselectAll(); + UpdateTangents(); + return true; + } + else if (options.Delete.Process(this)) { - case KeyboardKeys.Delete: RemoveKeyframes(); return true; - case KeyboardKeys.A: - if (Root.GetKey(KeyboardKeys.Control)) - { - SelectAll(); - UpdateTangents(); - return true; - } - break; - case KeyboardKeys.C: - if (Root.GetKey(KeyboardKeys.Control)) - { - CopyKeyframes(); - return true; - } - break; - case KeyboardKeys.V: - if (Root.GetKey(KeyboardKeys.Control)) - { - KeyframesEditorUtils.Paste(this); - return true; - } - break; } + else if (options.Copy.Process(this)) + { + CopyKeyframes(); + return true; + } + else if (options.Paste.Process(this)) + { + KeyframesEditorUtils.Paste(this); + return true; + } + return false; } diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs index 4fd496548..d10a1e51c 100644 --- a/Source/Editor/GUI/Input/ValueBox.cs +++ b/Source/Editor/GUI/Input/ValueBox.cs @@ -201,8 +201,9 @@ namespace FlaxEditor.GUI.Input if (_isSliding) { // Draw overlay - // TODO: render nicer overlay with some glow from the borders (inside) - Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), Color.Orange * 0.3f); + var bounds = new Rectangle(Float2.Zero, Size); + Render2D.FillRectangle(bounds, style.Selection); + Render2D.DrawRectangle(bounds, style.SelectionBorder); } } } diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 458db481f..78fbcd779 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -122,6 +122,10 @@ namespace FlaxEditor.GUI if (IsMouseOver || IsFocused) Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundHighlighted); + // Indent for drop panel items is handled by drop panel margin + if (Parent is not DropPanel) + textRect.Location += new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0); + // Draw all highlights if (_highlights != null) { @@ -262,6 +266,10 @@ namespace FlaxEditor.GUI } } category.Visible = anyVisible; + if (string.IsNullOrEmpty(_searchBox.Text)) + category.Close(false); + else + category.Open(false); } } @@ -338,9 +346,14 @@ namespace FlaxEditor.GUI var categoryPanel = new DropPanel { HeaderText = item.Category, + ArrowImageOpened = new SpriteBrush(Editor.Instance.Icons.ArrowDown12), + ArrowImageClosed = new SpriteBrush(Editor.Instance.Icons.ArrowRight12), + EnableDropDownIcon = true, + ItemsMargin = new Margin(28, 0, 2, 2), + HeaderColor = Style.Current.Background, Parent = parent, }; - categoryPanel.Open(false); + categoryPanel.Close(false); _categoryPanels.Add(categoryPanel); parent = categoryPanel; } @@ -382,6 +395,7 @@ namespace FlaxEditor.GUI item2.UpdateFilter(null); } category.Visible = true; + category.Close(false); } } diff --git a/Source/Editor/GUI/NavigationButton.cs b/Source/Editor/GUI/NavigationButton.cs index 2ed709948..4de6ac37e 100644 --- a/Source/Editor/GUI/NavigationButton.cs +++ b/Source/Editor/GUI/NavigationButton.cs @@ -45,7 +45,8 @@ namespace FlaxEditor.GUI // Draw background if (IsDragOver && _validDragOver) { - Render2D.FillRectangle(clientRect, Style.Current.BackgroundSelected * 0.6f); + Render2D.FillRectangle(clientRect, style.Selection); + Render2D.DrawRectangle(clientRect, style.SelectionBorder); } else if (_isPressed) { diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs index c84e4c01b..4ec634a0b 100644 --- a/Source/Editor/GUI/Timeline/GUI/Background.cs +++ b/Source/Editor/GUI/Timeline/GUI/Background.cs @@ -227,8 +227,8 @@ namespace FlaxEditor.GUI.Timeline.GUI if (_isSelecting) { var selectionRect = Rectangle.FromPoints(_selectingStartPos, _mousePos); - Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f); - Render2D.DrawRectangle(selectionRect, Color.Orange); + Render2D.FillRectangle(selectionRect, style.Selection); + Render2D.DrawRectangle(selectionRect, style.SelectionBorder); } DrawChildren(); diff --git a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs index 3ede72503..797a0a044 100644 --- a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs +++ b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using FlaxEditor.CustomEditors; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Options; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; @@ -432,6 +433,7 @@ namespace FlaxEditor.GUI if (_editor.EnableKeyframesValueEdit) cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location)); cm.AddButton("Select all keyframes", _editor.SelectAll).Enabled = _editor._points.Count > 0; + cm.AddButton("Deselect all keyframes", _editor.DeselectAll).Enabled = _editor._points.Count > 0; cm.AddButton("Copy all keyframes", () => { _editor.SelectAll(); @@ -902,7 +904,7 @@ namespace FlaxEditor.GUI var k = new Keyframe { Time = keyframesPos.X, - Value = DefaultValue, + Value = Utilities.Utils.CloneValue(DefaultValue), }; OnEditingStart(); AddKeyframe(k); @@ -1209,15 +1211,28 @@ namespace FlaxEditor.GUI } } + private void BulkSelectUpdate(bool select = true) + { + for (int i = 0; i < _points.Count; i++) + { + _points[i].IsSelected = select; + } + } + /// /// Selects all keyframes. /// public void SelectAll() { - for (int i = 0; i < _points.Count; i++) - { - _points[i].IsSelected = true; - } + BulkSelectUpdate(true); + } + + /// + /// Deselects all keyframes. + /// + public void DeselectAll() + { + BulkSelectUpdate(false); } /// @@ -1241,8 +1256,8 @@ namespace FlaxEditor.GUI _mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)), _mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos)) ); - Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f); - Render2D.DrawRectangle(selectionRect, Color.Orange); + Render2D.FillRectangle(selectionRect, style.Selection); + Render2D.DrawRectangle(selectionRect, style.SelectionBorder); } base.Draw(); @@ -1268,33 +1283,33 @@ namespace FlaxEditor.GUI if (base.OnKeyDown(key)) return true; - switch (key) + InputOptions options = Editor.Instance.Options.Options.Input; + if (options.SelectAll.Process(this)) + { + SelectAll(); + return true; + } + else if (options.DeselectAll.Process(this)) + { + DeselectAll(); + return true; + } + else if (options.Delete.Process(this)) { - case KeyboardKeys.Delete: RemoveKeyframes(); return true; - case KeyboardKeys.A: - if (Root.GetKey(KeyboardKeys.Control)) - { - SelectAll(); - return true; - } - break; - case KeyboardKeys.C: - if (Root.GetKey(KeyboardKeys.Control)) - { - CopyKeyframes(); - return true; - } - break; - case KeyboardKeys.V: - if (Root.GetKey(KeyboardKeys.Control)) - { - KeyframesEditorUtils.Paste(this); - return true; - } - break; } + else if (options.Copy.Process(this)) + { + CopyKeyframes(); + return true; + } + else if (options.Paste.Process(this)) + { + KeyframesEditorUtils.Paste(this); + return true; + } + return false; } diff --git a/Source/Editor/GUI/Timeline/Timeline.UI.cs b/Source/Editor/GUI/Timeline/Timeline.UI.cs index d06515688..64deadef4 100644 --- a/Source/Editor/GUI/Timeline/Timeline.UI.cs +++ b/Source/Editor/GUI/Timeline/Timeline.UI.cs @@ -304,7 +304,9 @@ namespace FlaxEditor.GUI.Timeline if (IsDragOver && _currentDragEffect != DragDropEffect.None) { var style = Style.Current; - Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f); + var bounds = new Rectangle(Float2.Zero, Size); + Render2D.FillRectangle(bounds, style.Selection); + Render2D.DrawRectangle(bounds, style.SelectionBorder); } base.Draw(); diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 7f8617bcc..f86730b99 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -187,6 +187,11 @@ namespace FlaxEditor.GUI.Timeline private bool _showPreviewValues = true; private PlaybackStates _state = PlaybackStates.Disabled; + /// + /// The Track that is being dragged over. This could have a value when not dragging. + /// + internal Track DraggedOverTrack = null; + /// /// Flag used to mark modified timeline data. /// diff --git a/Source/Editor/GUI/Timeline/Track.cs b/Source/Editor/GUI/Timeline/Track.cs index 227646ab2..929b213e4 100644 --- a/Source/Editor/GUI/Timeline/Track.cs +++ b/Source/Editor/GUI/Timeline/Track.cs @@ -774,14 +774,33 @@ namespace FlaxEditor.GUI.Timeline /// Updates the drag over mode based on the given mouse location. /// /// The location. - private void UpdateDrawPositioning(ref Float2 location) + private void UpdateDragPositioning(ref Float2 location) { + // Check collision with drag areas if (new Rectangle(0, 0 - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location)) _dragOverMode = DragItemPositioning.Above; else if (IsCollapsed && new Rectangle(0, Height - DefaultDragInsertPositionMargin, Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location)) _dragOverMode = DragItemPositioning.Below; else _dragOverMode = DragItemPositioning.At; + + // Update DraggedOverTrack + var timeline = Timeline; + if (_dragOverMode == DragItemPositioning.None) + { + if (timeline != null && timeline.DraggedOverTrack == this) + timeline.DraggedOverTrack = null; + } + else if (timeline != null) + timeline.DraggedOverTrack = this; + } + + private void ClearDragPositioning() + { + _dragOverMode = DragItemPositioning.None; + var timeline = Timeline; + if (timeline != null && timeline.DraggedOverTrack == this) + timeline.DraggedOverTrack = null; } /// @@ -975,26 +994,21 @@ namespace FlaxEditor.GUI.Timeline } // Draw drag and drop effect - if (IsDragOver && _isDragOverHeader) + if (IsDragOver && _timeline.DraggedOverTrack == this) { - Color dragOverColor = style.BackgroundSelected * 0.6f; - Rectangle rect; switch (_dragOverMode) { case DragItemPositioning.At: - rect = textRect; + Render2D.FillRectangle(textRect, style.Selection); + Render2D.DrawRectangle(textRect, style.SelectionBorder); break; case DragItemPositioning.Above: - rect = new Rectangle(textRect.X, textRect.Y - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, textRect.Width, DefaultDragInsertPositionMargin * 2.0f); + Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin * 0.5f - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder); break; case DragItemPositioning.Below: - rect = new Rectangle(textRect.X, textRect.Bottom - DefaultDragInsertPositionMargin, textRect.Width, DefaultDragInsertPositionMargin * 2.0f); - break; - default: - rect = Rectangle.Empty; + Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin * 0.5f, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder); break; } - Render2D.FillRectangle(rect, dragOverColor); } base.Draw(); @@ -1170,18 +1184,18 @@ namespace FlaxEditor.GUI.Timeline _dragOverMode = DragItemPositioning.None; if (result == DragDropEffect.None) { - UpdateDrawPositioning(ref location); + UpdateDragPositioning(ref location); // Check if mouse is over header _isDragOverHeader = TestHeaderHit(ref location); if (_isDragOverHeader) { - // Check if mouse is over arrow + if (Timeline != null) + Timeline.DraggedOverTrack = this; + + // Expand node if mouse goes over arrow if (_children.Count > 0 && ArrowRect.Contains(location)) - { - // Expand track Expand(); - } result = OnDragEnterHeader(data); } @@ -1199,21 +1213,18 @@ namespace FlaxEditor.GUI.Timeline var result = base.OnDragMove(ref location, data); // Check if no children handled that event - _dragOverMode = DragItemPositioning.None; + ClearDragPositioning(); if (result == DragDropEffect.None) { - UpdateDrawPositioning(ref location); + UpdateDragPositioning(ref location); // Check if mouse is over header bool isDragOverHeader = TestHeaderHit(ref location); if (isDragOverHeader) { - // Check if mouse is over arrow + // Expand node if mouse goes over arrow if (_children.Count > 0 && ArrowRect.Contains(location)) - { - // Expand track Expand(); - } if (!_isDragOverHeader) result = OnDragEnterHeader(data); @@ -1226,10 +1237,8 @@ namespace FlaxEditor.GUI.Timeline } _isDragOverHeader = isDragOverHeader; - if (result == DragDropEffect.None || !isDragOverHeader) - { + if (result == DragDropEffect.None) _dragOverMode = DragItemPositioning.None; - } } return result; @@ -1243,7 +1252,7 @@ namespace FlaxEditor.GUI.Timeline // Check if no children handled that event if (result == DragDropEffect.None) { - UpdateDrawPositioning(ref location); + UpdateDragPositioning(ref location); // Check if mouse is over header if (TestHeaderHit(ref location)) @@ -1254,7 +1263,7 @@ namespace FlaxEditor.GUI.Timeline // Clear cache _isDragOverHeader = false; - _dragOverMode = DragItemPositioning.None; + ClearDragPositioning(); return result; } @@ -1262,15 +1271,15 @@ namespace FlaxEditor.GUI.Timeline /// public override void OnDragLeave() { - base.OnDragLeave(); - // Clear cache if (_isDragOverHeader) { _isDragOverHeader = false; OnDragLeaveHeader(); } - _dragOverMode = DragItemPositioning.None; + ClearDragPositioning(); + + base.OnDragLeave(); } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs index 95b5dc57f..795674289 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs @@ -204,7 +204,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks b.TooltipText = Utilities.Utils.GetTooltip(actorNode.Actor); } } - menu.AddButton("Select...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track"; + menu.AddButton("Retarget...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track"; } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs index dbff7267a..ed1a53981 100644 --- a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs @@ -7,6 +7,8 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; using FlaxEditor.GUI.Timeline.Undo; +using FlaxEditor.Scripting; +using FlaxEditor.Utilities; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; @@ -56,7 +58,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks throw new Exception("Invalid track data."); var keyframes = new KeyframesEditor.Keyframe[keyframesCount]; - var dataBuffer = new byte[e.ValueSize]; var propertyType = TypeUtils.GetManagedType(e.MemberTypeName); if (propertyType == null) { @@ -67,20 +68,40 @@ namespace FlaxEditor.GUI.Timeline.Tracks return; } - GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned); - for (int i = 0; i < keyframesCount; i++) + if (e.ValueSize != 0) { - var time = stream.ReadSingle(); - stream.Read(dataBuffer, 0, e.ValueSize); - var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType); - - keyframes[i] = new KeyframesEditor.Keyframe + // POD value type - use raw memory + var dataBuffer = new byte[e.ValueSize]; + GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned); + for (int i = 0; i < keyframesCount; i++) { - Time = time, - Value = value, - }; + var time = stream.ReadSingle(); + stream.Read(dataBuffer, 0, e.ValueSize); + var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType); + + keyframes[i] = new KeyframesEditor.Keyframe + { + Time = time, + Value = value, + }; + } + handle.Free(); + } + else + { + // Generic value - use Json storage (as UTF-8) + for (int i = 0; i < keyframesCount; i++) + { + var time = stream.ReadSingle(); + var len = stream.ReadInt32(); + var value = len != 0 ? FlaxEngine.Json.JsonSerializer.Deserialize(Encoding.UTF8.GetString(stream.ReadBytes(len)), propertyType) : null; + keyframes[i] = new KeyframesEditor.Keyframe + { + Time = time, + Value = value, + }; + } } - handle.Free(); e.Keyframes.DefaultValue = e.GetDefaultValue(propertyType); e.Keyframes.SetKeyframes(keyframes); @@ -113,17 +134,35 @@ namespace FlaxEditor.GUI.Timeline.Tracks stream.Write(propertyTypeNameData); stream.Write('\0'); - var dataBuffer = new byte[e.ValueSize]; - IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize); - for (int i = 0; i < keyframes.Count; i++) + if (e.ValueSize != 0) { - var keyframe = keyframes[i]; - Marshal.StructureToPtr(keyframe.Value, ptr, true); - Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize); - stream.Write(keyframe.Time); - stream.Write(dataBuffer); + // POD value type - use raw memory + var dataBuffer = new byte[e.ValueSize]; + IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize); + for (int i = 0; i < keyframes.Count; i++) + { + var keyframe = keyframes[i]; + Marshal.StructureToPtr(keyframe.Value, ptr, true); + Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize); + stream.Write(keyframe.Time); + stream.Write(dataBuffer); + } + Marshal.FreeHGlobal(ptr); + } + else + { + // Generic value - use Json storage (as UTF-8) + for (int i = 0; i < keyframes.Count; i++) + { + var keyframe = keyframes[i]; + stream.Write(keyframe.Time); + var json = keyframe.Value != null ? FlaxEngine.Json.JsonSerializer.Serialize(keyframe.Value) : null; + var len = json?.Length ?? 0; + stream.Write(len); + if (len > 0) + stream.Write(Encoding.UTF8.GetBytes(json)); + } } - Marshal.FreeHGlobal(ptr); } private byte[] _keyframesEditingStartData; @@ -281,7 +320,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks private void OnKeyframesEditingEnd() { var after = EditTrackAction.CaptureData(this); - if (!Utils.ArraysEqual(_keyframesEditingStartData, after)) + if (!FlaxEngine.Utils.ArraysEqual(_keyframesEditingStartData, after)) Timeline.AddBatchedUndoAction(new EditTrackAction(Timeline, this, _keyframesEditingStartData, after)); _keyframesEditingStartData = null; } @@ -308,7 +347,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks /// The default value. protected virtual object GetDefaultValue(Type propertyType) { - return Activator.CreateInstance(propertyType); + var value = TypeUtils.GetDefaultValue(new ScriptType(propertyType)); + if (value == null) + value = Activator.CreateInstance(propertyType); + return value; } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs index eccd9ab06..f6fb4b4b2 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs @@ -424,6 +424,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks { typeof(Guid), KeyframesPropertyTrack.GetArchetype() }, { typeof(DateTime), KeyframesPropertyTrack.GetArchetype() }, { typeof(TimeSpan), KeyframesPropertyTrack.GetArchetype() }, + { typeof(LocalizedString), KeyframesPropertyTrack.GetArchetype() }, { typeof(string), StringPropertyTrack.GetArchetype() }, }; } diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index d01695abc..b702b530c 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; @@ -43,7 +44,7 @@ namespace FlaxEditor.GUI.Tree /// /// The TreeNode that is being dragged over. This could have a value when not dragging. /// - public TreeNode DraggedOverNode = null; + internal TreeNode DraggedOverNode = null; /// /// Action fired when tree nodes selection gets changed. @@ -315,10 +316,7 @@ namespace FlaxEditor.GUI.Tree } } - /// - /// Select all expanded nodes - /// - public void SelectAllExpanded() + private void BulkSelectUpdateExpanded(bool select = true) { if (_supportMultiSelect) { @@ -327,7 +325,8 @@ namespace FlaxEditor.GUI.Tree // Update selection Selection.Clear(); - WalkSelectExpandedTree(Selection, _children[0] as TreeNode); + if (select) + WalkSelectExpandedTree(Selection, _children[0] as TreeNode); // Check if changed if (Selection.Count != prev.Count || !Selection.SequenceEqual(prev)) @@ -338,6 +337,22 @@ namespace FlaxEditor.GUI.Tree } } + /// + /// Select all expanded nodes + /// + public void SelectAllExpanded() + { + BulkSelectUpdateExpanded(true); + } + + /// + /// Deselect all nodes + /// + public void DeselectAll() + { + BulkSelectUpdateExpanded(false); + } + /// public override void Update(float deltaTime) { @@ -472,14 +487,19 @@ namespace FlaxEditor.GUI.Tree // Check if can use multi selection if (_supportMultiSelect) { - bool isCtrlDown = Root.GetKey(KeyboardKeys.Control); + InputOptions options = Editor.Instance.Options.Options.Input; // Select all expanded nodes - if (key == KeyboardKeys.A && isCtrlDown) + if (options.SelectAll.Process(this)) { SelectAllExpanded(); return true; } + else if (options.DeselectAll.Process(this)) + { + DeselectAll(); + return true; + } } return base.OnKeyDown(key); diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index 3e1827438..36331d6d2 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -16,7 +16,7 @@ namespace FlaxEditor.GUI.Tree /// /// The default drag insert position margin. /// - public const float DefaultDragInsertPositionMargin = 2.0f; + public const float DefaultDragInsertPositionMargin = 3.0f; /// /// The default node offset on Y axis. @@ -42,6 +42,7 @@ namespace FlaxEditor.GUI.Tree private DragItemPositioning _dragOverMode; private bool _isDragOverHeader; + private static ulong _dragEndFrame; /// /// Gets or sets the text. @@ -546,14 +547,33 @@ namespace FlaxEditor.GUI.Tree /// Updates the drag over mode based on the given mouse location. /// /// The location. - private void UpdateDrawPositioning(ref Float2 location) + private void UpdateDragPositioning(ref Float2 location) { + // Check collision with drag areas if (new Rectangle(_headerRect.X, _headerRect.Y - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, _headerRect.Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location)) _dragOverMode = DragItemPositioning.Above; else if ((IsCollapsed || !HasAnyVisibleChild) && new Rectangle(_headerRect.X, _headerRect.Bottom - DefaultDragInsertPositionMargin, _headerRect.Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location)) _dragOverMode = DragItemPositioning.Below; else _dragOverMode = DragItemPositioning.At; + + // Update DraggedOverNode + var tree = ParentTree; + if (_dragOverMode == DragItemPositioning.None) + { + if (tree != null && tree.DraggedOverNode == this) + tree.DraggedOverNode = null; + } + else if (tree != null) + tree.DraggedOverNode = this; + } + + private void ClearDragPositioning() + { + _dragOverMode = DragItemPositioning.None; + var tree = ParentTree; + if (tree != null && tree.DraggedOverNode == this) + tree.DraggedOverNode = null; } /// @@ -661,27 +681,19 @@ namespace FlaxEditor.GUI.Tree // Draw drag and drop effect if (IsDragOver && _tree.DraggedOverNode == this) { - Color dragOverColor = style.BackgroundSelected; - Rectangle rect; switch (_dragOverMode) { case DragItemPositioning.At: - dragOverColor *= 0.6f; - rect = textRect; + Render2D.FillRectangle(textRect, style.Selection); + Render2D.DrawRectangle(textRect, style.SelectionBorder); break; case DragItemPositioning.Above: - dragOverColor *= 1.2f; - rect = new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin * 2.0f); + Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin * 0.5f - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder); break; case DragItemPositioning.Below: - dragOverColor *= 1.2f; - rect = new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin, textRect.Width, DefaultDragInsertPositionMargin * 2.0f); - break; - default: - rect = Rectangle.Empty; + Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin * 0.5f, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder); break; } - Render2D.FillRectangle(rect, dragOverColor); } // Base @@ -736,9 +748,8 @@ namespace FlaxEditor.GUI.Tree UpdateMouseOverFlags(location); // Clear flag for left button - if (button == MouseButton.Left) + if (button == MouseButton.Left && _isMouseDown) { - // Clear flag _isMouseDown = false; _mouseDownTime = -1; } @@ -746,6 +757,10 @@ namespace FlaxEditor.GUI.Tree // Check if mouse hits bar and node isn't a root if (_mouseOverHeader) { + // Skip mouse up event right after drag drop ends + if (button == MouseButton.Left && Engine.FrameCount - _dragEndFrame < 10) + return true; + // Prevent from selecting node when user is just clicking at an arrow if (!_mouseOverArrow) { @@ -937,20 +952,18 @@ namespace FlaxEditor.GUI.Tree _dragOverMode = DragItemPositioning.None; if (result == DragDropEffect.None) { - UpdateDrawPositioning(ref location); - if (ParentTree != null) - ParentTree.DraggedOverNode = this; + UpdateDragPositioning(ref location); // Check if mouse is over header _isDragOverHeader = TestHeaderHit(ref location); if (_isDragOverHeader) { - // Check if mouse is over arrow + if (ParentTree != null) + ParentTree.DraggedOverNode = this; + + // Expand node if mouse goes over arrow if (ArrowRect.Contains(location) && HasAnyVisibleChild) - { - // Expand node (no animation) Expand(true); - } result = OnDragEnterHeader(data); } @@ -968,26 +981,31 @@ namespace FlaxEditor.GUI.Tree var result = base.OnDragMove(ref location, data); // Check if no children handled that event - _dragOverMode = DragItemPositioning.None; + ClearDragPositioning(); if (result == DragDropEffect.None) { - UpdateDrawPositioning(ref location); + UpdateDragPositioning(ref location); // Check if mouse is over header bool isDragOverHeader = TestHeaderHit(ref location); if (isDragOverHeader) { - if (ArrowRect.Contains(location) && HasAnyVisibleChild) - { - // Expand node (no animation) - Expand(true); - } + if (ParentTree != null) + ParentTree.DraggedOverNode = this; + // Expand node if mouse goes over arrow + if (ArrowRect.Contains(location) && HasAnyVisibleChild) + Expand(true); + if (!_isDragOverHeader) result = OnDragEnterHeader(data); else result = OnDragMoveHeader(data); } + else if (_isDragOverHeader) + { + OnDragLeaveHeader(); + } _isDragOverHeader = isDragOverHeader; if (result == DragDropEffect.None) @@ -1005,7 +1023,8 @@ namespace FlaxEditor.GUI.Tree // Check if no children handled that event if (result == DragDropEffect.None) { - UpdateDrawPositioning(ref location); + UpdateDragPositioning(ref location); + _dragEndFrame = Engine.FrameCount; // Check if mouse is over header if (TestHeaderHit(ref location)) @@ -1016,9 +1035,7 @@ namespace FlaxEditor.GUI.Tree // Clear cache _isDragOverHeader = false; - _dragOverMode = DragItemPositioning.None; - if (ParentTree != null) - ParentTree.DraggedOverNode = null; + ClearDragPositioning(); return result; } @@ -1032,7 +1049,7 @@ namespace FlaxEditor.GUI.Tree _isDragOverHeader = false; OnDragLeaveHeader(); } - _dragOverMode = DragItemPositioning.None; + ClearDragPositioning(); base.OnDragLeave(); } diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index 4918743ce..64f969990 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -263,6 +263,8 @@ namespace FlaxEditor.Gizmo // Note: because selection may contain objects and their children we have to split them and get only parents. // Later during transformation we apply translation/scale/rotation only on them (children inherit transformations) SceneGraphTools.BuildNodesParents(_selection, _selectionParents); + + base.OnSelectionChanged(newSelection); } /// diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 99b1c8122..81f76d392 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -437,8 +437,6 @@ namespace FlaxEditor.Gizmo { case Mode.Translate: UpdateTranslateScale(); - if (Owner.SnapToVertex) - UpdateVertexSnapping(); break; case Mode.Scale: UpdateTranslateScale(); @@ -447,6 +445,8 @@ namespace FlaxEditor.Gizmo UpdateRotate(dt); break; } + if (Owner.SnapToVertex) + UpdateVertexSnapping(); } else { @@ -553,41 +553,21 @@ namespace FlaxEditor.Gizmo for (int i = 0; i < SelectionCount; i++) { var obj = GetSelectedObject(i); - if (obj.CanVertexSnap && obj.RayCastSelf(ref ray, out var distance, out _) && distance < closestDistance) + if (obj.RayCastSelf(ref ray, out var distance, out _) && distance < closestDistance) { closestDistance = distance; closestObject = obj; } } if (closestObject == null) - { - // Find the closest object in selection (in case ray didn't hit anything) - for (int i = 0; i < SelectionCount; i++) - { - var obj = GetSelectedObject(i); - if (obj.CanVertexSnap) - { - GetSelectedObjectsBounds(out var bounds, out _); - CollisionsHelper.ClosestPointBoxPoint(ref bounds, ref ray.Ray.Position, out var point); - var distance = Vector3.Distance(ref point, ref ray.Ray.Position); - if (distance < closestDistance) - { - closestDistance = distance; - closestObject = obj; - } - } - } - } - _vertexSnapObject = closestObject; - if (closestObject == null) - return; + return; // ignore it if there is nothing under the mouse closestObject is only null if ray caster missed everything or Selection Count == 0 - // Find the closest vertex to bounding box point (collision detection approximation) - var closestPoint = ray.Ray.GetPoint(closestDistance); - if (!closestObject.OnVertexSnap(ref closestPoint, out _vertexSnapPoint)) + _vertexSnapObject = closestObject; + if (!closestObject.OnVertexSnap(ref ray.Ray, closestDistance, out _vertexSnapPoint)) { - // Failed to get the closest point - _vertexSnapPoint = closestPoint; + // The OnVertexSnap is unimplemented or failed to get point return because there is nothing to do + _vertexSnapPoint = Vector3.Zero; + return; } // Transform back to the local space of the object to work when moving it @@ -619,12 +599,11 @@ namespace FlaxEditor.Gizmo for (int i = 0; i < SelectionCount; i++) rayCast.ExcludeObjects.Add(GetSelectedObject(i)); var hit = Owner.SceneGraphRoot.RayCast(ref rayCast, out var distance, out var _); - if (hit != null && hit.CanVertexSnap) + if (hit != null) { - var point = rayCast.Ray.GetPoint(distance); - if (hit.OnVertexSnap(ref point, out var pointSnapped) + if (hit.OnVertexSnap(ref rayCast.Ray, distance, out var pointSnapped) //&& Vector3.Distance(point, pointSnapped) <= 25.0f - ) + ) { _vertexSnapObjectTo = hit; _vertexSnapPointTo = hit.Transform.WorldToLocal(pointSnapped); @@ -712,5 +691,12 @@ namespace FlaxEditor.Gizmo protected virtual void OnDuplicate() { } + + /// + public override void OnSelectionChanged(List newSelection) + { + EndVertexSnapping(); + UpdateGizmoPosition(); + } } } diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 9c6c73dfd..43c805bf4 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -509,6 +509,21 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CanSetToRoot(Prefab* prefab, Actor* ta return true; } +DEFINE_INTERNAL_CALL(void) EditorInternal_GetPrefabNestedObject(Guid* prefabId, Guid* prefabObjectId, Guid* outPrefabId, Guid* outPrefabObjectId) +{ + *outPrefabId = Guid::Empty; + *outPrefabObjectId = Guid::Empty; + const auto prefab = Content::Load(*prefabId); + if (!prefab) + return; + const ISerializable::DeserializeStream** prefabObjectDataPtr = prefab->ObjectsDataCache.TryGet(*prefabObjectId); + if (!prefabObjectDataPtr) + return; + const ISerializable::DeserializeStream& prefabObjectData = **prefabObjectDataPtr; + JsonTools::GetGuidIfValid(*outPrefabId, prefabObjectData, "PrefabID"); + JsonTools::GetGuidIfValid(*outPrefabObjectId, prefabObjectData, "PrefabObjectID"); +} + DEFINE_INTERNAL_CALL(float) EditorInternal_GetAnimationTime(AnimatedModel* animatedModel) { return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f; diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index 0abe361ad..7a9e55b88 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -278,13 +278,7 @@ void ManagedEditor::Update() void ManagedEditor::Exit() { if (WasExitCalled) - { - // Ups xD - LOG(Warning, "Managed Editor exit called after exit or before init."); return; - } - - // Set flag WasExitCalled = true; // Skip if managed object is missing diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs index 5cd212db0..e0e464c04 100644 --- a/Source/Editor/Modules/PrefabsModule.cs +++ b/Source/Editor/Modules/PrefabsModule.cs @@ -225,8 +225,15 @@ namespace FlaxEditor.Modules throw new ArgumentException("Missing prefab to apply."); PrefabApplying?.Invoke(prefab, instance); + // When applying changes to prefab from actor in level ignore it's root transformation (see ActorEditor.ProcessDiff) + var originalTransform = instance.LocalTransform; + if (instance.IsPrefabRoot && instance.Scene != null) + instance.LocalTransform = prefab.GetDefaultInstance().Transform; + // Call backend - if (PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance))) + var failed = PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance)); + instance.LocalTransform = originalTransform; + if (failed) throw new Exception("Failed to apply the prefab. See log to learn more."); PrefabApplied?.Invoke(prefab, instance); diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 831ebf3e0..df9009cc8 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -60,13 +60,26 @@ namespace FlaxEditor.Modules { } + private void BulkScenesSelectUpdate(bool select = true) + { + // Blank list deselects all + Select(select ? Editor.Scene.Root.ChildNodes : new List()); + } + /// /// Selects all scenes. /// public void SelectAllScenes() { - // Select all scenes (linked to the root node) - Select(Editor.Scene.Root.ChildNodes); + BulkScenesSelectUpdate(true); + } + + /// + /// Deselects all scenes. + /// + public void DeselectAllScenes() + { + BulkScenesSelectUpdate(false); } /// @@ -320,7 +333,7 @@ namespace FlaxEditor.Modules actorNode.PostSpawn(); // Create undo action - IUndoAction action = new DeleteActorsAction(new List(1) { actorNode }, true); + IUndoAction action = new DeleteActorsAction(actorNode, true); if (autoSelect) { var before = Selection.ToArray(); diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 302d1e02a..9aae0b72f 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -6,6 +6,7 @@ using System.IO; using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.Actors; using FlaxEngine; +using FlaxEngine.GUI; using Object = FlaxEngine.Object; namespace FlaxEditor.Modules @@ -454,6 +455,41 @@ namespace FlaxEditor.Modules Profiler.EndEvent(); } + private Dictionary _uiRootSizes; + + internal void OnSaveStart(ContainerControl uiRoot) + { + // Force viewport UI to have fixed size during scene/prefabs saving to result in stable data (less mess in version control diffs) + if (_uiRootSizes == null) + _uiRootSizes = new Dictionary(); + _uiRootSizes[uiRoot] = uiRoot.Size; + uiRoot.Size = new Float2(1920, 1080); + } + + internal void OnSaveEnd(ContainerControl uiRoot) + { + // Restore cached size of the UI root container + if (_uiRootSizes != null && _uiRootSizes.Remove(uiRoot, out var size)) + { + uiRoot.Size = size; + } + } + + private void OnSceneSaving(Scene scene, Guid sceneId) + { + OnSaveStart(RootControl.GameRoot); + } + + private void OnSceneSaved(Scene scene, Guid sceneId) + { + OnSaveEnd(RootControl.GameRoot); + } + + private void OnSceneSaveError(Scene scene, Guid sceneId) + { + OnSaveEnd(RootControl.GameRoot); + } + private void OnSceneLoaded(Scene scene, Guid sceneId) { var startTime = DateTime.UtcNow; @@ -659,6 +695,9 @@ namespace FlaxEditor.Modules Root = new ScenesRootNode(); // Bind events + Level.SceneSaving += OnSceneSaving; + Level.SceneSaved += OnSceneSaved; + Level.SceneSaveError += OnSceneSaveError; Level.SceneLoaded += OnSceneLoaded; Level.SceneUnloading += OnSceneUnloading; Level.ActorSpawned += OnActorSpawned; @@ -673,6 +712,9 @@ namespace FlaxEditor.Modules public override void OnExit() { // Unbind events + Level.SceneSaving -= OnSceneSaving; + Level.SceneSaved -= OnSceneSaved; + Level.SceneSaveError -= OnSceneSaveError; Level.SceneLoaded -= OnSceneLoaded; Level.SceneUnloading -= OnSceneUnloading; Level.ActorSpawned -= OnActorSpawned; diff --git a/Source/Editor/Modules/SimulationModule.cs b/Source/Editor/Modules/SimulationModule.cs index 39c86d5c3..c77bc4492 100644 --- a/Source/Editor/Modules/SimulationModule.cs +++ b/Source/Editor/Modules/SimulationModule.cs @@ -264,11 +264,14 @@ namespace FlaxEditor.Modules _enterPlayFocusedWindow = gameWin; // Show Game widow if hidden - if (gameWin != null && gameWin.FocusOnPlay) + if (gameWin != null) { - gameWin.FocusGameViewport(); + if (gameWin.FocusOnPlay) + gameWin.FocusGameViewport(); + gameWin.SetWindowMode(Editor.Options.Options.Interface.DefaultGameWindowMode); } + Editor.Log("[PlayMode] Enter"); } diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index e1dc68fc2..22044d095 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -54,6 +54,7 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuEditDelete; private ContextMenuButton _menuEditDuplicate; private ContextMenuButton _menuEditSelectAll; + private ContextMenuButton _menuEditDeselectAll; private ContextMenuButton _menuEditFind; private ContextMenuButton _menuSceneMoveActorToViewport; private ContextMenuButton _menuSceneAlignActorWithViewport; @@ -554,6 +555,7 @@ namespace FlaxEditor.Modules _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); cm.AddSeparator(); _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); + _menuEditDeselectAll = cm.AddButton("Deselect all", inputOptions.DeselectAll, Editor.SceneEditing.DeselectAllScenes); _menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search); cm.AddSeparator(); @@ -673,6 +675,7 @@ namespace FlaxEditor.Modules _menuEditDelete.ShortKeys = inputOptions.Delete.ToString(); _menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString(); _menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString(); + _menuEditDeselectAll.ShortKeys = inputOptions.DeselectAll.ToString(); _menuEditFind.ShortKeys = inputOptions.Search.ToString(); _menuGamePlayGame.ShortKeys = inputOptions.Play.ToString(); _menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString(); @@ -739,6 +742,17 @@ namespace FlaxEditor.Modules playActionGroup.SelectedChanged = SetPlayAction; Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; }; + var windowModesGroup = new ContextMenuSingleSelectGroup(); + var windowTypeMenu = _toolStripPlay.ContextMenu.AddChildMenu("Game window mode"); + windowModesGroup.AddItem("Docked", InterfaceOptions.GameWindowMode.Docked, null, "Shows the game window docked, inside the editor"); + windowModesGroup.AddItem("Popup", InterfaceOptions.GameWindowMode.PopupWindow, null, "Shows the game window as a popup"); + windowModesGroup.AddItem("Maximized", InterfaceOptions.GameWindowMode.MaximizedWindow, null, "Shows the game window maximized (Same as pressing F11)"); + windowModesGroup.AddItem("Borderless", InterfaceOptions.GameWindowMode.BorderlessWindow, null, "Shows the game window borderless"); + windowModesGroup.AddItemsToContextMenu(windowTypeMenu.ContextMenu); + windowModesGroup.Selected = Editor.Options.Options.Interface.DefaultGameWindowMode; + windowModesGroup.SelectedChanged = SetGameWindowMode; + Editor.Options.OptionsChanged += options => { windowModesGroup.Selected = options.Interface.DefaultGameWindowMode; }; + _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})"); _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game"); @@ -871,6 +885,7 @@ namespace FlaxEditor.Modules _menuEditDelete.Enabled = hasSthSelected; _menuEditDuplicate.Enabled = hasSthSelected; _menuEditSelectAll.Enabled = Level.IsAnySceneLoaded; + _menuEditDeselectAll.Enabled = hasSthSelected; control.PerformLayout(); } @@ -1043,6 +1058,13 @@ namespace FlaxEditor.Modules Editor.Options.Apply(options); } + private void SetGameWindowMode(InterfaceOptions.GameWindowMode newGameWindowMode) + { + var options = Editor.Options.Options; + options.Interface.DefaultGameWindowMode = newGameWindowMode; + Editor.Options.Apply(options); + } + private void OnMainWindowClosing() { // Clear UI references (GUI cannot be used after window closing) diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index cb79373b7..9bd556f20 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -56,6 +56,10 @@ namespace FlaxEditor.Options [EditorDisplay("Common"), EditorOrder(190)] public InputBinding SelectAll = new InputBinding(KeyboardKeys.A, KeyboardKeys.Control); + [DefaultValue(typeof(InputBinding), "Ctrl+Shift+A")] + [EditorDisplay("Common"), EditorOrder(195)] + public InputBinding DeselectAll = new InputBinding(KeyboardKeys.A, KeyboardKeys.Shift, KeyboardKeys.Control); + [DefaultValue(typeof(InputBinding), "F")] [EditorDisplay("Common"), EditorOrder(200)] public InputBinding FocusSelection = new InputBinding(KeyboardKeys.F); diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index c3b934531..05557f3c0 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -90,6 +90,32 @@ namespace FlaxEditor.Options PlayScenes, } + /// + /// Available window modes for the game window. + /// + public enum GameWindowMode + { + /// + /// Shows the game window docked, inside the editor. + /// + Docked, + + /// + /// Shows the game window as a popup. + /// + PopupWindow, + + /// + /// Shows the game window maximized. (Same as pressing F11) + /// + MaximizedWindow, + + /// + /// Shows the game window borderless. + /// + BorderlessWindow, + } + /// /// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required. /// @@ -229,6 +255,13 @@ namespace FlaxEditor.Options [EditorDisplay("Play In-Editor", "Play Button Action"), EditorOrder(410)] public PlayAction PlayButtonAction { get; set; } = PlayAction.PlayScenes; + /// + /// Gets or sets a value indicating how the game window should be displayed when the game is launched. + /// + [DefaultValue(GameWindowMode.Docked)] + [EditorDisplay("Play In-Editor", "Game Window Mode"), EditorOrder(420), Tooltip("Determines how the game window is displayed when the game is launched.")] + public GameWindowMode DefaultGameWindowMode { get; set; } = GameWindowMode.Docked; + /// /// Gets or sets a value indicating the number of game clients to launch when building and/or running cooked game. /// diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index f04368a77..2dceb3400 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -212,6 +212,14 @@ namespace FlaxEditor.Options string styleName = themeOptions.SelectedStyle; if (styleName != ThemeOptions.DefaultName && styleName != ThemeOptions.LightDefault && themeOptions.Styles.TryGetValue(styleName, out var style) && style != null) { + // Setup defaults for newly added components that might be missing + if (style.Selection == Color.Transparent && style.SelectionBorder == Color.Transparent) + { + // [Deprecated on 6.03.2024, expires on 6.03.2026] + style.Selection = Color.Orange * 0.4f; + style.SelectionBorder = Color.Orange; + } + Style.Current = style; } else @@ -258,6 +266,8 @@ namespace FlaxEditor.Options TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46), CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), ProgressNormal = Color.FromBgra(0xFF0ad328), + Selection = Color.Orange * 0.4f, + SelectionBorder = Color.Orange, Statusbar = new Style.StatusbarStyle { @@ -318,6 +328,8 @@ namespace FlaxEditor.Options TextBoxBackgroundSelected = new Color(0.73f, 0.73f, 0.80f, 1f), CollectionBackgroundColor = new Color(0.85f, 0.85f, 0.88f, 1f), ProgressNormal = new Color(0.03f, 0.65f, 0.12f, 1f), + Selection = Color.Orange * 0.4f, + SelectionBorder = Color.Orange, // Fonts FontTitle = options.Interface.TitleFont.GetFont(), diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 5c6adb055..91bf26103 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -316,41 +316,7 @@ namespace FlaxEditor.SceneGraph { base.OnParentChanged(); - // Update UI (special case if actor is spawned and added to existing scene tree) - var parentTreeNode = (parentNode as ActorNode)?.TreeNode; - if (parentTreeNode != null && !parentTreeNode.IsLayoutLocked) - { - parentTreeNode.IsLayoutLocked = true; - _treeNode.Parent = parentTreeNode; - _treeNode.IndexInParent = _actor.OrderInParent; - parentTreeNode.IsLayoutLocked = false; - - // Skip UI update if node won't be in a view - if (parentTreeNode.IsCollapsed) - { - TreeNode.UnlockChildrenRecursive(); - } - else - { - // Try to perform layout at the level where it makes it the most performant (the least computations) - var tree = parentTreeNode.ParentTree; - if (tree != null) - { - if (tree.Parent is FlaxEngine.GUI.Panel treeParent) - treeParent.PerformLayout(); - else - tree.PerformLayout(); - } - else - { - parentTreeNode.PerformLayout(); - } - } - } - else - { - _treeNode.Parent = parentTreeNode; - } + _treeNode.OnParentChanged(_actor, parentNode as ActorNode); } /// diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index f1ab36041..60be5bef9 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -1,5 +1,11 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_LARGE_WORLDS +using Real = System.Double; +#else +using Real = System.Single; +#endif + using System; using System.Collections.Generic; using FlaxEditor.Content; @@ -25,18 +31,20 @@ namespace FlaxEditor.SceneGraph.Actors } /// - public override bool OnVertexSnap(ref Vector3 point, out Vector3 result) + public override bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result) { - result = point; + // Find the closest vertex to bounding box point (collision detection approximation) + result = ray.GetPoint(hitDistance); var model = ((StaticModel)Actor).Model; if (model && !model.WaitForLoaded()) { // TODO: move to C++ and use cached vertex buffer internally inside the Mesh if (_vertices == null) _vertices = new(); - var pointLocal = (Float3)Actor.Transform.WorldToLocal(point); - var minDistance = float.MaxValue; - foreach (var lod in model.LODs) + var pointLocal = (Float3)Actor.Transform.WorldToLocal(result); + var minDistance = Real.MaxValue; + var lodIndex = 0; // TODO: use LOD index based on the game view + var lod = model.LODs[lodIndex]; { var hit = false; foreach (var mesh in lod.Meshes) diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 270647433..6056cf68b 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.Actions; using FlaxEditor.Content; using FlaxEditor.GUI; using FlaxEditor.GUI.Drag; @@ -14,7 +15,6 @@ using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; -using Object = FlaxEngine.Object; namespace FlaxEditor.SceneGraph.GUI { @@ -82,8 +82,51 @@ namespace FlaxEditor.SceneGraph.GUI UpdateText(); } + internal void OnParentChanged(Actor actor, ActorNode parentNode) + { + // Update cached value + _orderInParent = actor.OrderInParent; + + // Update UI (special case if actor is spawned and added to existing scene tree) + var parentTreeNode = parentNode?.TreeNode; + if (parentTreeNode != null && !parentTreeNode.IsLayoutLocked) + { + parentTreeNode.IsLayoutLocked = true; + Parent = parentTreeNode; + IndexInParent = _orderInParent; + parentTreeNode.IsLayoutLocked = false; + + // Skip UI update if node won't be in a view + if (parentTreeNode.IsCollapsed) + { + UnlockChildrenRecursive(); + } + else + { + // Try to perform layout at the level where it makes it the most performant (the least computations) + var tree = parentTreeNode.ParentTree; + if (tree != null) + { + if (tree.Parent is Panel treeParent) + treeParent.PerformLayout(); + else + tree.PerformLayout(); + } + else + { + parentTreeNode.PerformLayout(); + } + } + } + else + { + Parent = parentTreeNode; + } + } + internal void OnOrderInParentChanged() { + // Use cached value to check if we need to update UI layout (and update siblings order at once) if (Parent is ActorTreeNode parent) { var anyChanged = false; @@ -419,134 +462,6 @@ namespace FlaxEditor.SceneGraph.GUI _dragHandlers.OnDragLeave(); } - [Serializable] - private class ReparentAction : IUndoAction - { - [Serialize] - private Guid[] _ids; - - [Serialize] - private int _actorsCount; - - [Serialize] - private Guid[] _prefabIds; - - [Serialize] - private Guid[] _prefabObjectIds; - - public ReparentAction(Actor actor) - : this(new List { actor }) - { - } - - public ReparentAction(List actors) - { - var allActors = new List(Mathf.NextPowerOfTwo(actors.Count)); - - for (int i = 0; i < actors.Count; i++) - { - GetAllActors(allActors, actors[i]); - } - - var allScripts = new List