diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs index 703c3f51b..d3533528a 100644 --- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs +++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs @@ -79,6 +79,7 @@ namespace FlaxEditor.CustomEditors _presenter = presenter; AnchorPreset = AnchorPresets.StretchAll; Offsets = Margin.Zero; + Pivot = Float2.Zero; IsScrollable = true; } @@ -195,6 +196,15 @@ namespace FlaxEditor.CustomEditors Presenter.AfterLayout?.Invoke(layout); } + /// + protected override void Deinitialize() + { + Editor = null; + _overrideEditor = null; + + base.Deinitialize(); + } + /// protected override void OnModified() { diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index a93969e36..e7c8d2699 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -205,7 +205,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (_linkedPrefabId != Guid.Empty) { _linkedPrefabId = Guid.Empty; - Editor.Instance.Prefabs.PrefabApplied -= OnPrefabApplying; + Editor.Instance.Prefabs.PrefabApplying -= OnPrefabApplying; Editor.Instance.Prefabs.PrefabApplied -= OnPrefabApplied; } } diff --git a/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs b/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs index ea41f91e9..d82d6ecf7 100644 --- a/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs @@ -56,6 +56,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var bottomLeftCell = new VerticalPanel { + Pivot = Float2.Zero, Spacing = 0, TopMargin = 0, BottomMargin = 0, diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 583241915..b0239dc8f 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -1057,6 +1057,7 @@ namespace FlaxEditor.CustomEditors.Dedicated protected override void Deinitialize() { _scriptToggles = null; + _scripts.Clear(); base.Deinitialize(); } diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 5497d085f..f76bd4d89 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -232,6 +232,7 @@ namespace FlaxEditor.CustomEditors.Editors public void Setup(CollectionEditor editor, int index, bool canReorder = true) { + Pivot = Float2.Zero; HeaderHeight = 18; _canReorder = canReorder; EnableDropDownIcon = true; @@ -884,6 +885,11 @@ namespace FlaxEditor.CustomEditors.Editors set => _pickerValidator.FileExtension = value; } + public DragAreaControl() + { + Pivot = Float2.Zero; + } + /// public override void Draw() { diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index bfcd8d046..aba1ddb81 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -819,6 +819,15 @@ namespace FlaxEditor.CustomEditors.Editors OnGroupsEnd(); } + /// + protected override void Deinitialize() + { + _visibleIfCaches = null; + _visibleIfPropertiesListsCache = null; + + base.Deinitialize(); + } + /// public override void Refresh() { diff --git a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs index 972f64ba6..2d43e34c3 100644 --- a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs +++ b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs @@ -18,6 +18,7 @@ namespace FlaxEditor.CustomEditors.Elements /// public readonly DropPanel Panel = new DropPanel { + Pivot = Float2.Zero, ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight), ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown), EnableDropDownIcon = true, diff --git a/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs b/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs index f78a1eac1..4f6b9436f 100644 --- a/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs +++ b/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.Elements @@ -13,7 +14,10 @@ namespace FlaxEditor.CustomEditors.Elements /// /// The panel. /// - public readonly HorizontalPanel Panel = new HorizontalPanel(); + public readonly HorizontalPanel Panel = new HorizontalPanel + { + Pivot = Float2.Zero, + }; /// public override ContainerControl ContainerControl => Panel; diff --git a/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs b/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs index d1b0039cf..3d15d1526 100644 --- a/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs +++ b/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.Elements @@ -13,7 +14,10 @@ namespace FlaxEditor.CustomEditors.Elements /// /// The panel. /// - public readonly VerticalPanel Panel = new VerticalPanel(); + public readonly VerticalPanel Panel = new VerticalPanel + { + Pivot = Float2.Zero, + }; /// public override ContainerControl ContainerControl => Panel; diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 7f2351426..ee9251a69 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -851,6 +851,11 @@ namespace FlaxEditor { LogWarning("Exception: " + ex.Message); LogWarning(ex.StackTrace); + if (ex.InnerException != null) + { + LogWarning("Inner exception:"); + LogWarning(ex.InnerException); + } } /// @@ -1509,7 +1514,8 @@ namespace FlaxEditor var win = Windows.GameWin?.Root; if (win?.RootWindow is WindowRootControl root) { - pos = Float2.Round(Windows.GameWin.Viewport.PointFromScreen(pos) * root.DpiScale); + pos = Windows.GameWin.Viewport.PointFromScreen(pos); + pos = Float2.Round(pos); } else { @@ -1522,7 +1528,8 @@ namespace FlaxEditor var win = Windows.GameWin?.Root; if (win?.RootWindow is WindowRootControl root) { - pos = Float2.Round(Windows.GameWin.Viewport.PointToScreen(pos / root.DpiScale)); + pos = Windows.GameWin.Viewport.PointToScreen(pos); + pos = Float2.Round(pos); } else { @@ -1554,6 +1561,7 @@ namespace FlaxEditor else result = gameWin.Viewport.Size; + result *= root.DpiScale; result = Float2.Round(result); } } diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index 937d6232f..88ae18df7 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -464,7 +464,7 @@ namespace FlaxEditor.GUI.Docking { base.Focus(); - SelectTab(); + SelectTab(false); BringToFront(); } diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index e23bb27f7..6034a8559 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -294,6 +294,7 @@ namespace FlaxEditor.GUI Parent = _scrollPanel, AnchorPreset = AnchorPresets.HorizontalStretchTop, IsScrollable = true, + Pivot = Float2.Zero, }; } diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 8dabba251..1575a82d5 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -833,6 +833,7 @@ namespace FlaxEditor.GUI.Timeline { AutoFocus = false, AnchorPreset = AnchorPresets.HorizontalStretchTop, + Pivot = Float2.Zero, Offsets = Margin.Zero, IsScrollable = true, BottomMargin = 40.0f, diff --git a/Source/Editor/Gizmo/EditorPrimitives.cs b/Source/Editor/Gizmo/EditorPrimitives.cs index bfc32a638..8172a5eb2 100644 --- a/Source/Editor/Gizmo/EditorPrimitives.cs +++ b/Source/Editor/Gizmo/EditorPrimitives.cs @@ -35,7 +35,7 @@ namespace FlaxEditor.Gizmo /// public EditorPrimitives() { - Order = -100; + Order = 100; } /// diff --git a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs index 0381c6535..36b1884ad 100644 --- a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs +++ b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs @@ -11,7 +11,7 @@ namespace FlaxEngine.Gizmo; /// /// Class for adding viewport rubber band selection. /// -public class ViewportRubberBandSelector +public sealed class ViewportRubberBandSelector { private bool _isMosueCaptured; private bool _isRubberBandSpanning; @@ -38,7 +38,7 @@ public class ViewportRubberBandSelector /// True if selection started, otherwise false. public bool TryStartingRubberBandSelection() { - if (!_isRubberBandSpanning && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) + if (!_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) { _tryStartRubberBand = true; return true; @@ -90,7 +90,7 @@ public class ViewportRubberBandSelector _rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero); _tryStartRubberBand = false; } - else if (_isRubberBandSpanning && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) + else if (_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) { _rubberBandRect.Width = mousePosition.X - _cachedStartingMousePosition.X; _rubberBandRect.Height = mousePosition.Y - _cachedStartingMousePosition.Y; @@ -162,8 +162,8 @@ public class ViewportRubberBandSelector projection.Init(_owner.Viewport); foreach (var node in nodes) { - // Check for custom can select code - if (!node.CanSelectActorNodeWithSelector()) + // Skip actors that cannot be selected + if (!node.CanSelectInViewport) continue; var a = node.Actor; @@ -232,30 +232,15 @@ public class ViewportRubberBandSelector } /// - /// Used to draw the rubber band. Begins render 2D. + /// Draws the ruber band during owner viewport UI drawing. /// - /// The GPU Context. - /// The GPU texture target. - /// The GPU texture target depth. - public void Draw(GPUContext context, GPUTexture target, GPUTexture targetDepth) - { - // Draw RubberBand for rect selection - if (!_isRubberBandSpanning) - return; - Render2D.Begin(context, target, targetDepth); - Draw2D(); - Render2D.End(); - } - - /// - /// Used to draw the rubber band. Use if already rendering 2D context. - /// - public void Draw2D() + public void Draw() { if (!_isRubberBandSpanning) return; - Render2D.FillRectangle(_rubberBandRect, Style.Current.Selection); - Render2D.DrawRectangle(_rubberBandRect, Style.Current.SelectionBorder); + var style = Style.Current; + Render2D.FillRectangle(_rubberBandRect, style.Selection); + Render2D.DrawRectangle(_rubberBandRect, style.SelectionBorder); } /// diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index 4de7b593b..a21fb3b53 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -21,6 +21,7 @@ namespace FlaxEditor.Modules private bool _enableEvents; private bool _isDuringFastSetup; private bool _rebuildFlag; + private bool _rebuildInitFlag; private int _itemsCreated; private int _itemsDeleted; private readonly HashSet _dirtyNodes = new HashSet(); @@ -61,7 +62,7 @@ namespace FlaxEditor.Modules public event Action WorkspaceModified; /// - /// Occurs when workspace has will be rebuilt. + /// Occurs when workspace will be rebuilt. /// public event Action WorkspaceRebuilding; @@ -88,6 +89,9 @@ namespace FlaxEditor.Modules // Register AssetItems serialization helper (serialize ref ID only) FlaxEngine.Json.JsonSerializer.Settings.Converters.Add(new AssetItemConverter()); + + ScriptsBuilder.ScriptsReload += OnScriptsReload; + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; } private void OnContentAssetDisposing(Asset asset) @@ -817,6 +821,7 @@ namespace FlaxEditor.Modules Profiler.BeginEvent("ContentDatabase.Rebuild"); var startTime = Platform.TimeSeconds; _rebuildFlag = false; + _rebuildInitFlag = false; _enableEvents = false; // Load all folders @@ -1230,8 +1235,6 @@ namespace FlaxEditor.Modules LoadProjects(Game.Project); } - RebuildInternal(); - Editor.ContentImporting.ImportFileEnd += (obj, failed) => { var path = obj.ResultUrl; @@ -1239,6 +1242,15 @@ namespace FlaxEditor.Modules FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileDone(path)); }; _enableEvents = true; + _rebuildInitFlag = true; + } + + /// + public override void OnEndInit() + { + // Handle init when project was loaded without scripts loading () + if (_rebuildInitFlag) + RebuildInternal(); } private void OnImportFileDone(string path) @@ -1313,6 +1325,52 @@ namespace FlaxEditor.Modules } } + private void OnScriptsReload() + { + var enabledEvents = _enableEvents; + _enableEvents = false; + _isDuringFastSetup = true; + var startItems = _itemsCreated; + foreach (var project in Projects) + { + if (project.Content != null) + { + //Dispose(project.Content.Folder); + for (int i = 0; i < project.Content.Folder.Children.Count; i++) + { + Dispose(project.Content.Folder.Children[i]); + i--; + } + } + if (project.Source != null) + { + //Dispose(project.Source.Folder); + for (int i = 0; i < project.Source.Folder.Children.Count; i++) + { + Dispose(project.Source.Folder.Children[i]); + i--; + } + } + } + + List removeProxies = new List(); + foreach (var proxy in Editor.Instance.ContentDatabase.Proxy) + { + if (proxy.GetType().IsCollectible) + removeProxies.Add(proxy); + } + foreach (var proxy in removeProxies) + RemoveProxy(proxy, false); + + _isDuringFastSetup = false; + _enableEvents = enabledEvents; + } + + private void OnScriptsReloadEnd() + { + RebuildInternal(); + } + /// public override void OnUpdate() { @@ -1340,6 +1398,8 @@ namespace FlaxEditor.Modules public override void OnExit() { FlaxEngine.Content.AssetDisposing -= OnContentAssetDisposing; + ScriptsBuilder.ScriptsReload -= OnScriptsReload; + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; // Disable events _enableEvents = false; diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs index 65821cd43..d235a792e 100644 --- a/Source/Editor/Modules/ContentImportingModule.cs +++ b/Source/Editor/Modules/ContentImportingModule.cs @@ -391,6 +391,20 @@ namespace FlaxEditor.Modules public override void OnInit() { ImportFileEntry.RegisterDefaultTypes(); + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } + + private void OnScriptsReloadBegin() + { + // Remove import file types from scripting assemblies + List removeFileTypes = new List(); + foreach (var pair in ImportFileEntry.FileTypes) + { + if (pair.Value.Method.IsCollectible || (pair.Value.Target != null && pair.Value.Target.GetType().IsCollectible)) + removeFileTypes.Add(pair.Key); + } + foreach (var fileType in removeFileTypes) + ImportFileEntry.FileTypes.Remove(fileType); } /// @@ -451,6 +465,7 @@ namespace FlaxEditor.Modules /// public override void OnExit() { + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; EndWorker(); } } diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 850e4df3d..0d37a464f 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -58,7 +58,7 @@ namespace FlaxEditor.Modules : base(editor) { // After editor cache but before the windows - InitOrder = -900; + InitOrder = -800; } /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 928c02dcc..2fb382dd6 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -160,7 +160,7 @@ namespace FlaxEditor.Modules internal UIModule(Editor editor) : base(editor) { - InitOrder = -90; + InitOrder = -70; VisjectSurfaceBackground = FlaxEngine.Content.LoadAsyncInternal("Editor/VisjectSurface"); ColorValueBox.ShowPickColorDialog += ShowPickColorDialog; } diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index fa7a04d9b..6c4af3b2f 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -5,10 +5,12 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using System.Xml; using FlaxEditor.Content; using FlaxEditor.GUI.Dialogs; +using FlaxEditor.GUI.Docking; using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Profiler; @@ -39,6 +41,7 @@ namespace FlaxEditor.Modules public DockState DockState; public DockPanel DockedTo; + public int DockedTabIndex; public float? SplitterValue = null; public bool SelectOnShow = false; @@ -48,6 +51,8 @@ namespace FlaxEditor.Modules public Float2 FloatSize; public Float2 FloatPosition; + public Guid AssetItemID; + // Constructor, to allow for default values public WindowRestoreData() { @@ -803,43 +808,64 @@ namespace FlaxEditor.Modules Level.SceneSaving += OnSceneSaving; Level.SceneUnloaded += OnSceneUnloaded; Level.SceneUnloading += OnSceneUnloading; - ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + Editor.ContentDatabase.WorkspaceRebuilt += OnWorkspaceRebuilt; Editor.StateMachine.StateChanged += OnEditorStateChanged; } + internal void AddToRestore(AssetEditorWindow win) + { + AddToRestore(win, win.GetType(), new WindowRestoreData + { + AssetItemID = win.Item.ID, + }); + } + internal void AddToRestore(CustomEditorWindow win) { - var type = win.GetType(); + AddToRestore(win.Window, win.GetType(), new WindowRestoreData()); + } + private void AddToRestore(EditorWindow win, Type type, WindowRestoreData winData) + { // Validate if can restore type var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (constructor == null || type.IsGenericType) return; - var winData = new WindowRestoreData(); - var panel = win.Window.ParentDockPanel; + var panel = win.ParentDockPanel; // Ensure that this window is only selected following recompilation // if it was the active tab in its dock panel. Otherwise, there is a // risk of interrupting the user's workflow by potentially selecting // background tabs. - winData.SelectOnShow = panel.SelectedTab == win.Window; - if (panel is FloatWindowDockPanel) + var window = win.RootWindow.Window; + winData.SelectOnShow = panel.SelectedTab == win; + winData.DockedTabIndex = 0; + if (panel is FloatWindowDockPanel && window != null && panel.TabsCount == 1) { winData.DockState = DockState.Float; - var window = win.Window.RootWindow.Window; winData.FloatPosition = window.Position; winData.FloatSize = window.ClientSize; winData.Maximize = window.IsMaximized; winData.Minimize = window.IsMinimized; + winData.DockedTo = panel; } else { + for (int i = 0; i < panel.Tabs.Count; i++) + { + if (panel.Tabs[i] == win) + { + winData.DockedTabIndex = i; + break; + } + } if (panel.TabsCount > 1) { winData.DockState = DockState.DockFill; winData.DockedTo = panel; - }else + } + else { winData.DockState = panel.TryGetDockState(out var splitterValue); winData.DockedTo = panel.ParentDockPanel; @@ -851,38 +877,93 @@ namespace FlaxEditor.Modules _restoreWindows.Add(winData); } - private void OnScriptsReloadEnd() + private void OnWorkspaceRebuilt() { - for (int i = 0; i < _restoreWindows.Count; i++) + // Go in reverse order to create floating Prefab windows first before docked windows + for (int i = _restoreWindows.Count - 1; i >= 0; i--) { var winData = _restoreWindows[i]; try { var assembly = Utils.GetAssemblyByName(winData.AssemblyName); - if (assembly != null) + if (assembly == null) + continue; + + var type = assembly.GetType(winData.TypeName); + if (type == null) + continue; + + if (type.IsAssignableTo(typeof(AssetEditorWindow))) { - var type = assembly.GetType(winData.TypeName); - if (type != null) + var ctor = type.GetConstructor(new Type[] { typeof(Editor), typeof(AssetItem) }); + var assetItem = Editor.ContentDatabase.FindAsset(winData.AssetItemID); + var win = (AssetEditorWindow)ctor.Invoke(new object[] { Editor.Instance, assetItem }); + win.Show(winData.DockState, winData.DockState != DockState.Float ? winData.DockedTo : null, winData.SelectOnShow, winData.SplitterValue); + if (winData.DockState == DockState.Float) { - var win = (CustomEditorWindow)Activator.CreateInstance(type); - win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue); - if (winData.DockState == DockState.Float) + var window = win.RootWindow.Window; + window.Position = winData.FloatPosition; + if (winData.Maximize) { - var window = win.Window.RootWindow.Window; - window.Position = winData.FloatPosition; - if (winData.Maximize) - { - window.Maximize(); - } - else if (winData.Minimize) - { - window.Minimize(); - } - else - { - window.ClientSize = winData.FloatSize; - } + window.Maximize(); + } + else if (winData.Minimize) + { + window.Minimize(); + } + else + { + window.ClientSize = winData.FloatSize; + } + + // Update panel reference in other windows docked to this panel + foreach (ref var otherData in CollectionsMarshal.AsSpan(_restoreWindows)) + { + if (otherData.DockedTo == winData.DockedTo) + otherData.DockedTo = win.ParentDockPanel; + } + } + var panel = win.ParentDockPanel; + int currentTabIndex = 0; + for (int pi = 0; pi < panel.TabsCount; pi++) + { + if (panel.Tabs[pi] == win) + { + currentTabIndex = pi; + break; + } + } + while (currentTabIndex > winData.DockedTabIndex) + { + win.ParentDockPanel.MoveTabLeft(currentTabIndex); + currentTabIndex--; + } + while (currentTabIndex < winData.DockedTabIndex) + { + win.ParentDockPanel.MoveTabRight(currentTabIndex); + currentTabIndex++; + } + } + else + { + var win = (CustomEditorWindow)Activator.CreateInstance(type); + win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue); + if (winData.DockState == DockState.Float) + { + var window = win.Window.RootWindow.Window; + window.Position = winData.FloatPosition; + if (winData.Maximize) + { + window.Maximize(); + } + else if (winData.Minimize) + { + window.Minimize(); + } + else + { + window.ClientSize = winData.FloatSize; } } } @@ -893,6 +974,11 @@ namespace FlaxEditor.Modules Editor.LogWarning(string.Format("Failed to restore window {0} (assembly: {1})", winData.TypeName, winData.AssemblyName)); } } + + // Restored windows stole the focus from Editor + if (_restoreWindows.Count > 0) + Editor.Instance.Windows.MainWindow.Focus(); + _restoreWindows.Clear(); } @@ -1048,7 +1134,7 @@ namespace FlaxEditor.Modules Level.SceneSaving -= OnSceneSaving; Level.SceneUnloaded -= OnSceneUnloaded; Level.SceneUnloading -= OnSceneUnloading; - ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; + Editor.ContentDatabase.WorkspaceRebuilt -= OnWorkspaceRebuilt; Editor.StateMachine.StateChanged -= OnEditorStateChanged; // Close main window diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 0740fb84f..7d279f86b 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -192,7 +192,7 @@ namespace FlaxEditor.SceneGraph GetAllChildActorNodes(nodes); return nodes.ToArray(); } - + /// /// Get all nested actor nodes under this actor node. /// @@ -213,12 +213,18 @@ namespace FlaxEditor.SceneGraph } /// - /// Whether an actor node can be selected with a selector. + /// Whether an actor node can be selected with a selector inside editor viewport. /// - /// True if the actor node can be selected - public virtual bool CanSelectActorNodeWithSelector() + public virtual bool CanSelectInViewport { - return Actor && Actor.HideFlags is not (HideFlags.DontSelect or HideFlags.FullyHidden) && Actor is not EmptyActor && IsActive; + get + { + var actor = Actor; + return actor && + actor.IsActiveInHierarchy && + (actor.HideFlags & HideFlags.DontSelect) == HideFlags.None && + actor.GetType() != typeof(EmptyActor); + } } /// diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs index 9e380ff91..88ae8b892 100644 --- a/Source/Editor/SceneGraph/Actors/SceneNode.cs +++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs @@ -33,12 +33,6 @@ namespace FlaxEditor.SceneGraph.Actors } } - /// - public override bool CanSelectActorNodeWithSelector() - { - return false; - } - /// /// Gets the scene. /// @@ -53,6 +47,9 @@ namespace FlaxEditor.SceneGraph.Actors { } + /// + public override bool CanSelectInViewport => false; + /// public override bool CanCreatePrefab => false; diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index 428341477..fcea90d19 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -169,7 +169,6 @@ namespace FlaxEditor.SceneGraph.Actors var actor = new BoxCollider { StaticFlags = staticModelNode.Actor.StaticFlags, - Transform = staticModelNode.Actor.Transform, }; staticModelNode.Root.Spawn(actor, staticModelNode.Actor); createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor)); @@ -180,7 +179,6 @@ namespace FlaxEditor.SceneGraph.Actors var actor = new SphereCollider { StaticFlags = staticModelNode.Actor.StaticFlags, - Transform = staticModelNode.Actor.Transform, }; staticModelNode.Root.Spawn(actor, staticModelNode.Actor); createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor)); @@ -191,7 +189,6 @@ namespace FlaxEditor.SceneGraph.Actors var actor = new BoxCollider { StaticFlags = staticModelNode.Actor.StaticFlags, - Transform = staticModelNode.Actor.Transform, Size = new Float3(100.0f, 100.0f, 1.0f), }; staticModelNode.Root.Spawn(actor, staticModelNode.Actor); @@ -203,7 +200,6 @@ namespace FlaxEditor.SceneGraph.Actors var actor = new CapsuleCollider { StaticFlags = staticModelNode.Actor.StaticFlags, - Transform = staticModelNode.Actor.Transform, Radius = 25.0f, Height = 50.0f, }; @@ -220,7 +216,6 @@ namespace FlaxEditor.SceneGraph.Actors var actor = new MeshCollider { StaticFlags = staticModelNode.Actor.StaticFlags, - Transform = staticModelNode.Actor.Transform, CollisionData = collisionData, }; staticModelNode.Root.Spawn(actor, staticModelNode.Actor); diff --git a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs index ef7663e5d..af1406e5d 100644 --- a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs +++ b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs @@ -80,9 +80,6 @@ namespace FlaxEditor.SceneGraph.Actors } /// - public override bool CanSelectActorNodeWithSelector() - { - return Actor is UICanvas uiCanvas && uiCanvas.Is3D && base.CanSelectActorNodeWithSelector(); - } + public override bool CanSelectInViewport => base.CanSelectInViewport && Actor is UICanvas uiCanvas && uiCanvas.Is3D; } } diff --git a/Source/Editor/SceneGraph/Actors/UIControlNode.cs b/Source/Editor/SceneGraph/Actors/UIControlNode.cs index 57a956665..677571d92 100644 --- a/Source/Editor/SceneGraph/Actors/UIControlNode.cs +++ b/Source/Editor/SceneGraph/Actors/UIControlNode.cs @@ -42,29 +42,31 @@ namespace FlaxEditor.SceneGraph.Actors } /// - public override bool CanSelectActorNodeWithSelector() + public override bool CanSelectInViewport { - // Check if control and skip if canvas is 2D - if (Actor is not UIControl uiControl) - return false; - UICanvas canvas = null; - var controlParent = uiControl.Parent; - while (controlParent != null && controlParent is not Scene) + get { - if (controlParent is UICanvas uiCanvas) - { - canvas = uiCanvas; - break; - } - controlParent = controlParent.Parent; - } - - if (canvas != null) - { - if (canvas.Is2D) + // Check if control and skip if canvas is 2D + if (Actor is not UIControl uiControl) return false; + UICanvas canvas = null; + var controlParent = uiControl.Parent; + while (controlParent != null && controlParent is not Scene) + { + if (controlParent is UICanvas uiCanvas) + { + canvas = uiCanvas; + break; + } + controlParent = controlParent.Parent; + } + if (canvas != null) + { + if (canvas.Is2D) + return false; + } + return base.CanSelectInViewport; } - return base.CanSelectActorNodeWithSelector(); } } } diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index 5becb69dc..37afc896e 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -469,6 +469,7 @@ namespace FlaxEditor.SceneGraph { ChildNodes[i].OnDispose(); } + ChildNodes.Clear(); SceneGraphFactory.Nodes.Remove(ID); } diff --git a/Source/Editor/States/ChangingScenesState.cs b/Source/Editor/States/ChangingScenesState.cs index 17fa30ff7..e1a95e6ab 100644 --- a/Source/Editor/States/ChangingScenesState.cs +++ b/Source/Editor/States/ChangingScenesState.cs @@ -164,10 +164,21 @@ namespace FlaxEditor.States { Assert.AreEqual(Guid.Empty, _lastSceneFromRequest, "Invalid state."); - // Bind events - Level.SceneLoaded += OnSceneEvent; - Level.SceneLoadError += OnSceneEvent; - Level.SceneUnloaded += OnSceneEvent; + // Bind events, only bind loading event and error if re-loading the same scene to avoid issues. + if (_scenesToUnload.Count == 1 && _scenesToLoad.Count == 1) + { + if (_scenesToLoad[0] == _scenesToUnload[0].ID) + { + Level.SceneLoaded += OnSceneEvent; + Level.SceneLoadError += OnSceneEvent; + } + } + else + { + Level.SceneLoaded += OnSceneEvent; + Level.SceneLoadError += OnSceneEvent; + Level.SceneUnloaded += OnSceneEvent; + } // Push scenes changing requests for (int i = 0; i < _scenesToUnload.Count; i++) diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index c33224072..30a15d7f4 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -161,6 +161,8 @@ namespace FlaxEditor.Surface private void OnScriptsReloadBegin() { + _nodesCache.Clear(); + // Check if any of the nodes comes from the game scripts - those can be reloaded at runtime so prevent crashes bool hasTypeFromGameScripts = Editor.Instance.CodeEditing.AnimGraphNodes.HasTypeFromGameScripts; diff --git a/Source/Editor/Surface/BehaviorTreeSurface.cs b/Source/Editor/Surface/BehaviorTreeSurface.cs index 647c438e8..2dba081b0 100644 --- a/Source/Editor/Surface/BehaviorTreeSurface.cs +++ b/Source/Editor/Surface/BehaviorTreeSurface.cs @@ -24,6 +24,7 @@ namespace FlaxEditor.Surface public BehaviorTreeSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo) : base(owner, onSave, undo, CreateStyle()) { + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; } private static SurfaceStyle CreateStyle() @@ -35,6 +36,11 @@ namespace FlaxEditor.Surface return style; } + private void OnScriptsReloadBegin() + { + _nodesCache.Clear(); + } + private static void DrawBox(Box box) { var rect = new Rectangle(Float2.Zero, box.Size); @@ -186,6 +192,7 @@ namespace FlaxEditor.Surface { if (IsDisposing) return; + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; _nodesCache.Wait(); base.OnDestroy(); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 21aa9ca7f..c835e658c 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -235,6 +235,7 @@ namespace FlaxEditor.Surface.ContextMenu { Parent = panel1, AnchorPreset = AnchorPresets.HorizontalStretchTop, + Pivot = Float2.Zero, IsScrollable = true, }; _groupsPanel = panel2; @@ -292,6 +293,7 @@ namespace FlaxEditor.Surface.ContextMenu X = 8, Width = Width * 0.5f - 16, AutoSize = true, + Pivot = Float2.Zero, }; _descriptionOutputPanel = new VerticalPanel() @@ -300,6 +302,7 @@ namespace FlaxEditor.Surface.ContextMenu X = Width * 0.5f + 8, Width = Width * 0.5f - 16, AutoSize = true, + Pivot = Float2.Zero, }; } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs index d794d1642..08dd8e6fd 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs @@ -43,6 +43,7 @@ namespace FlaxEditor.Surface.ContextMenu /// The group archetype. public VisjectCMGroup(VisjectCM cm, GroupArchetype archetype) { + Pivot = Float2.Zero; ContextMenu = cm; Archetypes.Add(archetype); Name = archetype.Name; diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 28ec17d53..66572324d 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -415,6 +415,15 @@ namespace FlaxEditor.Surface // Init drag handlers DragHandlers.Add(_dragAssets = new DragAssets(ValidateDragItem)); DragHandlers.Add(_dragParameters = new DragNames(SurfaceParameter.DragPrefix, ValidateDragParameter)); + + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } + + private void OnScriptsReloadBegin() + { + _activeVisjectCM = null; + _cmPrimaryMenu?.Dispose(); + _cmPrimaryMenu = null; } /// @@ -1023,6 +1032,8 @@ namespace FlaxEditor.Surface _activeVisjectCM = null; _cmPrimaryMenu?.Dispose(); + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + base.OnDestroy(); } } diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index ff0153e8a..672a527a9 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -62,6 +62,12 @@ namespace FlaxEditor.Surface { _supportsImplicitCastFromObjectToBoolean = true; DragHandlers.Add(_dragActors = new DragActors(ValidateDragActor)); + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } + + private void OnScriptsReloadBegin() + { + _nodesCache.Clear(); } private bool ValidateDragActor(ActorNode actor) @@ -631,6 +637,7 @@ namespace FlaxEditor.Surface { if (IsDisposing) return; + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; _nodesCache.Wait(); base.OnDestroy(); diff --git a/Source/Editor/Tools/Foliage/FoliageTypesTab.cs b/Source/Editor/Tools/Foliage/FoliageTypesTab.cs index f7a9d1ed3..93dc05de8 100644 --- a/Source/Editor/Tools/Foliage/FoliageTypesTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTypesTab.cs @@ -352,6 +352,7 @@ namespace FlaxEditor.Tools.Foliage { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(4, 4, 4, 0), + Pivot = Float2.Zero, IsScrollable = true, Parent = splitPanel.Panel1 }; diff --git a/Source/Editor/Tools/Foliage/PaintTab.cs b/Source/Editor/Tools/Foliage/PaintTab.cs index d1ff4cd81..29af974e8 100644 --- a/Source/Editor/Tools/Foliage/PaintTab.cs +++ b/Source/Editor/Tools/Foliage/PaintTab.cs @@ -204,6 +204,7 @@ namespace FlaxEditor.Tools.Foliage { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(4, 4, 4, 0), + Pivot = Float2.Zero, IsScrollable = true, Parent = splitPanel.Panel1 }; diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 12c5ebfd0..5daa78fd2 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1211,6 +1211,7 @@ namespace FlaxEditor.Utilities { Parent = panel1, AnchorPreset = AnchorPresets.HorizontalStretchTop, + Pivot = Float2.Zero, IsScrollable = true, }; tree = new Tree(false) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 943bb6e3c..ab1b8b312 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -7,14 +7,11 @@ using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; -using FlaxEditor.Tools; using FlaxEditor.Viewport.Modes; using FlaxEditor.Windows; using FlaxEngine; using FlaxEngine.Gizmo; using FlaxEngine.GUI; -using FlaxEngine.Tools; - using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport @@ -26,10 +23,8 @@ namespace FlaxEditor.Viewport public class MainEditorGizmoViewport : EditorGizmoViewport, IEditorPrimitivesOwner { private readonly Editor _editor; - private readonly ContextMenuButton _showGridButton; private readonly ContextMenuButton _showNavigationButton; - private SelectionOutline _customSelectionOutline; /// @@ -218,7 +213,7 @@ namespace FlaxEditor.Viewport TransformGizmo.ApplyTransformation += ApplyTransform; TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate; Gizmos.Active = TransformGizmo; - + // Add rubber band selector _rubberBandSelector = new ViewportRubberBandSelector(this); @@ -375,10 +370,7 @@ namespace FlaxEditor.Viewport { Gizmos[i].Draw(ref renderContext); } - - // Draw RubberBand for rect selection - _rubberBandSelector.Draw(context, target, targetDepth); - + // Draw selected objects debug shapes and visuals if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw) { @@ -594,6 +586,15 @@ namespace FlaxEditor.Viewport } } + /// + public override void Draw() + { + base.Draw(); + + // Draw rubber band for rectangle selection + _rubberBandSelector.Draw(); + } + /// protected override void OrientViewport(ref Quaternion orientation) { @@ -609,7 +610,8 @@ namespace FlaxEditor.Viewport base.OnMouseMove(location); // Don't allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled - bool canStart = !((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown || IsAltKeyDown); + bool canStart = !(IsControllingMouse || IsRightMouseButtonDown || IsAltKeyDown) && + Gizmos.Active is TransformGizmo && !Gizmos.Active.IsControllingMouse; _rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos, ViewFrustum); } diff --git a/Source/Editor/Windows/Assets/AssetEditorWindow.cs b/Source/Editor/Windows/Assets/AssetEditorWindow.cs index f781d49dc..e97cb7371 100644 --- a/Source/Editor/Windows/Assets/AssetEditorWindow.cs +++ b/Source/Editor/Windows/Assets/AssetEditorWindow.cs @@ -58,6 +58,8 @@ namespace FlaxEditor.Windows.Assets InputActions.Add(options => options.Save, Save); UpdateTitle(); + + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; } /// @@ -151,6 +153,8 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + if (_item != null) { // Ensure to remove linkage to the item @@ -160,6 +164,15 @@ namespace FlaxEditor.Windows.Assets base.OnDestroy(); } + /// + protected virtual void OnScriptsReloadBegin() + { + if (!IsHidden) + { + Editor.Instance.Windows.AddToRestore(this); + } + } + #region IEditable Implementation private bool _isEdited; diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index 0df851f5b..fe94b1b86 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -268,8 +268,11 @@ namespace FlaxEditor.Windows.Assets UpdateKnowledge(); } - private void OnScriptsReloadBegin() + /// + protected override void OnScriptsReloadBegin() { + base.OnScriptsReloadBegin(); + // TODO: impl hot-reload for BT to nicely refresh state (save asset, clear undo/properties, reload surface) Close(); } diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index 5bc6e62c5..e1bd19609 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -124,8 +124,10 @@ namespace FlaxEditor.Windows.Assets UpdateToolstrip(); } - private void OnScriptsReloadBegin() + /// + protected override void OnScriptsReloadBegin() { + base.OnScriptsReloadBegin(); Close(); } @@ -307,6 +309,20 @@ namespace FlaxEditor.Windows.Assets base.OnAssetLoadFailed(); } + /// + public override void OnLostFocus() + { + base.OnLostFocus(); + _optionsCM?.Dispose(); + } + + /// + public override void OnExit() + { + base.OnExit(); + _optionsCM?.Dispose(); + } + /// public override void OnItemReimported(ContentItem item) { @@ -329,6 +345,7 @@ namespace FlaxEditor.Windows.Assets _isRegisteredForScriptsReload = false; ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; } + _optionsCM?.Dispose(); _typeText = null; } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 191a6911e..097d56af2 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -198,7 +198,6 @@ namespace FlaxEditor.Windows.Assets Editor.Prefabs.PrefabApplied += OnPrefabApplied; ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; - ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; // Setup input actions InputActions.Add(options => options.Undo, () => @@ -291,8 +290,10 @@ namespace FlaxEditor.Windows.Assets return false; } - private void OnScriptsReloadBegin() + /// + protected override void OnScriptsReloadBegin() { + base.OnScriptsReloadBegin(); _isScriptsReloading = true; if (_asset == null || !_asset.IsLoaded) @@ -320,19 +321,8 @@ namespace FlaxEditor.Windows.Assets Graph.MainActor = null; _viewport.Prefab = null; _undo?.Clear(); // TODO: maybe don't clear undo? - } - private void OnScriptsReloadEnd() - { - _isScriptsReloading = false; - - if (_asset == null || !_asset.IsLoaded) - return; - - // Restore - OnPrefabOpened(); - _undo.Clear(); - ClearEditedFlag(); + Close(); } private void OnUndoEvent(IUndoAction action) @@ -555,7 +545,6 @@ namespace FlaxEditor.Windows.Assets Editor.Prefabs.PrefabApplied -= OnPrefabApplied; ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; - ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; _undo.Dispose(); Graph.Dispose(); diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 6089b2e07..f452c485a 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -29,6 +29,7 @@ namespace FlaxEditor.Windows private const string ProjectDataLastViewedFolder = "LastViewedFolder"; private bool _isWorkspaceDirty; private string _workspaceRebuildLocation; + private string _lastViewedFolderBeforeReload; private SplitPanel _split; private Panel _contentViewPanel; private Panel _contentTreePanel; @@ -144,26 +145,6 @@ namespace FlaxEditor.Windows FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); - // Content database events - editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true; - editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved; - editor.ContentDatabase.WorkspaceRebuilding += () => { _workspaceRebuildLocation = SelectedNode?.Path; }; - editor.ContentDatabase.WorkspaceRebuilt += () => - { - var selected = Editor.ContentDatabase.Find(_workspaceRebuildLocation); - if (selected is ContentFolder selectedFolder) - { - _navigationUnlocked = false; - RefreshView(selectedFolder.Node); - _tree.Select(selectedFolder.Node); - UpdateItemsSearch(); - _navigationUnlocked = true; - UpdateUI(); - } - else - ShowRoot(); - }; - var options = Editor.Options; options.OptionsChanged += OnOptionsChanged; @@ -1036,6 +1017,61 @@ namespace FlaxEditor.Windows /// public override void OnInit() + { + // Content database events + Editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true; + Editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved; + Editor.ContentDatabase.WorkspaceRebuilding += () => { _workspaceRebuildLocation = SelectedNode?.Path; }; + Editor.ContentDatabase.WorkspaceRebuilt += () => + { + var selected = Editor.ContentDatabase.Find(_workspaceRebuildLocation); + if (selected is ContentFolder selectedFolder) + { + _navigationUnlocked = false; + RefreshView(selectedFolder.Node); + _tree.Select(selectedFolder.Node); + UpdateItemsSearch(); + _navigationUnlocked = true; + UpdateUI(); + } + else if (_root != null) + ShowRoot(); + }; + + Refresh(); + + // Load last viewed folder + if (Editor.ProjectCache.TryGetCustomData(ProjectDataLastViewedFolder, out string lastViewedFolder)) + { + if (Editor.ContentDatabase.Find(lastViewedFolder) is ContentFolder folder) + _tree.Select(folder.Node); + } + + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + } + + private void OnScriptsReloadBegin() + { + var lastViewedFolder = _tree.Selection.Count == 1 ? _tree.SelectedNode as ContentTreeNode : null; + _lastViewedFolderBeforeReload = lastViewedFolder?.Path ?? string.Empty; + + _tree.RemoveChild(_root); + _root = null; + } + + private void OnScriptsReloadEnd() + { + Refresh(); + + if (!string.IsNullOrEmpty(_lastViewedFolderBeforeReload)) + { + if (Editor.ContentDatabase.Find(_lastViewedFolderBeforeReload) is ContentFolder folder) + _tree.Select(folder.Node); + } + } + + private void Refresh() { // Setup content root node _root = new RootContentTreeNode @@ -1072,13 +1108,6 @@ namespace FlaxEditor.Windows // Update UI layout _isLayoutLocked = false; PerformLayout(); - - // Load last viewed folder - if (Editor.ProjectCache.TryGetCustomData(ProjectDataLastViewedFolder, out string lastViewedFolder)) - { - if (Editor.ContentDatabase.Find(lastViewedFolder) is ContentFolder folder) - _tree.Select(folder.Node); - } } /// @@ -1226,6 +1255,8 @@ namespace FlaxEditor.Windows _viewDropdown = null; Editor.Options.OptionsChanged -= OnOptionsChanged; + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; base.OnDestroy(); } diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index 6eddcb81c..eaf5b792f 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -390,6 +390,7 @@ namespace FlaxEditor.Windows _entriesPanel = new VerticalPanel { AnchorPreset = AnchorPresets.HorizontalStretchTop, + Pivot = Float2.Zero, Offsets = Margin.Zero, IsScrollable = true, Parent = _split.Panel1, diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index cfa74d338..69359b349 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -582,7 +582,7 @@ namespace FlaxEditor.Windows private void OnOutputTextChanged() { - if (IsLayoutLocked) + if (IsLayoutLocked || _output == null) return; _hScroll.Maximum = Mathf.Max(_output.TextSize.X, _hScroll.Minimum); diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index 25dfb773c..282c1dfe9 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -162,11 +162,13 @@ namespace FlaxEditor.Windows { AnchorPreset = AnchorPresets.StretchAll, Offsets = Margin.Zero, + Pivot = Float2.Zero, Parent = this, }; var panel = new VerticalPanel { AnchorPreset = AnchorPresets.HorizontalStretchTop, + Pivot = Float2.Zero, Offsets = Margin.Zero, IsScrollable = true, Parent = scroll, @@ -187,6 +189,7 @@ namespace FlaxEditor.Windows var vp = new Panel { AnchorPreset = AnchorPresets.StretchAll, + Offsets = Margin.Zero, Parent = this, }; _addPluginProjectButton = new Button diff --git a/Source/Editor/Windows/Profiler/Assets.cs b/Source/Editor/Windows/Profiler/Assets.cs index bfd14a8ac..3115aa967 100644 --- a/Source/Editor/Windows/Profiler/Assets.cs +++ b/Source/Editor/Windows/Profiler/Assets.cs @@ -68,6 +68,7 @@ namespace FlaxEditor.Windows.Profiler { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = Margin.Zero, + Pivot = Float2.Zero, IsScrollable = true, Parent = panel, }; diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index 7e3444bf0..2db0258fc 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -88,6 +88,7 @@ namespace FlaxEditor.Windows.Profiler { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = Margin.Zero, + Pivot = Float2.Zero, IsScrollable = true, Parent = panel, }; diff --git a/Source/Editor/Windows/Profiler/GPU.cs b/Source/Editor/Windows/Profiler/GPU.cs index 58fbc043c..baaf9708e 100644 --- a/Source/Editor/Windows/Profiler/GPU.cs +++ b/Source/Editor/Windows/Profiler/GPU.cs @@ -65,6 +65,7 @@ namespace FlaxEditor.Windows.Profiler { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = Margin.Zero, + Pivot = Float2.Zero, IsScrollable = true, Parent = panel, }; diff --git a/Source/Editor/Windows/Profiler/Memory.cs b/Source/Editor/Windows/Profiler/Memory.cs index 5b026fa20..1b594fdf8 100644 --- a/Source/Editor/Windows/Profiler/Memory.cs +++ b/Source/Editor/Windows/Profiler/Memory.cs @@ -2,6 +2,7 @@ #if USE_PROFILER using System; +using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.Windows.Profiler @@ -29,6 +30,7 @@ namespace FlaxEditor.Windows.Profiler { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = Margin.Zero, + Pivot = Float2.Zero, IsScrollable = true, Parent = panel, }; diff --git a/Source/Editor/Windows/Profiler/MemoryGPU.cs b/Source/Editor/Windows/Profiler/MemoryGPU.cs index 205af8f58..b4fae80c3 100644 --- a/Source/Editor/Windows/Profiler/MemoryGPU.cs +++ b/Source/Editor/Windows/Profiler/MemoryGPU.cs @@ -69,6 +69,7 @@ namespace FlaxEditor.Windows.Profiler { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = Margin.Zero, + Pivot = Float2.Zero, IsScrollable = true, Parent = panel, }; diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs index 68c94186b..af248fcc6 100644 --- a/Source/Editor/Windows/Profiler/Network.cs +++ b/Source/Editor/Windows/Profiler/Network.cs @@ -63,6 +63,7 @@ namespace FlaxEditor.Windows.Profiler { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = Margin.Zero, + Pivot = Float2.Zero, IsScrollable = true, Parent = panel, }; diff --git a/Source/Editor/Windows/Profiler/Overall.cs b/Source/Editor/Windows/Profiler/Overall.cs index e70c01a54..14d423aec 100644 --- a/Source/Editor/Windows/Profiler/Overall.cs +++ b/Source/Editor/Windows/Profiler/Overall.cs @@ -33,6 +33,7 @@ namespace FlaxEditor.Windows.Profiler { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = Margin.Zero, + Pivot = Float2.Zero, IsScrollable = true, Parent = panel, }; diff --git a/Source/Editor/Windows/Profiler/Physics.cs b/Source/Editor/Windows/Profiler/Physics.cs index 3e5ec6605..c44a26beb 100644 --- a/Source/Editor/Windows/Profiler/Physics.cs +++ b/Source/Editor/Windows/Profiler/Physics.cs @@ -33,6 +33,7 @@ namespace FlaxEditor.Windows.Profiler { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = Margin.Zero, + Pivot = Float2.Zero, IsScrollable = true, Parent = panel, }; diff --git a/Source/Editor/Windows/SceneTreeWindow.RenameWindow.cs b/Source/Editor/Windows/SceneTreeWindow.RenameWindow.cs index 6278a5f3b..fff8bcb04 100644 --- a/Source/Editor/Windows/SceneTreeWindow.RenameWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.RenameWindow.cs @@ -92,6 +92,7 @@ namespace FlaxEditor.Windows Parent = this, AnchorPreset = AnchorPresets.StretchAll, Offset = Vector2.Zero, + Pivot = Float2.Zero, AutoSize = false, Bounds = Rectangle.Empty }; diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 4b347ea83..dd550b0f1 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -150,6 +150,22 @@ namespace FlaxEditor.Windows _searchBox.Clear(); _groupSearch.DisposeChildren(); _groupSearch.PerformLayout(); + + // Remove tabs + var tabs = new List(); + foreach (var child in _actorGroups.Children) + { + if (child is Tab tab) + { + if (tab.Text != "Search") + tabs.Add(tab); + } + } + foreach (var tab in tabs) + { + var group = _actorGroups.Children.Find(T => T == tab); + group.Dispose(); + } } private void OnScriptsReloadEnd() diff --git a/Source/Engine/Animations/AnimationData.cpp b/Source/Engine/Animations/AnimationData.cpp index e1115ea53..da285200a 100644 --- a/Source/Engine/Animations/AnimationData.cpp +++ b/Source/Engine/Animations/AnimationData.cpp @@ -73,7 +73,7 @@ void AnimationData::Swap(AnimationData& other) Channels.Swap(other.Channels); } -void AnimationData::Dispose() +void AnimationData::Release() { Name.Clear(); Duration = 0.0; diff --git a/Source/Engine/Animations/AnimationData.h b/Source/Engine/Animations/AnimationData.h index c37396243..1cf267a3d 100644 --- a/Source/Engine/Animations/AnimationData.h +++ b/Source/Engine/Animations/AnimationData.h @@ -170,5 +170,5 @@ public: /// /// Releases data. /// - void Dispose(); + void Release(); }; diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 856586129..be5768af2 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -738,7 +738,7 @@ void Animation::unload(bool isReloading) Level::ScriptsReloadStart.Unbind(this); } #endif - Data.Dispose(); + Data.Release(); for (const auto& e : Events) { for (const auto& k : e.Second.GetKeyframes()) diff --git a/Source/Engine/Content/Assets/AnimationGraph.cpp b/Source/Engine/Content/Assets/AnimationGraph.cpp index 37996b5aa..d894a2c02 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.cpp +++ b/Source/Engine/Content/Assets/AnimationGraph.cpp @@ -203,7 +203,7 @@ void AnimationGraph::FindDependencies(AnimGraphBase* graph) { for (const auto& node : graph->Nodes) { - if (node.Type == GRAPH_NODE_MAKE_TYPE(9, 24)) + if (node.Type == GRAPH_NODE_MAKE_TYPE(9, 24) && node.Assets.Count() > 0) { const auto function = node.Assets[0].As(); if (function) diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index 888cc303e..65bda2cb4 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -24,6 +24,7 @@ #include "Engine/Level/Scripts/ModelPrefab.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Utilities/RectPack.h" +#include "Engine/Scripting/Scripting.h" #include "Engine/Profiler/ProfilerCPU.h" #include "AssetsImportingManager.h" @@ -171,6 +172,33 @@ bool SortMeshGroups(IGrouping const& i1, IGroupingGetType().GetDefaultInstance(); + src->Serialize(writer, defaultInstance); + writer.EndObject(); + + // Parse json + rapidjson_flax::Document document; + document.Parse(buffer.GetString(), buffer.GetSize()); + + // Strip unwanted data + document.RemoveMember("ID"); + document.RemoveMember("ParentID"); + document.RemoveMember("PrefabID"); + document.RemoveMember("PrefabObjectID"); + if (stripName) + document.RemoveMember("Name"); + + // Deserialize destination + auto modifier = Cache::ISerializeModifier.Get(); + dst->Deserialize(document, &*modifier); +} + CreateAssetResult ImportModel::Import(CreateAssetContext& context) { // Get import options @@ -659,6 +687,8 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M // Create prefab structure Dictionary nodeToActor; + Dictionary newPrefabObjects; // Maps prefab object id to the restored and linked object + rapidjson_flax::StringBuffer jsonBuffer; Array nodeActors; Actor* rootActor = nullptr; for (int32 nodeIndex = 0; nodeIndex < data.Nodes.Count(); nodeIndex++) @@ -749,7 +779,6 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M // Link with object from prefab (if reimporting) if (prefab) { - rapidjson_flax::StringBuffer buffer; for (Actor* a : nodeActors) { for (const auto& i : prefab->ObjectsCache) @@ -761,33 +790,12 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M continue; // Preserve local changes made in the prefab - { - // Serialize - buffer.Clear(); - CompactJsonWriter writer(buffer); - writer.StartObject(); - const void* defaultInstance = o->GetType().GetDefaultInstance(); - o->Serialize(writer, defaultInstance); - writer.EndObject(); - - // Parse json - rapidjson_flax::Document document; - document.Parse(buffer.GetString(), buffer.GetSize()); - - // Strip unwanted data - document.RemoveMember("ID"); - document.RemoveMember("ParentID"); - document.RemoveMember("PrefabID"); - document.RemoveMember("PrefabObjectID"); - document.RemoveMember("Name"); - - // Deserialize object - auto modifier = Cache::ISerializeModifier.Get(); - a->Deserialize(document, &*modifier); - } + CloneObject(jsonBuffer, o, a, true); // Mark as this object already exists in prefab so will be preserved when updating it - a->LinkPrefab(o->GetPrefabID(), o->GetPrefabObjectID()); + const Guid prefabObjectId = o->GetPrefabObjectID(); + a->LinkPrefab(o->GetPrefabID(), prefabObjectId); + newPrefabObjects.Add(prefabObjectId, a); break; } } @@ -808,12 +816,43 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M { if (i.Value->GetTypeHandle() == modelPrefabScript->GetTypeHandle()) { - modelPrefabScript->LinkPrefab(i.Value->GetPrefabID(), i.Value->GetPrefabObjectID()); + const Guid prefabObjectId = i.Value->GetPrefabObjectID(); + modelPrefabScript->LinkPrefab(i.Value->GetPrefabID(), prefabObjectId); + newPrefabObjects.Add(prefabObjectId, modelPrefabScript); break; } } } } + if (prefab) + { + // Preserve existing objects added by user (eg. colliders, sfx, vfx, scripts) + for (const auto& i : prefab->ObjectsCache) + { + // Skip already restored objects + const Guid prefabObjectId = i.Key; + if (newPrefabObjects.ContainsKey(prefabObjectId)) + continue; + SceneObject* defaultObject = i.Value; + // TODO: ignore objects that were imported previously but not now (eg. mesh was removed from source asset) + + // Find parent to link + SceneObject* parent; + if (!newPrefabObjects.TryGet(defaultObject->GetParent()->GetPrefabObjectID(), parent)) + continue; + + // Duplicate object + SceneObject* restoredObject = (SceneObject*)Scripting::NewObject(defaultObject->GetTypeHandle()); + if (!restoredObject) + continue; + CloneObject(jsonBuffer, defaultObject, restoredObject); + restoredObject->SetParent((Actor*)parent); + + // Link with existing prefab instance + restoredObject->LinkPrefab(i.Value->GetPrefabID(), prefabObjectId); + newPrefabObjects.Add(prefabObjectId, restoredObject); + } + } // Create prefab instead of native asset bool failed; diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index c7507c778..148efb007 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -176,6 +176,7 @@ namespace FlaxEngine.Interop _managedHandle.Free(); _unmanagedData = IntPtr.Zero; } + _arrayType = _elementType = null; ManagedArrayPool.Put(this); } @@ -442,22 +443,25 @@ namespace FlaxEngine.Interop /// /// Tries to free all references to old weak handles so GC can collect them. /// - internal static void TryCollectWeakHandles() + internal static void TryCollectWeakHandles(bool force = false) { - if (weakHandleAccumulator < nextWeakPoolCollection) - return; + if (!force) + { + if (weakHandleAccumulator < nextWeakPoolCollection) + return; - nextWeakPoolCollection = weakHandleAccumulator + 1000; + nextWeakPoolCollection = weakHandleAccumulator + 1000; - // Try to swap pools after garbage collection or whenever the pool gets too large - var gc0CollectionCount = GC.CollectionCount(0); - if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold) - return; - nextWeakPoolGCCollection = gc0CollectionCount + 1; + // Try to swap pools after garbage collection or whenever the pool gets too large + var gc0CollectionCount = GC.CollectionCount(0); + if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold) + return; + nextWeakPoolGCCollection = gc0CollectionCount + 1; - // Prevent huge allocations from swapping the pools in the middle of the operation - if (System.Diagnostics.Stopwatch.GetElapsedTime(lastWeakPoolCollectionTime).TotalMilliseconds < WeakPoolCollectionTimeThreshold) - return; + // Prevent huge allocations from swapping the pools in the middle of the operation + if (System.Diagnostics.Stopwatch.GetElapsedTime(lastWeakPoolCollectionTime).TotalMilliseconds < WeakPoolCollectionTimeThreshold) + return; + } lastWeakPoolCollectionTime = System.Diagnostics.Stopwatch.GetTimestamp(); // Swap the pools and release the oldest pool for GC diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 6c6858a20..245d8a150 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -972,7 +972,49 @@ namespace FlaxEngine.Interop } [UnmanagedCallersOnly] - internal static void ReloadScriptingAssemblyLoadContext() + internal static void CreateScriptingAssemblyLoadContext() + { +#if FLAX_EDITOR + if (scriptingAssemblyLoadContext != null) + { + // Wait for previous ALC to finish unloading, track it without holding strong references to it + GCHandle weakRef = GCHandle.Alloc(scriptingAssemblyLoadContext, GCHandleType.WeakTrackResurrection); + scriptingAssemblyLoadContext = null; +#if false + // In case the ALC doesn't unload properly: https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability#debug-unloading-issues + while (true) +#else + for (int attempts = 5; attempts > 0; attempts--) +#endif + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + + if (!IsHandleAlive(weakRef)) + break; + System.Threading.Thread.Sleep(1); + } + if (IsHandleAlive(weakRef)) + Debug.Logger.LogHandler.LogWrite(LogType.Warning, "Scripting AssemblyLoadContext was not unloaded."); + weakRef.Free(); + + static bool IsHandleAlive(GCHandle weakRef) + { + // Checking the target in scope somehow holds a reference to it...? + return weakRef.Target != null; + } + } + + scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible: true); + scriptingAssemblyLoadContext.Resolving += OnScriptingAssemblyLoadContextResolving; +#else + scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible: false); +#endif + DelegateHelpers.InitMethods(); + } + + [UnmanagedCallersOnly] + internal static void UnloadScriptingAssemblyLoadContext() { #if FLAX_EDITOR // Clear all caches which might hold references to assemblies in collectible ALC @@ -990,24 +1032,86 @@ namespace FlaxEngine.Interop handle.Free(); propertyHandleCacheCollectible.Clear(); + foreach (var key in assemblyHandles.Keys.Where(x => x.IsCollectible)) + assemblyHandles.Remove(key); + foreach (var key in assemblyOwnedNativeLibraries.Keys.Where(x => x.IsCollectible)) + assemblyOwnedNativeLibraries.Remove(key); + _typeSizeCache.Clear(); foreach (var pair in classAttributesCacheCollectible) pair.Value.Free(); classAttributesCacheCollectible.Clear(); + ArrayFactory.marshalledTypes.Clear(); + ArrayFactory.arrayTypes.Clear(); + ArrayFactory.createArrayDelegates.Clear(); + FlaxEngine.Json.JsonSerializer.ResetCache(); + DelegateHelpers.Release(); + + // Ensure both pools are empty + ManagedHandle.ManagedHandlePool.TryCollectWeakHandles(true); + ManagedHandle.ManagedHandlePool.TryCollectWeakHandles(true); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + + { + // HACK: Workaround for TypeDescriptor holding references to collectible types (https://github.com/dotnet/runtime/issues/30656) + + Type typeDescriptionProviderType = typeof(System.ComponentModel.TypeDescriptionProvider); + MethodInfo clearCacheMethod = typeDescriptionProviderType?.Assembly.GetType("System.ComponentModel.ReflectionCachesUpdateHandler")?.GetMethod("ClearCache"); + if (clearCacheMethod != null) + clearCacheMethod.Invoke(null, new object[] { null }); + + Type TypeDescriptorType = typeof(System.ComponentModel.TypeDescriptor); + object s_providerTable = TypeDescriptorType?.GetField("s_providerTable", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null); + + // Added in .NET runtime 8.0.10, used as the main locking object + object s_commonSyncObject = TypeDescriptorType?.GetField("s_commonSyncObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null); + if (s_commonSyncObject == null) + s_commonSyncObject = s_providerTable; + + // Removed in .NET runtime 8.0.7 + object s_internalSyncObject = TypeDescriptorType?.GetField("s_internalSyncObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null); + object s_defaultProviders = TypeDescriptorType?.GetField("s_defaultProviders", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null); + if (s_internalSyncObject != null && s_defaultProviders != null) + { + lock (s_internalSyncObject) + InvokeClear(s_defaultProviders); + } + + // Replaces s_defaultProviders in 8.0.7 + object s_defaultProviderInitialized = TypeDescriptorType?.GetField("s_defaultProviderInitialized", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null); + if (s_commonSyncObject != null && s_defaultProviderInitialized != null) + { + lock (s_commonSyncObject) + InvokeClear(s_defaultProviderInitialized); + } + + object s_providerTypeTable = TypeDescriptorType?.GetField("s_providerTypeTable", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null); + if (s_providerTable != null && s_providerTypeTable != null) + { + lock (s_commonSyncObject) + InvokeClear(s_providerTypeTable); + InvokeClear(s_providerTable); + } + + static void InvokeClear(object instance) + { + Type type = instance.GetType(); + Assertions.Assert.IsTrue(type.Name == "ConcurrentDictionary`2" || type.Name == "Hashtable" || type.Name == "WeakHashtable"); + type.GetMethods(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(x => x.Name == "Clear")?.Invoke(instance, Array.Empty()); + } + } // Unload the ALC - bool unloading = true; - scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; }; scriptingAssemblyLoadContext.Unload(); + scriptingAssemblyLoadContext.Resolving -= OnScriptingAssemblyLoadContextResolving; - while (unloading) - System.Threading.Thread.Sleep(1); - - InitScriptingAssemblyLoadContext(); - DelegateHelpers.InitMethods(); + GC.Collect(); + GC.WaitForPendingFinalizers(); #endif } diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 35fe70221..7a9a8e5ff 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -73,19 +73,6 @@ namespace FlaxEngine.Interop return nativeLibrary; } - private static void InitScriptingAssemblyLoadContext() - { -#if FLAX_EDITOR - var isCollectible = true; -#else - var isCollectible = false; -#endif - scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible); -#if FLAX_EDITOR - scriptingAssemblyLoadContext.Resolving += OnScriptingAssemblyLoadContextResolving; -#endif - } - [UnmanagedCallersOnly] internal static unsafe void Init() { @@ -97,8 +84,6 @@ namespace FlaxEngine.Interop System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; - InitScriptingAssemblyLoadContext(); - DelegateHelpers.InitMethods(); } #if FLAX_EDITOR @@ -1475,11 +1460,11 @@ namespace FlaxEngine.Interop internal static class ArrayFactory { - private delegate Array CreateArrayDelegate(long size); + internal delegate Array CreateArrayDelegate(long size); - private static ConcurrentDictionary marshalledTypes = new ConcurrentDictionary(1, 3); - private static ConcurrentDictionary arrayTypes = new ConcurrentDictionary(1, 3); - private static ConcurrentDictionary createArrayDelegates = new ConcurrentDictionary(1, 3); + internal static ConcurrentDictionary marshalledTypes = new ConcurrentDictionary(1, 3); + internal static ConcurrentDictionary arrayTypes = new ConcurrentDictionary(1, 3); + internal static ConcurrentDictionary createArrayDelegates = new ConcurrentDictionary(1, 3); internal static Type GetMarshalledType(Type elementType) { @@ -1645,17 +1630,6 @@ namespace FlaxEngine.Interop return RegisterType(type, true).typeHolder; } - internal static (TypeHolder typeHolder, ManagedHandle handle) GetTypeHolderAndManagedHandle(Type type) - { - if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) - return tuple; -#if FLAX_EDITOR - if (managedTypesCollectible.TryGetValue(type, out tuple)) - return tuple; -#endif - return RegisterType(type, true); - } - /// /// Returns a static ManagedHandle to TypeHolder for given Type, and caches it if needed. /// @@ -1781,6 +1755,14 @@ namespace FlaxEngine.Interop #endif } + internal static void Release() + { + MakeNewCustomDelegateFunc = null; +#if FLAX_EDITOR + MakeNewCustomDelegateFuncCollectible = null; +#endif + } + internal static Type MakeNewCustomDelegate(Type[] parameters) { #if FLAX_EDITOR diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index ae644ad61..5c3e9670d 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -74,7 +74,7 @@ Float2 Screen::ScreenToGameViewport(const Float2& screenPos) return Editor::Managed->ScreenToGameViewport(screenPos); #else auto win = Engine::MainWindow; - return win ? win->ScreenToClient(screenPos) : Float2::Minimum; + return win ? win->ScreenToClient(screenPos) / win->GetDpiScale() : Float2::Minimum; #endif } @@ -84,7 +84,7 @@ Float2 Screen::GameViewportToScreen(const Float2& viewportPos) return Editor::Managed->GameViewportToScreen(viewportPos); #else auto win = Engine::MainWindow; - return win ? win->ClientToScreen(viewportPos) : Float2::Minimum; + return win ? win->ClientToScreen(viewportPos * win->GetDpiScale()) : Float2::Minimum; #endif } diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index 2b79ddeef..1557aa883 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -174,8 +174,10 @@ public: /// /// Determines whether depth buffer is binded to the pipeline. + /// [Deprecated in v1.10] /// /// true if depth buffer is binded; otherwise, false. + DEPRECATED("IsDepthBufferBinded has been deprecated and will be removed in ") virtual bool IsDepthBufferBinded() = 0; public: diff --git a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp index d6066adab..cd1810af7 100644 --- a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp @@ -34,8 +34,8 @@ void GUIMaterialShader::Bind(BindParameters& params) auto materialData = reinterpret_cast(cb.Get()); cb = cb.Slice(sizeof(GUIMaterialShaderData)); int32 srv = 0; - const auto ps = context->IsDepthBufferBinded() ? _cache.Depth : _cache.NoDepth; auto customData = (Render2D::CustomData*)params.CustomData; + const auto ps = customData->UseDepthBuffer ? _cache.Depth : _cache.NoDepth; // Setup parameters MaterialParameter::BindMeta bindMeta; @@ -83,26 +83,21 @@ void GUIMaterialShader::Unload() bool GUIMaterialShader::Load() { - GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultFullscreenTriangle; - psDesc0.Wireframe = EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::Wireframe); - psDesc0.VS = _shader->GetVS("VS_GUI"); - psDesc0.PS = _shader->GetPS("PS_GUI"); - psDesc0.BlendMode = BlendingMode::AlphaBlend; - - psDesc0.DepthEnable = psDesc0.DepthWriteEnable = true; + auto desc = GPUPipelineState::Description::DefaultFullscreenTriangle; + desc.Wireframe = EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::Wireframe); + desc.VS = _shader->GetVS("VS_GUI"); + desc.PS = _shader->GetPS("PS_GUI"); + desc.BlendMode = BlendingMode::AlphaBlend; + desc.DepthEnable = true; _cache.Depth = GPUDevice::Instance->CreatePipelineState(); _cache.NoDepth = GPUDevice::Instance->CreatePipelineState(); - - bool failed = _cache.Depth->Init(psDesc0); - - psDesc0.DepthEnable = psDesc0.DepthWriteEnable = false; - failed |= _cache.NoDepth->Init(psDesc0); - + bool failed = _cache.Depth->Init(desc); + desc.DepthEnable = false; + failed |= _cache.NoDepth->Init(desc); if (failed) { LOG(Warning, "Failed to create GUI material pipeline state."); return true; } - return false; } diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 4ffe29fa4..4f6d5673a 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -768,10 +768,7 @@ void Render2D::End() IsScissorsRectEmpty = false; for (int32 i = 0; i < DrawCalls.Count(); i++) { - // Peek draw call const auto& drawCall = DrawCalls[i]; - - // Check if cannot add element to the batching if (batchSize != 0 && !CanBatchDrawCalls(DrawCalls[batchStart], drawCall)) { // Flush batched elements @@ -999,6 +996,7 @@ void DrawBatch(int32 startIndex, int32 count) Render2D::CustomData customData; customData.ViewProjection = ViewProjection; customData.ViewSize = Float2::One; + customData.UseDepthBuffer = DepthBuffer != nullptr; bindParams.CustomData = &customData; material->Bind(bindParams); @@ -1035,6 +1033,7 @@ void DrawBatch(int32 startIndex, int32 count) Render2D::CustomData customData; customData.ViewProjection = ViewProjection; customData.ViewSize = Float2(d.AsMaterial.Width, d.AsMaterial.Height); + customData.UseDepthBuffer = DepthBuffer != nullptr; bindParams.CustomData = &customData; material->Bind(bindParams); diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 886c9e664..fabad97af 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -55,6 +55,7 @@ API_CLASS(Static) class FLAXENGINE_API Render2D { Matrix ViewProjection; Float2 ViewSize; + bool UseDepthBuffer; }; public: diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index fb6eb3d70..87866182b 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -796,12 +796,8 @@ 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 + // Initialize managed instance (ScriptingObject ctor copies managed object handle) + if (!params.Managed) { // Invoke managed ctor (to match C++ logic) object->CreateManaged(); diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.h b/Source/Engine/Scripting/ManagedCLR/MCore.h index 35227df56..d975f5af7 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.h +++ b/Source/Engine/Scripting/ManagedCLR/MCore.h @@ -45,9 +45,16 @@ public: /// static void UnloadEngine(); + /// + /// Creates the assembly load context for assemblies used by Scripting. + /// + static void CreateScriptingAssemblyLoadContext(); + #if USE_EDITOR - // Called by Scripting in a middle of hot-reload (after unloading modules but before loading them again). - static void ReloadScriptingAssemblyLoadContext(); + /// + /// Called by Scripting in a middle of hot-reload (after unloading modules but before loading them again). + /// + static void UnloadScriptingAssemblyLoadContext(); #endif public: diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 30b26760f..2b087bb43 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -330,9 +330,15 @@ void MCore::UnloadEngine() ShutdownHostfxr(); } +void MCore::CreateScriptingAssemblyLoadContext() +{ + static void* CreateScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("CreateScriptingAssemblyLoadContext")); + CallStaticMethod(CreateScriptingAssemblyLoadContextPtr); +} + #if USE_EDITOR -void MCore::ReloadScriptingAssemblyLoadContext() +void MCore::UnloadScriptingAssemblyLoadContext() { // Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108) for (const auto& e : CachedClassHandles) @@ -377,8 +383,8 @@ void MCore::ReloadScriptingAssemblyLoadContext() } } - static void* ReloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("ReloadScriptingAssemblyLoadContext")); - CallStaticMethod(ReloadScriptingAssemblyLoadContextPtr); + static void* UnloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("UnloadScriptingAssemblyLoadContext")); + CallStaticMethod(UnloadScriptingAssemblyLoadContextPtr); } #endif diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index 351e1569b..f4da8135c 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -715,9 +715,13 @@ void MCore::UnloadEngine() #endif } +void MCore::CreateScriptingAssemblyLoadContext() +{ +} + #if USE_EDITOR -void MCore::ReloadScriptingAssemblyLoadContext() +void MCore::UnloadScriptingAssemblyLoadContext() { } diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index 8731ed00d..98bf1f012 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -58,9 +58,13 @@ void MCore::UnloadEngine() MRootDomain = nullptr; } +void MCore::CreateScriptingAssemblyLoadContext() +{ +} + #if USE_EDITOR -void MCore::ReloadScriptingAssemblyLoadContext() +void MCore::UnloadScriptingAssemblyLoadContext() { } diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index a0b55376f..953118fca 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -182,6 +182,8 @@ bool ScriptingService::Init() return true; } + MCore::CreateScriptingAssemblyLoadContext(); + // Cache root domain _rootDomain = MCore::GetRootDomain(); @@ -710,7 +712,8 @@ void Scripting::Reload(bool canTriggerSceneReload) _hasGameModulesLoaded = false; // Release and create a new assembly load context for user assemblies - MCore::ReloadScriptingAssemblyLoadContext(); + MCore::UnloadScriptingAssemblyLoadContext(); + MCore::CreateScriptingAssemblyLoadContext(); // Give GC a try to cleanup old user objects and the other mess MCore::GC::Collect(); diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 39fdba696..ca5df5f39 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -170,6 +170,10 @@ namespace FlaxEngine AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; Localization.LocalizationChanged += OnLocalizationChanged; +#if FLAX_EDITOR + FlaxEditor.ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + FlaxEditor.ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; +#endif OnLocalizationChanged(); if (!Engine.IsEditor) @@ -178,6 +182,19 @@ namespace FlaxEngine } } +#if FLAX_EDITOR + private static void OnScriptsReloadBegin() + { + // Tooltip might hold references to scripting assemblies + Style.Current.SharedTooltip = null; + } + + private static void OnScriptsReloadEnd() + { + Style.Current.SharedTooltip = new Tooltip(); + } +#endif + private static void OnLocalizationChanged() { // Invariant-globalization only (see InitHostfxr with Mono) @@ -368,6 +385,10 @@ namespace FlaxEngine MainThreadTaskScheduler.Dispose(); Json.JsonSerializer.Dispose(); +#if FLAX_EDITOR + FlaxEditor.ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + FlaxEditor.ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; +#endif } /// diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 14ce087cf..9ac973cc9 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -89,7 +89,11 @@ void SerializableScriptingObject::Deserialize(DeserializeStream& stream, ISerial } ScriptingObject::ScriptingObject(const SpawnParams& params) - : _gcHandle(0) +#if USE_NETCORE + : _gcHandle((MGCHandle)params.Managed) +#elif !COMPILE_WITHOUT_CSHARP + : _gcHandle(params.Managed ? MCore::GCHandle::New(params.Managed) : 0) +#endif , _type(params.Type) , _id(params.ID) { diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index e23bca199..537463e97 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -473,6 +473,7 @@ namespace FlaxEngine.GUI { AnchorPreset = AnchorPresets.StretchAll, BackgroundColor = Color.Transparent, + Pivot = Float2.Zero, IsScrollable = true, AutoSize = true, Parent = popup.MainPanel, diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index dca68948c..7cb3e4d42 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -337,9 +337,7 @@ namespace FlaxEngine.GUI size.X = _textSize.X + Margin.Width; if (_autoHeight) size.Y = _textSize.Y + Margin.Height; - var pivotRelative = PivotRelative; - Size = size; - PivotRelative = pivotRelative; + Resize(ref size); } } } diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs index 599dcde30..c51557530 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs @@ -233,13 +233,7 @@ namespace FlaxEngine.GUI { ref TextBlock textBlock = ref textBlocks[i]; var textBlockSize = textBlock.Bounds.BottomRight - lineOrigin; - var ascender = textBlock.Ascender; - //if (ascender <= 0) - { - var textBlockFont = textBlock.Style.Font.GetFont(); - if (textBlockFont) - ascender = textBlockFont.Ascender; - } + var ascender = textBlock.GetAscender(); lineAscender = Mathf.Max(lineAscender, ascender); lineSize = Float2.Max(lineSize, textBlockSize); } @@ -256,13 +250,7 @@ namespace FlaxEngine.GUI case TextBlockStyle.Alignments.Baseline: { // Match the baseline of the line (use ascender) - var ascender = textBlock.Ascender; - if (ascender <= 0) - { - var textBlockFont = textBlock.Style.Font.GetFont(); - if (textBlockFont) - ascender = textBlockFont.Ascender; - } + var ascender = textBlock.GetAscender(); vOffset = lineAscender - ascender; textBlock.Bounds.Location.Y += vOffset; break; diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs index 6c5240977..c892f8d2f 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs @@ -176,7 +176,8 @@ namespace FlaxEngine.GUI var font = imageBlock.Style.Font.GetFont(); if (font) imageBlock.Bounds.Size = new Float2(font.Height); - imageBlock.Bounds.Size.X *= image.Size.X / image.Size.Y; // Keep original aspect ratio + var imageSize = image.Size; + imageBlock.Bounds.Size.X *= imageSize.X / imageSize.Y; // Keep original aspect ratio bool hasWidth = TryParseNumberTag(ref tag, "width", imageBlock.Bounds.Width, out var width); imageBlock.Bounds.Width = width; bool hasHeight = TryParseNumberTag(ref tag, "height", imageBlock.Bounds.Height, out var height); @@ -185,9 +186,9 @@ namespace FlaxEngine.GUI { // Maintain aspect ratio after scaling by just width or height if (hasHeight) - imageBlock.Bounds.Size.X = imageBlock.Bounds.Size.Y * image.Size.X / image.Size.Y; + imageBlock.Bounds.Size.X = imageBlock.Bounds.Size.Y * imageSize.X / imageSize.Y; else - imageBlock.Bounds.Size.Y = imageBlock.Bounds.Size.X * image.Size.Y / image.Size.X; + imageBlock.Bounds.Size.Y = imageBlock.Bounds.Size.X * imageSize.Y / imageSize.X; } TryParseNumberTag(ref tag, "scale", 1.0f, out var scale); imageBlock.Bounds.Size *= scale; diff --git a/Source/Engine/UI/GUI/Control.Bounds.cs b/Source/Engine/UI/GUI/Control.Bounds.cs index 6054250f8..de2711b25 100644 --- a/Source/Engine/UI/GUI/Control.Bounds.cs +++ b/Source/Engine/UI/GUI/Control.Bounds.cs @@ -419,6 +419,19 @@ namespace FlaxEngine.GUI } } + /// + /// Resizes the control based on where the pivot is rather than just the top-left. + /// + [NoAnimate] + public void Resize(ref Float2 value) + { + if (_bounds.Size.Equals(ref value)) + return; + var bounds = new Rectangle(_bounds.Location, value); + bounds.Location += (_bounds.Size - value) * Pivot; // Pivot-relative resizing + SetBounds(ref bounds); + } + /// /// Updates the control cached bounds (based on anchors and offsets). /// diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index 53fdd5502..f88cae3a5 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -122,8 +122,6 @@ namespace FlaxEngine.GUI if (_parent == value) return; - Defocus(); - Float2 oldParentSize; if (_parent != null) { diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index e053edd9c..28a1fb8c9 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -585,7 +585,8 @@ namespace FlaxEngine.GUI _cachedHeight = height; if (_animationProgress >= 1.0f && _isClosed) y = minHeight; - Height = Mathf.Max(minHeight, y); + var size = new Float2(Width, Mathf.Max(minHeight, y)); + Resize(ref size); } /// diff --git a/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs b/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs index 1c8e646e3..06d185f2d 100644 --- a/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs +++ b/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs @@ -78,7 +78,7 @@ namespace FlaxEngine.GUI size.X = left + right; if (!ControlChildSize) size.Y = maxHeight; - Size = size; + Resize(ref size); } else if (_alignment != TextAlignment.Near && hasAnyLeft) { diff --git a/Source/Engine/UI/GUI/Panels/VerticalPanel.cs b/Source/Engine/UI/GUI/Panels/VerticalPanel.cs index aae95bd43..d5c6dfb9f 100644 --- a/Source/Engine/UI/GUI/Panels/VerticalPanel.cs +++ b/Source/Engine/UI/GUI/Panels/VerticalPanel.cs @@ -78,7 +78,7 @@ namespace FlaxEngine.GUI size.Y = top + bottom; if (!ControlChildSize) size.X = maxWidth; - Size = size; + Resize(ref size); } else if (_alignment != TextAlignment.Near && hasAnyTop) { diff --git a/Source/Engine/UI/GUI/TextBlock.cs b/Source/Engine/UI/GUI/TextBlock.cs index a0a5b246b..ac494d895 100644 --- a/Source/Engine/UI/GUI/TextBlock.cs +++ b/Source/Engine/UI/GUI/TextBlock.cs @@ -31,5 +31,18 @@ namespace FlaxEngine.GUI /// The custom tag. /// public object Tag; + + internal float GetAscender() + { + float ascender = Ascender; + if (Mathf.IsZero(ascender)) + { + // Use ascender from the font + var textBlockFont = Style.Font.GetFont(); + if (textBlockFont) + ascender = textBlockFont.Ascender; + } + return ascender; + } } } diff --git a/Source/Engine/UI/GUI/TextBlockStyle.cs b/Source/Engine/UI/GUI/TextBlockStyle.cs index 7166bc4f1..fb70a7bf6 100644 --- a/Source/Engine/UI/GUI/TextBlockStyle.cs +++ b/Source/Engine/UI/GUI/TextBlockStyle.cs @@ -79,7 +79,7 @@ namespace FlaxEngine.GUI public Color Color; /// - /// The text shadow color (tint and opacity). Set to transparent to disable shadow drawing. + /// The text shadow color (tint and opacity). Transparent color disables shadow drawing. /// [EditorOrder(30)] public Color ShadowColor; diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 40aae0067..8c8e05b43 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -120,6 +120,7 @@ namespace FlaxEngine.GUI // Unlink IsLayoutLocked = true; Parent = null; + _showTarget = null; // Close window if (_window) diff --git a/Source/Engine/UI/UICanvas.cpp b/Source/Engine/UI/UICanvas.cpp index a7af9c8c7..f0a2f25e7 100644 --- a/Source/Engine/UI/UICanvas.cpp +++ b/Source/Engine/UI/UICanvas.cpp @@ -23,11 +23,11 @@ MMethod* UICanvas_EndPlay = nullptr; MMethod* UICanvas_ParentChanged = nullptr; #define UICANVAS_INVOKE(event) \ - auto instance = GetManagedInstance(); \ - if (instance) \ + auto* managed = GetManagedInstance(); \ + if (managed) \ { \ MObject* exception = nullptr; \ - UICanvas_##event->Invoke(instance, nullptr, &exception); \ + UICanvas_##event->Invoke(managed, nullptr, &exception); \ if (exception) \ { \ MException ex(exception); \ diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index c92b618a9..96443dd74 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -314,7 +314,8 @@ namespace FlaxEngine { _guiRoot = new CanvasRootControl(this) { - IsLayoutLocked = false + IsLayoutLocked = false, + Pivot = Float2.Zero, }; } diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index 72eed1f27..692843361 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -22,10 +22,11 @@ MMethod* UIControl_BeginPlay = nullptr; MMethod* UIControl_EndPlay = nullptr; #define UICONTROL_INVOKE(event) \ - if (HasManagedInstance()) \ + auto* managed = GetManagedInstance(); \ + if (managed) \ { \ MObject* exception = nullptr; \ - UIControl_##event->Invoke(GetManagedInstance(), nullptr, &exception); \ + UIControl_##event->Invoke(managed, nullptr, &exception); \ if (exception) \ { \ MException ex(exception); \ diff --git a/Source/Engine/UI/UIControl.cs b/Source/Engine/UI/UIControl.cs index 72e3f9625..1d31fcf27 100644 --- a/Source/Engine/UI/UIControl.cs +++ b/Source/Engine/UI/UIControl.cs @@ -35,38 +35,58 @@ namespace FlaxEngine // Set value _control = value; + if (_control == null) + return; - // Link the new one (events and parent) - if (_control != null) + // Setup control + var isDuringPlay = IsDuringPlay; + _blockEvents = true; + var container = _control as ContainerControl; + if (container != null) { - // Setup control - _blockEvents = true; - var containerControl = _control as ContainerControl; - if (containerControl != null) - containerControl.UnlockChildrenRecursive(); - _control.Visible = IsActive; - _control.Parent = GetParent(); - _control.IndexInParent = OrderInParent; - _control.Location = new Float2(LocalPosition); - _control.LocationChanged += OnControlLocationChanged; - - // Link children UI controls - if (containerControl != null) + if (isDuringPlay) + container.UnlockChildrenRecursive(); // Enable layout changes to any dynamically added UI + else + container.LockChildrenRecursive(); // Block layout changes during deserialization + } + _control.Visible = IsActive; + { + var parent = GetParent(); + if (parent != null && !parent.IsLayoutLocked && !isDuringPlay) { - var children = ChildrenCount; - var parent = Parent; - for (int i = 0; i < children; i++) + // Reparent but prevent layout if we're during pre-game setup (eg. deserialization) to avoid UI breaking during auto-layout resizing + parent.IsLayoutLocked = true; + _control.Parent = parent; + parent.IsLayoutLocked = false; + } + else + { + _control.Parent = parent; + } + } + _control.IndexInParent = OrderInParent; + _control.Location = new Float2(LocalPosition); + _control.LocationChanged += OnControlLocationChanged; + + // Link children UI controls + if (container != null) + { + var children = ChildrenCount; + var parent = Parent; + for (int i = 0; i < children; i++) + { + var child = GetChild(i) as UIControl; + if (child != null && child.HasControl && child != parent) { - var child = GetChild(i) as UIControl; - if (child != null && child.HasControl && child != parent) - { - child.Control.Parent = containerControl; - } + child.Control.Parent = container; } } + } - // Refresh - _blockEvents = false; + // Refresh layout + _blockEvents = false; + if (isDuringPlay) + { if (prevControl == null && _control.Parent != null) _control.Parent.PerformLayout(); else @@ -326,11 +346,13 @@ namespace FlaxEngine { if ((_control == null || _control.GetType() != controlType) && controlType != null) { + // Create a new control Control = (Control)Activator.CreateInstance(controlType); } if (_control != null) { + // Populate control object with properties Json.JsonSerializer.Deserialize(_control, json); // Synchronize actor with control location @@ -374,16 +396,32 @@ namespace FlaxEngine internal void BeginPlay() { - if (_control != null) + var control = _control; + if (control == null) + return; + + // Setup control + control.Visible = IsActive && control.Visible; + control.Parent = GetParent(); + control.IndexInParent = OrderInParent; + + // Setup navigation (all referenced controls are now loaded) + Internal_GetNavTargets(__unmanagedPtr, out UIControl up, out UIControl down, out UIControl left, out UIControl right); + control.NavTargetUp = up?.Control; + control.NavTargetDown = down?.Control; + control.NavTargetLeft = left?.Control; + control.NavTargetRight = right?.Control; + + // Refresh layout (BeginPlay is called for parents first, then skip if already called by outer control) + var container = control as ContainerControl; + if (control.Parent == null || (container != null && container.IsLayoutLocked)) { - _control.Visible = IsActive && _control.Visible; - _control.Parent = GetParent(); - _control.IndexInParent = OrderInParent; - Internal_GetNavTargets(__unmanagedPtr, out UIControl up, out UIControl down, out UIControl left, out UIControl right); - _control.NavTargetUp = up?.Control; - _control.NavTargetDown = down?.Control; - _control.NavTargetLeft = left?.Control; - _control.NavTargetRight = right?.Control; + if (container != null) + { + //container.UnlockChildrenRecursive(); + container.IsLayoutLocked = false; // Forces whole children tree lock/unlock sequence in PerformLayout + } + control.PerformLayout(); } } diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index 9bdc5709d..3df80230e 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -364,8 +364,8 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) // Fmod case 40: { - Value v1 = tryGetValue(node->GetBox(0), Value::Zero); - Value v2 = tryGetValue(node->GetBox(1), Value::Zero); + Value v1 = tryGetValue(node->GetBox(0), 0, Value::Zero); + Value v2 = tryGetValue(node->GetBox(1), 1, Value::Zero); value = writeFunction2(node, v1, v2, TEXT("fmod")); break; } diff --git a/Source/Tools/Flax.Build/ProjectInfo.cs b/Source/Tools/Flax.Build/ProjectInfo.cs index c64627e3c..2c3a600dd 100644 --- a/Source/Tools/Flax.Build/ProjectInfo.cs +++ b/Source/Tools/Flax.Build/ProjectInfo.cs @@ -260,7 +260,11 @@ namespace Flax.Build return true; var rules = Builder.GenerateRulesAssembly(); var target = rules.GetTarget(name); - return target == null || target.Modules.TrueForAll(x => !rules.GetModule(x).BuildNativeCode); + return target == null || target.Modules.TrueForAll(moduleName => + { + var module = rules.GetModule(moduleName); + return module != null && !module.BuildNativeCode; + }); } ///