From 0c645cbc78555e2b803d2ee50b47431082d10c7d Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 24 Dec 2024 12:12:59 -0600 Subject: [PATCH 01/82] Allow user to add splash image to splash screen. --- Source/Editor/Windows/SplashScreen.cpp | 67 ++++++++++++++++++++++++-- Source/Editor/Windows/SplashScreen.h | 3 ++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 49257d281..2c8cd2e21 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -214,6 +214,27 @@ void SplashScreen::Show() font->OnLoaded.Bind(this); } + // Load Splash image + String splashImagePath = String::Format(TEXT("{0}/{1}"), Globals::ProjectContentFolder, TEXT("SplashImage/SplashImage.flax")); +#if PLATFORM_WIN32 + splashImagePath.Replace('/', '\\'); +#else + splashImagePath.Replace('\\', '/'); +#endif + + auto texture = Content::LoadAsync(*splashImagePath); + if (texture == nullptr) + { + LOG(Info, "Cannot load splash Texture at {0}", splashImagePath); + } + else + { + if (texture->IsLoaded()) + OnTextureLoaded(texture); + else + texture->OnLoaded.Bind(this); + } + _window->Show(); } @@ -246,8 +267,19 @@ void SplashScreen::OnDraw() const float time = static_cast((DateTime::NowUTC() - _startTime).GetTotalSeconds()); // Background - const float lightBarHeight = 112 * s; - Render2D::FillRectangle(Rectangle(0, 0, width, 150 * s), Color::FromRGB(0x1C1C1C)); + float lightBarHeight = 112 * s; + if (_splashTexture != nullptr) + { + if (_splashTexture->IsLoaded()) + { + lightBarHeight = height - lightBarHeight; + Render2D::DrawTexture(_splashTexture, Rectangle(0, 0, width, height)); + } + } + else + { + Render2D::FillRectangle(Rectangle(0, 0, width, 150 * s), Color::FromRGB(0x1C1C1C)); + } Render2D::FillRectangle(Rectangle(0, lightBarHeight, width, height), Color::FromRGB(0x0C0C0C)); // Animated border @@ -276,10 +308,18 @@ void SplashScreen::OnDraw() for (int32 i = 0; i < 4 - static_cast(time * 2.0f) % 4; i++) subtitle += TEXT(' '); } - layout.Bounds = Rectangle(width - 224 * s, lightBarHeight - 39 * s, 220 * s, 35 * s); + if (_splashTexture != nullptr) + { + layout.Bounds = Rectangle(width - 224 * s, lightBarHeight + 2 * s, 220 * s, 35 * s); + layout.VerticalAlignment = TextAlignment::Near; + } + else + { + layout.Bounds = Rectangle(width - 224 * s, lightBarHeight - 39 * s, 220 * s, 35 * s); + layout.VerticalAlignment = TextAlignment::Far; + } layout.Scale = 1.0f; layout.HorizontalAlignment = TextAlignment::Far; - layout.VerticalAlignment = TextAlignment::Far; Render2D::DrawText(_subtitleFont, subtitle, Color::FromRGB(0x8C8C8C), layout); // Additional info @@ -307,3 +347,22 @@ void SplashScreen::OnFontLoaded(Asset* asset) _titleFont = font->CreateFont(35 * s); _subtitleFont = font->CreateFont(9 * s); } + +void SplashScreen::OnTextureLoaded(Asset* asset) +{ + ASSERT(asset && asset->IsLoaded()); + auto texture = (Texture*)asset; + + texture->OnLoaded.Unbind(this); + _splashTexture = texture; + + // Resize window to be larger if texture is being used. + auto desktopSize = Platform::GetDesktopSize(); + auto xSize = (desktopSize.X / (600.0f * 3.0f)) * 600.0f; + auto ySize = (desktopSize.Y / (200.0f * 3.0f)) * 200.0f; + + _window->SetClientSize(Float2(xSize, ySize)); + _width = _window->GetSize().X; + _height = _window->GetSize().Y; + _window->SetPosition((Platform::GetDesktopSize() - _window->GetSize()) / 2.0f); +} diff --git a/Source/Editor/Windows/SplashScreen.h b/Source/Editor/Windows/SplashScreen.h index c8f59432e..6c840975e 100644 --- a/Source/Editor/Windows/SplashScreen.h +++ b/Source/Editor/Windows/SplashScreen.h @@ -2,6 +2,7 @@ #pragma once +#include "Engine/Content/Assets/Texture.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Platform/Window.h" @@ -18,6 +19,7 @@ private: Window* _window = nullptr; Font* _titleFont = nullptr; Font* _subtitleFont = nullptr; + Texture* _splashTexture = nullptr; String _title; DateTime _startTime; String _infoText; @@ -78,4 +80,5 @@ private: void OnDraw(); bool HasLoadedFonts() const; void OnFontLoaded(Asset* asset); + void OnTextureLoaded(Asset* asset); }; From 819c93f6fbd015cf237e1b14bc2193855302089d Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 24 Dec 2024 14:26:59 -0600 Subject: [PATCH 02/82] Increase margins. --- Source/Editor/Windows/SplashScreen.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 2c8cd2e21..dc94da95c 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -272,7 +272,7 @@ void SplashScreen::OnDraw() { if (_splashTexture->IsLoaded()) { - lightBarHeight = height - lightBarHeight; + lightBarHeight = height - lightBarHeight + 20 * s; Render2D::DrawTexture(_splashTexture, Rectangle(0, 0, width, height)); } } @@ -310,7 +310,7 @@ void SplashScreen::OnDraw() } if (_splashTexture != nullptr) { - layout.Bounds = Rectangle(width - 224 * s, lightBarHeight + 2 * s, 220 * s, 35 * s); + layout.Bounds = Rectangle(width - 224 * s, lightBarHeight + 4 * s, 220 * s, 35 * s); layout.VerticalAlignment = TextAlignment::Near; } else @@ -324,7 +324,11 @@ void SplashScreen::OnDraw() // Additional info const float infoMargin = 6 * s; - layout.Bounds = Rectangle(infoMargin, lightBarHeight + infoMargin, width - (2 * infoMargin), height - lightBarHeight - (2 * infoMargin)); + if (_splashTexture != nullptr) + layout.Bounds = Rectangle(infoMargin + 4 * s, lightBarHeight + infoMargin, width - (2 * infoMargin), height - lightBarHeight - (2 * infoMargin)); + else + layout.Bounds = Rectangle(infoMargin, lightBarHeight + infoMargin, width - (2 * infoMargin), height - lightBarHeight - (2 * infoMargin)); + layout.HorizontalAlignment = TextAlignment::Near; layout.VerticalAlignment = TextAlignment::Center; Render2D::DrawText(_subtitleFont, _infoText, Color::FromRGB(0xFFFFFF) * 0.9f, layout); From 42d02a9e6368461df974f55aef134bffa99c63be Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 8 Feb 2025 18:49:04 -0600 Subject: [PATCH 03/82] Add small fade to bottom bar. --- Source/Editor/Windows/SplashScreen.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index dc94da95c..3a9e82cfe 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -274,13 +274,15 @@ void SplashScreen::OnDraw() { lightBarHeight = height - lightBarHeight + 20 * s; Render2D::DrawTexture(_splashTexture, Rectangle(0, 0, width, height)); + Color rectColor = Color::FromRGB(0x0C0C0C); + Render2D::FillRectangle(Rectangle(0, lightBarHeight, width, height - lightBarHeight),rectColor.AlphaMultiplied(0.85f), rectColor.AlphaMultiplied(0.85f), rectColor, rectColor); } } else { Render2D::FillRectangle(Rectangle(0, 0, width, 150 * s), Color::FromRGB(0x1C1C1C)); + Render2D::FillRectangle(Rectangle(0, lightBarHeight, width, height), Color::FromRGB(0x0C0C0C)); } - Render2D::FillRectangle(Rectangle(0, lightBarHeight, width, height), Color::FromRGB(0x0C0C0C)); // Animated border const float anim = Math::Sin(time * 4.0f) * 0.5f + 0.5f; From 570c3f7462f94f6967ac575535ab8ea7e2fbfc47 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Tue, 11 Feb 2025 16:39:15 +0100 Subject: [PATCH 04/82] add option for single tab tab header bar visibility --- Source/Editor/GUI/Docking/DockPanelProxy.cs | 11 +++-- Source/Editor/Options/InterfaceOptions.cs | 55 ++++++++++++--------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs index bc38e2953..ad25d6c85 100644 --- a/Source/Editor/GUI/Docking/DockPanelProxy.cs +++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs @@ -14,11 +14,11 @@ namespace FlaxEditor.GUI.Docking { private DockPanel _panel; private double _dragEnterTime = -1; - #if PLATFORM_WINDOWS - private const bool HideTabForSingleTab = true; - #else - private const bool HideTabForSingleTab = false; - #endif +#if PLATFORM_WINDOWS + private readonly bool _hideTabForSingleTab = Editor.Instance.Options.Options.Interface.HideSingleTabWindowTabBars; +#else + private readonly bool _hideTabForSingleTab = false; +#endif /// /// The is mouse down flag (left button). @@ -57,6 +57,7 @@ namespace FlaxEditor.GUI.Docking private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, DockPanel.DefaultHeaderHeight); private bool IsSingleFloatingWindow => HideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0; + private bool IsSingleFloatingWindow => _hideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0; /// /// Initializes a new instance of the class. diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 4cd76adc6..a6189e34c 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -167,15 +167,6 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(10), Tooltip("Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.")] public float InterfaceScale { get; set; } = 1.0f; -#if PLATFORM_WINDOWS - /// - /// Gets or sets a value indicating whether use native window title bar. Editor restart required. - /// - [DefaultValue(false)] - [EditorDisplay("Interface"), EditorOrder(70), Tooltip("Determines whether use native window title bar. Editor restart required.")] - public bool UseNativeWindowSystem { get; set; } = false; -#endif - /// /// Gets or sets a value indicating whether show selected camera preview in the editor window. /// @@ -183,20 +174,6 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(80), Tooltip("Determines whether show selected camera preview in the edit window.")] public bool ShowSelectedCameraPreview { get; set; } = true; - /// - /// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor. - /// - [DefaultValue(false)] - [EditorDisplay("Interface", "Center Mouse On Game Window Focus"), EditorOrder(100), Tooltip("Determines whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.")] - public bool CenterMouseOnGameWinFocus { get; set; } = false; - - /// - /// Gets or sets the method window opening. - /// - [DefaultValue(DockStateProxy.Float)] - [EditorDisplay("Interface", "New Window Location"), EditorOrder(150), Tooltip("Define the opening method for new windows, open in a new tab by default.")] - public DockStateProxy NewWindowLocation { get; set; } = DockStateProxy.Float; - /// /// Gets or sets the editor icons scale. Editor restart required. /// @@ -265,6 +242,38 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(322)] public bool ScrollToScriptOnAdd { get; set; } = true; +#if PLATFORM_WINDOWS + /// + /// Gets or sets a value indicating whether use native window title bar. Editor restart required. + /// + [DefaultValue(false)] + [EditorDisplay("Tabs & Windows"), EditorOrder(70), Tooltip("Determines whether use native window title bar. Editor restart required.")] + public bool UseNativeWindowSystem { get; set; } = false; +#endif + +#if PLATFORM_WINDOWS + /// + /// Gets or sets a value indicating whether a window containing a single tabs hides the tab bar. Editor restart recommended. + /// + [DefaultValue(true)] + [EditorDisplay("Tabs & Windows", "Hide Single-Tab Window Tab Bars"), EditorOrder(71)] + public bool HideSingleTabWindowTabBars { get; set; } = true; +#endif + + /// + /// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor. + /// + [DefaultValue(false)] + [EditorDisplay("Tabs & Windows", "Center Mouse On Game Window Focus"), EditorOrder(101), Tooltip("Determines whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.")] + public bool CenterMouseOnGameWinFocus { get; set; } = false; + + /// + /// Gets or sets the method window opening. + /// + [DefaultValue(DockStateProxy.Float)] + [EditorDisplay("Tabs & Windows", "New Window Location"), EditorOrder(150), Tooltip("Define the opening method for new windows, open in a new tab by default.")] + public DockStateProxy NewWindowLocation { get; set; } = DockStateProxy.Float; + /// /// Gets or sets the timestamps prefix mode for output log messages. /// From 409703d675b0bca4d680498b5972529cc684850c Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Tue, 11 Feb 2025 16:39:33 +0100 Subject: [PATCH 05/82] add editor option for tab height --- Source/Editor/GUI/Docking/DockHintWindow.cs | 4 +-- Source/Editor/GUI/Docking/DockPanelProxy.cs | 32 ++++++++++----------- Source/Editor/Options/InterfaceOptions.cs | 7 +++++ 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs index 2b0902cf9..10b204345 100644 --- a/Source/Editor/GUI/Docking/DockHintWindow.cs +++ b/Source/Editor/GUI/Docking/DockHintWindow.cs @@ -215,8 +215,8 @@ namespace FlaxEditor.GUI.Docking switch (state) { case DockState.DockFill: - result.Location.Y += DockPanel.DefaultHeaderHeight; - result.Size.Y -= DockPanel.DefaultHeaderHeight; + result.Location.Y += Editor.Instance.Options.Options.Interface.TabHeight; + result.Size.Y -= Editor.Instance.Options.Options.Interface.TabHeight; break; case DockState.DockTop: result.Size.Y *= DockPanel.DefaultSplitterValue; diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs index ad25d6c85..23ba6c577 100644 --- a/Source/Editor/GUI/Docking/DockPanelProxy.cs +++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; @@ -14,6 +15,7 @@ namespace FlaxEditor.GUI.Docking { private DockPanel _panel; private double _dragEnterTime = -1; + private float _tabHeight = Editor.Instance.Options.Options.Interface.TabHeight; #if PLATFORM_WINDOWS private readonly bool _hideTabForSingleTab = Editor.Instance.Options.Options.Interface.HideSingleTabWindowTabBars; #else @@ -54,9 +56,7 @@ namespace FlaxEditor.GUI.Docking /// The start drag asynchronous window. /// public DockWindow StartDragAsyncWindow; - - private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, DockPanel.DefaultHeaderHeight); - private bool IsSingleFloatingWindow => HideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0; + private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, _tabHeight); private bool IsSingleFloatingWindow => _hideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0; /// @@ -81,7 +81,7 @@ namespace FlaxEditor.GUI.Docking var tabsCount = _panel.TabsCount; if (tabsCount == 1) { - var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); + var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); if (HeaderRectangle.Contains(position)) { closeButton = crossRect.Contains(position); @@ -97,11 +97,11 @@ namespace FlaxEditor.GUI.Docking var titleSize = tab.TitleSize; var iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0; var width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth; - var tabRect = new Rectangle(x, 0, width, DockPanel.DefaultHeaderHeight); + var tabRect = new Rectangle(x, 0, width, HeaderRectangle.Height); var isMouseOver = tabRect.Contains(position); if (isMouseOver) { - var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); + var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); closeButton = crossRect.Contains(position); result = tab; break; @@ -133,7 +133,7 @@ namespace FlaxEditor.GUI.Docking float width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin; if (tab == win) { - bounds = new Rectangle(x, 0, width, DockPanel.DefaultHeaderHeight); + bounds = new Rectangle(x, 0, width, HeaderRectangle.Height); return; } x += width; @@ -213,7 +213,7 @@ namespace FlaxEditor.GUI.Docking { Render2D.DrawSprite( tab.Icon, - new Rectangle(DockPanel.DefaultLeftTextMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize), + new Rectangle(DockPanel.DefaultLeftTextMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize), style.Foreground); } @@ -222,13 +222,13 @@ namespace FlaxEditor.GUI.Docking Render2D.DrawText( style.FontMedium, tab.Title, - new Rectangle(DockPanel.DefaultLeftTextMargin + iconWidth, 0, Width - DockPanel.DefaultLeftTextMargin - DockPanel.DefaultButtonsSize - 2 * DockPanel.DefaultButtonsMargin, DockPanel.DefaultHeaderHeight), + new Rectangle(DockPanel.DefaultLeftTextMargin + iconWidth, 0, Width - DockPanel.DefaultLeftTextMargin - DockPanel.DefaultButtonsSize - 2 * DockPanel.DefaultButtonsMargin, HeaderRectangle.Height), style.Foreground, TextAlignment.Near, TextAlignment.Center); // Draw cross - var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); + var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition); if (isMouseOverCross) Render2D.FillRectangle(crossRect, (containsFocus ? style.BackgroundSelected : style.LightBackground) * 1.3f); @@ -249,7 +249,7 @@ namespace FlaxEditor.GUI.Docking var titleSize = tab.TitleSize; var iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0; var width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth; - var tabRect = new Rectangle(x, 0, width, DockPanel.DefaultHeaderHeight); + var tabRect = new Rectangle(x, 0, width, headerRect.Height); var isMouseOver = tabRect.Contains(MousePosition); var isSelected = _panel.SelectedTab == tab; @@ -276,7 +276,7 @@ namespace FlaxEditor.GUI.Docking { Render2D.DrawSprite( tab.Icon, - new Rectangle(x + DockPanel.DefaultLeftTextMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize), + new Rectangle(x + DockPanel.DefaultLeftTextMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize), style.Foreground); } @@ -285,7 +285,7 @@ namespace FlaxEditor.GUI.Docking Render2D.DrawText( style.FontMedium, tab.Title, - new Rectangle(x + DockPanel.DefaultLeftTextMargin + iconWidth, 0, 10000, DockPanel.DefaultHeaderHeight), + new Rectangle(x + DockPanel.DefaultLeftTextMargin + iconWidth, 0, 10000, HeaderRectangle.Height), style.Foreground, TextAlignment.Near, TextAlignment.Center); @@ -293,7 +293,7 @@ namespace FlaxEditor.GUI.Docking // Draw cross if (isSelected || isMouseOver) { - var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); + var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition); if (isMouseOverCross) Render2D.FillRectangle(crossRect, tabColor * 1.3f); @@ -305,7 +305,7 @@ namespace FlaxEditor.GUI.Docking } // Draw selected tab strip - Render2D.FillRectangle(new Rectangle(0, DockPanel.DefaultHeaderHeight - 2, Width, 2), containsFocus ? style.BackgroundSelected : style.BackgroundNormal); + Render2D.FillRectangle(new Rectangle(0, HeaderRectangle.Height - 2, Width, 2), containsFocus ? style.BackgroundSelected : style.BackgroundNormal); } } @@ -523,7 +523,7 @@ namespace FlaxEditor.GUI.Docking if (IsSingleFloatingWindow) rect = new Rectangle(0, 0, Width, Height); else - rect = new Rectangle(0, DockPanel.DefaultHeaderHeight, Width, Height - DockPanel.DefaultHeaderHeight); + rect = new Rectangle(0, HeaderRectangle.Height, Width, Height - HeaderRectangle.Height); } private DragDropEffect TrySelectTabUnderLocation(ref Float2 location) diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index a6189e34c..925fb8c60 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -260,6 +260,13 @@ namespace FlaxEditor.Options public bool HideSingleTabWindowTabBars { get; set; } = true; #endif + /// + /// Gets or sets a value indicating the height of window tabs. Editor restart required. + /// + [DefaultValue(20.0f), Limit(15.0f, 40.0f)] + [EditorDisplay("Tabs & Windows"), EditorOrder(100)] + public float TabHeight { get; set; } = 20.0f; + /// /// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor. /// From ba35123420710ff6ad9b6b665d8e28fc7aaf80b9 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Tue, 11 Feb 2025 18:15:46 +0100 Subject: [PATCH 06/82] add minimum tab width --- Source/Editor/GUI/Docking/DockPanelProxy.cs | 12 ++++++++++++ Source/Editor/Options/InterfaceOptions.cs | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs index 23ba6c577..ef6c2cb1a 100644 --- a/Source/Editor/GUI/Docking/DockPanelProxy.cs +++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs @@ -16,6 +16,8 @@ namespace FlaxEditor.GUI.Docking private DockPanel _panel; private double _dragEnterTime = -1; private float _tabHeight = Editor.Instance.Options.Options.Interface.TabHeight; + private bool _useMinimumTabWidth = Editor.Instance.Options.Options.Interface.UseMinimumTabWidth; + private float _minimumTabWidth = Editor.Instance.Options.Options.Interface.MinimumTabWidth; #if PLATFORM_WINDOWS private readonly bool _hideTabForSingleTab = Editor.Instance.Options.Options.Interface.HideSingleTabWindowTabBars; #else @@ -56,6 +58,7 @@ namespace FlaxEditor.GUI.Docking /// The start drag asynchronous window. /// public DockWindow StartDragAsyncWindow; + private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, _tabHeight); private bool IsSingleFloatingWindow => _hideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0; @@ -97,6 +100,10 @@ namespace FlaxEditor.GUI.Docking var titleSize = tab.TitleSize; var iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0; var width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth; + + if (_useMinimumTabWidth && width < _minimumTabWidth) + width = _minimumTabWidth; + var tabRect = new Rectangle(x, 0, width, HeaderRectangle.Height); var isMouseOver = tabRect.Contains(position); if (isMouseOver) @@ -248,7 +255,12 @@ namespace FlaxEditor.GUI.Docking var tabColor = Color.Black; var titleSize = tab.TitleSize; var iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0; + var width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth; + + if (_useMinimumTabWidth && width < _minimumTabWidth) + width = _minimumTabWidth; + var tabRect = new Rectangle(x, 0, width, headerRect.Height); var isMouseOver = tabRect.Contains(MousePosition); var isSelected = _panel.SelectedTab == tab; diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 925fb8c60..1e58b2a23 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -260,6 +260,20 @@ namespace FlaxEditor.Options public bool HideSingleTabWindowTabBars { get; set; } = true; #endif + /// + /// Gets or sets a value indicating wether the minum tab width should be used. Editor restart required. + /// + [DefaultValue(false)] + [EditorDisplay("Tabs & Windows"), EditorOrder(99)] + public bool UseMinimumTabWidth { get; set; } = false; + + /// + /// Gets or sets a value indicating the minimum tab width. If a tab is smaller than this width, its width will be set to this. Editor restart required. + /// + [DefaultValue(80.0f), Limit(50.0f, 150.0f)] + [EditorDisplay("Tabs & Windows"), EditorOrder(99), VisibleIf(nameof(UseMinimumTabWidth))] + public float MinimumTabWidth { get; set; } = 80.0f; + /// /// Gets or sets a value indicating the height of window tabs. Editor restart required. /// From 516d4263c91ceb33623607d7da4f986425da6203 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Tue, 11 Feb 2025 23:56:28 +0100 Subject: [PATCH 07/82] add broken link icon --- Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs | 2 +- Source/Editor/EditorIcons.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 1d65ec71c..da50423a5 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -114,7 +114,6 @@ namespace FlaxEditor.CustomEditors.Editors _linkButton = new Button { - BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Link32), Parent = LinkedLabel, Width = 18, Height = 18, @@ -201,6 +200,7 @@ namespace FlaxEditor.CustomEditors.Editors _linkButton.SetColors(backgroundColor); _linkButton.BorderColor = _linkButton.BorderColorSelected = _linkButton.BorderColorHighlighted = Color.Transparent; _linkButton.TooltipText = LinkValues ? "Unlinks scale components from uniform scaling" : "Links scale components for uniform scaling"; + _linkButton.BackgroundBrush = new SpriteBrush(LinkValues ? Editor.Instance.Icons.Link32 : Editor.Instance.Icons.BrokenLink32); } } } diff --git a/Source/Editor/EditorIcons.cs b/Source/Editor/EditorIcons.cs index 2122ad1e5..74324237a 100644 --- a/Source/Editor/EditorIcons.cs +++ b/Source/Editor/EditorIcons.cs @@ -39,6 +39,7 @@ namespace FlaxEditor public SpriteHandle Globe32; public SpriteHandle CamSpeed32; public SpriteHandle Link32; + public SpriteHandle BrokenLink32; public SpriteHandle Add32; public SpriteHandle Left32; public SpriteHandle Right32; @@ -94,6 +95,7 @@ namespace FlaxEditor public SpriteHandle Search64; public SpriteHandle Bone64; public SpriteHandle Link64; + public SpriteHandle BrokenLink64; public SpriteHandle Build64; public SpriteHandle Add64; public SpriteHandle ShipIt64; From 468babae87c628a838c01808aeb03e6d7dfd9e65 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sun, 16 Mar 2025 20:32:20 +0100 Subject: [PATCH 08/82] auto resize PropertiesList splitter bar based on longest text --- .../CustomEditors/GUI/PropertiesList.cs | 26 +++++++++++++++++++ Source/Editor/Options/InterfaceOptions.cs | 7 +++++ 2 files changed, 33 insertions(+) diff --git a/Source/Editor/CustomEditors/GUI/PropertiesList.cs b/Source/Editor/CustomEditors/GUI/PropertiesList.cs index 49eef13d2..43b05f483 100644 --- a/Source/Editor/CustomEditors/GUI/PropertiesList.cs +++ b/Source/Editor/CustomEditors/GUI/PropertiesList.cs @@ -14,6 +14,9 @@ namespace FlaxEditor.CustomEditors.GUI public class PropertiesList : PanelWithMargins { // TODO: sync splitter for whole presenter + + private const float splitterPadding = 15; + private const float editorsMinWdithRatio = 0.4f; /// /// The splitter size (in pixels). @@ -32,6 +35,7 @@ namespace FlaxEditor.CustomEditors.GUI private Rectangle _splitterRect; private bool _splitterClicked, _mouseOverSplitter; private bool _cursorChanged; + private bool _hasCustomSplitterValue; /// /// Gets or sets the splitter value (always in range [0; 1]). @@ -71,6 +75,26 @@ namespace FlaxEditor.CustomEditors.GUI UpdateSplitRect(); } + private void AutoSizeSplitter() + { + if (_hasCustomSplitterValue || !Editor.Instance.Options.Options.Interface.AutoSizePropertiesPanelSplitter) + return; + + Font font = Style.Current.FontMedium; + + float largestWidth = 0f; + for (int i = 0; i < _element.Labels.Count; i++) + { + Label currentLabel = _element.Labels[i]; + Float2 dimensions = font.MeasureText(currentLabel.Text); + float width = dimensions.X + currentLabel.Margin.Left + splitterPadding; + + largestWidth = Mathf.Max(largestWidth, width); + } + + SplitterValue = Mathf.Clamp(largestWidth / Width, 0, 1 - editorsMinWdithRatio); + } + private void UpdateSplitRect() { _splitterRect = new Rectangle(Mathf.Clamp(_splitterValue * Width - SplitterSizeHalf, 0.0f, Width), 0, SplitterSize, Height); @@ -127,6 +151,7 @@ namespace FlaxEditor.CustomEditors.GUI SplitterValue = location.X / Width; Cursor = CursorType.SizeWE; _cursorChanged = true; + _hasCustomSplitterValue = true; } else if (_mouseOverSplitter) { @@ -200,6 +225,7 @@ namespace FlaxEditor.CustomEditors.GUI // Refresh UpdateSplitRect(); PerformLayout(true); + AutoSizeSplitter(); } /// diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 3b80cd63c..baed5275a 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -232,6 +232,13 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(310)] public bool SeparateValueAndUnit { get; set; } + /// + /// Gets or sets the option to auto size the Properties panel splitter based on the longest property name. Editor restart recommended. + /// + [DefaultValue(false)] + [EditorDisplay("Interface"), EditorOrder(311)] + public bool AutoSizePropertiesPanelSplitter { get; set; } + /// /// Gets or sets tree line visibility. /// From 975cc790855be9b62d2f48c6066a9bca32508c7e Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sun, 16 Mar 2025 22:18:21 +0100 Subject: [PATCH 09/82] code style fixes --- Source/Editor/CustomEditors/GUI/PropertiesList.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Editor/CustomEditors/GUI/PropertiesList.cs b/Source/Editor/CustomEditors/GUI/PropertiesList.cs index 43b05f483..60bafc215 100644 --- a/Source/Editor/CustomEditors/GUI/PropertiesList.cs +++ b/Source/Editor/CustomEditors/GUI/PropertiesList.cs @@ -15,8 +15,8 @@ namespace FlaxEditor.CustomEditors.GUI { // TODO: sync splitter for whole presenter - private const float splitterPadding = 15; - private const float editorsMinWdithRatio = 0.4f; + private const float SplitterPadding = 15; + private const float EditorsMinWidthRatio = 0.4f; /// /// The splitter size (in pixels). @@ -87,12 +87,12 @@ namespace FlaxEditor.CustomEditors.GUI { Label currentLabel = _element.Labels[i]; Float2 dimensions = font.MeasureText(currentLabel.Text); - float width = dimensions.X + currentLabel.Margin.Left + splitterPadding; + float width = dimensions.X + currentLabel.Margin.Left + SplitterPadding; largestWidth = Mathf.Max(largestWidth, width); } - SplitterValue = Mathf.Clamp(largestWidth / Width, 0, 1 - editorsMinWdithRatio); + SplitterValue = Mathf.Clamp(largestWidth / Width, 0, 1 - EditorsMinWidthRatio); } private void UpdateSplitRect() From 391c67b1a94895e4909fb4680f13023337b7d277 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sun, 23 Mar 2025 14:09:59 +0100 Subject: [PATCH 10/82] add visibility options for the tab close button --- Source/Editor/GUI/Docking/DockPanelProxy.cs | 61 +++++++++++++++------ Source/Editor/Options/InterfaceOptions.cs | 26 +++++++++ 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs index ef6c2cb1a..983497c59 100644 --- a/Source/Editor/GUI/Docking/DockPanelProxy.cs +++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs @@ -14,6 +14,7 @@ namespace FlaxEditor.GUI.Docking public class DockPanelProxy : ContainerControl { private DockPanel _panel; + private InterfaceOptions.TabCloseButtonVisibility closeButtonVisibility; private double _dragEnterTime = -1; private float _tabHeight = Editor.Instance.Options.Options.Interface.TabHeight; private bool _useMinimumTabWidth = Editor.Instance.Options.Options.Interface.UseMinimumTabWidth; @@ -74,6 +75,14 @@ namespace FlaxEditor.GUI.Docking _panel = panel; AnchorPreset = AnchorPresets.StretchAll; Offsets = Margin.Zero; + + Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged; + OnEditorOptionsChanged(Editor.Instance.Options.Options); + } + + private void OnEditorOptionsChanged(EditorOptions options) + { + closeButtonVisibility = options.Interface.ShowTabCloseButton; } private DockWindow GetTabAtPos(Float2 position, out bool closeButton) @@ -87,8 +96,8 @@ namespace FlaxEditor.GUI.Docking var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); if (HeaderRectangle.Contains(position)) { - closeButton = crossRect.Contains(position); result = _panel.GetTab(0); + closeButton = crossRect.Contains(position) && IsCloseButtonVisible(result, closeButtonVisibility); } } else @@ -97,9 +106,7 @@ namespace FlaxEditor.GUI.Docking for (int i = 0; i < tabsCount; i++) { var tab = _panel.GetTab(i); - var titleSize = tab.TitleSize; - var iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0; - var width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth; + float width = CalculateTabWidth(tab, closeButtonVisibility); if (_useMinimumTabWidth && width < _minimumTabWidth) width = _minimumTabWidth; @@ -109,7 +116,7 @@ namespace FlaxEditor.GUI.Docking if (isMouseOver) { var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); - closeButton = crossRect.Contains(position); + closeButton = crossRect.Contains(position) && IsCloseButtonVisible(tab, closeButtonVisibility); result = tab; break; } @@ -120,6 +127,24 @@ namespace FlaxEditor.GUI.Docking return result; } + private bool IsCloseButtonVisible(DockWindow win, InterfaceOptions.TabCloseButtonVisibility visibilityMode) + { + return visibilityMode != InterfaceOptions.TabCloseButtonVisibility.Never && + (visibilityMode == InterfaceOptions.TabCloseButtonVisibility.Always || + (visibilityMode == InterfaceOptions.TabCloseButtonVisibility.SelectedTab && _panel.SelectedTab == win)); + } + + private float CalculateTabWidth(DockWindow win, InterfaceOptions.TabCloseButtonVisibility visibilityMode) + { + var iconWidth = win.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0; + var width = win.TitleSize.X + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth; + + if (IsCloseButtonVisible(win, visibilityMode)) + width += 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultButtonsSize; + + return width; + } + private void GetTabRect(DockWindow win, out Rectangle bounds) { FlaxEngine.Assertions.Assert.IsTrue(_panel.ContainsTab(win)); @@ -137,7 +162,7 @@ namespace FlaxEditor.GUI.Docking { var tab = _panel.GetTab(i); var titleSize = tab.TitleSize; - float width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin; + float width = CalculateTabWidth(tab, closeButtonVisibility); if (tab == win) { bounds = new Rectangle(x, 0, width, HeaderRectangle.Height); @@ -234,12 +259,15 @@ namespace FlaxEditor.GUI.Docking TextAlignment.Near, TextAlignment.Center); - // Draw cross - var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); - bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition); - if (isMouseOverCross) - Render2D.FillRectangle(crossRect, (containsFocus ? style.BackgroundSelected : style.LightBackground) * 1.3f); - Render2D.DrawSprite(style.Cross, crossRect, isMouseOverCross ? style.Foreground : style.ForegroundGrey); + if (IsCloseButtonVisible(tab, closeButtonVisibility)) + { + // Draw cross + var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); + bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition); + if (isMouseOverCross) + Render2D.FillRectangle(crossRect, (containsFocus ? style.BackgroundSelected : style.LightBackground) * 1.3f); + Render2D.DrawSprite(style.Cross, crossRect, isMouseOverCross ? style.Foreground : style.ForegroundGrey); + } } else { @@ -253,10 +281,9 @@ namespace FlaxEditor.GUI.Docking // Cache data var tab = _panel.GetTab(i); var tabColor = Color.Black; - var titleSize = tab.TitleSize; var iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0; - - var width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth; + + float width = CalculateTabWidth(tab, closeButtonVisibility); if (_useMinimumTabWidth && width < _minimumTabWidth) width = _minimumTabWidth; @@ -303,7 +330,7 @@ namespace FlaxEditor.GUI.Docking TextAlignment.Center); // Draw cross - if (isSelected || isMouseOver) + if (IsCloseButtonVisible(tab, closeButtonVisibility)) { var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition); @@ -312,7 +339,7 @@ namespace FlaxEditor.GUI.Docking Render2D.DrawSprite(style.Cross, crossRect, isMouseOverCross ? style.Foreground : style.ForegroundGrey); } - // Move + // Set the start position for the next tab x += width; } diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 1e58b2a23..06679f24b 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -76,6 +76,25 @@ namespace FlaxEditor.Options DockBottom = DockState.DockBottom } + /// + /// Options for the visibility status of the tab close button. + /// + public enum TabCloseButtonVisibility + { + /// + /// Never show the close button. + /// + Never, + /// + /// Show the close button on tabs that are currently selected. + /// + SelectedTab, + /// + /// Show the close button on all tabs that can be closed. + /// + Always + } + /// /// Options for the action taken by the play button. /// @@ -295,6 +314,13 @@ namespace FlaxEditor.Options [EditorDisplay("Tabs & Windows", "New Window Location"), EditorOrder(150), Tooltip("Define the opening method for new windows, open in a new tab by default.")] public DockStateProxy NewWindowLocation { get; set; } = DockStateProxy.Float; + /// + /// Gets or sets a value indicating when the tab close button should be visible. + /// + [DefaultValue(TabCloseButtonVisibility.SelectedTab)] + [EditorDisplay("Tabs & Windows"), EditorOrder(151)] + public TabCloseButtonVisibility ShowTabCloseButton { get; set; } = TabCloseButtonVisibility.SelectedTab; + /// /// Gets or sets the timestamps prefix mode for output log messages. /// From 9b6feb93673f30443671b6d82b3235e091a39c15 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 7 Apr 2025 10:02:20 -0500 Subject: [PATCH 11/82] Add Scaled Time to Material Time node. --- Source/Editor/Surface/Archetypes/Tools.cs | 5 +++-- .../Graphics/Materials/GUIMaterialShader.cpp | 2 +- Source/Engine/Graphics/Materials/IMaterial.h | 3 ++- .../Engine/Graphics/Materials/MaterialShader.cpp | 12 ++++++++---- .../Graphics/Materials/PostFxMaterialShader.cpp | 2 +- .../MaterialGenerator.Tools.cpp | 16 ++++++++++++++-- .../MaterialGenerator/MaterialGenerator.cpp | 3 ++- .../Tools/MaterialGenerator/MaterialGenerator.h | 4 ++-- Source/Shaders/MaterialCommon.hlsl | 3 ++- 9 files changed, 35 insertions(+), 15 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index e08171ca2..eb03ac2e6 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -1386,10 +1386,11 @@ namespace FlaxEditor.Surface.Archetypes Title = "Time", Description = "Game time constant", Flags = NodeFlags.MaterialGraph, - Size = new Float2(110, 20), + Size = new Float2(110, 40), Elements = new[] { - NodeElementArchetype.Factory.Output(0, "", typeof(float), 0), + NodeElementArchetype.Factory.Output(0, "Scaled Time", typeof(float), 0), + NodeElementArchetype.Factory.Output(1, "Unscaled Time", typeof(float), 1), } }, new NodeArchetype diff --git a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp index 13f8aa810..1a74348f1 100644 --- a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp @@ -55,7 +55,7 @@ void GUIMaterialShader::Bind(BindParameters& params) materialData->ViewPos = Float3::Zero; materialData->ViewFar = 0.0f; materialData->ViewDir = Float3::Forward; - materialData->TimeParam = params.TimeParam; + materialData->TimeParam = params.UnscaledTimeParam; materialData->ViewInfo = Float4::Zero; auto& viewport = Render2D::GetViewport(); materialData->ScreenSize = Float4(viewport.Width, viewport.Height, 1.0f / viewport.Width, 1.0f / viewport.Height); diff --git a/Source/Engine/Graphics/Materials/IMaterial.h b/Source/Engine/Graphics/Materials/IMaterial.h index ba0e2acae..d85f4a836 100644 --- a/Source/Engine/Graphics/Materials/IMaterial.h +++ b/Source/Engine/Graphics/Materials/IMaterial.h @@ -148,7 +148,8 @@ public: const ::DrawCall* DrawCall = nullptr; MaterialParamsLink* ParamsLink = nullptr; void* CustomData = nullptr; - float TimeParam; + float UnscaledTimeParam; + float ScaledTimeParam; bool Instanced = false; /// diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp index ac1b1c6f9..54be98f9d 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp @@ -30,7 +30,8 @@ GPU_CB_STRUCT(MaterialShaderDataPerView { Float3 ViewPos; float ViewFar; Float3 ViewDir; - float TimeParam; + float UnscaledTimeParam; + float ScaledTimeParam; Float4 ViewInfo; Float4 ScreenSize; Float4 TemporalAAJitter; @@ -41,7 +42,8 @@ GPU_CB_STRUCT(MaterialShaderDataPerView { IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderContext& renderContext) : GPUContext(context) , RenderContext(renderContext) - , TimeParam(Time::Draw.UnscaledTime.GetTotalSeconds()) + , UnscaledTimeParam(Time::Draw.UnscaledTime.GetTotalSeconds()) + , ScaledTimeParam(Time::Draw.Time.GetTotalSeconds()) { } @@ -49,7 +51,8 @@ IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderC : GPUContext(context) , RenderContext(renderContext) , DrawCall(&drawCall) - , TimeParam(Time::Draw.UnscaledTime.GetTotalSeconds()) + , UnscaledTimeParam(Time::Draw.UnscaledTime.GetTotalSeconds()) + , ScaledTimeParam(Time::Draw.Time.GetTotalSeconds()) , Instanced(instanced) { } @@ -77,7 +80,8 @@ void IMaterial::BindParameters::BindViewData() cb.ViewPos = view.Position; cb.ViewFar = view.Far; cb.ViewDir = view.Direction; - cb.TimeParam = TimeParam; + cb.UnscaledTimeParam = UnscaledTimeParam; + cb.ScaledTimeParam = ScaledTimeParam; cb.ViewInfo = view.ViewInfo; cb.ScreenSize = view.ScreenSize; cb.TemporalAAJitter = view.TemporalAAJitter; diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp index c820fc613..893fc6ed3 100644 --- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp @@ -51,7 +51,7 @@ void PostFxMaterialShader::Bind(BindParameters& params) materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; materialData->ViewDir = view.Direction; - materialData->TimeParam = params.TimeParam; + materialData->TimeParam = params.UnscaledTimeParam; materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; materialData->TemporalAAJitter = view.TemporalAAJitter; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp index 970aa3135..1849e5ee5 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp @@ -49,7 +49,19 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) // Time case 3: { - value = getTime; + switch (box->ID) + { + // Scaled Time + case 0: + value = getScaledTime; + break; + // Unscaled Time + case 1: + value = getUnscaledTime; + break; + default: + break; + } break; } // Panner @@ -57,7 +69,7 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) { // Get inputs const Value uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); - const Value time = tryGetValue(node->GetBox(1), getTime).AsFloat(); + const Value time = tryGetValue(node->GetBox(1), getUnscaledTime).AsFloat(); const Value speed = tryGetValue(node->GetBox(2), Value::One).AsFloat2(); const bool useFractionalPart = (bool)node->Values[0]; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index ea1478cad..3eb90b907 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -106,7 +106,8 @@ bool FeatureData::Init() } MaterialValue MaterialGenerator::getUVs(VariantType::Float2, TEXT("input.TexCoord")); -MaterialValue MaterialGenerator::getTime(VariantType::Float, TEXT("TimeParam")); +MaterialValue MaterialGenerator::getUnscaledTime(VariantType::Float, TEXT("UnscaledTimeParam")); +MaterialValue MaterialGenerator::getScaledTime(VariantType::Float, TEXT("ScaledTimeParam")); MaterialValue MaterialGenerator::getNormal(VariantType::Float3, TEXT("input.TBN[2]")); MaterialValue MaterialGenerator::getNormalZero(VariantType::Float3, TEXT("float3(0, 0, 1)")); MaterialValue MaterialGenerator::getVertexColor(VariantType::Float4, TEXT("GetVertexColor(input)")); diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h index 980148d97..318e43a2d 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h @@ -120,7 +120,6 @@ private: MaterialValue _ddx, _ddy, _cameraVector; public: - MaterialGenerator(); ~MaterialGenerator(); @@ -210,7 +209,8 @@ private: public: static MaterialValue getUVs; - static MaterialValue getTime; + static MaterialValue getUnscaledTime; + static MaterialValue getScaledTime; static MaterialValue getNormal; static MaterialValue getNormalZero; static MaterialValue getVertexColor; diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index bd2030b71..ded93e27b 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -167,7 +167,8 @@ cbuffer ViewData : register(b1) float3 ViewPos; float ViewFar; float3 ViewDir; - float TimeParam; + float UnscaledTimeParam; + float ScaledTimeParam; float4 ViewInfo; float4 ScreenSize; float4 TemporalAAJitter; From e17b96b2d64c4eb1a9690c169619d5e92647b4a9 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 7 Apr 2025 11:14:33 -0500 Subject: [PATCH 12/82] Add additional methods for layers. --- Source/Engine/Core/Types/LayersMask.cs | 42 ++++++++++++++++++++++++++ Source/Engine/Core/Types/LayersMask.h | 19 +++++++++++- Source/Engine/Level/Level.cpp | 34 ++++++++++++++++++++- Source/Engine/Level/Level.h | 7 +++++ 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Types/LayersMask.cs b/Source/Engine/Core/Types/LayersMask.cs index 96b190f64..c9409321d 100644 --- a/Source/Engine/Core/Types/LayersMask.cs +++ b/Source/Engine/Core/Types/LayersMask.cs @@ -39,6 +39,48 @@ namespace FlaxEngine return HasLayer(Level.GetLayerIndex(layerName)); } + /// + /// Gets a layer mask based on a specific layer names. + /// + /// The names of the layers (from layers settings). + /// A layer mask with the mask set to the layers found. Returns a mask with 0 if not found. + public static LayersMask GetMask(params string[] layerNames) + { + LayersMask mask = new LayersMask(); + foreach (var layerName in layerNames) + { + // Ignore blank entries + if (layerName.Length == 0) + continue; + int index = Level.GetLayerIndex(layerName); + if (index != -1 && !mask.HasLayer(index)) + { + mask.Mask |= (uint)(1 << index); + } + } + return mask; + } + + /// + /// Gets the layer index based on the layer name. + /// + /// The name of the layer. + /// The index if found, otherwise, returns -1. + public static int GetLayerIndex(string layerName) + { + return Level.GetLayerIndex(layerName); + } + + /// + /// Gets the layer name based on the layer index. + /// + /// The index of the layer. + /// The name of the layer if found, otherwise, a blank string. + public static string GetLayerName(int layerIndex) + { + return Level.GetLayerName(layerIndex); + } + /// /// Adds two masks. /// diff --git a/Source/Engine/Core/Types/LayersMask.h b/Source/Engine/Core/Types/LayersMask.h index a3a594894..f3a83a6ee 100644 --- a/Source/Engine/Core/Types/LayersMask.h +++ b/Source/Engine/Core/Types/LayersMask.h @@ -27,12 +27,29 @@ public: { } + /// + /// Determines whether the specified layer index is set in the mask. + /// + /// Index of the layer (zero-based). + /// true if the specified layer is set; otherwise, false. FORCE_INLINE bool HasLayer(int32 layerIndex) const { return (Mask & (1 << layerIndex)) != 0; } - bool HasLayer(const StringView& layerName) const; + /// + /// Determines whether the specified layer name is set in the mask. + /// + /// Name of the layer (from layers settings). + /// true if the specified layer is set; otherwise, false. + bool HasLayer(const StringView& layerName); + + /// + /// Gets a layers mask from a specific layer name. + /// + /// The layer names. + /// A layers mask with the Mask set to the same Mask as the layer name passed in. Returns a LayersMask with a mask of 0 if no layer found. + static LayersMask GetMask(StringView layerNames[]); operator uint32() const { diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 5d50c9339..1fa7687a6 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -59,11 +59,31 @@ void LargeWorlds::UpdateOrigin(Vector3& origin, const Vector3& position) } } -bool LayersMask::HasLayer(const StringView& layerName) const +bool LayersMask::HasLayer(const StringView& layerName) { return HasLayer(Level::GetLayerIndex(layerName)); } +LayersMask LayersMask::GetMask(StringView layerNames[]) +{ + LayersMask mask(0); + if (layerNames == nullptr) + return mask; + for (int i = 0; i < layerNames->Length(); i++) + { + StringView& layerName = layerNames[i]; + // Ignore blank entries + if (layerName.Length() == 0) + continue; + int index = Level::GetLayerIndex(layerName); + if (index != -1 && !mask.HasLayer(index)) + { + mask.Mask |= static_cast(1 << index); + } + } + return mask; +} + enum class SceneEventType { OnSceneSaving = 0, @@ -728,6 +748,18 @@ int32 Level::GetLayerIndex(const StringView& layer) return result; } +StringView Level::GetLayerName(const int32 layerIndex) +{ + for (int32 i = 0; i < 32; i++) + { + if (i == layerIndex) + { + return Layers[i]; + } + } + return TEXT(""); +} + void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b) { PROFILE_CPU(); diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index d68336beb..dbbe20629 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -534,6 +534,13 @@ public: /// API_FUNCTION() static int32 GetLayerIndex(const StringView& layer); + /// + /// Gets the name of the layer based on the index. + /// + /// The index to find the layer string. 0 - 32. + /// The layer string. Returns a blank string if index not found. + API_FUNCTION() static StringView GetLayerName(const int32 layerIndex); + private: // Actor API enum class ActorEventType From 19edce1770f09d2b9bb0ba8b1c179a5f47da2f3e Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Wed, 9 Apr 2025 10:43:52 +0200 Subject: [PATCH 13/82] add controls and filter to actor toolbox search --- Source/Editor/Windows/ToolboxWindow.cs | 165 +++++++++++++++++++------ 1 file changed, 130 insertions(+), 35 deletions(-) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 81f7b94d4..43b935557 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tabs; using FlaxEditor.GUI.Tree; @@ -98,10 +99,20 @@ namespace FlaxEditor.Windows } } + private enum SearchFilter + { + Ui = 1, + Actors = 2, + Models = 4, + } + private TextBox _searchBox; private ContainerControl _groupSearch; private Tabs _actorGroups; private ContainerControl groupPrimitives; + private Button _viewDropdown; + + private int _searchTypeShowMask = (int)SearchFilter.Ui | (int)SearchFilter.Actors | (int)SearchFilter.Models; /// /// The editor instance. @@ -127,16 +138,23 @@ namespace FlaxEditor.Windows UseScroll = true, AnchorPreset = AnchorPresets.StretchAll, Offsets = Margin.Zero, - TabsSize = new Float2(120, 32), + TabsSize = new Float2(90, 32), Parent = this, }; _groupSearch = CreateGroupWithList(_actorGroups, "Search", 26); - _searchBox = new SearchBox + + _viewDropdown = new Button(2, 2, 45.0f, TextBoxBase.DefaultHeight) + { + TooltipText = "Change search filter options", + Text = "Filters", + Parent = _groupSearch.Parent.Parent, + }; + _viewDropdown.Clicked += OnViewButtonClicked; + + _searchBox = new SearchBox(false, _viewDropdown.Right + 2, 2, _groupSearch.Width - _viewDropdown.Right - 4) { - AnchorPreset = AnchorPresets.HorizontalStretchTop, Parent = _groupSearch.Parent.Parent, - Bounds = new Rectangle(4, 4, _actorGroups.Width - 8, 18), }; _searchBox.TextChanged += OnSearchBoxTextChanged; @@ -145,6 +163,42 @@ namespace FlaxEditor.Windows _actorGroups.SelectedTabIndex = 1; } + private void OnViewButtonClicked() + { + var menu = new ContextMenu(); + + var infoLogButton = menu.AddButton("Ui"); + infoLogButton.AutoCheck = true; + infoLogButton.Checked = (_searchTypeShowMask & (int)SearchFilter.Ui) != 0; + infoLogButton.Clicked += () => ToggleSearchFilter(SearchFilter.Ui); + + var warningLogButton = menu.AddButton("Actors"); + warningLogButton.AutoCheck = true; + warningLogButton.Checked = (_searchTypeShowMask & (int)SearchFilter.Actors) != 0; + warningLogButton.Clicked += () => ToggleSearchFilter(SearchFilter.Actors); + + var errorLogButton = menu.AddButton("Models"); + errorLogButton.AutoCheck = true; + errorLogButton.Checked = (_searchTypeShowMask & (int)SearchFilter.Models) != 0; + errorLogButton.Clicked += () => ToggleSearchFilter(SearchFilter.Models); + + menu.Show(_viewDropdown.Parent, _viewDropdown.BottomLeft); + } + + private void ToggleSearchFilter(SearchFilter type) + { + _searchTypeShowMask ^= (int)type; + OnSearchBoxTextChanged(); + } + + /// + protected override void PerformLayoutBeforeChildren() + { + base.PerformLayoutBeforeChildren(); + + _searchBox.Width = _groupSearch.Width - _viewDropdown.Right - 4; + } + private void OnScriptsReload() { // Prevent any references to actor types from the game assemblies that will be reloaded @@ -213,7 +267,7 @@ namespace FlaxEditor.Windows { if (controlType.IsAbstract) continue; - _groupSearch.AddChild(CreateControlItem(Utilities.Utils.GetPropertyNameUI(controlType.Name), controlType)); + ActorToolboxAttribute attribute = null; foreach (var e in controlType.GetAttributes(false)) { @@ -312,51 +366,92 @@ namespace FlaxEditor.Windows _groupSearch.LockChildrenRecursive(); _groupSearch.DisposeChildren(); - foreach (var actorType in Editor.CodeEditing.Actors.Get()) + if (((int)SearchFilter.Actors & _searchTypeShowMask) != 0) { - ActorToolboxAttribute attribute = null; - foreach (var e in actorType.GetAttributes(false)) + foreach (var actorType in Editor.CodeEditing.Actors.Get()) { - if (e is ActorToolboxAttribute actorToolboxAttribute) + ActorToolboxAttribute attribute = null; + foreach (var e in actorType.GetAttributes(false)) { - attribute = actorToolboxAttribute; - break; + if (e is ActorToolboxAttribute actorToolboxAttribute) + { + attribute = actorToolboxAttribute; + break; + } } - } - var text = (attribute == null) ? actorType.Name : string.IsNullOrEmpty(attribute.Name) ? actorType.Name : attribute.Name; + var text = (attribute == null) ? actorType.Name : string.IsNullOrEmpty(attribute.Name) ? actorType.Name : attribute.Name; - // Display all actors on no search - if (string.IsNullOrEmpty(filterText)) - _groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType)); - - if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges)) - continue; - - var item = CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType); - SearchFilterHighlights(item, text, ranges); - } - - // Hack primitive models into the search results - foreach (var child in groupPrimitives.Children) - { - if (child is Item primitiveAssetItem) - { - var text = primitiveAssetItem.Text; + // Display all actors on no search + if (string.IsNullOrEmpty(filterText)) + _groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType)); if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges)) continue; - // Rebuild the path based on item name (it would be better to convert the drag data back to a string somehow) - string path = $"Primitives/{text}.flax"; - - var item = CreateEditorAssetItem(text, path); + var item = CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType); SearchFilterHighlights(item, text, ranges); } } + if (((int)SearchFilter.Models & _searchTypeShowMask) != 0) + { + // Hack primitive models into the search results + foreach (var child in groupPrimitives.Children) + { + if (child is Item primitiveAssetItem) + { + var text = primitiveAssetItem.Text; + + // Rebuild the path based on item name (it would be better to convert the drag data back to a string somehow) + string path = $"Primitives/{text}.flax"; + + // Display all primitives on no search + if (string.IsNullOrEmpty(filterText)) + _groupSearch.AddChild(CreateEditorAssetItem(text, path)); + + if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges)) + continue; + + var item = CreateEditorAssetItem(text, path); + SearchFilterHighlights(item, text, ranges); + } + } + } + + if (((int)SearchFilter.Ui & _searchTypeShowMask) != 0) + { + foreach (var controlType in Editor.Instance.CodeEditing.Controls.Get()) + { + if (controlType.IsAbstract) + continue; + + ActorToolboxAttribute attribute = null; + foreach (var e in controlType.GetAttributes(false)) + { + if (e is ActorToolboxAttribute actorToolboxAttribute) + { + attribute = actorToolboxAttribute; + break; + } + } + + var text = (attribute == null) ? controlType.Name : string.IsNullOrEmpty(attribute.Name) ? controlType.Name : attribute.Name; + + // Display all controls on no search + if (string.IsNullOrEmpty(filterText)) + _groupSearch.AddChild(CreateControlItem(Utilities.Utils.GetPropertyNameUI(controlType.Name), controlType)); + + if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges)) + continue; + + var item = CreateControlItem(Utilities.Utils.GetPropertyNameUI(controlType.Name), controlType); + SearchFilterHighlights(item, text, ranges); + } + } + if (string.IsNullOrEmpty(filterText)) - _groupSearch.SortChildren(); + _groupSearch.SortChildren(); _groupSearch.UnlockChildrenRecursive(); PerformLayout(); From 6bf90f29c52c198d855ed1fadce41cb4289c5ad0 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Wed, 9 Apr 2025 14:29:19 +0200 Subject: [PATCH 14/82] fixes --- Source/Editor/Windows/ToolboxWindow.cs | 30 ++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 43b935557..84cd94ff2 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -103,7 +103,7 @@ namespace FlaxEditor.Windows { Ui = 1, Actors = 2, - Models = 4, + Primitives = 4, } private TextBox _searchBox; @@ -113,6 +113,7 @@ namespace FlaxEditor.Windows private Button _viewDropdown; private int _searchTypeShowMask = (int)SearchFilter.Ui | (int)SearchFilter.Actors | (int)SearchFilter.Models; + private int _searchFilterMask = (int)SearchFilter.Ui | (int)SearchFilter.Actors | (int)SearchFilter.Primitives; /// /// The editor instance. @@ -167,8 +168,6 @@ namespace FlaxEditor.Windows { var menu = new ContextMenu(); - var infoLogButton = menu.AddButton("Ui"); - infoLogButton.AutoCheck = true; infoLogButton.Checked = (_searchTypeShowMask & (int)SearchFilter.Ui) != 0; infoLogButton.Clicked += () => ToggleSearchFilter(SearchFilter.Ui); @@ -179,15 +178,17 @@ namespace FlaxEditor.Windows var errorLogButton = menu.AddButton("Models"); errorLogButton.AutoCheck = true; - errorLogButton.Checked = (_searchTypeShowMask & (int)SearchFilter.Models) != 0; - errorLogButton.Clicked += () => ToggleSearchFilter(SearchFilter.Models); + var primitiveFilterButton = menu.AddButton("Primitives"); + primitiveFilterButton.AutoCheck = true; + primitiveFilterButton.Checked = (_searchFilterMask & (int)SearchFilter.Primitives) != 0; + primitiveFilterButton.Clicked += () => ToggleSearchFilter(SearchFilter.Primitives); menu.Show(_viewDropdown.Parent, _viewDropdown.BottomLeft); } private void ToggleSearchFilter(SearchFilter type) { - _searchTypeShowMask ^= (int)type; + _searchFilterMask ^= (int)type; OnSearchBoxTextChanged(); } @@ -246,14 +247,21 @@ namespace FlaxEditor.Windows group.Dispose(); } - // Setup primitives tabs + // Add primitives to primtives and search tab groupPrimitives = CreateGroupWithList(_actorGroups, "Primitives"); + groupPrimitives.AddChild(CreateEditorAssetItem("Cube", "Primitives/Cube.flax")); + _groupSearch.AddChild(CreateEditorAssetItem("Cube", "Primitives/Cube.flax")); groupPrimitives.AddChild(CreateEditorAssetItem("Sphere", "Primitives/Sphere.flax")); + _groupSearch.AddChild(CreateEditorAssetItem("Sphere", "Primitives/Sphere.flax")); groupPrimitives.AddChild(CreateEditorAssetItem("Plane", "Primitives/Plane.flax")); + _groupSearch.AddChild(CreateEditorAssetItem("Plane", "Primitives/Plane.flax")); groupPrimitives.AddChild(CreateEditorAssetItem("Cylinder", "Primitives/Cylinder.flax")); + _groupSearch.AddChild(CreateEditorAssetItem("Cylinder", "Primitives/Cylinder.flax")); groupPrimitives.AddChild(CreateEditorAssetItem("Cone", "Primitives/Cone.flax")); + _groupSearch.AddChild(CreateEditorAssetItem("Cone", "Primitives/Cone.flax")); groupPrimitives.AddChild(CreateEditorAssetItem("Capsule", "Primitives/Capsule.flax")); + _groupSearch.AddChild(CreateEditorAssetItem("Capsule", "Primitives/Capsule.flax")); // Created first to order specific tabs CreateGroupWithList(_actorGroups, "Lights"); @@ -267,7 +275,7 @@ namespace FlaxEditor.Windows { if (controlType.IsAbstract) continue; - + _groupSearch.AddChild(CreateControlItem(Utilities.Utils.GetPropertyNameUI(controlType.Name), controlType)); ActorToolboxAttribute attribute = null; foreach (var e in controlType.GetAttributes(false)) { @@ -366,7 +374,7 @@ namespace FlaxEditor.Windows _groupSearch.LockChildrenRecursive(); _groupSearch.DisposeChildren(); - if (((int)SearchFilter.Actors & _searchTypeShowMask) != 0) + if (((int)SearchFilter.Actors & _searchFilterMask) != 0) { foreach (var actorType in Editor.CodeEditing.Actors.Get()) { @@ -394,7 +402,7 @@ namespace FlaxEditor.Windows } } - if (((int)SearchFilter.Models & _searchTypeShowMask) != 0) + if (((int)SearchFilter.Primitives & _searchFilterMask) != 0) { // Hack primitive models into the search results foreach (var child in groupPrimitives.Children) @@ -419,7 +427,7 @@ namespace FlaxEditor.Windows } } - if (((int)SearchFilter.Ui & _searchTypeShowMask) != 0) + if (((int)SearchFilter.Ui & _searchFilterMask) != 0) { foreach (var controlType in Editor.Instance.CodeEditing.Controls.Get()) { From aecbab5613daf1769b00b4fa6b25393cb33ad3e5 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Wed, 9 Apr 2025 14:30:08 +0200 Subject: [PATCH 15/82] more fixes --- Source/Editor/Windows/ToolboxWindow.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 84cd94ff2..44854f029 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -112,7 +112,6 @@ namespace FlaxEditor.Windows private ContainerControl groupPrimitives; private Button _viewDropdown; - private int _searchTypeShowMask = (int)SearchFilter.Ui | (int)SearchFilter.Actors | (int)SearchFilter.Models; private int _searchFilterMask = (int)SearchFilter.Ui | (int)SearchFilter.Actors | (int)SearchFilter.Primitives; /// @@ -168,16 +167,16 @@ namespace FlaxEditor.Windows { var menu = new ContextMenu(); - infoLogButton.Checked = (_searchTypeShowMask & (int)SearchFilter.Ui) != 0; - infoLogButton.Clicked += () => ToggleSearchFilter(SearchFilter.Ui); + var uiFilterButton = menu.AddButton("Ui"); + uiFilterButton.AutoCheck = true; + uiFilterButton.Checked = (_searchFilterMask & (int)SearchFilter.Ui) != 0; + uiFilterButton.Clicked += () => ToggleSearchFilter(SearchFilter.Ui); - var warningLogButton = menu.AddButton("Actors"); - warningLogButton.AutoCheck = true; - warningLogButton.Checked = (_searchTypeShowMask & (int)SearchFilter.Actors) != 0; - warningLogButton.Clicked += () => ToggleSearchFilter(SearchFilter.Actors); + var actorFilterButton = menu.AddButton("Actors"); + actorFilterButton.AutoCheck = true; + actorFilterButton.Checked = (_searchFilterMask & (int)SearchFilter.Actors) != 0; + actorFilterButton.Clicked += () => ToggleSearchFilter(SearchFilter.Actors); - var errorLogButton = menu.AddButton("Models"); - errorLogButton.AutoCheck = true; var primitiveFilterButton = menu.AddButton("Primitives"); primitiveFilterButton.AutoCheck = true; primitiveFilterButton.Checked = (_searchFilterMask & (int)SearchFilter.Primitives) != 0; From 6e44eebb9eee8f8a4f518bec2baeae04032a8d5a Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Wed, 9 Apr 2025 17:01:17 +0200 Subject: [PATCH 16/82] another fix --- Source/Editor/Windows/ToolboxWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 44854f029..789ff0290 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -458,7 +458,7 @@ namespace FlaxEditor.Windows } if (string.IsNullOrEmpty(filterText)) - _groupSearch.SortChildren(); + _groupSearch.SortChildren(); _groupSearch.UnlockChildrenRecursive(); PerformLayout(); From 5049f3b2d811a4b130e318528b86901f4504133c Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Wed, 9 Apr 2025 23:45:00 +0200 Subject: [PATCH 17/82] add hint if no filters are active --- Source/Editor/Windows/ToolboxWindow.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 789ff0290..a4a34c9fe 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -146,7 +146,7 @@ namespace FlaxEditor.Windows _viewDropdown = new Button(2, 2, 45.0f, TextBoxBase.DefaultHeight) { - TooltipText = "Change search filter options", + TooltipText = "Change search filter options.", Text = "Filters", Parent = _groupSearch.Parent.Parent, }; @@ -465,6 +465,22 @@ namespace FlaxEditor.Windows PerformLayout(); } + /// + { + base.Draw(); + + bool showFilterHint = ((int)SearchFilter.Actors & _searchFilterMask) == 0 && + ((int)SearchFilter.Primitives & _searchFilterMask) == 0 && + ((int)SearchFilter.Ui & _searchFilterMask) == 0; + + if (showFilterHint) + { + var textRect = _groupSearch.Parent.Parent.Bounds; + var style = Style.Current; + Render2D.DrawText(style.FontMedium, "No search filter active, please enable at least one filter.", textRect, style.ForegroundGrey, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords); + } + } + private void SearchFilterHighlights(Item item, string text, QueryFilterHelper.Range[] ranges) { _groupSearch.AddChild(item); From d58a9beb3df09b559635490f3a5bc347debb2ff5 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Wed, 9 Apr 2025 23:45:43 +0200 Subject: [PATCH 18/82] why was this not in the last commit? --- Source/Editor/Windows/ToolboxWindow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index a4a34c9fe..6666fcdb7 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -466,6 +466,7 @@ namespace FlaxEditor.Windows } /// + public override void Draw() { base.Draw(); From 41706993483723e8d5148682c4323929e3dd2766 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Thu, 10 Apr 2025 09:25:52 +0200 Subject: [PATCH 19/82] add hint if there are no search results --- Source/Editor/Windows/ToolboxWindow.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 6666fcdb7..020aa53b1 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -470,15 +470,20 @@ namespace FlaxEditor.Windows { base.Draw(); - bool showFilterHint = ((int)SearchFilter.Actors & _searchFilterMask) == 0 && + // Show a text to hint the user that either no filter is active or the search does not return any results + bool noSearchResults = _groupSearch.Children.Count == 0 && !string.IsNullOrEmpty(_searchBox.Text); + bool showHint = (((int)SearchFilter.Actors & _searchFilterMask) == 0 && ((int)SearchFilter.Primitives & _searchFilterMask) == 0 && - ((int)SearchFilter.Ui & _searchFilterMask) == 0; - - if (showFilterHint) + ((int)SearchFilter.Ui & _searchFilterMask) == 0) || + noSearchResults; + + String hint = noSearchResults ? "No results." : "No search filter active, please enable at least one filter."; + + if (showHint) { var textRect = _groupSearch.Parent.Parent.Bounds; var style = Style.Current; - Render2D.DrawText(style.FontMedium, "No search filter active, please enable at least one filter.", textRect, style.ForegroundGrey, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords); + Render2D.DrawText(style.FontMedium, hint, textRect, style.ForegroundGrey, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords); } } From 63655d18c59662b0d71d5580739cad0685297f53 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Thu, 10 Apr 2025 21:31:12 +0200 Subject: [PATCH 20/82] add a info message on Debug Log pause on warning --- Source/Editor/Windows/DebugLogWindow.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index fd6b333ef..a21ad14b8 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -335,12 +335,12 @@ namespace FlaxEditor.Windows { Parent = this, }; - toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries"); + toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries."); _clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play", () => { editor.Options.Options.Interface.DebugLogClearOnPlay = _clearOnPlayButton.Checked; editor.Options.Apply(editor.Options.Options); - }).SetAutoCheck(true).LinkTooltip("Clears all log entries on enter playmode"); + }).SetAutoCheck(true).LinkTooltip("Clears all log entries on enter playmode."); _collapseLogsButton = (ToolStripButton)toolstrip.AddButton("Collapse", () => { editor.Options.Options.Interface.DebugLogCollapse = _collapseLogsButton.Checked; @@ -350,26 +350,26 @@ namespace FlaxEditor.Windows { editor.Options.Options.Interface.DebugLogPauseOnError = _pauseOnErrorButton.Checked; editor.Options.Apply(editor.Options.Options); - }).SetAutoCheck(true).LinkTooltip("Performs auto pause on error"); + }).SetAutoCheck(true).LinkTooltip("Performs auto pause on error."); toolstrip.AddSeparator(); _groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => { UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked); editor.Options.Options.Interface.DebugLogShowErrorMessages = _groupButtons[0].Checked; editor.Options.Apply(editor.Options.Options); - }).SetAutoCheck(true).LinkTooltip("Shows/hides error messages"); + }).SetAutoCheck(true).LinkTooltip("Shows/hides error messages."); _groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () => { UpdateLogTypeVisibility(LogGroup.Warning, _groupButtons[1].Checked); editor.Options.Options.Interface.DebugLogShowWarningMessages = _groupButtons[1].Checked; editor.Options.Apply(editor.Options.Options); - }).SetAutoCheck(true).LinkTooltip("Shows/hides warning messages"); + }).SetAutoCheck(true).LinkTooltip("Shows/hides warning messages."); _groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () => { UpdateLogTypeVisibility(LogGroup.Info, _groupButtons[2].Checked); editor.Options.Options.Interface.DebugLogShowInfoMessages = _groupButtons[2].Checked; editor.Options.Apply(editor.Options.Options); - }).SetAutoCheck(true).LinkTooltip("Shows/hides info messages"); + }).SetAutoCheck(true).LinkTooltip("Shows/hides info messages."); UpdateCount(); // Split panel @@ -488,6 +488,7 @@ namespace FlaxEditor.Windows // Pause on Error (we should do it as fast as possible) if (newEntry.Group == LogGroup.Error && _pauseOnErrorButton.Checked && Editor.StateMachine.CurrentState == Editor.StateMachine.PlayingState) { + Debug.Write(LogType.Info, "Pause Play mode on error (toggle this behaviour in the Debug Log panel)."); Editor.Simulation.RequestPausePlay(); } } From 6884df02fd8cb85ae1ade13691409a9a54875cea Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Thu, 10 Apr 2025 21:34:00 +0200 Subject: [PATCH 21/82] remove trailing . on message --- Source/Editor/Windows/DebugLogWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index a21ad14b8..c64025491 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -488,7 +488,7 @@ namespace FlaxEditor.Windows // Pause on Error (we should do it as fast as possible) if (newEntry.Group == LogGroup.Error && _pauseOnErrorButton.Checked && Editor.StateMachine.CurrentState == Editor.StateMachine.PlayingState) { - Debug.Write(LogType.Info, "Pause Play mode on error (toggle this behaviour in the Debug Log panel)."); + Debug.Write(LogType.Info, "Pause Play mode on error (toggle this behaviour in the Debug Log panel)"); Editor.Simulation.RequestPausePlay(); } } From 0c1e0e48d420600cad47d6fd89c11581af26e75d Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Thu, 10 Apr 2025 22:12:47 +0200 Subject: [PATCH 22/82] always sort the search results alphabetically --- Source/Editor/Windows/ToolboxWindow.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 020aa53b1..160084dd2 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -455,10 +455,10 @@ namespace FlaxEditor.Windows var item = CreateControlItem(Utilities.Utils.GetPropertyNameUI(controlType.Name), controlType); SearchFilterHighlights(item, text, ranges); } - } + } - if (string.IsNullOrEmpty(filterText)) - _groupSearch.SortChildren(); + // Sort the search results alphabetically + _groupSearch.SortChildren(); _groupSearch.UnlockChildrenRecursive(); PerformLayout(); From 4e44831bbe663d32bc46e910ee2709125e873fbf Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Fri, 11 Apr 2025 19:07:39 +0200 Subject: [PATCH 23/82] fix searchbox width --- Source/Editor/Windows/ToolboxWindow.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 160084dd2..13ebf554d 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -152,9 +152,11 @@ namespace FlaxEditor.Windows }; _viewDropdown.Clicked += OnViewButtonClicked; - _searchBox = new SearchBox(false, _viewDropdown.Right + 2, 2, _groupSearch.Width - _viewDropdown.Right - 4) + _searchBox = new SearchBox { + AnchorPreset = AnchorPresets.HorizontalStretchTop, Parent = _groupSearch.Parent.Parent, + Bounds = new Rectangle(_viewDropdown.Right + 2, 2, _actorGroups.Width - 4, TextBoxBase.DefaultHeight), }; _searchBox.TextChanged += OnSearchBoxTextChanged; From 1704cfba4d06732eccada9ddf564bb5dad971b6e Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Fri, 25 Apr 2025 18:32:05 +0200 Subject: [PATCH 24/82] do not clear user search on script reload --- Source/Editor/Windows/ToolboxWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 13ebf554d..1533d037a 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -204,7 +204,6 @@ namespace FlaxEditor.Windows private void OnScriptsReload() { // Prevent any references to actor types from the game assemblies that will be reloaded - _searchBox.Clear(); _groupSearch.DisposeChildren(); _groupSearch.PerformLayout(); @@ -228,6 +227,7 @@ namespace FlaxEditor.Windows private void OnScriptsReloadEnd() { RefreshActorTabs(); + OnSearchBoxTextChanged(); } private void RefreshActorTabs() From 49e0cc937e6b43821bc221a18e8b2fc271a4e93a Mon Sep 17 00:00:00 2001 From: Olly Date: Wed, 14 May 2025 19:28:29 +1000 Subject: [PATCH 25/82] Added SetNodeTransformation with ModelBoneNode To get and set a series of bones based on their ID (cherry picked from commit e0a113483e910660e45c53e059502733ce1d6ad6) --- Source/Engine/Level/Actors/AnimatedModel.cpp | 40 +++++++++++++++++++- Source/Engine/Level/Actors/AnimatedModel.h | 26 ++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 54fe6119a..d438f94c6 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -174,6 +174,14 @@ void AnimatedModel::GetNodeTransformation(const StringView& nodeName, Matrix& no GetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace); } +void AnimatedModel::GetNodeTransformation(Array& modelBoneNodes, bool worldSpace) const +{ + for (ModelBoneNode& item : modelBoneNodes) + { + GetNodeTransformation(item.NodeIndex, item.NodeMatrix, worldSpace); + } +} + void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTransformation, bool worldSpace) { if (GraphInstance.NodesPose.IsEmpty()) @@ -191,6 +199,33 @@ void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTra OnAnimationUpdated(); } +void AnimatedModel::SetNodeTransformation(Array& modelBoneNodes, bool worldSpace) +{ + if (GraphInstance.NodesPose.IsEmpty()) + const_cast(this)->PreInitSkinningData(); // Ensure to have valid nodes pose to return + + // Calculate it once, outside loop + Matrix invWorld; + if (worldSpace) + { + Matrix world; + GetLocalToWorldMatrix(world); + Matrix::Invert(world, invWorld); + } + + for (int i = 0; i < modelBoneNodes.Count(); i++) + { + int nodeIndex = modelBoneNodes[i].NodeIndex; + CHECK(nodeIndex >= 0 && nodeIndex < GraphInstance.NodesPose.Count()); + GraphInstance.NodesPose[nodeIndex] = modelBoneNodes[i].NodeMatrix; + if (worldSpace) + { + GraphInstance.NodesPose[nodeIndex] = GraphInstance.NodesPose[nodeIndex] * invWorld; + } + } + OnAnimationUpdated(); +} + void AnimatedModel::SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace) { SetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace); @@ -809,7 +844,10 @@ void AnimatedModel::OnAnimationUpdated_Async() _skinningData.OnDataChanged(!PerBoneMotionBlur); } - UpdateBounds(); + if (UpdateWhenOffscreen) + { + UpdateBounds(); + } } void AnimatedModel::OnAnimationUpdated_Sync() diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 57224f83e..b7bf51108 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -9,6 +9,14 @@ #include "Engine/Renderer/DrawCall.h" #include "Engine/Core/Delegate.h" +API_STRUCT() struct ModelBoneNode +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(ModelBoneNode); + + API_FIELD() uint32 NodeIndex; + API_FIELD() Matrix NodeMatrix; +}; + /// /// Performs an animation and renders a skinned model. /// @@ -236,13 +244,21 @@ public: /// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor. API_FUNCTION() void GetNodeTransformation(const StringView& nodeName, API_PARAM(Out) Matrix& nodeTransformation, bool worldSpace = false) const; + /// + /// Gets the node final transformation for a series of nodes. + /// + /// The series of nodes that will be returned + /// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor. + /// + API_FUNCTION() void GetNodeTransformation(API_PARAM(Out) Array& modelBoneNodes, bool worldSpace = false) const; + /// /// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices. /// /// The index of the skinned model skeleton node. /// The final node transformation matrix. /// True if convert matrices from world-space, otherwise values will be in local-space of the actor. - API_FUNCTION() void SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTransformation, bool worldSpace = false); + API_FUNCTION() void SetNodeTransformation(int32 nodeIndex, const Matrix& modelBoneNodes, bool worldSpace = false); /// /// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices. @@ -252,6 +268,14 @@ public: /// True if convert matrices from world-space, otherwise values will be in local-space of the actor. API_FUNCTION() void SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace = false); + /// + /// Sets a group of nodes final transformation. + /// + /// Array of the final node transformation matrix. + /// True if convert matrices from world-space, otherwise values will be in local-space of the actor. + /// + API_FUNCTION() void SetNodeTransformation(Array& nodesTransformations, bool worldSpace = false); + /// /// Finds the closest node to a given location. /// From 245d7de818571953668ad5c5ccadc1c28437d490 Mon Sep 17 00:00:00 2001 From: Olly Rybak Date: Wed, 14 May 2025 19:32:11 +1000 Subject: [PATCH 26/82] Fixed renamed parameters --- Source/Engine/Level/Actors/AnimatedModel.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index b7bf51108..9e5a34c69 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -258,7 +258,7 @@ public: /// The index of the skinned model skeleton node. /// The final node transformation matrix. /// True if convert matrices from world-space, otherwise values will be in local-space of the actor. - API_FUNCTION() void SetNodeTransformation(int32 nodeIndex, const Matrix& modelBoneNodes, bool worldSpace = false); + API_FUNCTION() void SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTransformation, bool worldSpace = false); /// /// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices. @@ -271,10 +271,10 @@ public: /// /// Sets a group of nodes final transformation. /// - /// Array of the final node transformation matrix. + /// Array of the final node transformation matrix. /// True if convert matrices from world-space, otherwise values will be in local-space of the actor. /// - API_FUNCTION() void SetNodeTransformation(Array& nodesTransformations, bool worldSpace = false); + API_FUNCTION() void SetNodeTransformation(Array& modelBoneNodes, bool worldSpace = false); /// /// Finds the closest node to a given location. From 28eaac37dcbd361064d90a235c0f8d7602610300 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 11 Jun 2025 18:17:19 -0500 Subject: [PATCH 27/82] Initial work on nuget packages. --- .../Flax.Build/Build/Builder.Projects.cs | 1 + .../Flax.Build/Build/DotNet/Builder.DotNet.cs | 21 ++++++++++ Source/Tools/Flax.Build/Build/EngineTarget.cs | 1 + .../Build/NativeCpp/BuildOptions.cs | 39 +++++++++++++++++++ .../Build/NativeCpp/Builder.NativeCpp.cs | 18 +++++++++ Source/Tools/Flax.Build/Projects/Project.cs | 6 +++ .../VisualStudio/CSProjectGenerator.cs | 13 +++++++ .../VisualStudio/CSSDKProjectGenerator.cs | 14 +++++++ 8 files changed, 113 insertions(+) diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 008e9d982..59538364c 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -513,6 +513,7 @@ namespace Flax.Build // Combine build options from this module project.CSharp.SystemReferences.AddRange(moduleBuildOptions.ScriptingAPI.SystemReferences); project.CSharp.FileReferences.AddRange(moduleBuildOptions.ScriptingAPI.FileReferences); + project.CSharp.NugetPackageReferences.AddRange(moduleBuildOptions.NugetPackageReferences); // Find references based on the modules dependencies (external or from projects) foreach (var dependencyName in moduleBuildOptions.PublicDependencies.Concat(moduleBuildOptions.PrivateDependencies)) diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index 2619f1a17..75a916296 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -109,6 +109,7 @@ namespace Flax.Build // Merge module into target environment buildData.TargetOptions.LinkEnv.InputFiles.AddRange(moduleOptions.OutputFiles); buildData.TargetOptions.DependencyFiles.AddRange(moduleOptions.DependencyFiles); + buildData.TargetOptions.NugetPackageReferences.AddRange(moduleOptions.NugetPackageReferences); buildData.TargetOptions.OptionalDependencyFiles.AddRange(moduleOptions.OptionalDependencyFiles); buildData.TargetOptions.Libraries.AddRange(moduleOptions.Libraries); buildData.TargetOptions.DelayLoadLibraries.AddRange(moduleOptions.DelayLoadLibraries); @@ -141,6 +142,14 @@ namespace Flax.Build var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile)); graph.AddCopyFile(dstFile, srcFile); } + + var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + foreach (var reference in targetBuildOptions.NugetPackageReferences) + { + var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); + var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); + graph.AddCopyFile(dstFile, path); + } } } @@ -283,6 +292,18 @@ namespace Flax.Build args.Add(string.Format("/reference:\"{0}{1}.dll\"", referenceAssemblies, reference)); foreach (var reference in fileReferences) args.Add(string.Format("/reference:\"{0}\"", reference)); + + // Reference Nuget package + if (buildData.TargetOptions.NugetPackageReferences.Any()) + { + var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + foreach (var reference in buildOptions.NugetPackageReferences) + { + var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); + args.Add(string.Format("/reference:\"{0}\"", path)); + } + } + #if USE_NETCORE foreach (var systemAnalyzer in buildOptions.ScriptingAPI.SystemAnalyzers) args.Add(string.Format("/analyzer:\"{0}{1}.dll\"", referenceAnalyzers, systemAnalyzer)); diff --git a/Source/Tools/Flax.Build/Build/EngineTarget.cs b/Source/Tools/Flax.Build/Build/EngineTarget.cs index 6a44991a2..827205e91 100644 --- a/Source/Tools/Flax.Build/Build/EngineTarget.cs +++ b/Source/Tools/Flax.Build/Build/EngineTarget.cs @@ -220,6 +220,7 @@ namespace Flax.Build exeBuildOptions.LinkEnv.InputLibraries.Add(Path.Combine(buildOptions.OutputFolder, buildOptions.Platform.GetLinkOutputFileName(LibraryName, engineLibraryType))); exeBuildOptions.LinkEnv.InputFiles.AddRange(mainModuleOptions.OutputFiles); exeBuildOptions.DependencyFiles.AddRange(mainModuleOptions.DependencyFiles); + exeBuildOptions.NugetPackageReferences.AddRange(mainModuleOptions.NugetPackageReferences); exeBuildOptions.OptionalDependencyFiles.AddRange(mainModuleOptions.OptionalDependencyFiles); exeBuildOptions.Libraries.AddRange(mainModuleOptions.Libraries); exeBuildOptions.DelayLoadLibraries.AddRange(mainModuleOptions.DelayLoadLibraries); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs index 0aa37b84e..7bda689e1 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs @@ -70,6 +70,40 @@ namespace Flax.Build.NativeCpp Annotations, } + /// + /// Defines a Nuget Package + /// + public struct NugetPackage + { + /// + /// The name of the nuget package. + /// + public string Name; + + /// + /// The version of the nuget package. + /// + public string Version; + + /// + /// The target framework. ex. net8.0, netstandard2.1 + /// + public string Framework; + + /// + /// Initialize the nuget package. + /// + /// The name of the package. + /// The version of the package. + /// The target framework. ex. net8.0, netstandard2.1, etc. + public NugetPackage(string name, string version, string framework) + { + Name = name; + Version = version; + Framework = framework; + } + } + /// /// The native C++ module build settings container. /// @@ -129,6 +163,11 @@ namespace Flax.Build.NativeCpp /// The collection of the modules that are required by this module (for linking). /// public List PrivateDependencies = new List(); + + /// + /// The nuget package references. + /// + public List NugetPackageReferences = new List(); /// /// The collection of defines with preprocessing symbol for a source files of this module. Inherited by the modules that include it. diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 1d0d3c695..664bfb63f 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -425,6 +425,7 @@ namespace Flax.Build moduleOptions.LinkEnv.InputFiles.AddRange(dependencyOptions.OutputFiles); moduleOptions.DependencyFiles.AddRange(dependencyOptions.DependencyFiles); moduleOptions.OptionalDependencyFiles.AddRange(dependencyOptions.OptionalDependencyFiles); + moduleOptions.NugetPackageReferences.AddRange(dependencyOptions.NugetPackageReferences); moduleOptions.PrivateIncludePaths.AddRange(dependencyOptions.PublicIncludePaths); moduleOptions.Libraries.AddRange(dependencyOptions.Libraries); moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries); @@ -440,6 +441,7 @@ namespace Flax.Build moduleOptions.LinkEnv.InputFiles.AddRange(dependencyOptions.OutputFiles); moduleOptions.DependencyFiles.AddRange(dependencyOptions.DependencyFiles); moduleOptions.OptionalDependencyFiles.AddRange(dependencyOptions.OptionalDependencyFiles); + moduleOptions.NugetPackageReferences.AddRange(dependencyOptions.NugetPackageReferences); moduleOptions.PublicIncludePaths.AddRange(dependencyOptions.PublicIncludePaths); moduleOptions.Libraries.AddRange(dependencyOptions.Libraries); moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries); @@ -934,6 +936,7 @@ namespace Flax.Build buildData.TargetOptions.LinkEnv.InputFiles.AddRange(moduleOptions.OutputFiles); buildData.TargetOptions.DependencyFiles.AddRange(moduleOptions.DependencyFiles); buildData.TargetOptions.OptionalDependencyFiles.AddRange(moduleOptions.OptionalDependencyFiles); + buildData.TargetOptions.NugetPackageReferences.AddRange(moduleOptions.NugetPackageReferences); buildData.TargetOptions.Libraries.AddRange(moduleOptions.Libraries); buildData.TargetOptions.DelayLoadLibraries.AddRange(moduleOptions.DelayLoadLibraries); buildData.TargetOptions.ScriptingAPI.Add(moduleOptions.ScriptingAPI); @@ -1054,6 +1057,13 @@ namespace Flax.Build var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile)); graph.AddCopyFile(dstFile, srcFile); } + var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + foreach (var reference in targetBuildOptions.NugetPackageReferences) + { + var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); + var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); + graph.AddCopyFile(dstFile, path); + } } } @@ -1192,6 +1202,7 @@ namespace Flax.Build buildData.TargetOptions.ExternalModules.AddRange(moduleOptions.ExternalModules); buildData.TargetOptions.DependencyFiles.AddRange(moduleOptions.DependencyFiles); buildData.TargetOptions.OptionalDependencyFiles.AddRange(moduleOptions.OptionalDependencyFiles); + buildData.TargetOptions.NugetPackageReferences.AddRange(moduleOptions.NugetPackageReferences); } } } @@ -1253,6 +1264,13 @@ namespace Flax.Build var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile)); graph.AddCopyFile(dstFile, srcFile); } + var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + foreach (var reference in targetBuildOptions.NugetPackageReferences) + { + var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); + var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); + graph.AddCopyFile(dstFile, path); + } } } diff --git a/Source/Tools/Flax.Build/Projects/Project.cs b/Source/Tools/Flax.Build/Projects/Project.cs index 5aa77a453..46ae6102b 100644 --- a/Source/Tools/Flax.Build/Projects/Project.cs +++ b/Source/Tools/Flax.Build/Projects/Project.cs @@ -229,6 +229,11 @@ namespace Flax.Build.Projects /// The .Net libraries references (dll or exe files paths). /// public HashSet FileReferences; + + /// + /// The nuget references. + /// + public HashSet NugetPackageReferences; /// /// The output folder path (optional). @@ -248,6 +253,7 @@ namespace Flax.Build.Projects { SystemReferences = new HashSet(), FileReferences = new HashSet(), + NugetPackageReferences = new HashSet(), }; /// diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs index da6907f78..35bc7b0ac 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs @@ -175,6 +175,19 @@ namespace Flax.Build.Projects.VisualStudio csProjectFileContent.AppendLine(" "); } + // Nuget + if (project.CSharp.NugetPackageReferences.Any()) + { + csProjectFileContent.AppendLine(" "); + + foreach (var reference in project.CSharp.NugetPackageReferences) + { + csProjectFileContent.AppendLine(string.Format(" ", reference.Name, reference.Version)); + } + + csProjectFileContent.AppendLine(" "); + } + // References csProjectFileContent.AppendLine(" "); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs index df1424e95..f126bd3ef 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs @@ -134,6 +134,20 @@ namespace Flax.Build.Projects.VisualStudio } // References + + // Nuget + if (project.CSharp.NugetPackageReferences.Any()) + { + csProjectFileContent.AppendLine(" "); + + foreach (var reference in project.CSharp.NugetPackageReferences) + { + csProjectFileContent.AppendLine(string.Format(" ", reference.Name, reference.Version)); + } + + csProjectFileContent.AppendLine(" "); + csProjectFileContent.AppendLine(""); + } csProjectFileContent.AppendLine(" "); From ecaae2b4580deb5447e70904a6247d3db48efcbe Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 19 Jun 2025 21:34:55 -0500 Subject: [PATCH 28/82] Add downloading nuget package if needed. --- .../Flax.Build/Build/DotNet/Builder.DotNet.cs | 37 +++++++-- .../Build/NativeCpp/Builder.NativeCpp.cs | 75 ++++++++++++++++--- 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index 75a916296..895810e9e 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -143,12 +143,39 @@ namespace Flax.Build graph.AddCopyFile(dstFile, srcFile); } - var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); - foreach (var reference in targetBuildOptions.NugetPackageReferences) + if (targetBuildOptions.NugetPackageReferences.Any()) { - var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); - var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); - graph.AddCopyFile(dstFile, path); + var buildPlatform = Platform.BuildTargetPlatform; + var dotnetSdk = DotNetSdk.Instance; + if (!dotnetSdk.IsValid) + throw new DotNetSdk.MissingException(); + var dotnetPath = "dotnet"; + switch (buildPlatform) + { + case TargetPlatform.Windows: + dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe"); + break; + case TargetPlatform.Linux: break; + case TargetPlatform.Mac: + dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet"); + break; + default: throw new InvalidPlatformException(buildPlatform); + } + var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + foreach (var reference in targetBuildOptions.NugetPackageReferences) + { + var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); + if (!File.Exists(path)) + { + var task = graph.Add(); + task.WorkingDirectory = target.FolderPath; + task.InfoMessage = $"Adding Nuget Package: {reference.Name}, Version {reference.Version}"; + task.CommandPath = dotnetPath; + task.CommandArguments = $"add package {reference.Name} --version {reference.Version}"; + } + var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); + graph.AddCopyFile(dstFile, path); + } } } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 664bfb63f..beaebbb2d 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -1057,12 +1057,39 @@ namespace Flax.Build var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile)); graph.AddCopyFile(dstFile, srcFile); } - var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); - foreach (var reference in targetBuildOptions.NugetPackageReferences) + if (targetBuildOptions.NugetPackageReferences.Any()) { - var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); - var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); - graph.AddCopyFile(dstFile, path); + var buildPlatform = Platform.BuildTargetPlatform; + var dotnetSdk = DotNetSdk.Instance; + if (!dotnetSdk.IsValid) + throw new DotNetSdk.MissingException(); + var dotnetPath = "dotnet"; + switch (buildPlatform) + { + case TargetPlatform.Windows: + dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe"); + break; + case TargetPlatform.Linux: break; + case TargetPlatform.Mac: + dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet"); + break; + default: throw new InvalidPlatformException(buildPlatform); + } + var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + foreach (var reference in targetBuildOptions.NugetPackageReferences) + { + var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); + if (!File.Exists(path)) + { + var task = graph.Add(); + task.WorkingDirectory = target.FolderPath; + task.InfoMessage = $"Adding Nuget Package: {reference.Name}, Version {reference.Version}"; + task.CommandPath = dotnetPath; + task.CommandArguments = $"add package {reference.Name} --version {reference.Version}"; + } + var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); + graph.AddCopyFile(dstFile, path); + } } } } @@ -1264,12 +1291,40 @@ namespace Flax.Build var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile)); graph.AddCopyFile(dstFile, srcFile); } - var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); - foreach (var reference in targetBuildOptions.NugetPackageReferences) + + if (targetBuildOptions.NugetPackageReferences.Any()) { - var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); - var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); - graph.AddCopyFile(dstFile, path); + var buildPlatform = Platform.BuildTargetPlatform; + var dotnetSdk = DotNetSdk.Instance; + if (!dotnetSdk.IsValid) + throw new DotNetSdk.MissingException(); + var dotnetPath = "dotnet"; + switch (buildPlatform) + { + case TargetPlatform.Windows: + dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe"); + break; + case TargetPlatform.Linux: break; + case TargetPlatform.Mac: + dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet"); + break; + default: throw new InvalidPlatformException(buildPlatform); + } + var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + foreach (var reference in targetBuildOptions.NugetPackageReferences) + { + var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); + if (!File.Exists(path)) + { + var task = graph.Add(); + task.WorkingDirectory = target.FolderPath; + task.InfoMessage = $"Adding Nuget Package: {reference.Name}, Version {reference.Version}"; + task.CommandPath = dotnetPath; + task.CommandArguments = $"add package {reference.Name} --version {reference.Version}"; + } + var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); + graph.AddCopyFile(dstFile, path); + } } } } From 53761df85e3509bcf744d03fbecab21a3d5fb792 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 19 Jun 2025 22:07:28 -0500 Subject: [PATCH 29/82] Add utility methods for redundant code. --- .../Flax.Build/Build/DotNet/Builder.DotNet.cs | 24 +-------- .../Build/NativeCpp/Builder.NativeCpp.cs | 51 ++----------------- .../Tools/Flax.Build/Utilities/Utilities.cs | 43 ++++++++++++++++ 3 files changed, 48 insertions(+), 70 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index 895810e9e..5d1e90a5a 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -145,34 +145,12 @@ namespace Flax.Build if (targetBuildOptions.NugetPackageReferences.Any()) { - var buildPlatform = Platform.BuildTargetPlatform; - var dotnetSdk = DotNetSdk.Instance; - if (!dotnetSdk.IsValid) - throw new DotNetSdk.MissingException(); - var dotnetPath = "dotnet"; - switch (buildPlatform) - { - case TargetPlatform.Windows: - dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe"); - break; - case TargetPlatform.Linux: break; - case TargetPlatform.Mac: - dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet"); - break; - default: throw new InvalidPlatformException(buildPlatform); - } var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); foreach (var reference in targetBuildOptions.NugetPackageReferences) { var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); if (!File.Exists(path)) - { - var task = graph.Add(); - task.WorkingDirectory = target.FolderPath; - task.InfoMessage = $"Adding Nuget Package: {reference.Name}, Version {reference.Version}"; - task.CommandPath = dotnetPath; - task.CommandArguments = $"add package {reference.Name} --version {reference.Version}"; - } + Utilities.AddNugetPackage(graph, target, reference); var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); graph.AddCopyFile(dstFile, path); } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index beaebbb2d..f88a111f6 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -1057,36 +1057,15 @@ namespace Flax.Build var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile)); graph.AddCopyFile(dstFile, srcFile); } + if (targetBuildOptions.NugetPackageReferences.Any()) { - var buildPlatform = Platform.BuildTargetPlatform; - var dotnetSdk = DotNetSdk.Instance; - if (!dotnetSdk.IsValid) - throw new DotNetSdk.MissingException(); - var dotnetPath = "dotnet"; - switch (buildPlatform) - { - case TargetPlatform.Windows: - dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe"); - break; - case TargetPlatform.Linux: break; - case TargetPlatform.Mac: - dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet"); - break; - default: throw new InvalidPlatformException(buildPlatform); - } var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); foreach (var reference in targetBuildOptions.NugetPackageReferences) { var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); if (!File.Exists(path)) - { - var task = graph.Add(); - task.WorkingDirectory = target.FolderPath; - task.InfoMessage = $"Adding Nuget Package: {reference.Name}, Version {reference.Version}"; - task.CommandPath = dotnetPath; - task.CommandArguments = $"add package {reference.Name} --version {reference.Version}"; - } + Utilities.AddNugetPackage(graph, target, reference); var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); graph.AddCopyFile(dstFile, path); } @@ -1291,37 +1270,15 @@ namespace Flax.Build var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile)); graph.AddCopyFile(dstFile, srcFile); } - + if (targetBuildOptions.NugetPackageReferences.Any()) { - var buildPlatform = Platform.BuildTargetPlatform; - var dotnetSdk = DotNetSdk.Instance; - if (!dotnetSdk.IsValid) - throw new DotNetSdk.MissingException(); - var dotnetPath = "dotnet"; - switch (buildPlatform) - { - case TargetPlatform.Windows: - dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe"); - break; - case TargetPlatform.Linux: break; - case TargetPlatform.Mac: - dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet"); - break; - default: throw new InvalidPlatformException(buildPlatform); - } var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); foreach (var reference in targetBuildOptions.NugetPackageReferences) { var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); if (!File.Exists(path)) - { - var task = graph.Add(); - task.WorkingDirectory = target.FolderPath; - task.InfoMessage = $"Adding Nuget Package: {reference.Name}, Version {reference.Version}"; - task.CommandPath = dotnetPath; - task.CommandArguments = $"add package {reference.Name} --version {reference.Version}"; - } + Utilities.AddNugetPackage(graph, target, reference); var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); graph.AddCopyFile(dstFile, path); } diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index 2b971f153..39b9cedba 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -16,6 +16,49 @@ namespace Flax.Build /// public static class Utilities { + /// + /// Gets the .Net SDK path. + /// + /// The path. + public static string GetDotNetPath() + { + var buildPlatform = Platform.BuildTargetPlatform; + var dotnetSdk = DotNetSdk.Instance; + if (!dotnetSdk.IsValid) + throw new DotNetSdk.MissingException(); + var dotnetPath = "dotnet"; + switch (buildPlatform) + { + case TargetPlatform.Windows: + dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe"); + break; + case TargetPlatform.Linux: break; + case TargetPlatform.Mac: + dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet"); + break; + default: throw new InvalidPlatformException(buildPlatform); + } + return dotnetPath; + } + + /// + /// Downloads a nuget package. + /// + /// The task graph. + /// The target. + /// The dotnet path. + /// + public static void AddNugetPackage(Graph.TaskGraph graph, Target target, NativeCpp.NugetPackage package) + { + var dotNetPath = GetDotNetPath(); + var task = graph.Add(); + task.WorkingDirectory = target.FolderPath; + task.InfoMessage = $"Add Nuget Package: {package.Name}, Version {package.Version}"; + task.CommandPath = dotNetPath; + //task.CommandArguments = $"add package {package.Name} --version {package.Version}"; + task.CommandArguments = $"restore"; + } + /// /// Gets the hash code for the string (the same for all platforms). Matches Engine algorithm for string hashing. /// From fdd22c3380d3a171d2888909e0f4b3420d885029 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 19 Jun 2025 22:09:33 -0500 Subject: [PATCH 30/82] Remove extra code. --- Source/Tools/Flax.Build/Utilities/Utilities.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index 39b9cedba..484aad59f 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -55,7 +55,6 @@ namespace Flax.Build task.WorkingDirectory = target.FolderPath; task.InfoMessage = $"Add Nuget Package: {package.Name}, Version {package.Version}"; task.CommandPath = dotNetPath; - //task.CommandArguments = $"add package {package.Name} --version {package.Version}"; task.CommandArguments = $"restore"; } From c8622d180122f60ba93fcbaa7080eb469c86fbbe Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 20 Jun 2025 15:26:58 -0500 Subject: [PATCH 31/82] Change method name from add to restore. --- Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs | 2 +- .../Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs | 4 ++-- Source/Tools/Flax.Build/Utilities/Utilities.cs | 7 +++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index 5d1e90a5a..23646a3d7 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -150,7 +150,7 @@ namespace Flax.Build { var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); if (!File.Exists(path)) - Utilities.AddNugetPackage(graph, target, reference); + Utilities.RestoreNugetPackages(graph, target); var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); graph.AddCopyFile(dstFile, path); } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index f88a111f6..18459ddb5 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -1065,7 +1065,7 @@ namespace Flax.Build { var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); if (!File.Exists(path)) - Utilities.AddNugetPackage(graph, target, reference); + Utilities.RestoreNugetPackages(graph, target); var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); graph.AddCopyFile(dstFile, path); } @@ -1278,7 +1278,7 @@ namespace Flax.Build { var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll"); if (!File.Exists(path)) - Utilities.AddNugetPackage(graph, target, reference); + Utilities.RestoreNugetPackages(graph, target); var dstFile = Path.Combine(outputPath, Path.GetFileName(path)); graph.AddCopyFile(dstFile, path); } diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index 484aad59f..7552849b3 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -42,18 +42,17 @@ namespace Flax.Build } /// - /// Downloads a nuget package. + /// Restores a targets nuget packages. /// /// The task graph. /// The target. /// The dotnet path. - /// - public static void AddNugetPackage(Graph.TaskGraph graph, Target target, NativeCpp.NugetPackage package) + public static void RestoreNugetPackages(Graph.TaskGraph graph, Target target) { var dotNetPath = GetDotNetPath(); var task = graph.Add(); task.WorkingDirectory = target.FolderPath; - task.InfoMessage = $"Add Nuget Package: {package.Name}, Version {package.Version}"; + task.InfoMessage = $"Restoring Nuget Packages for {target.Name}"; task.CommandPath = dotNetPath; task.CommandArguments = $"restore"; } From a41fc51f92aeaf99aa6675bba7ea77a6eed52b1c Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sat, 28 Jun 2025 15:53:32 +0200 Subject: [PATCH 32/82] add visject node formatting option to straighten node connections Also fixes a bunch of missing trailing "." in doc comments and changes "Node editors" category in the input options to "Node Editors" to match the case of all other categories. --- Source/Editor/Options/InputOptions.cs | 24 ++-- Source/Editor/Surface/NodeAlignmentType.cs | 16 +-- .../Surface/VisjectSurface.ContextMenu.cs | 3 +- .../Surface/VisjectSurface.Formatting.cs | 106 ++++++++++++++---- Source/Editor/Surface/VisjectSurface.cs | 1 + 5 files changed, 107 insertions(+), 43 deletions(-) diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 871156662..0281d8ce5 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -652,42 +652,46 @@ namespace FlaxEditor.Options #endregion - #region Node editors + #region Node Editors [DefaultValue(typeof(InputBinding), "Shift+W")] - [EditorDisplay("Node editors"), EditorOrder(4500)] + [EditorDisplay("Node Editors"), EditorOrder(4500)] public InputBinding NodesAlignTop = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift); [DefaultValue(typeof(InputBinding), "Shift+A")] - [EditorDisplay("Node editors"), EditorOrder(4510)] + [EditorDisplay("Node Editors"), EditorOrder(4510)] public InputBinding NodesAlignLeft = new InputBinding(KeyboardKeys.A, KeyboardKeys.Shift); [DefaultValue(typeof(InputBinding), "Shift+S")] - [EditorDisplay("Node editors"), EditorOrder(4520)] + [EditorDisplay("Node Editors"), EditorOrder(4520)] public InputBinding NodesAlignBottom = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift); [DefaultValue(typeof(InputBinding), "Shift+D")] - [EditorDisplay("Node editors"), EditorOrder(4530)] + [EditorDisplay("Node Editors"), EditorOrder(4530)] public InputBinding NodesAlignRight = new InputBinding(KeyboardKeys.D, KeyboardKeys.Shift); [DefaultValue(typeof(InputBinding), "Alt+Shift+W")] - [EditorDisplay("Node editors"), EditorOrder(4540)] + [EditorDisplay("Node Editors"), EditorOrder(4540)] public InputBinding NodesAlignMiddle = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift, KeyboardKeys.Alt); [DefaultValue(typeof(InputBinding), "Alt+Shift+S")] - [EditorDisplay("Node editors"), EditorOrder(4550)] + [EditorDisplay("Node Editors"), EditorOrder(4550)] public InputBinding NodesAlignCenter = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift, KeyboardKeys.Alt); [DefaultValue(typeof(InputBinding), "Q")] - [EditorDisplay("Node editors"), EditorOrder(4560)] + [EditorDisplay("Node Editors"), EditorOrder(4560)] public InputBinding NodesAutoFormat = new InputBinding(KeyboardKeys.Q); + [DefaultValue(typeof(InputBinding), "Shift+Q")] + [EditorDisplay("Node Editors"), EditorOrder(4560)] + public InputBinding NodesStraightenConnections = new InputBinding(KeyboardKeys.Q, KeyboardKeys.Shift); + [DefaultValue(typeof(InputBinding), "None")] - [EditorDisplay("Node editors"), EditorOrder(4570)] + [EditorDisplay("Node Editors"), EditorOrder(4570)] public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.None); [DefaultValue(typeof(InputBinding), "None")] - [EditorDisplay("Node editors"), EditorOrder(4580)] + [EditorDisplay("Node Editors"), EditorOrder(4580)] public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.None); #endregion diff --git a/Source/Editor/Surface/NodeAlignmentType.cs b/Source/Editor/Surface/NodeAlignmentType.cs index 141235783..07e1d8dc4 100644 --- a/Source/Editor/Surface/NodeAlignmentType.cs +++ b/Source/Editor/Surface/NodeAlignmentType.cs @@ -5,39 +5,39 @@ using FlaxEngine; namespace FlaxEditor.Surface { /// - /// Node Alignment type + /// Node Alignment type. /// [HideInEditor] public enum NodeAlignmentType { /// - /// Align nodes vertically to top, matching top-most node + /// Align nodes vertically to top, matching top-most node. /// Top, /// - /// Align nodes vertically to middle, using average of all nodes + /// Align nodes vertically to middle, using average of all nodes. /// Middle, /// - /// Align nodes vertically to bottom, matching bottom-most node + /// Align nodes vertically to bottom, matching bottom-most node. /// Bottom, /// - /// Align nodes horizontally to left, matching left-most node + /// Align nodes horizontally to left, matching left-most node. /// Left, /// - /// Align nodes horizontally to center, using average of all nodes + /// Align nodes horizontally to center, using average of all nodes. /// Center, /// - /// Align nodes horizontally to right, matching right-most node + /// Align nodes horizontally to right, matching right-most node. /// Right, } -} \ No newline at end of file +} diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index 84055aaf0..41020c962 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -28,7 +28,7 @@ namespace FlaxEditor.Surface /// /// The input type to process. /// Node groups cache that can be used for reusing groups for different nodes. - /// The cache version number. Can be used to reject any cached data after rebuilt. + /// The cache version number. Can be used to reject any cached data after. rebuilt. public delegate void IterateType(ScriptType scriptType, Dictionary, GroupArchetype> cache, int version); internal static readonly List Caches = new List(8); @@ -412,6 +412,7 @@ namespace FlaxEditor.Surface _cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection; _cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Auto format", Editor.Instance.Options.Options.Input.NodesAutoFormat, () => { FormatGraph(SelectedNodes); }); + _cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Straighten connections", Editor.Instance.Options.Options.Input.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); }); _cmFormatNodesMenu.ContextMenu.AddSeparator(); _cmAlignNodesTopButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align top", Editor.Instance.Options.Options.Input.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); }); diff --git a/Source/Editor/Surface/VisjectSurface.Formatting.cs b/Source/Editor/Surface/VisjectSurface.Formatting.cs index 2ff48b290..8b557c357 100644 --- a/Source/Editor/Surface/VisjectSurface.Formatting.cs +++ b/Source/Editor/Surface/VisjectSurface.Formatting.cs @@ -1,9 +1,9 @@ +using FlaxEditor.Surface.Elements; +using FlaxEditor.Surface.Undo; +using FlaxEngine; using System; using System.Collections.Generic; using System.Linq; -using FlaxEngine; -using FlaxEditor.Surface.Elements; -using FlaxEditor.Surface.Undo; namespace FlaxEditor.Surface { @@ -14,26 +14,26 @@ namespace FlaxEditor.Surface private class NodeFormattingData { /// - /// Starting from 0 at the main nodes + /// Starting from 0 at the main nodes. /// public int Layer; /// - /// Position in the layer + /// Position in the layer. /// public int Offset; /// - /// How far the subtree needs to be moved additionally + /// How far the subtree needs to be moved additionally. /// public int SubtreeOffset; } /// /// Formats a graph where the nodes can be disjointed. - /// Uses the Sugiyama method + /// Uses the Sugiyama method. /// - /// List of nodes + /// List of nodes. public void FormatGraph(List nodes) { if (nodes.Count <= 1) @@ -78,9 +78,9 @@ namespace FlaxEditor.Surface } /// - /// Formats a graph where all nodes are connected + /// Formats a graph where all nodes are connected. /// - /// List of connected nodes + /// List of connected nodes. protected void FormatConnectedGraph(List nodes) { if (nodes.Count <= 1) @@ -160,11 +160,69 @@ namespace FlaxEditor.Surface } /// - /// Assigns a layer to every node + /// Straightens every connection between nodes in . /// - /// The exta node data - /// The end nodes - /// The number of the maximum layer + /// List of nodes. + public void StraightenGraphConnections(List nodes) + { + if (nodes.Count <= 1) + return; + + List undoActions = new List(); + + // Only process nodes that have any connection + List connectedNodes = nodes.Where(n => n.GetBoxes().Any(b => b.HasAnyConnection)).ToList(); + + if (connectedNodes.Count == 0) + return; + + for (int i = 0; i < connectedNodes.Count - 1; i++) + { + SurfaceNode nodeA = connectedNodes[i]; + List connectedOutputBoxes = nodeA.GetBoxes().Where(b => b.IsOutput && b.HasAnyConnection).ToList(); + + for (int j = 0; j < connectedOutputBoxes.Count; j++) + { + Box boxA = connectedOutputBoxes[j]; + + for (int b = 0; b < boxA.Connections.Count; b++) + { + Box boxB = boxA.Connections[b]; + + // Ensure the other node is selected + if (!connectedNodes.Contains(boxB.ParentNode)) + continue; + + // Node with no outgoing connections reached. Advance to next node in list + if (boxA == null || boxB == null) + continue; + + SurfaceNode nodeB = boxB.ParentNode; + + // Calculate the Y offset needed for nodeB to align boxB's Y to boxA's Y + float boxASurfaceY = boxA.PointToParent(this, Float2.Zero).Y; + float boxBSurfaceY = boxB.PointToParent(this, Float2.Zero).Y; + float deltaY = (boxASurfaceY - boxBSurfaceY) / ViewScale; + Float2 delta = new Float2(0f, deltaY); + + nodeB.Location += delta; + + if (Undo != null) + undoActions.Add(new MoveNodesAction(Context, new[] { nodeB.ID }, delta)); + } + } + } + + Undo?.AddAction(new MultiUndoAction(undoActions, "Straightned ")); + MarkAsEdited(false); + } + + /// + /// Assigns a layer to every node. + /// + /// The exta node data. + /// The end nodes. + /// The number of the maximum layer. private int SetLayers(Dictionary nodeData, List endNodes) { // Longest path layering @@ -201,12 +259,12 @@ namespace FlaxEditor.Surface /// - /// Sets the node offsets + /// Sets the node offsets. /// - /// The exta node data - /// The end nodes - /// The number of the maximum layer - /// The number of the maximum offset + /// The exta node data. + /// The end nodes. + /// The number of the maximum layer. + /// The number of the maximum offset. private int SetOffsets(Dictionary nodeData, List endNodes, int maxLayer) { int maxOffset = 0; @@ -287,10 +345,10 @@ namespace FlaxEditor.Surface /// Align given nodes on a graph using the given alignment type. /// Ignores any potential overlap. /// - /// List of nodes - /// Alignemnt type + /// List of nodes. + /// Alignemnt type. public void AlignNodes(List nodes, NodeAlignmentType alignmentType) - { + { if(nodes.Count <= 1) return; @@ -328,8 +386,8 @@ namespace FlaxEditor.Surface /// /// Distribute the given nodes as equally as possible inside the bounding box, if no fit can be done it will use a default pad of 10 pixels between nodes. /// - /// List of nodes - /// If false will be done horizontally, if true will be done vertically + /// List of nodes. + /// If false will be done horizontally, if true will be done vertically. public void DistributeNodes(List nodes, bool vertically) { if(nodes.Count <= 1) diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 1201318d8..3bdad7eab 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -416,6 +416,7 @@ namespace FlaxEditor.Surface new InputActionsContainer.Binding(options => options.Cut, Cut), new InputActionsContainer.Binding(options => options.Duplicate, Duplicate), new InputActionsContainer.Binding(options => options.NodesAutoFormat, () => { FormatGraph(SelectedNodes); }), + new InputActionsContainer.Binding(options => options.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); }), new InputActionsContainer.Binding(options => options.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); }), new InputActionsContainer.Binding(options => options.NodesAlignMiddle, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Middle); }), new InputActionsContainer.Binding(options => options.NodesAlignBottom, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Bottom); }), From 683a48a6e371f02425581858d7b9abddc5d4465d Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sat, 28 Jun 2025 16:02:44 +0200 Subject: [PATCH 33/82] add default shortcuts to distribute node options --- Source/Editor/Options/InputOptions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 0281d8ce5..af919c1f3 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -686,13 +686,13 @@ namespace FlaxEditor.Options [EditorDisplay("Node Editors"), EditorOrder(4560)] public InputBinding NodesStraightenConnections = new InputBinding(KeyboardKeys.Q, KeyboardKeys.Shift); - [DefaultValue(typeof(InputBinding), "None")] + [DefaultValue(typeof(InputBinding), "Alt+W")] [EditorDisplay("Node Editors"), EditorOrder(4570)] - public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.None); + public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.W, KeyboardKeys.Alt); - [DefaultValue(typeof(InputBinding), "None")] + [DefaultValue(typeof(InputBinding), "Alt+A")] [EditorDisplay("Node Editors"), EditorOrder(4580)] - public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.None); + public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.A, KeyboardKeys.Alt); #endregion } From d7ab497b0e0a2d57c7e5f2bfae0f0a430784764f Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sat, 28 Jun 2025 16:22:29 +0200 Subject: [PATCH 34/82] fix adding empty multi action to undo stack --- Source/Editor/Surface/VisjectSurface.Formatting.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Surface/VisjectSurface.Formatting.cs b/Source/Editor/Surface/VisjectSurface.Formatting.cs index 8b557c357..39ac58242 100644 --- a/Source/Editor/Surface/VisjectSurface.Formatting.cs +++ b/Source/Editor/Surface/VisjectSurface.Formatting.cs @@ -213,7 +213,9 @@ namespace FlaxEditor.Surface } } - Undo?.AddAction(new MultiUndoAction(undoActions, "Straightned ")); + if (undoActions.Count > 0) + Undo?.AddAction(new MultiUndoAction(undoActions, "Straightned ")); + MarkAsEdited(false); } From 75647d149a4c5e57062edc10bd10270b688765c9 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Sat, 28 Jun 2025 19:40:03 +0200 Subject: [PATCH 35/82] make visual script editor method override cm searchable --- .../Windows/Assets/VisualScriptWindow.cs | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 756daf66a..35b2d927d 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Threading; using System.Xml; @@ -471,7 +470,7 @@ namespace FlaxEditor.Windows.Assets private void OnOverrideMethodClicked() { - var cm = new ContextMenu(); + var cm = new ItemsListContextMenu(235); var window = (VisualScriptWindow)Values[0]; var scriptMeta = window.Asset.Meta; var baseType = TypeUtils.GetType(scriptMeta.BaseTypename); @@ -499,27 +498,39 @@ namespace FlaxEditor.Windows.Assets if (isAlreadyAdded) continue; - var cmButton = cm.AddButton($"{name} (in {member.DeclaringType.Name})"); - cmButton.TooltipText = Editor.Instance.CodeDocs.GetTooltip(member); - cmButton.Clicked += () => + var item = new ItemsListContextMenu.Item { - var surface = ((VisualScriptWindow)Values[0]).Surface; - var surfaceBounds = surface.AllNodesBounds; - surface.ShowArea(new Rectangle(surfaceBounds.BottomLeft, new Float2(200, 150)).MakeExpanded(400.0f)); - var node = surface.Context.SpawnNode(16, 3, surfaceBounds.BottomLeft + new Float2(0, 50), new object[] - { - name, - parameters.Length, - Utils.GetEmptyArray() - }); - surface.Select(node); + Name = $"{name} (in {member.DeclaringType.Name})", + TooltipText = Editor.Instance.CodeDocs.GetTooltip(member), + Tag = new object[] { name, parameters.Length, Utils.GetEmptyArray() }, + // Do some basic sorting based on if the method is defined directly in the script base class + SortScore = member.DeclaringType == member.Type.ReflectedType ? 1 : 0, }; + cm.AddItem(item); } } - if (!cm.Items.Any()) + + cm.ItemClicked += (item) => { - cm.AddButton("Nothing to override"); + var surface = ((VisualScriptWindow)Values[0]).Surface; + var surfaceBounds = surface.AllNodesBounds; + surface.ShowArea(new Rectangle(surfaceBounds.BottomLeft, new Float2(200, 150)).MakeExpanded(400.0f)); + var node = surface.Context.SpawnNode(16, 3, surfaceBounds.BottomLeft + new Float2(0, 50), item.Tag as object[]); + surface.Select(node); + }; + + if (cm.ItemsPanel.ChildrenCount == 0) + { + var item = new ItemsListContextMenu.Item + { + Name = "Nothing to override" + }; + item.Enabled = false; + + cm.AddItem(item); } + + cm.SortItems(); cm.Show(_overrideButton, new Float2(0, _overrideButton.Height)); } } From 3e353db1fab0520f200bfaf1fc3ffa2d02bfa872 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 3 Jul 2025 17:46:31 +0300 Subject: [PATCH 36/82] Refactor UnixFileSystem --- .../Engine/Platform/Apple/AppleFileSystem.cpp | 426 ----------------- .../Engine/Platform/Apple/AppleFileSystem.h | 20 +- .../Engine/Platform/Linux/LinuxFileSystem.cpp | 430 ----------------- .../Engine/Platform/Linux/LinuxFileSystem.h | 20 +- .../Engine/Platform/Unix/UnixFileSystem.cpp | 451 ++++++++++++++++++ Source/Engine/Platform/Unix/UnixFileSystem.h | 35 ++ 6 files changed, 491 insertions(+), 891 deletions(-) create mode 100644 Source/Engine/Platform/Unix/UnixFileSystem.cpp create mode 100644 Source/Engine/Platform/Unix/UnixFileSystem.h diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.cpp b/Source/Engine/Platform/Apple/AppleFileSystem.cpp index 6e596bf1b..450507009 100644 --- a/Source/Engine/Platform/Apple/AppleFileSystem.cpp +++ b/Source/Engine/Platform/Apple/AppleFileSystem.cpp @@ -7,10 +7,6 @@ #include "Engine/Platform/File.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" -#include "Engine/Core/Types/TimeSpan.h" -#include "Engine/Core/Collections/Array.h" -#include "Engine/Core/Math/Math.h" -#include "Engine/Core/Log.h" #include "Engine/Utilities/StringConverter.h" #include #include @@ -18,281 +14,9 @@ #include #include #include -#include #include #include -const DateTime UnixEpoch(1970, 1, 1); - -bool AppleFileSystem::CreateDirectory(const StringView& path) -{ - const StringAsANSI<> pathAnsi(*path, path.Length()); - - // Skip if already exists - struct stat fileInfo; - if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode)) - { - return false; - } - - // Recursively do it all again for the parent directory, if any - const int32 slashIndex = path.FindLast('/'); - if (slashIndex > 1) - { - if (CreateDirectory(path.Substring(0, slashIndex))) - { - return true; - } - } - - // Create the last directory on the path (the recursive calls will have taken care of the parent directories by now) - return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST; -} - -bool DeletePathTree(const char* path) -{ - size_t pathLength; - DIR* dir; - struct stat statPath, statEntry; - struct dirent* entry; - - // Stat for the path - stat(path, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - if ((dir = opendir(path)) == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(path); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char full_path[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, path); - strcat(full_path, "/"); - strcat(full_path, entry->d_name); - - // Stat for the entry - stat(full_path, &statEntry); - - // Recursively remove a nested directory - if (S_ISDIR(statEntry.st_mode) != 0) - { - if (DeletePathTree(full_path)) - return true; - continue; - } - - // Remove a file object - if (unlink(full_path) != 0) - return true; - } - - // Remove the devastated directory and close the object of it - if (rmdir(path) != 0) - return true; - - closedir(dir); - - return false; -} - -bool AppleFileSystem::DeleteDirectory(const String& path, bool deleteContents) -{ - const StringAsANSI<> pathANSI(*path, path.Length()); - if (deleteContents) - return DeletePathTree(pathANSI.Get()); - return rmdir(pathANSI.Get()) != 0; -} - -bool AppleFileSystem::DirectoryExists(const StringView& path) -{ - struct stat fileInfo; - const StringAsANSI<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - return S_ISDIR(fileInfo.st_mode); - } - return false; -} - -bool AppleFileSystem::DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option) -{ - const StringAsANSI<> pathANSI(*path, path.Length()); - const StringAsANSI<> searchPatternANSI(searchPattern); - - // Check if use only top directory - if (option == DirectorySearchOption::TopDirectoryOnly) - return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get()); - return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get()); -} - -bool AppleFileSystem::GetChildDirectories(Array& results, const String& path) -{ - size_t pathLength; - DIR* dir; - struct stat statPath, statEntry; - struct dirent* entry; - const StringAsANSI<> pathANSI(*path, path.Length()); - const char* pathStr = pathANSI.Get(); - - // Stat for the path - stat(pathStr, &statPath); - - // If path does not exist or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - if ((dir = opendir(pathStr)) == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(pathStr); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char fullPath[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath)); - strcpy(fullPath, pathStr); - strcat(fullPath, "/"); - strcat(fullPath, entry->d_name); - - // Stat for the entry - stat(fullPath, &statEntry); - - // Check for directory - if (S_ISDIR(statEntry.st_mode) != 0) - { - // Add directory - results.Add(String(fullPath)); - } - } - - closedir(dir); - - return false; -} - -bool AppleFileSystem::FileExists(const StringView& path) -{ - struct stat fileInfo; - const StringAsANSI<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - return S_ISREG(fileInfo.st_mode); - } - return false; -} - -bool AppleFileSystem::DeleteFile(const StringView& path) -{ - const StringAsANSI<> pathANSI(*path, path.Length()); - return unlink(pathANSI.Get()) != 0; -} - -uint64 AppleFileSystem::GetFileSize(const StringView& path) -{ - struct stat fileInfo; - fileInfo.st_size = 0; - const StringAsANSI<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - // Check for directories - if (S_ISDIR(fileInfo.st_mode)) - { - fileInfo.st_size = 0; - } - } - return fileInfo.st_size; -} - -bool AppleFileSystem::IsReadOnly(const StringView& path) -{ - const StringAsANSI<> pathANSI(*path, path.Length()); - if (access(pathANSI.Get(), W_OK) == -1) - { - return errno == EACCES; - } - return false; -} - -bool AppleFileSystem::SetReadOnly(const StringView& path, bool isReadOnly) -{ - const StringAsANSI<> pathANSI(*path, path.Length()); - struct stat fileInfo; - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - if (isReadOnly) - { - fileInfo.st_mode &= ~S_IWUSR; - } - else - { - fileInfo.st_mode |= S_IWUSR; - } - return chmod(pathANSI.Get(), fileInfo.st_mode) == 0; - } - return false; -} - -bool AppleFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite) -{ - if (!overwrite && FileExists(dst)) - { - // Already exists - return true; - } - - if (overwrite) - { - unlink(StringAsANSI<>(*dst, dst.Length()).Get()); - } - if (rename(StringAsANSI<>(*src, src.Length()).Get(), StringAsANSI<>(*dst, dst.Length()).Get()) != 0) - { - if (errno == EXDEV) - { - if (!CopyFile(dst, src)) - { - unlink(StringAsANSI<>(*src, src.Length()).Get()); - return false; - } - } - return true; - } - return false; -} - bool AppleFileSystem::CopyFile(const StringView& dst, const StringView& src) { const StringAsANSI<> srcANSI(*src, src.Length()); @@ -352,156 +76,6 @@ out_error: return true; } -bool AppleFileSystem::getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern) -{ - size_t pathLength; - struct stat statPath, statEntry; - struct dirent* entry; - - // Stat for the path - stat(path, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - DIR* dir = opendir(path); - if (dir == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(path); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char fullPath[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath)); - strcpy(fullPath, path); - strcat(fullPath, "/"); - strcat(fullPath, entry->d_name); - - // Stat for the entry - stat(fullPath, &statEntry); - - // Check for file - if (S_ISREG(statEntry.st_mode) != 0) - { - // Validate with filter - const int32 fullPathLength = StringUtils::Length(fullPath); - const int32 searchPatternLength = StringUtils::Length(searchPattern); - if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0) - { - // All files - } - else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0) - { - // Path ending - } - else - { - // TODO: implement all cases in a generic way - continue; - } - - // Add file - results.Add(String(fullPath)); - } - } - - closedir(dir); - - return false; -} - -bool AppleFileSystem::getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern) -{ - // Find all files in this directory - getFilesFromDirectoryTop(results, path, searchPattern); - - size_t pathLength; - DIR* dir; - struct stat statPath, statEntry; - struct dirent* entry; - - // Stat for the path - stat(path, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - if ((dir = opendir(path)) == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(path); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char full_path[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, path); - strcat(full_path, "/"); - strcat(full_path, entry->d_name); - - // Stat for the entry - stat(full_path, &statEntry); - - // Check for directory - if (S_ISDIR(statEntry.st_mode) != 0) - { - if (getFilesFromDirectoryAll(results, full_path, searchPattern)) - { - closedir(dir); - return true; - } - } - } - - closedir(dir); - - return false; -} - -DateTime AppleFileSystem::GetFileLastEditTime(const StringView& path) -{ - struct stat fileInfo; - const StringAsANSI<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) == -1) - { - return DateTime::MinValue(); - } - - const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime); - return UnixEpoch + timeSinceEpoch; -} - void AppleFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result) { String home; diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.h b/Source/Engine/Platform/Apple/AppleFileSystem.h index 5fa6ad1b7..45d9e4df6 100644 --- a/Source/Engine/Platform/Apple/AppleFileSystem.h +++ b/Source/Engine/Platform/Apple/AppleFileSystem.h @@ -4,33 +4,17 @@ #if PLATFORM_MAC || PLATFORM_IOS -#include "Engine/Platform/Base/FileSystemBase.h" +#include "Engine/Platform/Unix/UnixFileSystem.h" /// /// Apple platform implementation of filesystem service. /// -class FLAXENGINE_API AppleFileSystem : public FileSystemBase +class FLAXENGINE_API AppleFileSystem : public UnixFileSystem { public: // [FileSystemBase] - static bool CreateDirectory(const StringView& path); - static bool DeleteDirectory(const String& path, bool deleteContents = true); - static bool DirectoryExists(const StringView& path); - static bool DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories); - static bool GetChildDirectories(Array& results, const String& path); - static bool FileExists(const StringView& path); - static bool DeleteFile(const StringView& path); - static uint64 GetFileSize(const StringView& path); - static bool IsReadOnly(const StringView& path); - static bool SetReadOnly(const StringView& path, bool isReadOnly); - static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false); static bool CopyFile(const StringView& dst, const StringView& src); - static DateTime GetFileLastEditTime(const StringView& path); static void GetSpecialFolderPath(const SpecialFolder type, String& result); - -private: - static bool getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern); - static bool getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern); }; #endif diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index 3e2055572..9b31dc28f 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -4,22 +4,16 @@ #include "LinuxFileSystem.h" #include "Engine/Platform/File.h" -#include "Engine/Platform/StringUtils.h" -#include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringBuilder.h" -#include "Engine/Core/Types/StringView.h" -#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Log.h" #include "Engine/Utilities/StringConverter.h" -#include #include #include #include #include #include -#include #include #include #include @@ -165,280 +159,6 @@ bool LinuxFileSystem::ShowFileExplorer(const StringView& path) return false; } -bool LinuxFileSystem::CreateDirectory(const StringView& path) -{ - const StringAsUTF8<> pathAnsi(*path, path.Length()); - - // Skip if already exists - struct stat fileInfo; - if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode)) - { - return false; - } - - // Recursively do it all again for the parent directory, if any - const int32 slashIndex = path.FindLast('/'); - if (slashIndex > 1) - { - if (CreateDirectory(path.Substring(0, slashIndex))) - { - return true; - } - } - - // Create the last directory on the path (the recursive calls will have taken care of the parent directories by now) - return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST; -} - -bool DeleteUnixPathTree(const char* path) -{ - size_t pathLength; - DIR* dir; - struct stat statPath, statEntry; - struct dirent* entry; - - // Stat for the path - stat(path, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - if ((dir = opendir(path)) == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(path); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char full_path[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, path); - strcat(full_path, "/"); - strcat(full_path, entry->d_name); - - // Stat for the entry - stat(full_path, &statEntry); - - // Recursively remove a nested directory - if (S_ISDIR(statEntry.st_mode) != 0) - { - if (DeleteUnixPathTree(full_path)) - return true; - continue; - } - - // Remove a file object - if (unlink(full_path) != 0) - return true; - } - - // Remove the devastated directory and close the object of it - if (rmdir(path) != 0) - return true; - - closedir(dir); - - return false; -} - -bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents) -{ - const StringAsUTF8<> pathANSI(*path, path.Length()); - if (deleteContents) - { - return DeleteUnixPathTree(pathANSI.Get()); - } - else - { - return rmdir(pathANSI.Get()) != 0; - } -} - -bool LinuxFileSystem::DirectoryExists(const StringView& path) -{ - struct stat fileInfo; - const StringAsUTF8<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - return S_ISDIR(fileInfo.st_mode); - } - return false; -} - -bool LinuxFileSystem::DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option) -{ - const StringAsUTF8<> pathANSI(*path, path.Length()); - const StringAsUTF8<> searchPatternANSI(searchPattern); - - // Check if use only top directory - if (option == DirectorySearchOption::TopDirectoryOnly) - return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get()); - return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get()); -} - -bool LinuxFileSystem::GetChildDirectories(Array& results, const String& path) -{ - size_t pathLength; - DIR* dir; - struct stat statPath, statEntry; - struct dirent* entry; - const StringAsUTF8<> pathANSI(*path, path.Length()); - const char* pathStr = pathANSI.Get(); - - // Stat for the path - stat(pathStr, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - if ((dir = opendir(pathStr)) == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(pathStr); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char fullPath[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath)); - strcpy(fullPath, pathStr); - strcat(fullPath, "/"); - strcat(fullPath, entry->d_name); - - // Stat for the entry - stat(fullPath, &statEntry); - - // Check for directory - if (S_ISDIR(statEntry.st_mode) != 0) - { - // Add directory - results.Add(String(fullPath)); - } - } - - closedir(dir); - - return false; -} - -bool LinuxFileSystem::FileExists(const StringView& path) -{ - struct stat fileInfo; - const StringAsUTF8<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - return S_ISREG(fileInfo.st_mode); - } - return false; -} - -bool LinuxFileSystem::DeleteFile(const StringView& path) -{ - const StringAsUTF8<> pathANSI(*path, path.Length()); - return unlink(pathANSI.Get()) != 0; -} - -uint64 LinuxFileSystem::GetFileSize(const StringView& path) -{ - struct stat fileInfo; - fileInfo.st_size = 0; - const StringAsUTF8<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - // Check for directories - if (S_ISDIR(fileInfo.st_mode)) - { - fileInfo.st_size = 0; - } - } - return fileInfo.st_size; -} - -bool LinuxFileSystem::IsReadOnly(const StringView& path) -{ - const StringAsUTF8<> pathANSI(*path, path.Length()); - if (access(pathANSI.Get(), W_OK) == -1) - { - return errno == EACCES; - } - return false; -} - -bool LinuxFileSystem::SetReadOnly(const StringView& path, bool isReadOnly) -{ - const StringAsUTF8<> pathANSI(*path, path.Length()); - struct stat fileInfo; - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - if (isReadOnly) - { - fileInfo.st_mode &= ~S_IWUSR; - } - else - { - fileInfo.st_mode |= S_IWUSR; - } - return chmod(pathANSI.Get(), fileInfo.st_mode) == 0; - } - return false; -} - -bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite) -{ - if (!overwrite && FileExists(dst)) - { - // Already exists - return true; - } - - if (overwrite) - { - unlink(StringAsUTF8<>(*dst, dst.Length()).Get()); - } - if (rename(StringAsUTF8<>(*src, src.Length()).Get(), StringAsUTF8<>(*dst, dst.Length()).Get()) != 0) - { - if (errno == EXDEV) - { - if (!CopyFile(dst, src)) - { - unlink(StringAsUTF8<>(*src, src.Length()).Get()); - return false; - } - } - return true; - } - return false; -} - bool LinuxFileSystem::CopyFile(const StringView& dst, const StringView& src) { const StringAsUTF8<> srcANSI(*src, src.Length()); @@ -612,156 +332,6 @@ bool LinuxFileSystem::UrnEncodePath(const char *path, char *result, const int ma return true; } -bool LinuxFileSystem::getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern) -{ - size_t pathLength; - struct stat statPath, statEntry; - struct dirent* entry; - - // Stat for the path - stat(path, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - DIR* dir = opendir(path); - if (dir == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(path); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char fullPath[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath)); - strcpy(fullPath, path); - strcat(fullPath, "/"); - strcat(fullPath, entry->d_name); - - // Stat for the entry - stat(fullPath, &statEntry); - - // Check for file - if (S_ISREG(statEntry.st_mode) != 0) - { - // Validate with filter - const int32 fullPathLength = StringUtils::Length(fullPath); - const int32 searchPatternLength = StringUtils::Length(searchPattern); - if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0) - { - // All files - } - else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0) - { - // Path ending - } - else - { - // TODO: implement all cases in a generic way - continue; - } - - // Add file - results.Add(String(fullPath)); - } - } - - closedir(dir); - - return false; -} - -bool LinuxFileSystem::getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern) -{ - // Find all files in this directory - getFilesFromDirectoryTop(results, path, searchPattern); - - size_t pathLength; - DIR* dir; - struct stat statPath, statEntry; - struct dirent* entry; - - // Stat for the path - stat(path, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - if ((dir = opendir(path)) == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(path); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char full_path[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, path); - strcat(full_path, "/"); - strcat(full_path, entry->d_name); - - // Stat for the entry - stat(full_path, &statEntry); - - // Check for directory - if (S_ISDIR(statEntry.st_mode) != 0) - { - if (getFilesFromDirectoryAll(results, full_path, searchPattern)) - { - closedir(dir); - return true; - } - } - } - - closedir(dir); - - return false; -} - -DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path) -{ - struct stat fileInfo; - const StringAsUTF8<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) == -1) - { - return DateTime::MinValue(); - } - - const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime); - return UnixEpoch + timeSinceEpoch; -} - void LinuxFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result) { const String& home = LinuxPlatform::GetHomeDirectory(); diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.h b/Source/Engine/Platform/Linux/LinuxFileSystem.h index f77c3dd4f..377bbbd99 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.h +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.h @@ -4,38 +4,24 @@ #if PLATFORM_LINUX -#include "Engine/Platform/Base/FileSystemBase.h" +#include "Engine/Platform/Unix/UnixFileSystem.h" /// /// Linux platform implementation of filesystem service. /// -class FLAXENGINE_API LinuxFileSystem : public FileSystemBase +class FLAXENGINE_API LinuxFileSystem : public UnixFileSystem { public: // [FileSystemBase] static bool ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array& filenames); static bool ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path); static bool ShowFileExplorer(const StringView& path); - static bool CreateDirectory(const StringView& path); - static bool DeleteDirectory(const String& path, bool deleteContents = true); - static bool DirectoryExists(const StringView& path); - static bool DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories); - static bool GetChildDirectories(Array& results, const String& path); - static bool FileExists(const StringView& path); - static bool DeleteFile(const StringView& path); - static bool MoveFileToRecycleBin(const StringView& path); - static uint64 GetFileSize(const StringView& path); - static bool IsReadOnly(const StringView& path); - static bool SetReadOnly(const StringView& path, bool isReadOnly); - static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false); static bool CopyFile(const StringView& dst, const StringView& src); - static DateTime GetFileLastEditTime(const StringView& path); + static bool MoveFileToRecycleBin(const StringView& path); static void GetSpecialFolderPath(const SpecialFolder type, String& result); private: static bool UrnEncodePath(const char *path, char *result, int maxLength); - static bool getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern); - static bool getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern); static String getBaseName(const StringView& path); static String getNameWithoutExtension(const StringView& path); }; diff --git a/Source/Engine/Platform/Unix/UnixFileSystem.cpp b/Source/Engine/Platform/Unix/UnixFileSystem.cpp new file mode 100644 index 000000000..ceca4cb28 --- /dev/null +++ b/Source/Engine/Platform/Unix/UnixFileSystem.cpp @@ -0,0 +1,451 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#if PLATFORM_UNIX + +#include "UnixFileSystem.h" +#include "Engine/Platform/File.h" +#include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Math/Math.h" +#include "Engine/Core/Log.h" +#include "Engine/Utilities/StringConverter.h" +#include +#include +#include +#include +#include + +#if PLATFORM_MAC || PLATFORM_IOS +typedef StringAsANSI<> UnixString; +#else +typedef StringAsUTF8<> UnixString; +#endif + +const DateTime UnixEpoch(1970, 1, 1); + +bool UnixFileSystem::CreateDirectory(const StringView& path) +{ + const UnixString pathAnsi(*path, path.Length()); + + // Skip if already exists + struct stat fileInfo; + if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode)) + { + return false; + } + + // Recursively do it all again for the parent directory, if any + const int32 slashIndex = path.FindLast('/'); + if (slashIndex > 1) + { + if (CreateDirectory(path.Substring(0, slashIndex))) + { + return true; + } + } + + // Create the last directory on the path (the recursive calls will have taken care of the parent directories by now) + return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST; +} + +bool DeleteUnixPathTree(const char* path) +{ + size_t pathLength; + DIR* dir; + struct stat statPath, statEntry; + struct dirent* entry; + + // Stat for the path + stat(path, &statPath); + + // If path does not exists or is not dir - exit with status -1 + if (S_ISDIR(statPath.st_mode) == 0) + { + // Is not directory + return true; + } + + // If not possible to read the directory for this user + if ((dir = opendir(path)) == NULL) + { + // Cannot open directory + return true; + } + + // The length of the path + pathLength = strlen(path); + + // Iteration through entries in the directory + while ((entry = readdir(dir)) != NULL) + { + // Skip entries "." and ".." + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + + // Determinate a full path of an entry + char full_path[256]; + ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); + strcpy(full_path, path); + strcat(full_path, "/"); + strcat(full_path, entry->d_name); + + // Stat for the entry + stat(full_path, &statEntry); + + // Recursively remove a nested directory + if (S_ISDIR(statEntry.st_mode) != 0) + { + if (DeleteUnixPathTree(full_path)) + return true; + continue; + } + + // Remove a file object + if (unlink(full_path) != 0) + return true; + } + + // Remove the devastated directory and close the object of it + if (rmdir(path) != 0) + return true; + + closedir(dir); + + return false; +} + +bool UnixFileSystem::DeleteDirectory(const String& path, bool deleteContents) +{ + const UnixString pathANSI(*path, path.Length()); + if (deleteContents) + { + return DeleteUnixPathTree(pathANSI.Get()); + } + else + { + return rmdir(pathANSI.Get()) != 0; + } +} + +bool UnixFileSystem::DirectoryExists(const StringView& path) +{ + struct stat fileInfo; + const UnixString pathANSI(*path, path.Length()); + if (stat(pathANSI.Get(), &fileInfo) != -1) + { + return S_ISDIR(fileInfo.st_mode); + } + return false; +} + +bool UnixFileSystem::DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option) +{ + const UnixString pathANSI(*path, path.Length()); + const UnixString searchPatternANSI(searchPattern); + + // Check if use only top directory + if (option == DirectorySearchOption::TopDirectoryOnly) + return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get()); + return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get()); +} + +bool UnixFileSystem::GetChildDirectories(Array& results, const String& path) +{ + size_t pathLength; + DIR* dir; + struct stat statPath, statEntry; + struct dirent* entry; + const UnixString pathANSI(*path, path.Length()); + const char* pathStr = pathANSI.Get(); + + // Stat for the path + stat(pathStr, &statPath); + + // If path does not exists or is not dir - exit with status -1 + if (S_ISDIR(statPath.st_mode) == 0) + { + // Is not directory + return true; + } + + // If not possible to read the directory for this user + if ((dir = opendir(pathStr)) == NULL) + { + // Cannot open directory + return true; + } + + // The length of the path + pathLength = strlen(pathStr); + + // Iteration through entries in the directory + while ((entry = readdir(dir)) != NULL) + { + // Skip entries "." and ".." + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + + // Determinate a full path of an entry + char fullPath[256]; + ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath)); + strcpy(fullPath, pathStr); + strcat(fullPath, "/"); + strcat(fullPath, entry->d_name); + + // Stat for the entry + stat(fullPath, &statEntry); + + // Check for directory + if (S_ISDIR(statEntry.st_mode) != 0) + { + // Add directory + results.Add(String(fullPath)); + } + } + + closedir(dir); + + return false; +} + +bool UnixFileSystem::FileExists(const StringView& path) +{ + struct stat fileInfo; + const UnixString pathANSI(*path, path.Length()); + if (stat(pathANSI.Get(), &fileInfo) != -1) + { + return S_ISREG(fileInfo.st_mode); + } + return false; +} + +bool UnixFileSystem::DeleteFile(const StringView& path) +{ + const UnixString pathANSI(*path, path.Length()); + return unlink(pathANSI.Get()) != 0; +} + +uint64 UnixFileSystem::GetFileSize(const StringView& path) +{ + struct stat fileInfo; + fileInfo.st_size = 0; + const UnixString pathANSI(*path, path.Length()); + if (stat(pathANSI.Get(), &fileInfo) != -1) + { + // Check for directories + if (S_ISDIR(fileInfo.st_mode)) + { + fileInfo.st_size = 0; + } + } + return fileInfo.st_size; +} + +bool UnixFileSystem::IsReadOnly(const StringView& path) +{ + const UnixString pathANSI(*path, path.Length()); + if (access(pathANSI.Get(), W_OK) == -1) + { + return errno == EACCES; + } + return false; +} + +bool UnixFileSystem::SetReadOnly(const StringView& path, bool isReadOnly) +{ + const UnixString pathANSI(*path, path.Length()); + struct stat fileInfo; + if (stat(pathANSI.Get(), &fileInfo) != -1) + { + if (isReadOnly) + { + fileInfo.st_mode &= ~S_IWUSR; + } + else + { + fileInfo.st_mode |= S_IWUSR; + } + return chmod(pathANSI.Get(), fileInfo.st_mode) == 0; + } + return false; +} + +bool UnixFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite) +{ + if (!overwrite && FileExists(dst)) + { + // Already exists + return true; + } + + if (overwrite) + { + unlink(UnixString(*dst, dst.Length()).Get()); + } + if (rename(UnixString(*src, src.Length()).Get(), UnixString(*dst, dst.Length()).Get()) != 0) + { + if (errno == EXDEV) + { + if (!CopyFile(dst, src)) + { + unlink(UnixString(*src, src.Length()).Get()); + return false; + } + } + return true; + } + return false; +} + +bool UnixFileSystem::getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern) +{ + size_t pathLength; + struct stat statPath, statEntry; + struct dirent* entry; + + // Stat for the path + stat(path, &statPath); + + // If path does not exists or is not dir - exit with status -1 + if (S_ISDIR(statPath.st_mode) == 0) + { + // Is not directory + return true; + } + + // If not possible to read the directory for this user + DIR* dir = opendir(path); + if (dir == NULL) + { + // Cannot open directory + return true; + } + + // The length of the path + pathLength = strlen(path); + + // Iteration through entries in the directory + while ((entry = readdir(dir)) != NULL) + { + // Skip entries "." and ".." + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + + // Determinate a full path of an entry + char fullPath[256]; + const int32 pathLength = strlen(entry->d_name); + ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath)); + strcpy(fullPath, path); + strcat(fullPath, "/"); + strcat(fullPath, entry->d_name); + + // Stat for the entry + stat(fullPath, &statEntry); + + // Check for file + if (S_ISREG(statEntry.st_mode) != 0) + { + // Validate with filter + const int32 fullPathLength = StringUtils::Length(fullPath); + const int32 searchPatternLength = StringUtils::Length(searchPattern); + if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0) + { + // All files + } + else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0) + { + // Path ending + } + else + { + // TODO: implement all cases in a generic way + continue; + } + + // Add file + results.Add(String(fullPath)); + } + } + + closedir(dir); + + return false; +} + +bool UnixFileSystem::getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern) +{ + // Find all files in this directory + getFilesFromDirectoryTop(results, path, searchPattern); + + size_t pathLength; + DIR* dir; + struct stat statPath, statEntry; + struct dirent* entry; + + // Stat for the path + stat(path, &statPath); + + // If path does not exists or is not dir - exit with status -1 + if (S_ISDIR(statPath.st_mode) == 0) + { + // Is not directory + return true; + } + + // If not possible to read the directory for this user + if ((dir = opendir(path)) == NULL) + { + // Cannot open directory + return true; + } + + // The length of the path + pathLength = strlen(path); + + // Iteration through entries in the directory + while ((entry = readdir(dir)) != NULL) + { + // Skip entries "." and ".." + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + + // Determinate a full path of an entry + char full_path[256]; + ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); + strcpy(full_path, path); + strcat(full_path, "/"); + strcat(full_path, entry->d_name); + + // Stat for the entry + stat(full_path, &statEntry); + + // Check for directory + if (S_ISDIR(statEntry.st_mode) != 0) + { + if (getFilesFromDirectoryAll(results, full_path, searchPattern)) + { + closedir(dir); + return true; + } + } + } + + closedir(dir); + + return false; +} + +DateTime UnixFileSystem::GetFileLastEditTime(const StringView& path) +{ + struct stat fileInfo; + const UnixString pathANSI(*path, path.Length()); + if (stat(pathANSI.Get(), &fileInfo) == -1) + { + return DateTime::MinValue(); + } + + const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime); + return UnixEpoch + timeSinceEpoch; +} + +#endif diff --git a/Source/Engine/Platform/Unix/UnixFileSystem.h b/Source/Engine/Platform/Unix/UnixFileSystem.h new file mode 100644 index 000000000..4c7256aca --- /dev/null +++ b/Source/Engine/Platform/Unix/UnixFileSystem.h @@ -0,0 +1,35 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_UNIX + +#include "Engine/Platform/Base/FileSystemBase.h" + +/// +/// Unix platform implementation of filesystem service. +/// +class FLAXENGINE_API UnixFileSystem : public FileSystemBase +{ +public: + // [FileSystemBase] + static bool CreateDirectory(const StringView& path); + static bool DeleteDirectory(const String& path, bool deleteContents = true); + static bool DirectoryExists(const StringView& path); + static bool DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories); + static bool GetChildDirectories(Array& results, const String& path); + static bool FileExists(const StringView& path); + static bool DeleteFile(const StringView& path); + static bool MoveFileToRecycleBin(const StringView& path); + static uint64 GetFileSize(const StringView& path); + static bool IsReadOnly(const StringView& path); + static bool SetReadOnly(const StringView& path, bool isReadOnly); + static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false); + static DateTime GetFileLastEditTime(const StringView& path); + +private: + static bool getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern); + static bool getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern); +}; + +#endif From a93a9406308147db6e1f3705083bad1afd587f82 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 3 Jul 2025 17:59:20 +0300 Subject: [PATCH 37/82] Fix hot-reload files not getting cleaned up during startup Implements minimal required filter support for `FileSystem::DirectoryGetFiles` in order to support removing hot-reload files on Linux/Apple systems. --- .../Engine/Platform/Unix/UnixFileSystem.cpp | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Unix/UnixFileSystem.cpp b/Source/Engine/Platform/Unix/UnixFileSystem.cpp index ceca4cb28..1fb47f0f5 100644 --- a/Source/Engine/Platform/Unix/UnixFileSystem.cpp +++ b/Source/Engine/Platform/Unix/UnixFileSystem.cpp @@ -348,7 +348,9 @@ bool UnixFileSystem::getFilesFromDirectoryTop(Array& results, const char // Validate with filter const int32 fullPathLength = StringUtils::Length(fullPath); const int32 searchPatternLength = StringUtils::Length(searchPattern); - if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0) + if (searchPatternLength == 0 || + StringUtils::Compare(searchPattern, "*") == 0 || + StringUtils::Compare(searchPattern, "*.*") == 0) { // All files } @@ -356,9 +358,26 @@ bool UnixFileSystem::getFilesFromDirectoryTop(Array& results, const char { // Path ending } + else if (searchPattern[0] == '*' && searchPatternLength > 2 && searchPattern[searchPatternLength-1] == '*') + { + // Contains pattern + bool match = false; + for (int32 i = 0; i < pathLength - searchPatternLength - 1; i++) + { + int32 len = Math::Min(searchPatternLength - 2, pathLength - i); + if (StringUtils::Compare(&entry->d_name[i], &searchPattern[1], len) == 0) + { + match = true; + break; + } + } + if (!match) + continue; + } else { // TODO: implement all cases in a generic way + LOG(Warning, "DirectoryGetFiles: Wildcard filter is not implemented"); continue; } From 3981d5090cca4ecfc196acd8238613cddc3156c8 Mon Sep 17 00:00:00 2001 From: MrCapy0 <167662176+MrCapy0@users.noreply.github.com> Date: Sat, 5 Jul 2025 10:27:45 -0400 Subject: [PATCH 38/82] change color gradiant node 8 to 11 stops --- Source/Editor/Surface/Archetypes/Tools.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index 1debf5b60..0fb92cdb2 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -199,7 +199,7 @@ namespace FlaxEditor.Surface.Archetypes private Label _labelValue; private FloatValueBox _timeValue; private ColorValueBox _colorValue; - private const int MaxStops = 8; + private const int MaxStops = 12; /// public ColorGradientNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) @@ -1506,7 +1506,11 @@ namespace FlaxEditor.Surface.Archetypes 0.95f, Color.White, - // Empty stops 2-7 + // Empty stops 2-11 + 0.0f, Color.Black, + 0.0f, Color.Black, + 0.0f, Color.Black, + 0.0f, Color.Black, 0.0f, Color.Black, 0.0f, Color.Black, 0.0f, Color.Black, From 7418d60f24a605dabdb64418c140f8a2350f5b1c Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 5 Jul 2025 18:09:30 -0500 Subject: [PATCH 39/82] Add editor option for build configuration when using cook and run. --- Source/Editor/Options/GeneralOptions.cs | 6 ++++++ Source/Editor/Windows/GameCookerWindow.cs | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs index 59fac7a63..23dc17733 100644 --- a/Source/Editor/Options/GeneralOptions.cs +++ b/Source/Editor/Options/GeneralOptions.cs @@ -127,6 +127,12 @@ namespace FlaxEditor.Options BuildAction.NavMesh, }; + /// + /// Gets or sets the build configuration to use when using Cook and Run option in the editor. + /// + [EditorDisplay("General"), EditorOrder(201), ExpandGroups, Tooltip("The build configuration to use when using Cook and Run option in the editor.")] + public BuildConfiguration CookAndRunBuildConfiguration { get; set; } = BuildConfiguration.Development; + /// /// Gets or sets a value indicating whether perform automatic scripts reload on main window focus. /// diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 54aec19ab..cb0ab4c7a 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -976,6 +976,7 @@ namespace FlaxEditor.Windows Editor.Log("Building and running"); GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration); var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch; + var buildConfig = Editor.Options.Options.General.CookAndRunBuildConfiguration; for (int i = 0; i < numberOfClients; i++) { var buildOptions = BuildOptions.AutoRun; @@ -988,7 +989,7 @@ namespace FlaxEditor.Windows { Output = _buildTabProxy.PerPlatformOptions[platform].Output, Platform = buildPlatform, - Mode = buildConfiguration, + Mode = buildConfig, }, Options = buildOptions, }); @@ -1003,6 +1004,7 @@ namespace FlaxEditor.Windows Editor.Log("Running cooked build"); GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration); var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch; + var buildConfig = Editor.Options.Options.General.CookAndRunBuildConfiguration; for (int i = 0; i < numberOfClients; i++) { _buildingQueue.Enqueue(new QueueItem @@ -1011,7 +1013,7 @@ namespace FlaxEditor.Windows { Output = _buildTabProxy.PerPlatformOptions[platform].Output, Platform = buildPlatform, - Mode = buildConfiguration, + Mode = buildConfig, }, Options = BuildOptions.AutoRun | BuildOptions.NoCook, }); From b3f88e156c7fc702498703ed7d9da8bc25ccafd0 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 5 Jul 2025 18:11:26 -0500 Subject: [PATCH 40/82] Small change to out variable that is not used anymore. --- Source/Editor/Windows/GameCookerWindow.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index cb0ab4c7a..eb65fd62b 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -974,7 +974,7 @@ namespace FlaxEditor.Windows public void BuildAndRun() { Editor.Log("Building and running"); - GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration); + GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out _); var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch; var buildConfig = Editor.Options.Options.General.CookAndRunBuildConfiguration; for (int i = 0; i < numberOfClients; i++) @@ -1002,7 +1002,7 @@ namespace FlaxEditor.Windows public void RunCooked() { Editor.Log("Running cooked build"); - GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration); + GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out _); var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch; var buildConfig = Editor.Options.Options.General.CookAndRunBuildConfiguration; for (int i = 0; i < numberOfClients; i++) From d698bf96cca0f156a4d152bac1e5f618567d674c Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 5 Jul 2025 19:36:45 -0500 Subject: [PATCH 41/82] Apply particle effect parameter overrides when emitter is changed and when activated in the tree. --- Source/Engine/Particles/ParticleEffect.cpp | 14 ++++++++++++++ Source/Engine/Particles/ParticleEffect.h | 1 + 2 files changed, 15 insertions(+) diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index c1031f4ac..de4abb53d 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -546,6 +546,16 @@ void ParticleEffect::OnParticleSystemModified() } void ParticleEffect::OnParticleSystemLoaded() +{ + ApplyModifiedParameters(); + auto& emitters = ParticleSystem.Get()->Emitters; + for (auto& emitter : emitters) + { + emitter.Loaded.BindUnique(this); + } +} + +void ParticleEffect::OnParticleEmitterLoaded() { ApplyModifiedParameters(); } @@ -814,6 +824,10 @@ void ParticleEffect::OnActiveInTreeChanged() CacheModifiedParameters(); Instance.ClearState(); } + else + { + ApplyModifiedParameters(); + } } void ParticleEffect::OnTransformChanged() diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index 9be31c4c7..2727ac794 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -387,6 +387,7 @@ private: void ApplyModifiedParameters(); void OnParticleSystemModified(); void OnParticleSystemLoaded(); + void OnParticleEmitterLoaded(); public: // [Actor] From 50871d8885084e08f1886849a85a1308ecc0803a Mon Sep 17 00:00:00 2001 From: Saas Date: Wed, 9 Jul 2025 18:41:41 +0200 Subject: [PATCH 42/82] simplify and fix edge case in item list scrolling while holding control --- Source/Editor/GUI/ItemsListContextMenu.cs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 6cb5a84fb..9049eb9b4 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -514,19 +514,14 @@ namespace FlaxEditor.GUI var items = ItemsPanel.Children; for (int i = 0; i < items.Count; i++) { - if (items[i] is Item item && item.Visible) - result.Add(item); - } - if (_categoryPanels != null) - { - for (int i = 0; i < _categoryPanels.Count; i++) + var item = items[i]; + if (item is Item item1 && item1.Visible) + result.Add(item1); + else if (!ignoreFoldedCategories && item is DropPanel panel && item.Visible) { - var category = _categoryPanels[i]; - if (!category.Visible || (ignoreFoldedCategories && category is DropPanel panel && panel.IsClosed)) - continue; - for (int j = 0; j < category.Children.Count; j++) + for (int j = 0; j < panel.Children.Count; j++) { - if (category.Children[j] is Item item2 && item2.Visible) + if (panel.Children[j] is Item item2 && item2.Visible) result.Add(item2); } } @@ -591,10 +586,6 @@ namespace FlaxEditor.GUI var items = GetVisibleItems(!controlDown); var focusedIndex = items.IndexOf(focusedItem); - // If the user hasn't selected anything yet and is holding control, focus first folded item - if (focusedIndex == -1 && controlDown) - focusedIndex = GetVisibleItems(true).Count - 1; - int delta = key == KeyboardKeys.ArrowDown ? -1 : 1; int nextIndex = Mathf.Wrap(focusedIndex - delta, 0, items.Count - 1); var nextItem = items[nextIndex]; From 33b540ed9e8df3957cb8b9b3a0d444baffac3acf Mon Sep 17 00:00:00 2001 From: Saas Date: Thu, 10 Jul 2025 20:07:52 +0200 Subject: [PATCH 43/82] fix naming and treat unfolded category items as normal items --- Source/Editor/GUI/ItemsListContextMenu.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 9049eb9b4..8fdf21e4c 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -514,15 +514,15 @@ namespace FlaxEditor.GUI var items = ItemsPanel.Children; for (int i = 0; i < items.Count; i++) { - var item = items[i]; - if (item is Item item1 && item1.Visible) - result.Add(item1); - else if (!ignoreFoldedCategories && item is DropPanel panel && item.Visible) + var currentItem = items[i]; + if (currentItem is Item item && item.Visible) + result.Add(item); + else if (currentItem is DropPanel category && (!ignoreFoldedCategories || !category.IsClosed) && currentItem.Visible) { - for (int j = 0; j < panel.Children.Count; j++) + for (int j = 0; j < category.Children.Count; j++) { - if (panel.Children[j] is Item item2 && item2.Visible) - result.Add(item2); + if (category.Children[j] is Item categoryItem && categoryItem.Visible) + result.Add(categoryItem); } } } From 2546e19d6588238fc959a3f7f77dfca349626678 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 17 Jul 2025 23:07:06 -0500 Subject: [PATCH 44/82] Add shift selection for tree nodes --- Source/Editor/GUI/Tree/Tree.cs | 71 ++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index 8df26c211..fdedf0203 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -41,6 +41,7 @@ namespace FlaxEditor.GUI.Tree private Margin _margin; private bool _autoSize = true; private bool _deferLayoutUpdate = false; + private TreeNode _lastSelectedNode; /// /// The TreeNode that is being dragged over. This could have a value when not dragging. @@ -383,20 +384,27 @@ namespace FlaxEditor.GUI.Tree AfterDeferredLayout?.Invoke(); _deferLayoutUpdate = false; } + var window = Root; + bool shiftDown = window.GetKey(KeyboardKeys.Shift); + bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp); + bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown); + + // Use last selection for last selected node if sift is down + if (shiftDown) + _lastSelectedNode ??= Selection[^1]; + + var node = _lastSelectedNode ?? SelectedNode; - var node = SelectedNode; + if (Selection.Count == 0) + _lastSelectedNode = null; // Check if has focus and if any node is focused and it isn't a root if (ContainsFocus && node != null && node.AutoFocus) { - var window = Root; if (window.GetKeyDown(KeyboardKeys.ArrowUp) || window.GetKeyDown(KeyboardKeys.ArrowDown)) _keyUpdateTime = KeyUpdateTimeout; if (_keyUpdateTime >= KeyUpdateTimeout && window is WindowRootControl windowRoot && windowRoot.Window.IsFocused) { - bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp); - bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown); - // Check if arrow flags are different if (keyDownArrow != keyUpArrow) { @@ -406,24 +414,39 @@ namespace FlaxEditor.GUI.Tree Assert.AreNotEqual(-1, myIndex); // Up - TreeNode toSelect = null; + List toSelect = new List(); + if (shiftDown) + { + toSelect.AddRange(Selection); + } + //TreeNode toSelect = null; if (keyUpArrow) { if (myIndex == 0) { // Select parent - toSelect = parentNode; + if (toSelect.Contains(parentNode)) + toSelect.Remove(node); + else + toSelect.Add(parentNode); + _lastSelectedNode = parentNode; } else { // Select previous parent child - toSelect = nodeParent.GetChild(myIndex - 1) as TreeNode; + var select = nodeParent.GetChild(myIndex - 1) as TreeNode; // Select last child if is valid and expanded and has any children - if (toSelect != null && toSelect.IsExpanded && toSelect.HasAnyVisibleChild) + if (select != null && select.IsExpanded && select.HasAnyVisibleChild) { - toSelect = toSelect.GetChild(toSelect.ChildrenCount - 1) as TreeNode; + select = select.GetChild(select.ChildrenCount - 1) as TreeNode; } + + if (toSelect.Contains(select)) + toSelect.Remove(node); + else + toSelect.Add(select); + _lastSelectedNode = select; } } // Down @@ -432,32 +455,48 @@ namespace FlaxEditor.GUI.Tree if (node.IsExpanded && node.HasAnyVisibleChild) { // Select the first child - toSelect = node.GetChild(0) as TreeNode; + var select = node.GetChild(0) as TreeNode; + if (toSelect.Contains(select)) + toSelect.Remove(node); + else + toSelect.Add(select); + _lastSelectedNode = select; } else if (myIndex == nodeParent.ChildrenCount - 1) { // Select next node after parent - while (parentNode != null && toSelect == null) + TreeNode select = null; + while (parentNode != null && select == null) { int parentIndex = parentNode.IndexInParent; if (parentIndex != -1 && parentIndex < parentNode.Parent.ChildrenCount - 1) { - toSelect = parentNode.Parent.GetChild(parentIndex + 1) as TreeNode; + select = parentNode.Parent.GetChild(parentIndex + 1) as TreeNode; } parentNode = parentNode.Parent as TreeNode; } + if (toSelect.Contains(select)) + toSelect.Remove(node); + else + toSelect.Add(select); + _lastSelectedNode = select; } else { // Select next parent child - toSelect = nodeParent.GetChild(myIndex + 1) as TreeNode; + var select = nodeParent.GetChild(myIndex + 1) as TreeNode; + if (toSelect.Contains(select)) + toSelect.Remove(node); + else + toSelect.Add(select); + _lastSelectedNode = select; } } - if (toSelect != null && toSelect.AutoFocus) + if (toSelect.Count > 0) { // Select Select(toSelect); - toSelect.Focus(); + _lastSelectedNode?.Focus(); } // Reset time From fc46219a82d8a5872f716e9fcbd4c1ab9259288b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 17 Jul 2025 23:09:36 -0500 Subject: [PATCH 45/82] Add support for multi-select disable. --- Source/Editor/GUI/Tree/Tree.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index fdedf0203..22483f1ac 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -415,7 +415,7 @@ namespace FlaxEditor.GUI.Tree // Up List toSelect = new List(); - if (shiftDown) + if (shiftDown && _supportMultiSelect) { toSelect.AddRange(Selection); } From f8dadac453b9945806648c4f771177bde4399f6a Mon Sep 17 00:00:00 2001 From: Olly Rybak Date: Sat, 19 Jul 2025 22:37:35 +1000 Subject: [PATCH 46/82] Fixed up some names and added docs --- Source/Engine/Level/Actors/AnimatedModel.cpp | 12 +++---- Source/Engine/Level/Actors/AnimatedModel.h | 34 +++++++++++++------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index d438f94c6..82c9b163d 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -174,9 +174,9 @@ void AnimatedModel::GetNodeTransformation(const StringView& nodeName, Matrix& no GetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace); } -void AnimatedModel::GetNodeTransformation(Array& modelBoneNodes, bool worldSpace) const +void AnimatedModel::GetNodeTransformation(Array& nodeTransformations, bool worldSpace) const { - for (ModelBoneNode& item : modelBoneNodes) + for (NodeTransformation& item : nodeTransformations) { GetNodeTransformation(item.NodeIndex, item.NodeMatrix, worldSpace); } @@ -199,7 +199,7 @@ void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTra OnAnimationUpdated(); } -void AnimatedModel::SetNodeTransformation(Array& modelBoneNodes, bool worldSpace) +void AnimatedModel::SetNodeTransformation(const Array& nodeTransformations, bool worldSpace) { if (GraphInstance.NodesPose.IsEmpty()) const_cast(this)->PreInitSkinningData(); // Ensure to have valid nodes pose to return @@ -213,11 +213,11 @@ void AnimatedModel::SetNodeTransformation(Array& modelBoneNodes, Matrix::Invert(world, invWorld); } - for (int i = 0; i < modelBoneNodes.Count(); i++) + for (int i = 0; i < nodeTransformations.Count(); i++) { - int nodeIndex = modelBoneNodes[i].NodeIndex; + int nodeIndex = nodeTransformations[i].NodeIndex; CHECK(nodeIndex >= 0 && nodeIndex < GraphInstance.NodesPose.Count()); - GraphInstance.NodesPose[nodeIndex] = modelBoneNodes[i].NodeMatrix; + GraphInstance.NodesPose[nodeIndex] = nodeTransformations[i].NodeMatrix; if (worldSpace) { GraphInstance.NodesPose[nodeIndex] = GraphInstance.NodesPose[nodeIndex] * invWorld; diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 9e5a34c69..50ab6be2e 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -9,14 +9,6 @@ #include "Engine/Renderer/DrawCall.h" #include "Engine/Core/Delegate.h" -API_STRUCT() struct ModelBoneNode -{ - DECLARE_SCRIPTING_TYPE_MINIMAL(ModelBoneNode); - - API_FIELD() uint32 NodeIndex; - API_FIELD() Matrix NodeMatrix; -}; - /// /// Performs an animation and renders a skinned model. /// @@ -26,6 +18,24 @@ class FLAXENGINE_API AnimatedModel : public ModelInstanceActor DECLARE_SCENE_OBJECT(AnimatedModel); friend class AnimationsSystem; + /// + /// Keeps the data of a Node and its relevant Transform Matrix together when passing it between functions. + /// + API_STRUCT() struct NodeTransformation + { + DECLARE_SCRIPTING_TYPE_MINIMAL(NodeTransformation); + + /// + /// The index of the node in the node hierarchy. + /// + API_FIELD() uint32 NodeIndex; + + /// + /// The transformation matrix of the node + /// + API_FIELD() Matrix NodeMatrix; + }; + /// /// Describes the animation graph updates frequency for the animated model. /// @@ -247,10 +257,10 @@ public: /// /// Gets the node final transformation for a series of nodes. /// - /// The series of nodes that will be returned + /// The series of nodes that will be returned /// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor. /// - API_FUNCTION() void GetNodeTransformation(API_PARAM(Out) Array& modelBoneNodes, bool worldSpace = false) const; + API_FUNCTION() void GetNodeTransformation(API_PARAM(Ref) Array& nodeTransformations, bool worldSpace = false) const; /// /// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices. @@ -271,10 +281,10 @@ public: /// /// Sets a group of nodes final transformation. /// - /// Array of the final node transformation matrix. + /// Array of the final node transformation matrix. /// True if convert matrices from world-space, otherwise values will be in local-space of the actor. /// - API_FUNCTION() void SetNodeTransformation(Array& modelBoneNodes, bool worldSpace = false); + API_FUNCTION() void SetNodeTransformation(const Array& nodeTransformations, bool worldSpace = false); /// /// Finds the closest node to a given location. From e6265105b522fc5f2042626d7d0697bcc60458b8 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 19 Jul 2025 15:39:11 -0500 Subject: [PATCH 47/82] Move to interface options. --- Source/Editor/Options/GeneralOptions.cs | 6 ------ Source/Editor/Options/InterfaceOptions.cs | 6 ++++++ Source/Editor/Windows/GameCookerWindow.cs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs index 23dc17733..59fac7a63 100644 --- a/Source/Editor/Options/GeneralOptions.cs +++ b/Source/Editor/Options/GeneralOptions.cs @@ -127,12 +127,6 @@ namespace FlaxEditor.Options BuildAction.NavMesh, }; - /// - /// Gets or sets the build configuration to use when using Cook and Run option in the editor. - /// - [EditorDisplay("General"), EditorOrder(201), ExpandGroups, Tooltip("The build configuration to use when using Cook and Run option in the editor.")] - public BuildConfiguration CookAndRunBuildConfiguration { get; set; } = BuildConfiguration.Development; - /// /// Gets or sets a value indicating whether perform automatic scripts reload on main window focus. /// diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 617c1c3de..e86f5cf42 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -420,6 +420,12 @@ namespace FlaxEditor.Options [DefaultValue(1), Range(1, 4)] [EditorDisplay("Cook & Run"), EditorOrder(600)] public int NumberOfGameClientsToLaunch = 1; + + /// + /// Gets or sets the build configuration to use when using Cook and Run option in the editor. + /// + [EditorDisplay("Cook & Run"), EditorOrder(601), ExpandGroups, Tooltip("The build configuration to use when using Cook and Run option in the editor.")] + public BuildConfiguration CookAndRunBuildConfiguration { get; set; } = BuildConfiguration.Development; /// /// Gets or sets the curvature of the line connecting to connected visject nodes. diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index eb65fd62b..01c482c19 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -976,7 +976,7 @@ namespace FlaxEditor.Windows Editor.Log("Building and running"); GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out _); var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch; - var buildConfig = Editor.Options.Options.General.CookAndRunBuildConfiguration; + var buildConfig = Editor.Options.Options.Interface.CookAndRunBuildConfiguration; for (int i = 0; i < numberOfClients; i++) { var buildOptions = BuildOptions.AutoRun; @@ -1004,7 +1004,7 @@ namespace FlaxEditor.Windows Editor.Log("Running cooked build"); GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out _); var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch; - var buildConfig = Editor.Options.Options.General.CookAndRunBuildConfiguration; + var buildConfig = Editor.Options.Options.Interface.CookAndRunBuildConfiguration; for (int i = 0; i < numberOfClients; i++) { _buildingQueue.Enqueue(new QueueItem From b6e18ccae5396802aadd56e6db49201c10f1ebb9 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 19 Jul 2025 21:05:57 -0500 Subject: [PATCH 48/82] Fix edge case for anim event on min or max frame when looping. Fix anim event playback when is running negative. --- .../Animations/Graph/AnimGroup.Animation.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index c42b500fd..bef6b2dcc 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -229,18 +229,12 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float auto& context = *Context.Get(); float eventTimeMin = animPrevPos; float eventTimeMax = animPos; - if (loop && context.DeltaTime * speed < 0) + + if (eventTimeMin > eventTimeMax) { - // Check if animation looped (for anim events shooting during backwards playback) - //const float posNotLooped = startTimePos + oldTimePos; - //if (posNotLooped < 0.0f || posNotLooped > length) - //const int32 animPosCycle = Math::CeilToInt(animPos / anim->GetDuration()); - //const int32 animPrevPosCycle = Math::CeilToInt(animPrevPos / anim->GetDuration()); - //if (animPosCycle != animPrevPosCycle) - { - Swap(eventTimeMin, eventTimeMax); - } + Swap(eventTimeMin, eventTimeMax); } + const float eventTime = (float)(animPos / anim->Data.FramesPerSecond); const float eventDeltaTime = (float)((animPos - animPrevPos) / anim->Data.FramesPerSecond); for (const auto& track : anim->Events) @@ -251,7 +245,13 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float continue; const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f; #define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type }) - if (k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration) + if ((k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration + && (Math::FloorToInt(animPos) != 0 && Math::FloorToInt(animPrevPos) < Math::FloorToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::FloorToInt(animPos) < Math::FloorToInt(anim->GetDuration()))) + // Handle the edge case of an event on 0 or on max animation duration during looping + || (loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == anim->GetDuration()) + || (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f) + || (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f) + ) { int32 stateIndex = -1; if (duration > 1) From b8e00f2ed1f1ae9e16fb8d0a83752b50820bbe1c Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 19 Jul 2025 21:30:49 -0500 Subject: [PATCH 49/82] Change checking max to use ceiltoint --- Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index bef6b2dcc..f75a8abd1 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -246,7 +246,7 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f; #define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type }) if ((k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration - && (Math::FloorToInt(animPos) != 0 && Math::FloorToInt(animPrevPos) < Math::FloorToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::FloorToInt(animPos) < Math::FloorToInt(anim->GetDuration()))) + && (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration()))) // Handle the edge case of an event on 0 or on max animation duration during looping || (loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == anim->GetDuration()) || (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f) From 6cbd40e6d84defc51f781bd4d9e24949cf6476dc Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 22 Jul 2025 22:01:55 -0500 Subject: [PATCH 50/82] Make StringView::Empty const to fix issues with user accidentally changing the value. --- Source/Engine/Core/Types/StringView.cpp | 2 +- Source/Engine/Core/Types/StringView.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Types/StringView.cpp b/Source/Engine/Core/Types/StringView.cpp index 505d9ec05..168a537c1 100644 --- a/Source/Engine/Core/Types/StringView.cpp +++ b/Source/Engine/Core/Types/StringView.cpp @@ -9,7 +9,7 @@ StringView StringBuilder::ToStringView() const return StringView(_data.Get(), _data.Count()); } -StringView StringView::Empty; +const StringView StringView::Empty; StringView::StringView(const String& str) : StringViewBase(str.Get(), str.Length()) diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h index 25d156c64..5dc0c14a1 100644 --- a/Source/Engine/Core/Types/StringView.h +++ b/Source/Engine/Core/Types/StringView.h @@ -219,7 +219,7 @@ public: /// /// Instance of the empty string. /// - static StringView Empty; + static const StringView Empty; public: /// From d6a33d5a1c217c1eab88c7cbb8dcbcffa6145ee1 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 23 Jul 2025 10:23:30 -0500 Subject: [PATCH 51/82] Add const to String::Empty --- Source/Engine/Core/Types/String.cpp | 2 +- Source/Engine/Core/Types/String.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Types/String.cpp b/Source/Engine/Core/Types/String.cpp index 112434426..ff0e19eba 100644 --- a/Source/Engine/Core/Types/String.cpp +++ b/Source/Engine/Core/Types/String.cpp @@ -4,7 +4,7 @@ #include "StringView.h" #include "Engine/Core/Collections/Array.h" -String String::Empty; +const String String::Empty; String::String(const StringAnsi& str) { diff --git a/Source/Engine/Core/Types/String.h b/Source/Engine/Core/Types/String.h index 4578ac0b3..0630ff6e3 100644 --- a/Source/Engine/Core/Types/String.h +++ b/Source/Engine/Core/Types/String.h @@ -548,7 +548,7 @@ public: /// /// Instance of the empty string. /// - static String Empty; + static const String Empty; public: /// From 2dc44ac1a6ea4578f641bbc9a633b4a4e4750cbb Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 23 Jul 2025 19:52:42 -0500 Subject: [PATCH 52/82] Fix infinite loop on rich text box tag parsing with incomplete end of tag. --- Source/Engine/Utilities/HtmlParser.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Engine/Utilities/HtmlParser.cs b/Source/Engine/Utilities/HtmlParser.cs index 37b614b80..74f252c77 100644 --- a/Source/Engine/Utilities/HtmlParser.cs +++ b/Source/Engine/Utilities/HtmlParser.cs @@ -204,6 +204,10 @@ namespace FlaxEngine.Utilities SkipWhitespace(); while (Peek() != '>') { + // Return false if start of new html tag is detected. + if (Peek() == '<') + return false; + if (Peek() == '/') { // Handle trailing forward slash From 753035c4520bcd0ba10b12002d24e0bc0b93e0b3 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 24 Jul 2025 18:04:39 -0500 Subject: [PATCH 53/82] Fix issue with infinite loop if `\` is used instead of `/` for tag closing. --- Source/Engine/Utilities/HtmlParser.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Engine/Utilities/HtmlParser.cs b/Source/Engine/Utilities/HtmlParser.cs index 74f252c77..708829ba3 100644 --- a/Source/Engine/Utilities/HtmlParser.cs +++ b/Source/Engine/Utilities/HtmlParser.cs @@ -134,6 +134,10 @@ namespace FlaxEngine.Utilities if (isLeadingSlash) Move(); + // Dont process if wrong slash is used. + if (c =='\\') + return false; + // Parse tag bool result = ParseTag(ref tag, name); From 6132e45e252a943e13b7ce6c2a6140769d5ead44 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 25 Jul 2025 16:00:44 +0200 Subject: [PATCH 54/82] Fix shadow lights checking loop if shadows are disabled --- Source/Engine/Renderer/ShadowsPass.cpp | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 3937f8554..910ec3cef 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -1070,26 +1070,26 @@ void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch& // Early out and skip shadows setup if no lights is actively casting shadows // RenderBuffers will automatically free any old ShadowsCustomBuffer after a few frames if we don't update LastFrameUsed Array shadowedLights; - for (auto& light : renderContext.List->DirectionalLights) + if (_shadowMapFormat != PixelFormat::Unknown && EnumHasAllFlags(renderContext.View.Flags, ViewFlags::Shadows) && !checkIfSkipPass()) { - if (light.CanRenderShadow(renderContext.View)) - shadowedLights.Add(&light); - } - for (auto& light : renderContext.List->SpotLights) - { - if (light.CanRenderShadow(renderContext.View)) - shadowedLights.Add(&light); - } - for (auto& light : renderContext.List->PointLights) - { - if (light.CanRenderShadow(renderContext.View)) - shadowedLights.Add(&light); + for (auto& light : renderContext.List->DirectionalLights) + { + if (light.CanRenderShadow(renderContext.View)) + shadowedLights.Add(&light); + } + for (auto& light : renderContext.List->SpotLights) + { + if (light.CanRenderShadow(renderContext.View)) + shadowedLights.Add(&light); + } + for (auto& light : renderContext.List->PointLights) + { + if (light.CanRenderShadow(renderContext.View)) + shadowedLights.Add(&light); + } } const auto currentFrame = Engine::FrameCount; - if (_shadowMapFormat == PixelFormat::Unknown || - EnumHasNoneFlags(renderContext.View.Flags, ViewFlags::Shadows) || - checkIfSkipPass() || - shadowedLights.IsEmpty()) + if (shadowedLights.IsEmpty()) { // Invalidate any existing custom buffer that could have been used by the same task (eg. when rendering 6 sides of env probe) if (auto* old = (ShadowsCustomBuffer*)renderContext.Buffers->FindCustomBuffer(TEXT("Shadows"), false)) From f37b75df7b43b9fc46431e44f6edf49f0deaa5c5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 25 Jul 2025 19:59:58 +0200 Subject: [PATCH 55/82] Add support for using shadow maps from linked scene rendering (eg. for 1p weapons) --- Source/Engine/Graphics/RenderBuffers.h | 6 ++ Source/Engine/Renderer/ShadowsPass.cpp | 87 ++++++++++++++++++++------ Source/Engine/Renderer/ShadowsPass.h | 2 + 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index 159ab13a9..647d28c4c 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -175,6 +175,12 @@ public: return (const T*)FindCustomBuffer(name, withLinked); } + template + const T* FindLinkedBuffer(const StringView& name) const + { + return LinkedCustomBuffers ? (const T*)LinkedCustomBuffers->FindCustomBuffer(name, true) : nullptr; + } + template T* GetCustomBuffer(const StringView& name, bool withLinked = true) { diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 910ec3cef..19985f0be 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -69,6 +69,7 @@ struct ShadowAtlasLightTile { ShadowsAtlasRectTile* RectTile; ShadowsAtlasRectTile* StaticRectTile; + const ShadowsAtlasRectTile* LinkedRectTile; Matrix WorldToShadow; float FramesToUpdate; // Amount of frames (with fraction) until the next shadow update can happen bool SkipUpdate; @@ -94,6 +95,7 @@ struct ShadowAtlasLightTile void ClearStatic() { StaticRectTile = nullptr; + LinkedRectTile = nullptr; FramesToUpdate = 0; SkipUpdate = false; } @@ -301,6 +303,7 @@ public: GPUTexture* StaticShadowMapAtlas = nullptr; DynamicTypedBuffer ShadowsBuffer; GPUBufferView* ShadowsBufferView = nullptr; + const ShadowsCustomBuffer* LinkedShadows = nullptr; RectPackAtlas Atlas; RectPackAtlas StaticAtlas; Dictionary Lights; @@ -1046,6 +1049,32 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render } } +void ShadowsPass::ClearShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, QuadShaderData& quadShaderData) const +{ + // Color.r is used by PS_DepthClear in Quad shader to clear depth + quadShaderData.Color = Float4::One; + context->UpdateCB(quadShaderCB, &quadShaderData); + context->BindCB(0, quadShaderCB); + + // Clear tile depth + context->SetState(_psDepthClear); + context->DrawFullscreenTriangle(); +} + +void ShadowsPass::CopyShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, QuadShaderData& quadShaderData, const GPUTexture* srcShadowMap, const ShadowsAtlasRectTile* srcTile) const +{ + // Color.xyzw is used by PS_DepthCopy in Quad shader to scale input texture UVs + const float staticAtlasResolutionInv = 1.0f / (float)srcShadowMap->Width(); + quadShaderData.Color = Float4(srcTile->Width, srcTile->Height, srcTile->X, srcTile->Y) * staticAtlasResolutionInv; + context->UpdateCB(quadShaderCB, &quadShaderData); + context->BindCB(0, quadShaderCB); + + // Copy tile depth + context->BindSR(0, srcShadowMap->View()); + context->SetState(_psDepthCopy); + context->DrawFullscreenTriangle(); +} + void ShadowsPass::Dispose() { // Base @@ -1102,11 +1131,14 @@ void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch& // Initialize shadow atlas auto& shadows = *renderContext.Buffers->GetCustomBuffer(TEXT("Shadows"), false); + shadows.LinkedShadows = renderContext.Buffers->FindLinkedBuffer(TEXT("Shadows")); + if (shadows.LinkedShadows && (shadows.LinkedShadows->LastFrameUsed != currentFrame || shadows.LinkedShadows->ViewOrigin != renderContext.View.Origin)) + shadows.LinkedShadows = nullptr; // Don't use incompatible linked shadows buffer if (shadows.LastFrameUsed == currentFrame) shadows.Reset(); shadows.LastFrameUsed = currentFrame; shadows.MaxShadowsQuality = Math::Clamp(Math::Min((int32)Graphics::ShadowsQuality, (int32)renderContext.View.MaxShadowsQuality), 0, (int32)Quality::MAX - 1); - shadows.EnableStaticShadows = !renderContext.View.IsOfflinePass && !renderContext.View.IsSingleFrame; + shadows.EnableStaticShadows = !renderContext.View.IsOfflinePass && !renderContext.View.IsSingleFrame && !shadows.LinkedShadows; int32 atlasResolution; switch (Graphics::ShadowMapsQuality) { @@ -1325,6 +1357,29 @@ RETRY_ATLAS_SETUP: SetupLight(shadows, renderContext, renderContextBatch, *(RenderSpotLightData*)light, atlasLight); else //if (light->IsDirectionalLight) SetupLight(shadows, renderContext, renderContextBatch, *(RenderDirectionalLightData*)light, atlasLight); + + // Check if that light exists in linked shadows buffer to reuse shadow maps + const ShadowAtlasLight* linkedAtlasLight; + if (shadows.LinkedShadows && ((linkedAtlasLight = shadows.LinkedShadows->Lights.TryGet(light->ID))) && linkedAtlasLight->TilesCount == atlasLight.TilesCount) + { + for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++) + { + auto& tile = atlasLight.Tiles[tileIndex]; + tile.LinkedRectTile = nullptr; + auto& linkedTile = linkedAtlasLight->Tiles[tileIndex]; + + // Check if both lights use the same projections + if (tile.WorldToShadow == linkedTile.WorldToShadow && linkedTile.RectTile) + { + tile.LinkedRectTile = linkedTile.RectTile; + } + } + } + else + { + for (auto& tile : atlasLight.Tiles) + tile.LinkedRectTile = nullptr; + } } } if (shadows.StaticAtlas.IsInitialized()) @@ -1495,29 +1550,21 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch) // Set viewport for tile context->SetViewportAndScissors(tile.CachedViewport); - if (tile.StaticRectTile && atlasLight.StaticState == ShadowAtlasLight::CopyStaticShadow) + if (tile.LinkedRectTile) { - // Color.xyzw is used by PS_DepthCopy in Quad shader to scale input texture UVs - const float staticAtlasResolutionInv = 1.0f / shadows.StaticShadowMapAtlas->Width(); - quadShaderData.Color = Float4(tile.StaticRectTile->Width, tile.StaticRectTile->Height, tile.StaticRectTile->X, tile.StaticRectTile->Y) * staticAtlasResolutionInv; - context->UpdateCB(quadShaderCB, &quadShaderData); - context->BindCB(0, quadShaderCB); - - // Copy tile depth - context->BindSR(0, shadows.StaticShadowMapAtlas->View()); - context->SetState(_psDepthCopy); - context->DrawFullscreenTriangle(); + // Copy linked shadow + ASSERT(shadows.LinkedShadows); + CopyShadowMapTile(context, quadShaderCB, quadShaderData, shadows.LinkedShadows->ShadowMapAtlas, tile.LinkedRectTile); + } + else if (tile.StaticRectTile && atlasLight.StaticState == ShadowAtlasLight::CopyStaticShadow) + { + // Copy static shadow + CopyShadowMapTile(context, quadShaderCB, quadShaderData, shadows.StaticShadowMapAtlas, tile.StaticRectTile); } else if (!shadows.ClearShadowMapAtlas) { - // Color.r is used by PS_DepthClear in Quad shader to clear depth - quadShaderData.Color = Float4::One; - context->UpdateCB(quadShaderCB, &quadShaderData); - context->BindCB(0, quadShaderCB); - - // Clear tile depth - context->SetState(_psDepthClear); - context->DrawFullscreenTriangle(); + // Clear shadow + ClearShadowMapTile(context, quadShaderCB, quadShaderData); } // Draw objects depth diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h index 748a7c084..8e64a205d 100644 --- a/Source/Engine/Renderer/ShadowsPass.h +++ b/Source/Engine/Renderer/ShadowsPass.h @@ -60,6 +60,8 @@ private: static void SetupLight(ShadowsCustomBuffer& shadows, RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, ShadowAtlasLight& atlasLight); static void SetupLight(ShadowsCustomBuffer& shadows, RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light, ShadowAtlasLight& atlasLight); static void SetupLight(ShadowsCustomBuffer& shadows, RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light, ShadowAtlasLight& atlasLight); + void ClearShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, struct QuadShaderData& quadShaderData) const; + void CopyShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, struct QuadShaderData& quadShaderData, const GPUTexture* srcShadowMap, const struct ShadowsAtlasRectTile* srcTile) const; #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) From 303087c4c4b223b1b933dec815fa7c40b0cf478c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 12 Aug 2025 23:18:18 +0200 Subject: [PATCH 56/82] Fix regression in renaming textbox placement of newly added actor to prefab --- Source/Editor/GUI/Popups/RenamePopup.cs | 4 ++++ Source/Editor/GUI/Tree/Tree.cs | 19 ++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Source/Editor/GUI/Popups/RenamePopup.cs b/Source/Editor/GUI/Popups/RenamePopup.cs index 353830a21..d17a4180d 100644 --- a/Source/Editor/GUI/Popups/RenamePopup.cs +++ b/Source/Editor/GUI/Popups/RenamePopup.cs @@ -130,6 +130,10 @@ namespace FlaxEditor.GUI /// Created popup. public static RenamePopup Show(Control control, Rectangle area, string value, bool isMultiline) { + // hardcoded flushing layout for tree controls + if (control is Tree.TreeNode treeNode && treeNode.ParentTree != null) + treeNode.ParentTree.FlushPendingPerformLayout(); + // Calculate the control size in the window space to handle scaled controls var upperLeft = control.PointToWindow(area.UpperLeft); var bottomRight = control.PointToWindow(area.BottomRight); diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index 8df26c211..facc2c36d 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -364,6 +364,19 @@ namespace FlaxEditor.GUI.Tree BulkSelectUpdateExpanded(false); } + /// + /// Flushes any pending layout perming action that has been delayed until next update to optimize performance of the complex tree hierarchy. + /// + public void FlushPendingPerformLayout() + { + if (_deferLayoutUpdate) + { + base.PerformLayout(); + AfterDeferredLayout?.Invoke(); + _deferLayoutUpdate = false; + } + } + /// public override void PerformLayout(bool force = false) { @@ -378,11 +391,7 @@ namespace FlaxEditor.GUI.Tree public override void Update(float deltaTime) { if (_deferLayoutUpdate) - { - base.PerformLayout(); - AfterDeferredLayout?.Invoke(); - _deferLayoutUpdate = false; - } + FlushPendingPerformLayout(); var node = SelectedNode; From 6fd4ef735ed71d9c35f1c296e9b4bc72b3b997af Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 12 Aug 2025 23:35:12 +0200 Subject: [PATCH 57/82] Add `InvokeOnUpdate` to C++ scripting api --- Source/Engine/Scripting/Scripting.cpp | 29 +++++++++++++++++++++++++++ Source/Engine/Scripting/Scripting.h | 5 +++++ 2 files changed, 34 insertions(+) diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 593092b8f..db6867d34 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -106,6 +106,7 @@ namespace MMethod* _method_LateFixedUpdate = nullptr; MMethod* _method_Draw = nullptr; MMethod* _method_Exit = nullptr; + Array> UpdateActions; Dictionary> _nonNativeModules; #if USE_EDITOR bool LastBinariesLoadTriggeredCompilation = false; @@ -242,6 +243,27 @@ void ScriptingService::Update() PROFILE_CPU_NAMED("Scripting::Update"); INVOKE_EVENT(Update); + // Flush update actions + _objectsLocker.Lock(); + int32 count = UpdateActions.Count(); + for (int32 i = 0; i < count; i++) + { + UpdateActions[i](); + } + int32 newlyAdded = UpdateActions.Count() - count; + if (newlyAdded == 0) + UpdateActions.Clear(); + else + { + // Someone added another action within current callback + Array> tmp; + for (int32 i = newlyAdded; i < UpdateActions.Count(); i++) + tmp.Add(UpdateActions[i]); + UpdateActions.Clear(); + UpdateActions.Add(tmp); + } + _objectsLocker.Unlock(); + #ifdef USE_NETCORE // Force GC to run in background periodically to avoid large blocking collections causing hitches if (Time::Update.TicksCount % 60 == 0) @@ -303,6 +325,13 @@ void Scripting::ProcessBuildInfoPath(String& path, const String& projectFolderPa path = projectFolderPath / path; } +void Scripting::InvokeOnUpdate(const Function& action) +{ + _objectsLocker.Lock(); + UpdateActions.Add(action); + _objectsLocker.Unlock(); +} + bool Scripting::LoadBinaryModules(const String& path, const String& projectFolderPath) { PROFILE_CPU_NAMED("LoadBinaryModules"); diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index be5e9ca34..1e7daf6f0 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -230,6 +230,11 @@ public: static void ProcessBuildInfoPath(String& path, const String& projectFolderPath); + /// + /// Calls the given action on the next scripting update. + /// + /// The action to invoke. + static void InvokeOnUpdate(const Function& action); private: static bool LoadBinaryModules(const String& path, const String& projectFolderPath); From cf503cf92154283374d91a898e7dc84c1a29a757 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 12 Aug 2025 21:03:35 -0500 Subject: [PATCH 58/82] Add material instance ctor for `MaterialBrush` --- Source/Engine/UI/GUI/Brushes/MaterialBrush.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs b/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs index 6210ca2d0..423e5ad70 100644 --- a/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs @@ -29,6 +29,14 @@ namespace FlaxEngine.GUI { Material = material; } + /// + /// Initializes a new instance of the struct. + /// + /// The material. + public MaterialBrush(MaterialInstance material) + { + Material = material; + } /// public Float2 Size => Float2.One; From 169d3e964d9e51e074319242a57509b84176bb3d Mon Sep 17 00:00:00 2001 From: Saas Date: Wed, 13 Aug 2025 13:29:13 +0200 Subject: [PATCH 59/82] Switch from Debug.Write to Editor.Log --- Source/Editor/Windows/DebugLogWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index c64025491..e8d815c28 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -488,7 +488,7 @@ namespace FlaxEditor.Windows // Pause on Error (we should do it as fast as possible) if (newEntry.Group == LogGroup.Error && _pauseOnErrorButton.Checked && Editor.StateMachine.CurrentState == Editor.StateMachine.PlayingState) { - Debug.Write(LogType.Info, "Pause Play mode on error (toggle this behaviour in the Debug Log panel)"); + Editor.Log("Pause Play mode on error (toggle this behaviour in the Debug Log panel)"); Editor.Simulation.RequestPausePlay(); } } From 69e12d77be2caa0eb27a40eff300b881b08da045 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 13 Aug 2025 23:25:57 +0200 Subject: [PATCH 60/82] Add preventing autosave when using editor context menus #3220 --- Source/Editor/Editor.cs | 6 +++++- Source/Editor/Managed/ManagedEditor.cpp | 9 +++++++++ Source/Editor/Managed/ManagedEditor.h | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 8c3256eb9..853a6eb7d 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -528,7 +528,11 @@ namespace FlaxEditor var timeSinceLastSave = Time.UnscaledGameTime - _lastAutoSaveTimer; var timeToNextSave = options.AutoSaveFrequency * 60.0f - timeSinceLastSave; - if (timeToNextSave <= 0.0f || _autoSaveNow) + if (timeToNextSave <= 0.0f && GetWindows().Any(x => x.GUI.Children.Any(c => c is GUI.ContextMenu.ContextMenuBase))) + { + // Skip aut-save if any context menu is opened to wait for user to end interaction + } + else if (timeToNextSave <= 0.0f || _autoSaveNow) { Log("Auto save"); _lastAutoSaveTimer = Time.UnscaledGameTime; diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index d65ae6e0a..6ff8e814f 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -11,6 +11,7 @@ #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MException.h" #include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h" +#include "Engine/Platform/WindowsManager.h" #include "Engine/Content/Assets/VisualScript.h" #include "Engine/Content/Content.h" #include "Engine/CSG/CSGBuilder.h" @@ -622,6 +623,14 @@ void ManagedEditor::WipeOutLeftoverSceneObjects() ObjectsRemovalService::Flush(); } +Array ManagedEditor::GetWindows() +{ + WindowsManager::WindowsLocker.Lock(); + auto result = WindowsManager::Windows; + WindowsManager::WindowsLocker.Unlock(); + return result; +} + void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly) { ASSERT(!HasManagedInstance()); diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 3d4f49708..15649b97b 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -259,6 +259,7 @@ public: API_FUNCTION(Internal) static Array GetVisualScriptLocals(); API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local); API_FUNCTION(Internal) static void WipeOutLeftoverSceneObjects(); + API_FUNCTION(Internal) static Array GetWindows(); private: void OnEditorAssemblyLoaded(MAssembly* assembly); From 1087bd24451f7b2842b5db5bcb6c7a33cfdc9a31 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Aug 2025 11:53:25 +0200 Subject: [PATCH 61/82] Fix #3342 to properly place new param in Constant Buffer #3299 --- Content/Editor/MaterialTemplates/GUI.shader | 2 ++ .../MaterialTemplates/PostProcess.shader | 2 ++ Source/Editor/Surface/Archetypes/Tools.cs | 2 +- .../Graphics/Materials/GUIMaterialShader.cpp | 5 ++++- Source/Engine/Graphics/Materials/IMaterial.h | 3 +-- .../Graphics/Materials/MaterialShader.cpp | 17 +++++++++-------- .../Engine/Graphics/Materials/MaterialShader.h | 2 +- .../Materials/PostFxMaterialShader.cpp | 5 ++++- .../MaterialGenerator.Tools.cpp | 18 ++---------------- .../MaterialGenerator/MaterialGenerator.cpp | 2 +- .../MaterialGenerator/MaterialGenerator.h | 2 +- Source/Shaders/MaterialCommon.hlsl | 5 +++-- 12 files changed, 31 insertions(+), 34 deletions(-) diff --git a/Content/Editor/MaterialTemplates/GUI.shader b/Content/Editor/MaterialTemplates/GUI.shader index fad7afa32..69b6d3539 100644 --- a/Content/Editor/MaterialTemplates/GUI.shader +++ b/Content/Editor/MaterialTemplates/GUI.shader @@ -20,6 +20,8 @@ float TimeParam; float4 ViewInfo; float4 ScreenSize; float4 ViewSize; +float3 ViewPadding0; +float UnscaledTimeParam; @1META_CB_END // Shader resources diff --git a/Content/Editor/MaterialTemplates/PostProcess.shader b/Content/Editor/MaterialTemplates/PostProcess.shader index 927d63c8c..753ccb253 100644 --- a/Content/Editor/MaterialTemplates/PostProcess.shader +++ b/Content/Editor/MaterialTemplates/PostProcess.shader @@ -19,6 +19,8 @@ float4 ViewInfo; float4 ScreenSize; float4 TemporalAAJitter; float4x4 InverseViewProjectionMatrix; +float3 ViewPadding0; +float UnscaledTimeParam; @1META_CB_END // Shader resources diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index 3ba4813bd..0e4b567b9 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -1389,7 +1389,7 @@ namespace FlaxEditor.Surface.Archetypes Size = new Float2(110, 40), Elements = new[] { - NodeElementArchetype.Factory.Output(0, "Scaled Time", typeof(float), 0), + NodeElementArchetype.Factory.Output(0, "Time", typeof(float), 0), NodeElementArchetype.Factory.Output(1, "Unscaled Time", typeof(float), 1), } }, diff --git a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp index 002736355..874694702 100644 --- a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp @@ -23,6 +23,8 @@ PACK_STRUCT(struct GUIMaterialShaderData { Float4 ViewInfo; Float4 ScreenSize; Float4 ViewSize; + Float3 ViewPadding0; + float UnscaledTimeParam; }); void GUIMaterialShader::Bind(BindParameters& params) @@ -55,7 +57,8 @@ void GUIMaterialShader::Bind(BindParameters& params) materialData->ViewPos = Float3::Zero; materialData->ViewFar = 0.0f; materialData->ViewDir = Float3::Forward; - materialData->TimeParam = params.UnscaledTimeParam; + materialData->TimeParam = params.Time; + materialData->UnscaledTimeParam = params.UnscaledTime; materialData->ViewInfo = Float4::Zero; auto& viewport = Render2D::GetViewport(); materialData->ScreenSize = Float4(viewport.Width, viewport.Height, 1.0f / viewport.Width, 1.0f / viewport.Height); diff --git a/Source/Engine/Graphics/Materials/IMaterial.h b/Source/Engine/Graphics/Materials/IMaterial.h index 879925f07..23d06589b 100644 --- a/Source/Engine/Graphics/Materials/IMaterial.h +++ b/Source/Engine/Graphics/Materials/IMaterial.h @@ -148,8 +148,7 @@ public: const ::DrawCall* DrawCall = nullptr; MaterialParamsLink* ParamsLink = nullptr; void* CustomData = nullptr; - float UnscaledTimeParam; - float ScaledTimeParam; + float Time, UnscaledTime; bool Instanced = false; /// diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp index 527914f6e..98edca2bf 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp @@ -30,20 +30,21 @@ GPU_CB_STRUCT(MaterialShaderDataPerView { Float3 ViewPos; float ViewFar; Float3 ViewDir; - float UnscaledTimeParam; - float ScaledTimeParam; + float TimeParam; Float4 ViewInfo; Float4 ScreenSize; Float4 TemporalAAJitter; Float3 LargeWorldsChunkIndex; float LargeWorldsChunkSize; + Float3 ViewPadding0; + float UnscaledTimeParam; }); IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderContext& renderContext) : GPUContext(context) , RenderContext(renderContext) - , UnscaledTimeParam(Time::Draw.UnscaledTime.GetTotalSeconds()) - , ScaledTimeParam(Time::Draw.Time.GetTotalSeconds()) + , Time(Time::Draw.Time.GetTotalSeconds()) + , UnscaledTime(Time::Draw.UnscaledTime.GetTotalSeconds()) { } @@ -51,8 +52,8 @@ IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderC : GPUContext(context) , RenderContext(renderContext) , DrawCall(&drawCall) - , UnscaledTimeParam(Time::Draw.UnscaledTime.GetTotalSeconds()) - , ScaledTimeParam(Time::Draw.Time.GetTotalSeconds()) + , Time(Time::Draw.Time.GetTotalSeconds()) + , UnscaledTime(Time::Draw.UnscaledTime.GetTotalSeconds()) , Instanced(instanced) { } @@ -80,8 +81,8 @@ void IMaterial::BindParameters::BindViewData() cb.ViewPos = view.Position; cb.ViewFar = view.Far; cb.ViewDir = view.Direction; - cb.UnscaledTimeParam = UnscaledTimeParam; - cb.ScaledTimeParam = ScaledTimeParam; + cb.TimeParam = Time; + cb.UnscaledTimeParam = UnscaledTime; cb.ViewInfo = view.ViewInfo; cb.ScreenSize = view.ScreenSize; cb.TemporalAAJitter = view.TemporalAAJitter; diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index ccc11c8cf..aedf2e870 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 173 +#define MATERIAL_GRAPH_VERSION 174 class Material; class GPUShader; diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp index 61ca23248..c82dd7447 100644 --- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp @@ -21,6 +21,8 @@ PACK_STRUCT(struct PostFxMaterialShaderData { Float4 ScreenSize; Float4 TemporalAAJitter; Matrix InverseViewProjectionMatrix; + Float3 ViewPadding0; + float UnscaledTimeParam; }); void PostFxMaterialShader::Bind(BindParameters& params) @@ -51,7 +53,8 @@ void PostFxMaterialShader::Bind(BindParameters& params) materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; materialData->ViewDir = view.Direction; - materialData->TimeParam = params.UnscaledTimeParam; + materialData->TimeParam = params.Time; + materialData->UnscaledTimeParam = params.UnscaledTime; materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; materialData->TemporalAAJitter = view.TemporalAAJitter; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp index dcaee67d8..056ddd7ce 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp @@ -48,28 +48,14 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) } // Time case 3: - { - switch (box->ID) - { - // Scaled Time - case 0: - value = getScaledTime; - break; - // Unscaled Time - case 1: - value = getUnscaledTime; - break; - default: - break; - } + value = box->ID == 1 ? getUnscaledTime : getTime; break; - } // Panner case 6: { // Get inputs const Value uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); - const Value time = tryGetValue(node->GetBox(1), getUnscaledTime).AsFloat(); + const Value time = tryGetValue(node->GetBox(1), getTime).AsFloat(); const Value speed = tryGetValue(node->GetBox(2), Value::One).AsFloat2(); const bool useFractionalPart = (bool)node->Values[0]; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 1eccf69fb..b958f5f56 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -106,8 +106,8 @@ bool FeatureData::Init() } MaterialValue MaterialGenerator::getUVs(VariantType::Float2, TEXT("input.TexCoord")); +MaterialValue MaterialGenerator::getTime(VariantType::Float, TEXT("TimeParam")); MaterialValue MaterialGenerator::getUnscaledTime(VariantType::Float, TEXT("UnscaledTimeParam")); -MaterialValue MaterialGenerator::getScaledTime(VariantType::Float, TEXT("ScaledTimeParam")); MaterialValue MaterialGenerator::getNormal(VariantType::Float3, TEXT("input.TBN[2]")); MaterialValue MaterialGenerator::getNormalZero(VariantType::Float3, TEXT("float3(0, 0, 1)")); MaterialValue MaterialGenerator::getVertexColor(VariantType::Float4, TEXT("GetVertexColor(input)")); diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h index 54ca9616c..706929d75 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h @@ -209,8 +209,8 @@ private: public: static MaterialValue getUVs; + static MaterialValue getTime; static MaterialValue getUnscaledTime; - static MaterialValue getScaledTime; static MaterialValue getNormal; static MaterialValue getNormalZero; static MaterialValue getVertexColor; diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index c8bd1a3ec..897b01d7e 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -170,13 +170,14 @@ cbuffer ViewData : register(b1) float3 ViewPos; float ViewFar; float3 ViewDir; - float UnscaledTimeParam; - float ScaledTimeParam; + float TimeParam; float4 ViewInfo; float4 ScreenSize; float4 TemporalAAJitter; float3 LargeWorldsChunkIndex; float LargeWorldsChunkSize; + float3 ViewPadding0; + float UnscaledTimeParam; }; #endif From 2f7d7a0f2a899235e153eae495a9006ca657a6e3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Aug 2025 12:24:02 +0200 Subject: [PATCH 62/82] Allow pasting State nodes in Anim Graph #3596 --- Source/Editor/Surface/Archetypes/Animation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index f71f50596..9f3112905 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -810,7 +810,7 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new StateMachineState(id, context, arch, groupArch), Title = "State", Description = "The animation states machine state node", - Flags = NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste, + Flags = NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI, Size = new Float2(100, 0), DefaultValues = new object[] { From 8a73d79936d6ed609de1742d54913ed788adeed8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Aug 2025 12:24:12 +0200 Subject: [PATCH 63/82] Update engine materials --- Content/Editor/Camera/M_Camera.flax | 4 ++-- Content/Editor/Gizmo/Material.flax | 4 ++-- Content/Editor/Gizmo/SelectionOutlineMaterial.flax | 4 ++-- Content/Editor/Highlight Material.flax | 4 ++-- Content/Editor/Icons/IconsMaterial.flax | 4 ++-- Content/Editor/Particles/Particle Material Color.flax | 4 ++-- Content/Engine/DefaultMaterial.flax | 4 ++-- Content/Engine/DefaultRadialMenu.flax | 4 ++-- Content/Engine/DefaultTerrainMaterial.flax | 4 ++-- Content/Engine/SkyboxMaterial.flax | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index 7ac9bab2d..5012b16e9 100644 --- a/Content/Editor/Camera/M_Camera.flax +++ b/Content/Editor/Camera/M_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4dc3a7d0a8287c7c2c26b7685b726583677f96e1fe86ccb5f8518cb189b51a92 -size 29407 +oid sha256:6a2936be1789e6a7c663f84ddfea8c897fe8273cd2d29910ac37720907d7b930 +size 29533 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index d7815f10f..cce270410 100644 --- a/Content/Editor/Gizmo/Material.flax +++ b/Content/Editor/Gizmo/Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e68032a48b3196f73c614a6302223765485e8fa3e1e278a2395c9d9ae1c5064 -size 32519 +oid sha256:271c80d9d9971d96d6ea430dfaf8f8f57d9b5f9fe1770b387f426d3c8721c3d8 +size 32713 diff --git a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax index 70a58006b..962179da0 100644 --- a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax +++ b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de0b19b33a2aac03a0fb7beda935c8a22cbb506f9ca9383bcce3eaac858a54e3 -size 16166 +oid sha256:fc2facc8fa980e5baa399fa7510a87d33d21bbd4c97eaab24856f6db49b13172 +size 16212 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index 7c4be9cc2..e944ae62d 100644 --- a/Content/Editor/Highlight Material.flax +++ b/Content/Editor/Highlight Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1ec249ecd34cc66f9236431705adfaccff3e9ac5825ea72eb8b18449a96ccb7 -size 29910 +oid sha256:391606b1f7563d9a8e414baf6c19f3d99694a28f3f574b7aca64a325294d8e39 +size 30104 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index 27e1e992f..320547f9a 100644 --- a/Content/Editor/Icons/IconsMaterial.flax +++ b/Content/Editor/Icons/IconsMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:652de424a7e366c1dcafd42cfe552f2d4f1327cd9ef033fb554d961e0a29ae2b -size 29827 +oid sha256:7951a44b138e60aa9dee4fdaf000eba8a7faef7b31c2e387f78b4a393d0cd0bc +size 30021 diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index 07d6d0587..bde834456 100644 --- a/Content/Editor/Particles/Particle Material Color.flax +++ b/Content/Editor/Particles/Particle Material Color.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c71a6394a3a0668f6d2b18281aaa36d83b1b150fd10a869edace21e1ffa5b07 -size 30361 +oid sha256:b600cd725f5550de72d5a2544571ca2c1ea8de1a3d45038bac273d2b6f3b04c2 +size 30429 diff --git a/Content/Engine/DefaultMaterial.flax b/Content/Engine/DefaultMaterial.flax index a7949d435..a253452df 100644 --- a/Content/Engine/DefaultMaterial.flax +++ b/Content/Engine/DefaultMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1db3d343643eec7c6f7c3ac33a205618480ce41b4b196ebfc6b63e00085a555f -size 31205 +oid sha256:b00388390410aabb11f1d9b032361902d2f284daa765d536c8f2a821f659effe +size 31331 diff --git a/Content/Engine/DefaultRadialMenu.flax b/Content/Engine/DefaultRadialMenu.flax index db233a854..2b3d7f0de 100644 --- a/Content/Engine/DefaultRadialMenu.flax +++ b/Content/Engine/DefaultRadialMenu.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9e9e01ae0222d9dc0db7c51741c3e2c2595bb550fa9dd1665d11ce4fac0afc9 -size 20468 +oid sha256:2b0272c8f2df095e6609f49845a3d329daaf634e0776ca764e4c51596cac60ff +size 20514 diff --git a/Content/Engine/DefaultTerrainMaterial.flax b/Content/Engine/DefaultTerrainMaterial.flax index 83e892360..8d195ab98 100644 --- a/Content/Engine/DefaultTerrainMaterial.flax +++ b/Content/Engine/DefaultTerrainMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c890a5b43c0158d1885c3905ab02c8b0ee36a558c4770d79892837960ee07e5 -size 23628 +oid sha256:a86caeef4de5a84783ba34208701c0f272f3b4b3ff82c64c2553d6aec631e07b +size 23301 diff --git a/Content/Engine/SkyboxMaterial.flax b/Content/Engine/SkyboxMaterial.flax index a8b1b327f..8faccf8c0 100644 --- a/Content/Engine/SkyboxMaterial.flax +++ b/Content/Engine/SkyboxMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e7d0fe3697a0b65eefb8756e0f9aa48bc2d505eb94710b5972097eb61b6b029 -size 30944 +oid sha256:8149367ccbef36932866e6af53fedf79931f26677db5dfcce71ba33caeff5980 +size 31070 From cb92a2b8cb4158826981830bf88ad81375b75426 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Aug 2025 13:04:57 +0200 Subject: [PATCH 64/82] Optimize decals rendering with depth test #3599 --- .../Materials/DecalMaterialShader.cpp | 20 +++++++++---------- Source/Engine/Renderer/GBufferPass.cpp | 9 +++++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index e93338604..dc510f331 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -47,7 +47,9 @@ void DecalMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); // Decals use depth buffer to draw on top of the objects - context->BindSR(0, GET_TEXTURE_VIEW_SAFE(params.RenderContext.Buffers->DepthBuffer)); + GPUTexture* depthBuffer = params.RenderContext.Buffers->DepthBuffer; + GPUTextureView* depthBufferView = EnumHasAnyFlags(depthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View(); + context->BindSR(0, depthBufferView); // Setup material constants { @@ -90,16 +92,20 @@ void DecalMaterialShader::Unload() bool DecalMaterialShader::Load() { GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultNoDepth; - psDesc0.VS = _shader->GetVS("VS_Decal"); + psDesc0.VS = _shader->GetVS("VS_Decal"); // TODO: move VS_Decal to be shared (eg. in GBuffer.shader) if (psDesc0.VS == nullptr) return true; psDesc0.PS = _shader->GetPS("PS_Decal"); psDesc0.CullMode = CullMode::Normal; + if (GPUDevice::Instance->Limits.HasReadOnlyDepth) + { + psDesc0.DepthEnable = true; + psDesc0.DepthWriteEnable = false; + } switch (_info.DecalBlendingMode) { case MaterialDecalBlendingMode::Translucent: - { psDesc0.BlendMode.BlendEnable = true; psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::SrcAlpha; psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha; @@ -107,9 +113,7 @@ bool DecalMaterialShader::Load() psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One; psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB; break; - } case MaterialDecalBlendingMode::Stain: - { psDesc0.BlendMode.BlendEnable = true; psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::DestColor; psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha; @@ -117,9 +121,7 @@ bool DecalMaterialShader::Load() psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One; psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB; break; - } case MaterialDecalBlendingMode::Normal: - { psDesc0.BlendMode.BlendEnable = true; psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::SrcAlpha; psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha; @@ -127,13 +129,10 @@ bool DecalMaterialShader::Load() psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One; psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB; break; - } case MaterialDecalBlendingMode::Emissive: - { psDesc0.BlendMode = BlendingMode::Additive; break; } - } _cache.Outside = GPUDevice::Instance->CreatePipelineState(); if (_cache.Outside->Init(psDesc0)) @@ -143,6 +142,7 @@ bool DecalMaterialShader::Load() } psDesc0.CullMode = CullMode::Inverted; + psDesc0.DepthEnable = false; _cache.Inside = GPUDevice::Instance->CreatePipelineState(); if (_cache.Inside->Init(psDesc0)) { diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index ee021d0ea..9d3684010 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -434,6 +434,7 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light PROFILE_GPU_CPU("Decals"); auto context = GPUDevice::Instance->GetMainContext(); auto buffers = renderContext.Buffers; + GPUTextureView* depthBuffer = EnumHasAnyFlags(buffers->DepthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? buffers->DepthBuffer->ViewReadOnlyDepth() : nullptr; // Sort decals from the lowest order to the highest order Sorting::QuickSort(decals.Get(), decals.Count(), &SortDecal); @@ -484,22 +485,22 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light count++; targetBuffers[2] = buffers->GBuffer1->View(); } - context->SetRenderTarget(nullptr, ToSpan(targetBuffers, count)); + context->SetRenderTarget(depthBuffer, ToSpan(targetBuffers, count)); break; } case MaterialDecalBlendingMode::Stain: { - context->SetRenderTarget(buffers->GBuffer0->View()); + context->SetRenderTarget(depthBuffer, buffers->GBuffer0->View()); break; } case MaterialDecalBlendingMode::Normal: { - context->SetRenderTarget(buffers->GBuffer1->View()); + context->SetRenderTarget(depthBuffer, buffers->GBuffer1->View()); break; } case MaterialDecalBlendingMode::Emissive: { - context->SetRenderTarget(lightBuffer); + context->SetRenderTarget(depthBuffer, lightBuffer); break; } } From bf9ca14deb6ee5180f074c5cbeb3aa44f05af62f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Aug 2025 22:14:03 +0200 Subject: [PATCH 65/82] Fix sampling textures in decals to use custom mip-level #3599 --- Content/Editor/MaterialTemplates/Decal.shader | 61 +++++++++++++++++-- .../Materials/DecalMaterialShader.cpp | 4 +- .../MaterialGenerator.Textures.cpp | 19 ++++-- 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Decal.shader b/Content/Editor/MaterialTemplates/Decal.shader index 06b44d498..b933fcbb3 100644 --- a/Content/Editor/MaterialTemplates/Decal.shader +++ b/Content/Editor/MaterialTemplates/Decal.shader @@ -13,7 +13,7 @@ META_CB_BEGIN(0, Data) float4x4 WorldMatrix; float4x4 InvWorld; -float4x4 SVPositionToWorld; +float4x4 SvPositionToWorld; @1META_CB_END // Use depth buffer for per-pixel decal layering @@ -27,12 +27,63 @@ struct MaterialInput float3 WorldPosition; float TwoSidedSign; float2 TexCoord; + float4 TexCoord_DDX_DDY; float3x3 TBN; float4 SvPosition; float3 PreSkinnedPosition; float3 PreSkinnedNormal; }; +// Calculates decal texcoords for a given pixel position (sampels depth buffer and projects value to decal space). +float2 SvPositionToDecalUV(float4 svPosition) +{ + float2 screenUV = svPosition.xy * ScreenSize.zw; + svPosition.z = SAMPLE_RT(DepthBuffer, screenUV).r; + float4 positionHS = mul(float4(svPosition.xyz, 1), SvPositionToWorld); + float3 positionWS = positionHS.xyz / positionHS.w; + float3 positionOS = mul(float4(positionWS, 1), InvWorld).xyz; + return positionOS.xz + 0.5f; +} + +// Manually compute ddx/ddy for decal texture cooordinates to avoid the 2x2 pixels artifacts on the edges of geometry under decal +// [Reference: https://www.humus.name/index.php?page=3D&ID=84] +float4 CalculateTextureDerivatives(float4 svPosition, float2 texCoord) +{ + float4 svDiffX = float4(1, 0, 0, 0); + float2 uvDiffX0 = texCoord - SvPositionToDecalUV(svPosition - svDiffX); + float2 uvDiffX1 = SvPositionToDecalUV(svPosition + svDiffX) - texCoord; + float2 dx = dot(uvDiffX0, uvDiffX0) < dot(uvDiffX1, uvDiffX1) ? uvDiffX0 : uvDiffX1; + + float4 svDiffY = float4(0, 1, 0, 0); + float2 uvDiffY0 = texCoord - SvPositionToDecalUV(svPosition - svDiffY); + float2 uvDiffY1 = SvPositionToDecalUV(svPosition + svDiffY) - texCoord; + float2 dy = dot(uvDiffY0, uvDiffY0) < dot(uvDiffY1, uvDiffY1) ? uvDiffY0 : uvDiffY1; + + return float4(dx, dy); +} + +// Computes the mipmap level for a specific texture dimensions to be sampled at decal texture cooordinates. +// [Reference: https://hugi.scene.org/online/coding/hugi%2014%20-%20comipmap.htm] +float CalculateTextureMipmap(MaterialInput input, float2 textureSize) +{ + float2 dx = input.TexCoord_DDX_DDY.xy * textureSize; + float2 dy = input.TexCoord_DDX_DDY.zw * textureSize; + float d = max(dot(dx, dx), dot(dy, dy)); + return (0.5 * 0.5) * log2(d); // Hardcoded half-mip rate reduction to avoid artifacts when decal is moved over dither texture +} +float CalculateTextureMipmap(MaterialInput input, Texture2D t) +{ + float2 textureSize; + t.GetDimensions(textureSize.x, textureSize.y); + return CalculateTextureMipmap(input, textureSize); +} +float CalculateTextureMipmap(MaterialInput input, TextureCube t) +{ + float2 textureSize; + t.GetDimensions(textureSize.x, textureSize.y); + return CalculateTextureMipmap(input, textureSize); +} + // Transforms a vector from tangent space to world space float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) { @@ -116,7 +167,6 @@ Material GetMaterialPS(MaterialInput input) } // Input macro specified by the material: DECAL_BLEND_MODE - #define DECAL_BLEND_MODE_TRANSLUCENT 0 #define DECAL_BLEND_MODE_STAIN 1 #define DECAL_BLEND_MODE_NORMAL 2 @@ -153,7 +203,7 @@ void PS_Decal( float2 screenUV = SvPosition.xy * ScreenSize.zw; SvPosition.z = SAMPLE_RT(DepthBuffer, screenUV).r; - float4 positionHS = mul(float4(SvPosition.xyz, 1), SVPositionToWorld); + float4 positionHS = mul(float4(SvPosition.xyz, 1), SvPositionToWorld); float3 positionWS = positionHS.xyz / positionHS.w; float3 positionOS = mul(float4(positionWS, 1), InvWorld).xyz; @@ -166,8 +216,9 @@ void PS_Decal( materialInput.TexCoord = decalUVs; materialInput.TwoSidedSign = 1; materialInput.SvPosition = SvPosition; - - // Build tangent to world transformation matrix + materialInput.TexCoord_DDX_DDY = CalculateTextureDerivatives(materialInput.SvPosition, materialInput.TexCoord); + + // Calculate tangent-space float3 ddxWp = ddx(positionWS); float3 ddyWp = ddy(positionWS); materialInput.TBN[0] = normalize(ddyWp); diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index dc510f331..ffc8fa241 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -16,7 +16,7 @@ PACK_STRUCT(struct DecalMaterialShaderData { Matrix WorldMatrix; Matrix InvWorld; - Matrix SVPositionToWorld; + Matrix SvPositionToWorld; }); DrawPass DecalMaterialShader::GetDrawModes() const @@ -67,7 +67,7 @@ void DecalMaterialShader::Bind(BindParameters& params) 0, 0, 1, 0, -1.0f, 1.0f, 0, 1); const Matrix svPositionToWorld = offsetMatrix * view.IVP; - Matrix::Transpose(svPositionToWorld, materialData->SVPositionToWorld); + Matrix::Transpose(svPositionToWorld, materialData->SvPositionToWorld); } // Bind constants diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index 608f57ec3..6bd88f2ae 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -34,7 +34,6 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B const bool isArray = texture->Type == MaterialParameterType::GPUTextureArray; const bool isVolume = texture->Type == MaterialParameterType::GPUTextureVolume; const bool isNormalMap = texture->Type == MaterialParameterType::NormalMap; - const bool canUseSample = CanUseSample(_treeType); MaterialGraphBox* valueBox = parent->GetBox(1); // Check if has variable assigned @@ -63,6 +62,16 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B // Check if hasn't been sampled during that tree eating if (valueBox->Cache.IsInvalid()) { + bool canUseSample = CanUseSample(_treeType); + String mipLevel = TEXT("0"); + const auto layer = GetRootLayer(); + if (layer && layer->Domain == MaterialDomain::Decal && _treeType == MaterialTreeType::PixelShader) + { + // Decals use computed mip level due to ddx/ddy being unreliable + canUseSample = false; + mipLevel = String::Format(TEXT("CalculateTextureMipmap(input, {})"), texture->ShaderName); + } + // Check if use custom UVs String uv; MaterialGraphBox* uvBox = parent->GetBox(0); @@ -94,10 +103,10 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B // Sample texture if (isNormalMap) { - const Char* format = canUseSample ? TEXT("{0}.Sample({1}, {2}).xyz") : TEXT("{0}.SampleLevel({1}, {2}, 0).xyz"); + const Char* format = canUseSample ? TEXT("{0}.Sample({1}, {2}).xyz") : TEXT("{0}.SampleLevel({1}, {2}, {3}).xyz"); // Sample encoded normal map - const String sampledValue = String::Format(format, texture->ShaderName, sampler, uv); + const String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, mipLevel); const auto normalVector = writeLocal(VariantType::Float3, sampledValue, parent); // Decode normal vector @@ -123,12 +132,12 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B } else*/ { - format = canUseSample ? TEXT("{0}.Sample({1}, {2})") : TEXT("{0}.SampleLevel({1}, {2}, 0)"); + format = canUseSample ? TEXT("{0}.Sample({1}, {2})") : TEXT("{0}.SampleLevel({1}, {2}, {3})"); } } // Sample texture - String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, _ddx.Value, _ddy.Value); + String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, mipLevel); valueBox->Cache = writeLocal(VariantType::Float4, sampledValue, parent); } } From 774b6bd72c5db479caba54c9ec83ee83953e23d3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 15 Aug 2025 13:09:05 +0200 Subject: [PATCH 66/82] Update engine materials --- Content/Editor/CubeTexturePreviewMaterial.flax | 4 ++-- Content/Editor/DebugMaterials/DDGIDebugProbes.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Decal.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Particle.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Surface.flax | 4 ++-- .../Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Terrain.flax | 4 ++-- Content/Editor/DefaultFontMaterial.flax | 4 ++-- Content/Editor/Gizmo/FoliageBrushMaterial.flax | 4 ++-- Content/Editor/Gizmo/MaterialWire.flax | 4 ++-- Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax | 4 ++-- Content/Editor/IesProfilePreviewMaterial.flax | 4 ++-- Content/Editor/Particles/Smoke Material.flax | 4 ++-- Content/Editor/SpriteMaterial.flax | 4 ++-- Content/Editor/Terrain/Circle Brush Material.flax | 4 ++-- Content/Editor/Terrain/Highlight Terrain Material.flax | 4 ++-- Content/Editor/TexturePreviewMaterial.flax | 4 ++-- Content/Editor/Wires Debug Material.flax | 4 ++-- Content/Engine/DefaultDeformableMaterial.flax | 4 ++-- Content/Engine/SingleColorMaterial.flax | 4 ++-- 20 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Content/Editor/CubeTexturePreviewMaterial.flax b/Content/Editor/CubeTexturePreviewMaterial.flax index b0b54b394..973a01177 100644 --- a/Content/Editor/CubeTexturePreviewMaterial.flax +++ b/Content/Editor/CubeTexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:857edec8daa8bbd5bf16c839f65042df7a921154dcf48c1ac7a06aab4e40eab6 -size 30999 +oid sha256:6ecf44ea82025d0f491c68b0470e1704ca5f385bd54ad196d6912aeb2f3aee0f +size 31125 diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax index ee6bd8d7b..d598ac9bb 100644 --- a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax +++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb32df5fa9255c8d27f968819ab7f59236640661c83ae33588733542ea635b0f -size 40232 +oid sha256:f6e897a2fcbbb21efdd589604b4e3fc657f457ea124384842b380295af66cf13 +size 40358 diff --git a/Content/Editor/DebugMaterials/SingleColor/Decal.flax b/Content/Editor/DebugMaterials/SingleColor/Decal.flax index 02126626b..6024d3961 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Decal.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Decal.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fddc7cd2d8732f8eef008d0a2bdc47948b87a14849d9e6fabcfe60ddb218aa42 -size 7617 +oid sha256:d8301da35f0b45f58f5c59221bd22bf0a8500555d31ab84ec1c8cd5eca9fc101 +size 9973 diff --git a/Content/Editor/DebugMaterials/SingleColor/Particle.flax b/Content/Editor/DebugMaterials/SingleColor/Particle.flax index bbab3ee4b..9c2c0755a 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Particle.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Particle.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e06851af881852106892b2bd03df0127c84db3a601abd9d7aaac7baf40343369 -size 32040 +oid sha256:4e432328bb19eaa58caf35f60cd6495a46cca694314828010f3be401d7de9434 +size 32108 diff --git a/Content/Editor/DebugMaterials/SingleColor/Surface.flax b/Content/Editor/DebugMaterials/SingleColor/Surface.flax index dd9785be9..0fb0d1c6f 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Surface.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Surface.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34080fbc40ed62bc5501969640403013291b3104dbb4374d9268994f3902e5a8 -size 29180 +oid sha256:1688861997cc2e8a433cdea81ee62662f45b261bc863cdb9431beed612f0aad7 +size 29306 diff --git a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax index 2ec83813f..e5bda89f6 100644 --- a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax +++ b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71acbd5242dd2d2f02554af0e5a95da9a5bd90d77aef80dc6fcdaefb88251d23 -size 31365 +oid sha256:42363fad30e29b0e0c4cf7ad9e02dc91040eb6821c3c28bd49996771e65893c4 +size 31559 diff --git a/Content/Editor/DebugMaterials/SingleColor/Terrain.flax b/Content/Editor/DebugMaterials/SingleColor/Terrain.flax index ac31c7161..09a253a8d 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Terrain.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Terrain.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dcc185a2ec2e02ee9f52c806ba3756827c6d12f586c91a12b44295a95dd093dc -size 21331 +oid sha256:eb957ea2ee358b0e611d6612703261c9837099b6d03d48484cdab1151a461d8f +size 21004 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index 23676ca21..b4f659e34 100644 --- a/Content/Editor/DefaultFontMaterial.flax +++ b/Content/Editor/DefaultFontMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1500a05e700f3b12d7e3984a978773c61f4c9ecc34ec96720fae724a87bab8e -size 29501 +oid sha256:7a46410b49a7e38c4c2569e4d8d8c8f4744cf26f79c632a53899ce753ab7c88a +size 29627 diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax index d88a8eea3..1d8e89a65 100644 --- a/Content/Editor/Gizmo/FoliageBrushMaterial.flax +++ b/Content/Editor/Gizmo/FoliageBrushMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10aff82173474030b7ce41b491c803cc0de265ea0317a3aca87102099d65ca79 -size 37392 +oid sha256:f377cc4e05d89e4edbec6382a052abe571f3261bc273cd4475541b7b7051cffb +size 37586 diff --git a/Content/Editor/Gizmo/MaterialWire.flax b/Content/Editor/Gizmo/MaterialWire.flax index 26f3956c5..4b30df5f5 100644 --- a/Content/Editor/Gizmo/MaterialWire.flax +++ b/Content/Editor/Gizmo/MaterialWire.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbc08de78faa00aab330168dce166867d85dcae142081219d6a7eeb5f285ffd7 -size 31216 +oid sha256:ff3e7b3e77afa7191f1db9cf12f21908b80bb8f71e832c37e55547dc9dcab31c +size 31410 diff --git a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax index 01ba9f46a..305c6a4c2 100644 --- a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax +++ b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a32d40e1f13606fea29e45cb0df1a45dbcc51eb329963d8db18ff64fa66bd4e -size 30293 +oid sha256:4458a9483e81fb0526cc395f93eeae238f4f91fa5d4889e3196b6530a8f17ec2 +size 30419 diff --git a/Content/Editor/IesProfilePreviewMaterial.flax b/Content/Editor/IesProfilePreviewMaterial.flax index c23d9981d..c7a025108 100644 --- a/Content/Editor/IesProfilePreviewMaterial.flax +++ b/Content/Editor/IesProfilePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6388c4a40a0de927abdd532d5bfaff5b7c651f8daa434c6f04a0579e33a1015b -size 20399 +oid sha256:41210c6c490513503f01e8a628d80dd98e58fc0482f9966f9342700118ccd04c +size 20445 diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index d5b8cb872..7a8fdeb1a 100644 --- a/Content/Editor/Particles/Smoke Material.flax +++ b/Content/Editor/Particles/Smoke Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a16a3fa5bed3bc8030c40fbe0e946f2bdec28745542bf08db1d7b4a43180f785 -size 38900 +oid sha256:96d3865771cfa47ad59e0493c8f1b6e9fd9950593b2b929c91ea2692eca71efd +size 38495 diff --git a/Content/Editor/SpriteMaterial.flax b/Content/Editor/SpriteMaterial.flax index 8d001b9c7..876a38a56 100644 --- a/Content/Editor/SpriteMaterial.flax +++ b/Content/Editor/SpriteMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ed88e5e8b513f7460e94942ad722d8b193c013434586f4382673f7dc3074e98 -size 30376 +oid sha256:5d02a6d11ea83ea6c519a1644950750e58433e6a2aea9a2daa646db1d2b0c293 +size 30502 diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax index f2f21d189..af191fdaa 100644 --- a/Content/Editor/Terrain/Circle Brush Material.flax +++ b/Content/Editor/Terrain/Circle Brush Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ab7e9c88c9896f37ac5a43efe176e13f98eafacc3117b49cd7173b31376b21d -size 28003 +oid sha256:8e9ef5186642a38af8ebb5856a891215686576b23841aabe16b7dde7f2bcb57f +size 27676 diff --git a/Content/Editor/Terrain/Highlight Terrain Material.flax b/Content/Editor/Terrain/Highlight Terrain Material.flax index d1e4226f5..14bd86c35 100644 --- a/Content/Editor/Terrain/Highlight Terrain Material.flax +++ b/Content/Editor/Terrain/Highlight Terrain Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40e38d672c13c06a84366689b8ce2b346179b4aa0de3cf1fc6035b33ad036e4a -size 21506 +oid sha256:5f1a9524d9bc7ee41df761b9fb34613fc04357377a81836fb28b3ee5a6f2dcf4 +size 21179 diff --git a/Content/Editor/TexturePreviewMaterial.flax b/Content/Editor/TexturePreviewMaterial.flax index 0bcb9b77f..fac30b4f1 100644 --- a/Content/Editor/TexturePreviewMaterial.flax +++ b/Content/Editor/TexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93b0220308d2a3051d7e449505ca18dab7a82d46bf6a86e4ecfb9b741909f91b -size 10698 +oid sha256:c178081b59417439b2d523486f3c7e4f7f391ff57e5ab5a565aadf3fd31f5488 +size 10744 diff --git a/Content/Editor/Wires Debug Material.flax b/Content/Editor/Wires Debug Material.flax index bdf2bde08..8fff1a174 100644 --- a/Content/Editor/Wires Debug Material.flax +++ b/Content/Editor/Wires Debug Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7e22035ac54615fee78cad71d5e5268ff139811d4390fe9a5025ab4d234ff4e -size 29910 +oid sha256:c31daed51b38e6aa9eeceaad85498d6ae7079f7b125c6f71f036799278c34e22 +size 30104 diff --git a/Content/Engine/DefaultDeformableMaterial.flax b/Content/Engine/DefaultDeformableMaterial.flax index b9e972b63..8af3db999 100644 --- a/Content/Engine/DefaultDeformableMaterial.flax +++ b/Content/Engine/DefaultDeformableMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:feac2f9ced2d8edf6a43f1c56cdfdc27bbd018a4c23a527a403c30bdb9217e63 -size 18922 +oid sha256:df767eeb060d058d502271f1cd89581c57ad339cd690cc9c588f9a34cc9344b1 +size 18985 diff --git a/Content/Engine/SingleColorMaterial.flax b/Content/Engine/SingleColorMaterial.flax index 515c48cea..b6906b930 100644 --- a/Content/Engine/SingleColorMaterial.flax +++ b/Content/Engine/SingleColorMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27acfeeafdb50abe76a2feee0616ea90ee5d4fd72c678244d80f15ed4a97894a -size 29381 +oid sha256:74d77dbfdf72c9281b0760a266adac7f1eb849f9656ea8da5cd8951f2fab5343 +size 29507 From 38b4ace1a85b90e7f5b31c2abcceeb3f43729406 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 15 Aug 2025 07:01:48 -0500 Subject: [PATCH 67/82] Use material base as ctor parameter. --- Source/Engine/UI/GUI/Brushes/MaterialBrush.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs b/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs index 423e5ad70..13e9dab24 100644 --- a/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs @@ -25,15 +25,7 @@ namespace FlaxEngine.GUI /// Initializes a new instance of the struct. /// /// The material. - public MaterialBrush(Material material) - { - Material = material; - } - /// - /// Initializes a new instance of the struct. - /// - /// The material. - public MaterialBrush(MaterialInstance material) + public MaterialBrush(MaterialBase material) { Material = material; } From f21accd4662b5c728a09f2b1119c997850c16993 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 15 Aug 2025 14:19:59 +0200 Subject: [PATCH 68/82] Fix crash when memory stream reading fails and perform soft error handling #3612 --- Source/Engine/Content/Assets/Animation.cpp | 2 +- Source/Engine/Content/Assets/Model.cpp | 4 ++-- Source/Engine/Content/Assets/ModelBase.cpp | 2 +- Source/Engine/Content/Assets/SkinnedModel.cpp | 2 +- Source/Engine/Serialization/MemoryReadStream.cpp | 6 +++++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 54c4d898e..62fc737dc 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -531,7 +531,7 @@ bool Animation::SaveHeader(const ModelData& modelData, WriteStream& stream, int3 // Nested animations stream.WriteInt32(0); // Empty list - return false; + return stream.HasError(); } void Animation::GetReferences(Array& assets, Array& files) const diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 246bee3b4..32a610206 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -395,7 +395,7 @@ bool Model::LoadHeader(ReadStream& stream, byte& headerVersion) } } - return false; + return stream.HasError(); } #if USE_EDITOR @@ -454,7 +454,7 @@ bool Model::SaveHeader(WriteStream& stream, const ModelData& modelData) } } - return false; + return stream.HasError(); } bool Model::Save(bool withMeshDataFromGpu, Function& getChunk) const diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index e993bbec6..2db652424 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -329,7 +329,7 @@ bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion) stream.Read(slot.Name, 11); } - return false; + return stream.HasError(); } bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly) diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 9ca530a53..ac72dba59 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -656,7 +656,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion) } } - return false; + return stream.HasError(); } #if USE_EDITOR diff --git a/Source/Engine/Serialization/MemoryReadStream.cpp b/Source/Engine/Serialization/MemoryReadStream.cpp index b84cb3af6..e6a1acea1 100644 --- a/Source/Engine/Serialization/MemoryReadStream.cpp +++ b/Source/Engine/Serialization/MemoryReadStream.cpp @@ -61,7 +61,11 @@ void MemoryReadStream::ReadBytes(void* data, uint32 bytes) { if (bytes > 0) { - ASSERT(data && GetLength() - GetPosition() >= bytes); + if (!data || GetLength() - GetPosition() < bytes) + { + _hasError = true; + return; + } Platform::MemoryCopy(data, _position, bytes); _position += bytes; } From 6ccfbfeff16e7c2ff7c0101c4905676a705fc56e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 15 Aug 2025 14:20:16 +0200 Subject: [PATCH 69/82] Fix saving skinned models with blend shapes #3612 --- Source/Engine/Content/Assets/SkinnedModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index ac72dba59..1f6ceff4d 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -686,7 +686,7 @@ bool SkinnedModel::SaveHeader(WriteStream& stream) const const int32 blendShapes = mesh.BlendShapes.Count(); stream.Write((uint16)blendShapes); for (const auto& blendShape : mesh.BlendShapes) - blendShape.Save(stream); + blendShape.SaveHeader(stream); } } From fc2112ec9378d0699b11b085951b3cf3766d9aaf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 15 Aug 2025 14:34:02 +0200 Subject: [PATCH 70/82] Fix SSAO artifacts with "flat normals" look #3617 --- Content/Shaders/SSAO.flax | 4 ++-- Source/Shaders/SSAO.shader | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Content/Shaders/SSAO.flax b/Content/Shaders/SSAO.flax index d628818a4..d9374b133 100644 --- a/Content/Shaders/SSAO.flax +++ b/Content/Shaders/SSAO.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0a576e8a8b818d06ff258874c8d73b273d05c2b8856ff6c14cf0accb2f23f52 -size 36832 +oid sha256:3470722e78ef45616e1d86373e32e40e219f911c88dd98313ee567cccfd10cf5 +size 36833 diff --git a/Source/Shaders/SSAO.shader b/Source/Shaders/SSAO.shader index 6fc3c523c..5116b3cb9 100644 --- a/Source/Shaders/SSAO.shader +++ b/Source/Shaders/SSAO.shader @@ -56,7 +56,7 @@ static const uint g_numTaps[4] = { 3, 5, 8, 12 }; #define SSAO_NORMAL_BASED_EDGES_ENABLE_AT_QUALITY_PRESET (2) // to disable simply set to 99 or similar #define SSAO_NORMAL_BASED_EDGES_DOT_THRESHOLD (0.5) // use 0-0.1 for super-sharp normal-based edges // -#define SSAO_DETAIL_AO_ENABLE_AT_QUALITY_PRESET (1) // whether to use DetailAOStrength; to disable simply set to 99 or similar +#define SSAO_DETAIL_AO_ENABLE_AT_QUALITY_PRESET (99) // whether to use DetailAOStrength; to disable simply set to 99 or similar // #define SSAO_DEPTH_MIPS_ENABLE_AT_QUALITY_PRESET (99) // !!warning!! the MIP generation on the C++ side will be enabled on quality preset 2 regardless of this value, so if changing here, change the C++ side too #define SSAO_DEPTH_MIPS_GLOBAL_OFFSET (-4.3) // best noise/quality/performance tradeoff, found empirically From a02b7d4a1ad4c02c429e17a49d6b19110e26cc9f Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 17 Aug 2025 11:58:42 -0500 Subject: [PATCH 71/82] Add events for when audio settings are changed for the game window. --- Source/Editor/Windows/GameWindow.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index acb2deda3..12897eb3c 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -63,6 +63,16 @@ namespace FlaxEditor.Windows }, }; + /// + /// Fired when the game window audio is muted. + /// + public event Action MuteAudio; + + /// + /// Fired when the game window master audio volume is changed. + /// + public event Action MasterVolumeChanged; + /// /// Gets the viewport. /// @@ -120,6 +130,7 @@ namespace FlaxEditor.Windows { Audio.MasterVolume = value ? 0 : AudioVolume; _audioMuted = value; + MuteAudio?.Invoke(); } } @@ -134,6 +145,7 @@ namespace FlaxEditor.Windows if (!AudioMuted) Audio.MasterVolume = value; _audioVolume = value; + MasterVolumeChanged?.Invoke(value); } } From 6a8553a2777f8dd7fa2ffa9e7ec4db070252c462 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Aug 2025 11:03:50 +0200 Subject: [PATCH 72/82] Fix incorrect Lambert Diffuse shadowing to use just `N dot L` for accurate lighting #3615 #3616 --- Source/Shaders/Lighting.hlsl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/Shaders/Lighting.hlsl b/Source/Shaders/Lighting.hlsl index 1429e45d1..9b14db5ed 100644 --- a/Source/Shaders/Lighting.hlsl +++ b/Source/Shaders/Lighting.hlsl @@ -122,6 +122,10 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f // Calculate shadow ShadowSample shadow = GetShadow(lightData, gBuffer, shadowMask); +#if !LIGHTING_NO_DIRECTIONAL + // Directional shadowing + shadow.SurfaceShadow *= NoL; +#endif // Calculate attenuation if (isRadial) @@ -135,11 +139,6 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f shadow.TransmissionShadow *= attenuation; } -#if !LIGHTING_NO_DIRECTIONAL - // Reduce shadow mapping artifacts - shadow.SurfaceShadow *= saturate(NoL * 6.0f - 0.2f) * NoL; -#endif - BRANCH if (shadow.SurfaceShadow + shadow.TransmissionShadow > 0) { From 2c1713d300c34be0b13fdd8997871add248ab576 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 19 Aug 2025 23:00:29 +0200 Subject: [PATCH 73/82] Fix rare issues with shift tree selection --- Source/Editor/GUI/Tree/Tree.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index 5405a6f24..5530a1738 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -399,14 +399,17 @@ namespace FlaxEditor.GUI.Tree bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown); // Use last selection for last selected node if sift is down - if (shiftDown) + if (Selection.Count < 2) + _lastSelectedNode = null; + else if (shiftDown) _lastSelectedNode ??= Selection[^1]; - var node = _lastSelectedNode ?? SelectedNode; - - if (Selection.Count == 0) + // Skip root to prevent blocking input + if (_lastSelectedNode != null && _lastSelectedNode.IsRoot) _lastSelectedNode = null; + var node = _lastSelectedNode ?? SelectedNode; + // Check if has focus and if any node is focused and it isn't a root if (ContainsFocus && node != null && node.AutoFocus) { @@ -435,7 +438,7 @@ namespace FlaxEditor.GUI.Tree // Select parent if (toSelect.Contains(parentNode)) toSelect.Remove(node); - else + else if (parentNode != null) toSelect.Add(parentNode); _lastSelectedNode = parentNode; } @@ -450,7 +453,7 @@ namespace FlaxEditor.GUI.Tree select = select.GetChild(select.ChildrenCount - 1) as TreeNode; } - if (toSelect.Contains(select)) + if (select == null || toSelect.Contains(select)) toSelect.Remove(node); else toSelect.Add(select); @@ -464,7 +467,7 @@ namespace FlaxEditor.GUI.Tree { // Select the first child var select = node.GetChild(0) as TreeNode; - if (toSelect.Contains(select)) + if (select == null || toSelect.Contains(select)) toSelect.Remove(node); else toSelect.Add(select); @@ -483,7 +486,7 @@ namespace FlaxEditor.GUI.Tree } parentNode = parentNode.Parent as TreeNode; } - if (toSelect.Contains(select)) + if (select == null || toSelect.Contains(select)) toSelect.Remove(node); else toSelect.Add(select); @@ -493,7 +496,7 @@ namespace FlaxEditor.GUI.Tree { // Select next parent child var select = nodeParent.GetChild(myIndex + 1) as TreeNode; - if (toSelect.Contains(select)) + if (select == null || toSelect.Contains(select)) toSelect.Remove(node); else toSelect.Add(select); From 4a28b4bd6c166875bd8f946fba84007cae6079f4 Mon Sep 17 00:00:00 2001 From: Zode Date: Fri, 22 Aug 2025 01:26:34 +0300 Subject: [PATCH 74/82] Expose hideOnClose and scrollBars in constructor for CustomEditorWindows. --- Source/Editor/CustomEditorWindow.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Editor/CustomEditorWindow.cs b/Source/Editor/CustomEditorWindow.cs index f28a844ef..3df453ef8 100644 --- a/Source/Editor/CustomEditorWindow.cs +++ b/Source/Editor/CustomEditorWindow.cs @@ -18,8 +18,8 @@ namespace FlaxEditor private readonly CustomEditorPresenter _presenter; private CustomEditorWindow _customEditor; - public Win(CustomEditorWindow customEditor) - : base(Editor.Instance, false, ScrollBars.Vertical) + public Win(CustomEditorWindow customEditor, bool hideOnClose, ScrollBars scrollBars) + : base(Editor.Instance, hideOnClose, scrollBars) { Title = customEditor.GetType().Name; _customEditor = customEditor; @@ -64,9 +64,9 @@ namespace FlaxEditor /// /// Initializes a new instance of the class. /// - protected CustomEditorWindow() + protected CustomEditorWindow(bool hideOnClose = false, ScrollBars scrollBars = ScrollBars.Vertical) { - _win = new Win(this); + _win = new Win(this, hideOnClose, scrollBars); ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; } From cb07ee77aa19432da67418980da6708e9818d4bf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 22 Aug 2025 22:31:08 +0200 Subject: [PATCH 75/82] Change #3588 to be editor-only --- Source/Engine/Particles/ParticleEffect.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 25369934d..de92358f0 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -548,11 +548,14 @@ void ParticleEffect::OnParticleSystemModified() void ParticleEffect::OnParticleSystemLoaded() { ApplyModifiedParameters(); +#if USE_EDITOR + // When one of the emitters gets edited, cached parameters need to be applied auto& emitters = ParticleSystem.Get()->Emitters; for (auto& emitter : emitters) { emitter.Loaded.BindUnique(this); } +#endif } void ParticleEffect::OnParticleEmitterLoaded() From d6c75b3f8668c34748919c2c85d60573d902507d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 24 Aug 2025 13:37:48 +0200 Subject: [PATCH 76/82] Simplify code in #3119 --- Source/Editor/Windows/SplashScreen.cpp | 53 +++++++++----------------- Source/Editor/Windows/SplashScreen.h | 8 ++-- 2 files changed, 22 insertions(+), 39 deletions(-) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 4707ffd6f..7e2889ca1 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -9,6 +9,7 @@ #include "Engine/Render2D/Font.h" #include "Engine/Render2D/TextLayoutOptions.h" #include "Engine/Render2D/Render2D.h" +#include "Engine/Platform/FileSystem.h" #include "Engine/Content/Content.h" #include "FlaxEngine.Gen.h" @@ -188,8 +189,7 @@ void SplashScreen::Show() // Setup _dpiScale = dpiScale; - _width = settings.Size.X; - _height = settings.Size.Y; + _size = settings.Size; _startTime = DateTime::NowUTC(); auto str = Globals::ProjectFolder; #if PLATFORM_WIN32 @@ -214,26 +214,11 @@ void SplashScreen::Show() font->OnLoaded.Bind(this); } - // Load Splash image - String splashImagePath = String::Format(TEXT("{0}/{1}"), Globals::ProjectContentFolder, TEXT("SplashImage/SplashImage.flax")); -#if PLATFORM_WIN32 - splashImagePath.Replace('/', '\\'); -#else - splashImagePath.Replace('\\', '/'); -#endif - - auto texture = Content::LoadAsync(*splashImagePath); - if (texture == nullptr) - { - LOG(Info, "Cannot load splash Texture at {0}", splashImagePath); - } - else - { - if (texture->IsLoaded()) - OnTextureLoaded(texture); - else - texture->OnLoaded.Bind(this); - } + // Load custom image + _splashTexture.Loaded.Bind(this); + String splashImagePath = Globals::ProjectContentFolder / TEXT("SplashImage.flax"); + if (FileSystem::FileExists(splashImagePath)) + _splashTexture = Content::LoadAsync(splashImagePath); _window->Show(); } @@ -248,6 +233,10 @@ void SplashScreen::Close() // Close window _window->Close(ClosingReason::CloseEvent); _window = nullptr; + + _titleFont = nullptr; + _subtitleFont = nullptr; + _splashTexture = nullptr; } void SplashScreen::OnShown() @@ -260,8 +249,8 @@ void SplashScreen::OnShown() void SplashScreen::OnDraw() { const float s = _dpiScale; - const float width = _width; - const float height = _height; + const float width = _size.X; + const float height = _size.Y; // Peek time const float time = static_cast((DateTime::NowUTC() - _startTime).GetTotalSeconds()); @@ -354,21 +343,13 @@ void SplashScreen::OnFontLoaded(Asset* asset) _subtitleFont = font->CreateFont(9 * s); } -void SplashScreen::OnTextureLoaded(Asset* asset) +void SplashScreen::OnSplashLoaded() { - ASSERT(asset && asset->IsLoaded()); - auto texture = (Texture*)asset; - - texture->OnLoaded.Unbind(this); - _splashTexture = texture; - - // Resize window to be larger if texture is being used. + // Resize window to be larger if texture is being used auto desktopSize = Platform::GetDesktopSize(); auto xSize = (desktopSize.X / (600.0f * 3.0f)) * 600.0f; auto ySize = (desktopSize.Y / (200.0f * 3.0f)) * 200.0f; - _window->SetClientSize(Float2(xSize, ySize)); - _width = _window->GetSize().X; - _height = _window->GetSize().Y; - _window->SetPosition((Platform::GetDesktopSize() - _window->GetSize()) / 2.0f); + _size = _window->GetSize(); + _window->SetPosition((desktopSize - _size) * 0.5f); } diff --git a/Source/Editor/Windows/SplashScreen.h b/Source/Editor/Windows/SplashScreen.h index c5b3d0e5a..6f8f17a7f 100644 --- a/Source/Editor/Windows/SplashScreen.h +++ b/Source/Editor/Windows/SplashScreen.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Content/Assets/Texture.h" +#include "Engine/Content/AssetReference.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Platform/Window.h" @@ -19,11 +20,12 @@ private: Window* _window = nullptr; Font* _titleFont = nullptr; Font* _subtitleFont = nullptr; - Texture* _splashTexture = nullptr; + AssetReference _splashTexture; String _title; DateTime _startTime; String _infoText; - float _dpiScale, _width, _height; + float _dpiScale; + Float2 _size; StringView _quote; public: @@ -80,5 +82,5 @@ private: void OnDraw(); bool HasLoadedFonts() const; void OnFontLoaded(Asset* asset); - void OnTextureLoaded(Asset* asset); + void OnSplashLoaded(); }; From df6f8fd8ae9c772e17cf211ffd1a3a11212c251f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 24 Aug 2025 13:46:53 +0200 Subject: [PATCH 77/82] Codestyle adjustments #3343 --- Source/Engine/Core/Types/LayersMask.cs | 12 +++++------- Source/Engine/Core/Types/LayersMask.h | 8 ++++---- Source/Engine/Level/Level.cpp | 17 ++++++----------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/Source/Engine/Core/Types/LayersMask.cs b/Source/Engine/Core/Types/LayersMask.cs index 2838e7219..7d5c57066 100644 --- a/Source/Engine/Core/Types/LayersMask.cs +++ b/Source/Engine/Core/Types/LayersMask.cs @@ -32,7 +32,7 @@ namespace FlaxEngine /// /// Determines whether the specified layer is set in the mask. /// - /// Name of the layer (from layers settings). + /// Name of the layer (from Layers settings). /// true if the specified layer is set; otherwise, false. public bool HasLayer(string layerName) { @@ -42,21 +42,19 @@ namespace FlaxEngine /// /// Gets a layer mask based on a specific layer names. /// - /// The names of the layers (from layers settings). - /// A layer mask with the mask set to the layers found. Returns a mask with 0 if not found. + /// The names of the layers (from Layers settings). + /// A layer mask with the mask set to the layers found. Returns a mask with 0 if not found. public static LayersMask GetMask(params string[] layerNames) { LayersMask mask = new LayersMask(); foreach (var layerName in layerNames) { // Ignore blank entries - if (layerName.Length == 0) + if (string.IsNullOrEmpty(layerName)) continue; int index = Level.GetLayerIndex(layerName); - if (index != -1 && !mask.HasLayer(index)) - { + if (index != -1) mask.Mask |= (uint)(1 << index); - } } return mask; } diff --git a/Source/Engine/Core/Types/LayersMask.h b/Source/Engine/Core/Types/LayersMask.h index 3f175773e..25edbc05b 100644 --- a/Source/Engine/Core/Types/LayersMask.h +++ b/Source/Engine/Core/Types/LayersMask.h @@ -40,16 +40,16 @@ public: /// /// Determines whether the specified layer name is set in the mask. /// - /// Name of the layer (from layers settings). + /// Name of the layer (from Layers settings). /// true if the specified layer is set; otherwise, false. - bool HasLayer(const StringView& layerName); + bool HasLayer(const StringView& layerName) const; /// /// Gets a layers mask from a specific layer name. /// - /// The layer names. + /// The names of the layers (from Layers settings). /// A layers mask with the Mask set to the same Mask as the layer name passed in. Returns a LayersMask with a mask of 0 if no layer found. - static LayersMask GetMask(StringView layerNames[]); + static LayersMask GetMask(Span layerNames); operator uint32() const { diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 1ff7d2cad..3db39144d 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -60,27 +60,22 @@ void LargeWorlds::UpdateOrigin(Vector3& origin, const Vector3& position) } } -bool LayersMask::HasLayer(const StringView& layerName) +bool LayersMask::HasLayer(const StringView& layerName) const { return HasLayer(Level::GetLayerIndex(layerName)); } -LayersMask LayersMask::GetMask(StringView layerNames[]) +LayersMask LayersMask::GetMask(Span layerNames) { LayersMask mask(0); - if (layerNames == nullptr) - return mask; - for (int i = 0; i < layerNames->Length(); i++) + for (StringView& layerName : layerNames) { - StringView& layerName = layerNames[i]; // Ignore blank entries if (layerName.Length() == 0) continue; - int index = Level::GetLayerIndex(layerName); - if (index != -1 && !mask.HasLayer(index)) - { - mask.Mask |= static_cast(1 << index); - } + int32 index = Level::GetLayerIndex(layerName); + if (index != -1) + mask.Mask |= (uint32)(1 << index); } return mask; } From 47caa6af28a9f4fd0e7422b81b7254a04e7ea13b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 24 Aug 2025 21:11:32 +0200 Subject: [PATCH 78/82] Add `BrokenLink32`/`BrokenLink64` icons #3218 --- Content/Editor/IconsAtlas.flax | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content/Editor/IconsAtlas.flax b/Content/Editor/IconsAtlas.flax index bbac95de6..1c65c9f31 100644 --- a/Content/Editor/IconsAtlas.flax +++ b/Content/Editor/IconsAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f43bf2050d47a1e4d6db35e5da66c2f999236939b9d962cd0fba56dcb171852 -size 5611913 +oid sha256:8f2236762a9e54d6de50415e39b9a6b055bb88ba1ff0621787b96f87c437e520 +size 5612435 From b965ca6c8cb0d2c2faca2174177096a91628d2f5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Aug 2025 14:38:55 +0200 Subject: [PATCH 79/82] Fix Content Window navigation bar to expand toolstrip for proper scroll bar display #3326 --- Source/Editor/GUI/NavigationBar.cs | 36 +++++++++++++++++++ Source/Editor/GUI/ToolStrip.cs | 35 +++++++++++------- Source/Editor/Surface/VisjectSurface.cs | 5 +-- .../Windows/ContentWindow.Navigation.cs | 8 +++-- Source/Editor/Windows/ContentWindow.cs | 19 ++++++++-- 5 files changed, 83 insertions(+), 20 deletions(-) diff --git a/Source/Editor/GUI/NavigationBar.cs b/Source/Editor/GUI/NavigationBar.cs index bedf4c070..eb87eaafc 100644 --- a/Source/Editor/GUI/NavigationBar.cs +++ b/Source/Editor/GUI/NavigationBar.cs @@ -11,6 +11,9 @@ namespace FlaxEditor.GUI /// public class NavigationBar : Panel { + private float _toolstripHeight = 0; + private Margin _toolstripMargin; + /// /// The default buttons margin. /// @@ -50,9 +53,42 @@ namespace FlaxEditor.GUI { if (toolstrip == null) return; + + if (_toolstripHeight <= 0.0f) + { + // Cache initial toolstrip state + _toolstripHeight = toolstrip.Height; + _toolstripMargin = toolstrip.ItemsMargin; + } + + // Control toolstrip bottom margin to prevent navigation bar scroll going over the buttons + var toolstripLocked = toolstrip.IsLayoutLocked; + toolstrip.IsLayoutLocked = true; + var toolstripHeight = _toolstripHeight; + var toolstripMargin = _toolstripMargin; + if (HScrollBar.Visible) + { + float scrollMargin = 8; + toolstripHeight += scrollMargin; + toolstripMargin.Bottom += scrollMargin; + } + toolstrip.Height = toolstripHeight; + toolstrip.IsLayoutLocked = toolstripLocked; + toolstrip.ItemsMargin = toolstripMargin; + var lastToolstripButton = toolstrip.LastButton; var parentSize = Parent.Size; Bounds = new Rectangle(lastToolstripButton.Right + 8.0f, 0, parentSize.X - X - 8.0f, toolstrip.Height); } + + /// + public override void PerformLayout(bool force = false) + { + base.PerformLayout(force); + + // Stretch excluding toolstrip margin to fill the space + if (Parent is ToolStrip toolStrip) + Height = toolStrip.Height; + } } } diff --git a/Source/Editor/GUI/ToolStrip.cs b/Source/Editor/GUI/ToolStrip.cs index 09f61b71d..eeba67cde 100644 --- a/Source/Editor/GUI/ToolStrip.cs +++ b/Source/Editor/GUI/ToolStrip.cs @@ -13,15 +13,7 @@ namespace FlaxEditor.GUI /// public class ToolStrip : ContainerControl { - /// - /// The default margin vertically. - /// - public const int DefaultMarginV = 1; - - /// - /// The default margin horizontally. - /// - public const int DefaultMarginH = 2; + private Margin _itemsMargin; /// /// Event fired when button gets clicked with the primary mouse button. @@ -66,10 +58,26 @@ namespace FlaxEditor.GUI } } + /// + /// Gets or sets the space around items. + /// + public Margin ItemsMargin + { + get => _itemsMargin; + set + { + if (_itemsMargin != value) + { + _itemsMargin = value; + PerformLayout(); + } + } + } + /// /// Gets the height for the items. /// - public float ItemsHeight => Height - 2 * DefaultMarginV; + public float ItemsHeight => Height - _itemsMargin.Height; /// /// Initializes a new instance of the class. @@ -82,6 +90,7 @@ namespace FlaxEditor.GUI AnchorPreset = AnchorPresets.HorizontalStretchTop; BackgroundColor = Style.Current.LightBackground; Offsets = new Margin(0, 0, y, height * Editor.Instance.Options.Options.Interface.IconsScale); + _itemsMargin = new Margin(2, 2, 1, 1); } /// @@ -161,7 +170,7 @@ namespace FlaxEditor.GUI protected override void PerformLayoutBeforeChildren() { // Arrange controls - float x = DefaultMarginH; + float x = _itemsMargin.Left; float h = ItemsHeight; for (int i = 0; i < _children.Count; i++) { @@ -169,8 +178,8 @@ namespace FlaxEditor.GUI if (c.Visible) { var w = c.Width; - c.Bounds = new Rectangle(x, DefaultMarginV, w, h); - x += w + DefaultMarginH; + c.Bounds = new Rectangle(x, _itemsMargin.Top, w, h); + x += w + _itemsMargin.Width; } } } diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 3bdad7eab..8cbcb4a21 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -543,11 +543,12 @@ namespace FlaxEditor.Surface nodes.Add(context); context = context.Parent; } + float margin = 1; float x = NavigationBar.DefaultButtonsMargin; - float h = toolStrip.ItemsHeight - 2 * ToolStrip.DefaultMarginV; + float h = toolStrip.ItemsHeight - 2 * margin; for (int i = nodes.Count - 1; i >= 0; i--) { - var button = new VisjectContextNavigationButton(this, nodes[i].Context, x, ToolStrip.DefaultMarginV, h); + var button = new VisjectContextNavigationButton(this, nodes[i].Context, x, margin, h); button.PerformLayout(); x += button.Width + NavigationBar.DefaultButtonsMargin; navigationBar.AddChild(button); diff --git a/Source/Editor/Windows/ContentWindow.Navigation.cs b/Source/Editor/Windows/ContentWindow.Navigation.cs index 1033db472..53dd6447b 100644 --- a/Source/Editor/Windows/ContentWindow.Navigation.cs +++ b/Source/Editor/Windows/ContentWindow.Navigation.cs @@ -194,17 +194,18 @@ namespace FlaxEditor.Windows nodes.Add(node); node = node.ParentNode; } + float margin = 1; float x = NavigationBar.DefaultButtonsMargin; - float h = _toolStrip.ItemsHeight - 2 * ToolStrip.DefaultMarginV; + float h = _toolStrip.ItemsHeight - 2 * margin; for (int i = nodes.Count - 1; i >= 0; i--) { - var button = new ContentNavigationButton(nodes[i], x, ToolStrip.DefaultMarginV, h); + var button = new ContentNavigationButton(nodes[i], x, margin, h); button.PerformLayout(); x += button.Width + NavigationBar.DefaultButtonsMargin; _navigationBar.AddChild(button); if (i > 0) { - var separator = new ContentNavigationSeparator(button, x, ToolStrip.DefaultMarginV, h); + var separator = new ContentNavigationSeparator(button, x, margin, h); separator.PerformLayout(); x += separator.Width + NavigationBar.DefaultButtonsMargin; _navigationBar.AddChild(separator); @@ -215,6 +216,7 @@ namespace FlaxEditor.Windows // Update _navigationBar.IsLayoutLocked = wasLayoutLocked; _navigationBar.PerformLayout(); + UpdateNavigationBarBounds(); } /// diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index cf33d0b63..3d2ec4a66 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -1016,6 +1016,21 @@ namespace FlaxEditor.Windows _navigateUpButton.Enabled = folder != null && _tree.SelectedNode != _root; } + private void UpdateNavigationBarBounds() + { + if (_navigationBar != null && _toolStrip != null) + { + var bottomPrev = _toolStrip.Bottom; + _navigationBar.UpdateBounds(_toolStrip); + if (bottomPrev != _toolStrip.Bottom) + { + // Navigation bar changed toolstrip height + _split.Offsets = new Margin(0, 0, _toolStrip.Bottom, 0); + PerformLayout(); + } + } + } + /// public override void OnInit() { @@ -1200,9 +1215,9 @@ namespace FlaxEditor.Windows /// protected override void PerformLayoutBeforeChildren() { - base.PerformLayoutBeforeChildren(); + UpdateNavigationBarBounds(); - _navigationBar?.UpdateBounds(_toolStrip); + base.PerformLayoutBeforeChildren(); } /// From 00dd432fbcd1a464ead11efbffa2cbb70ddaa3f5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Aug 2025 22:15:00 +0200 Subject: [PATCH 80/82] Simplify code #3360 --- Source/Editor/Windows/ToolboxWindow.cs | 50 +++++++++++--------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 78f5f18cd..7ad66794d 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -99,11 +99,14 @@ namespace FlaxEditor.Windows } } + [Flags] private enum SearchFilter { - Ui = 1, + UI = 1, Actors = 2, Primitives = 4, + [HideInEditor] + Default = UI | Actors | Primitives, } private TextBox _searchBox; @@ -111,8 +114,7 @@ namespace FlaxEditor.Windows private Tabs _actorGroups; private ContainerControl groupPrimitives; private Button _viewDropdown; - - private int _searchFilterMask = (int)SearchFilter.Ui | (int)SearchFilter.Actors | (int)SearchFilter.Primitives; + private int _searchFilterMask = (int)SearchFilter.Default; /// /// The editor instance. @@ -168,29 +170,22 @@ namespace FlaxEditor.Windows private void OnViewButtonClicked() { var menu = new ContextMenu(); - - var uiFilterButton = menu.AddButton("Ui"); - uiFilterButton.AutoCheck = true; - uiFilterButton.Checked = (_searchFilterMask & (int)SearchFilter.Ui) != 0; - uiFilterButton.Clicked += () => ToggleSearchFilter(SearchFilter.Ui); - - var actorFilterButton = menu.AddButton("Actors"); - actorFilterButton.AutoCheck = true; - actorFilterButton.Checked = (_searchFilterMask & (int)SearchFilter.Actors) != 0; - actorFilterButton.Clicked += () => ToggleSearchFilter(SearchFilter.Actors); - - var primitiveFilterButton = menu.AddButton("Primitives"); - primitiveFilterButton.AutoCheck = true; - primitiveFilterButton.Checked = (_searchFilterMask & (int)SearchFilter.Primitives) != 0; - primitiveFilterButton.Clicked += () => ToggleSearchFilter(SearchFilter.Primitives); - + AddSearchFilterButton(menu, SearchFilter.UI, "UI"); + AddSearchFilterButton(menu, SearchFilter.Actors, "Actors"); + AddSearchFilterButton(menu, SearchFilter.Primitives, "Primitives"); menu.Show(_viewDropdown.Parent, _viewDropdown.BottomLeft); } - private void ToggleSearchFilter(SearchFilter type) + private void AddSearchFilterButton(ContextMenu menu, SearchFilter value, string name) { - _searchFilterMask ^= (int)type; - OnSearchBoxTextChanged(); + var button = menu.AddButton(name); + button.AutoCheck = true; + button.Checked = (_searchFilterMask & (int)value) != 0; + button.Clicked += () => + { + _searchFilterMask ^= (int)value; + OnSearchBoxTextChanged(); + }; } /// @@ -428,7 +423,7 @@ namespace FlaxEditor.Windows } } - if (((int)SearchFilter.Ui & _searchFilterMask) != 0) + if (((int)SearchFilter.UI & _searchFilterMask) != 0) { foreach (var controlType in Editor.Instance.CodeEditing.Controls.Get()) { @@ -474,15 +469,10 @@ namespace FlaxEditor.Windows // Show a text to hint the user that either no filter is active or the search does not return any results bool noSearchResults = _groupSearch.Children.Count == 0 && !string.IsNullOrEmpty(_searchBox.Text); - bool showHint = (((int)SearchFilter.Actors & _searchFilterMask) == 0 && - ((int)SearchFilter.Primitives & _searchFilterMask) == 0 && - ((int)SearchFilter.Ui & _searchFilterMask) == 0) || - noSearchResults; - - String hint = noSearchResults ? "No results." : "No search filter active, please enable at least one filter."; - + bool showHint = _searchFilterMask == 0 || noSearchResults; if (showHint) { + string hint = noSearchResults ? "No results" : "No search filter active, please enable at least one filter"; var textRect = _groupSearch.Parent.Parent.Bounds; var style = Style.Current; Render2D.DrawText(style.FontMedium, hint, textRect, style.ForegroundGrey, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords); From c1f022520de1d00b230b0f74f749945d674c41fe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Aug 2025 22:15:44 +0200 Subject: [PATCH 81/82] Update ReSharper settings --- Flax.sln.DotSettings | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index 3611934dd..f54190003 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -73,6 +73,24 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + CCD + GPU + ID + <NamingElement Priority="11" Title="Class and struct public fields"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="PUBLIC"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="9" Title="Class and struct methods"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="17" Title="Typedefs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="type alias" /><type Name="typedef" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="14" Title="Other constants"><Descriptor Static="True" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="local variable" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="13" Title="Enum members"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="scoped enumerator" /><type Name="unscoped enumerator" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="15" Title="Global constants"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="7" Title="Global variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="5" Title="Parameters"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="function parameter" /><type Name="lambda parameter" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement> + <NamingElement Priority="1" Title="Classes and structs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="__interface" /><type Name="class" /><type Name="struct" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="8" Title="Global functions"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="16" Title="Namespaces"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="namespace" /><type Name="namespace alias" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="6" Title="Local variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="local variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement> + <NamingElement Priority="10" Title="Class and struct fields"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb" /></NamingElement> + <NamingElement Priority="12" Title="Union members"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="union member" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="2" Title="Enums"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="enum" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> AI ARGB LO @@ -213,6 +231,7 @@ YZ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> C:\Users\Wojtek\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v08_f9eacea9\SolutionCaches + True True True True From 4ca399af71150b370f4d791c2534d28b91918f1f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Aug 2025 23:15:37 +0200 Subject: [PATCH 82/82] Add model import options auto-restore from model prefab --- .../Engine/ContentImporters/ImportModel.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index d2b4256b3..be71236ab 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -55,6 +55,27 @@ bool ImportModel::TryGetImportOptions(const StringView& path, Options& options) } } } + else + { + // Try model prefab + String pathPrefab = String(StringUtils::GetPathWithoutExtension(path)) + DEFAULT_PREFAB_EXTENSION_DOT; + if (FileSystem::FileExists(pathPrefab)) + { + auto prefab = Content::Load(pathPrefab); + if (prefab) + { + for (const auto& e : prefab->ObjectsDataCache) + { + auto importOptionsMember = e.Value->FindMember("ImportOptions"); + if (importOptionsMember != e.Value->MemberEnd() && importOptionsMember->value.IsObject()) + { + options.Deserialize(*(ISerializable::DeserializeStream*)&importOptionsMember->value, nullptr); + return true; + } + } + } + } + } return false; }