From 475453aa601d4f8ced173ae4a05c913e21ac1c0f Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Tue, 28 Nov 2023 00:06:28 +0100 Subject: [PATCH 01/22] add load scene add. to context menus --- Source/Editor/Content/Proxy/SceneProxy.cs | 7 ++++++ .../Windows/SceneTreeWindow.ContextMenu.cs | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Source/Editor/Content/Proxy/SceneProxy.cs b/Source/Editor/Content/Proxy/SceneProxy.cs index 004c2aed7..a232c672b 100644 --- a/Source/Editor/Content/Proxy/SceneProxy.cs +++ b/Source/Editor/Content/Proxy/SceneProxy.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Windows; using FlaxEngine; @@ -68,5 +69,11 @@ namespace FlaxEditor.Content { return new SceneItem(path, id); } + + /// + public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item) + { + menu.AddButton("Open additionally", () => { Editor.Instance.Scene.OpenScene(((SceneItem)item).ID, true); }); + } } } diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index a99c3ac7d..3e078bcb8 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEngine; @@ -148,6 +150,26 @@ namespace FlaxEditor.Windows // Spawning actors options + if (!hasSthSelected) + { + var allScenes = FlaxEngine.Content.GetAllAssetsByType(typeof(SceneAsset)); + var loadedSceneIds = Editor.Instance.Scene.Root.ChildNodes.Select(node => node.ID).ToList(); + var unloadedScenes = allScenes.Where(sceneId => !loadedSceneIds.Contains(sceneId)).ToList(); + if (unloadedScenes.Count > 0) + { + contextMenu.AddSeparator(); + var childCM = contextMenu.GetOrAddChildMenu("Open Scene additionally"); + foreach (var sceneGuid in unloadedScenes.Where(sceneGuid => !Level.FindScene(sceneGuid))) + { + if (FlaxEngine.Content.GetAssetInfo(sceneGuid, out var unloadedScene)) + { + var splitPath = unloadedScene.Path.Split('/'); + childCM.ContextMenu.AddButton(splitPath[^1], () => { Editor.Instance.Scene.OpenScene(sceneGuid, true); }); + } + } + } + } + contextMenu.AddSeparator(); // go through each actor and add it to the context menu if it has the ActorContextMenu attribute From a06a0798041e22ee6f48a9b5195f33be75982190 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Tue, 28 Nov 2023 00:37:29 +0100 Subject: [PATCH 02/22] change submenu name to the shorter and less complicated "Add Scene" --- Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 3e078bcb8..6261c0c07 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -158,13 +158,16 @@ namespace FlaxEditor.Windows if (unloadedScenes.Count > 0) { contextMenu.AddSeparator(); - var childCM = contextMenu.GetOrAddChildMenu("Open Scene additionally"); + var childCM = contextMenu.GetOrAddChildMenu("Add Scene"); foreach (var sceneGuid in unloadedScenes.Where(sceneGuid => !Level.FindScene(sceneGuid))) { if (FlaxEngine.Content.GetAssetInfo(sceneGuid, out var unloadedScene)) { var splitPath = unloadedScene.Path.Split('/'); - childCM.ContextMenu.AddButton(splitPath[^1], () => { Editor.Instance.Scene.OpenScene(sceneGuid, true); }); + var sceneName = splitPath[^1]; + if (splitPath[^1].EndsWith(".scene")) + sceneName = sceneName[..^6]; + childCM.ContextMenu.AddButton(sceneName, () => { Editor.Instance.Scene.OpenScene(sceneGuid, true); }); } } } From 84f3d509252bac9d351376c3bb0e2d7e39092846 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Tue, 28 Nov 2023 00:43:55 +0100 Subject: [PATCH 03/22] moved a comment line back to the suitable place --- Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 6261c0c07..ea4714df8 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -148,7 +148,7 @@ namespace FlaxEditor.Windows contextMenu.AddButton("Break Prefab Link", Editor.Prefabs.BreakLinks); } - // Spawning actors options + // Load additional scenes option if (!hasSthSelected) { @@ -173,6 +173,8 @@ namespace FlaxEditor.Windows } } + // Spawning actors options + contextMenu.AddSeparator(); // go through each actor and add it to the context menu if it has the ActorContextMenu attribute From a3f1dc269474eaee3de64334d9ddf6d0319fcf76 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Tue, 28 Nov 2023 00:50:44 +0100 Subject: [PATCH 04/22] removed unnecessary check --- Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index ea4714df8..8b5318190 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -159,7 +159,7 @@ namespace FlaxEditor.Windows { contextMenu.AddSeparator(); var childCM = contextMenu.GetOrAddChildMenu("Add Scene"); - foreach (var sceneGuid in unloadedScenes.Where(sceneGuid => !Level.FindScene(sceneGuid))) + foreach (var sceneGuid in unloadedScenes) { if (FlaxEngine.Content.GetAssetInfo(sceneGuid, out var unloadedScene)) { From 78d9262b0515588769276aea142d8198fa626b81 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Fri, 1 Dec 2023 21:25:00 +0100 Subject: [PATCH 05/22] skip WM for non-regular windows and add mouse tracking --- .../Engine/Platform/Linux/LinuxPlatform.cpp | 33 ++++++++++++++++--- Source/Engine/Platform/Linux/LinuxPlatform.h | 2 ++ Source/Engine/Platform/Linux/LinuxWindow.cpp | 19 ++++++++--- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index a7634710f..83c3e3ae6 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -94,6 +94,7 @@ X11::XcursorImage* CursorsImg[(int32)CursorType::MAX]; Dictionary KeyNameMap; Array KeyCodeMap; Delegate LinuxPlatform::xEventRecieved; +const Window* mouseTrackingWindow; // Message boxes configuration #define LINUX_DIALOG_MIN_BUTTON_WIDTH 64 @@ -1917,6 +1918,8 @@ bool LinuxPlatform::Init() if (PlatformBase::Init()) return true; + mouseTrackingWindow = nullptr; + char fileNameBuffer[1024]; // Init timing @@ -2260,6 +2263,17 @@ bool LinuxPlatform::Init() return false; } +void LinuxPlatform::StartTrackingMouse(const Window* window) +{ + mouseTrackingWindow = window; +} + +void LinuxPlatform::EndTrackingMouse(const Window* window) +{ + if (mouseTrackingWindow == window) + mouseTrackingWindow = nullptr; +} + void LinuxPlatform::BeforeRun() { } @@ -2398,7 +2412,7 @@ void LinuxPlatform::Tick() // Update input context focus X11::XSetICFocus(IC); window = WindowsManager::GetByNativePtr((void*)event.xfocus.window); - if (window) + if (window && mouseTrackingWindow == nullptr) { window->OnGotFocus(); } @@ -2407,7 +2421,7 @@ void LinuxPlatform::Tick() // Update input context focus X11::XUnsetICFocus(IC); window = WindowsManager::GetByNativePtr((void*)event.xfocus.window); - if (window) + if (window && mouseTrackingWindow == nullptr) { window->OnLostFocus(); } @@ -2514,23 +2528,32 @@ void LinuxPlatform::Tick() break; case ButtonPress: window = WindowsManager::GetByNativePtr((void*)event.xbutton.window); - if (window) + if (mouseTrackingWindow) + ((LinuxWindow*)mouseTrackingWindow)->OnButtonPress(&event.xbutton); + else if (window) window->OnButtonPress(&event.xbutton); break; case ButtonRelease: window = WindowsManager::GetByNativePtr((void*)event.xbutton.window); - if (window) + if (mouseTrackingWindow) + ((LinuxWindow*)mouseTrackingWindow)->OnButtonRelease(&event.xbutton); + else if (window) window->OnButtonRelease(&event.xbutton); break; case MotionNotify: window = WindowsManager::GetByNativePtr((void*)event.xmotion.window); - if (window) + if (mouseTrackingWindow) + ((LinuxWindow*)mouseTrackingWindow)->OnMotionNotify(&event.xmotion); + else if (window) window->OnMotionNotify(&event.xmotion); break; case EnterNotify: + // nothing? break; case LeaveNotify: window = WindowsManager::GetByNativePtr((void*)event.xcrossing.window); + if (mouseTrackingWindow) + ((LinuxWindow*)mouseTrackingWindow)->OnLeaveNotify(&event.xcrossing); if (window) window->OnLeaveNotify(&event.xcrossing); break; diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index 54590adf2..00e7743d7 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.h +++ b/Source/Engine/Platform/Linux/LinuxPlatform.h @@ -139,6 +139,8 @@ public: static String GetWorkingDirectory(); static bool SetWorkingDirectory(const String& path); static Window* CreateWindow(const CreateWindowSettings& settings); + static void StartTrackingMouse(const Window* window); + static void EndTrackingMouse(const Window *window); static void GetEnvironmentVariables(Dictionary& result); static bool GetEnvironmentVariable(const String& name, String& value); static bool SetEnvironmentVariable(const String& name, const String& value); diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index b3bae0276..9168beb29 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -54,7 +54,7 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) return; auto screen = XDefaultScreen(display); - // Cache data + // Cache data int32 width = Math::TruncToInt(settings.Size.X); int32 height = Math::TruncToInt(settings.Size.Y); _clientSize = Float2((float)width, (float)height); @@ -111,6 +111,12 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) windowAttributes.border_pixel = XBlackPixel(display, screen); windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; + if (!settings.IsRegularWindow) + { + windowAttributes.save_under = true; + windowAttributes.override_redirect = true; + } + // TODO: implement all window settings /* bool Fullscreen; @@ -118,11 +124,16 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) bool AllowMaximize; */ + unsigned long valueMask = CWBackPixel | CWBorderPixel | CWEventMask | CWColormap; + if (!settings.IsRegularWindow) + { + valueMask |= CWOverrideRedirect | CWSaveUnder; + } const X11::Window window = X11::XCreateWindow( display, X11::XRootWindow(display, screen), x, y, width, height, 0, visualInfo->depth, InputOutput, visualInfo->visual, - CWBackPixel | CWBorderPixel | CWEventMask | CWColormap, &windowAttributes); + valueMask, &windowAttributes); _window = window; LinuxWindow::SetTitle(settings.Title); @@ -811,12 +822,12 @@ void LinuxWindow::SetTitle(const StringView& title) void LinuxWindow::StartTrackingMouse(bool useMouseScreenOffset) { - // TODO: impl this + LinuxPlatform::StartTrackingMouse(this); } void LinuxWindow::EndTrackingMouse() { - // TODO: impl this + LinuxPlatform::EndTrackingMouse(this); } void LinuxWindow::SetCursor(CursorType type) From c0ef2a1f5855d09d4f1a16da0d4cde01861d2bd5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 12:39:10 +0100 Subject: [PATCH 06/22] Cleamnup code for #2020 and use internal pointer for current tracking window --- .../Engine/Platform/Linux/LinuxPlatform.cpp | 36 ++++++------------- Source/Engine/Platform/Linux/LinuxPlatform.h | 2 -- Source/Engine/Platform/Linux/LinuxWindow.cpp | 6 ++-- 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 83c3e3ae6..41a2a33f6 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -94,7 +94,7 @@ X11::XcursorImage* CursorsImg[(int32)CursorType::MAX]; Dictionary KeyNameMap; Array KeyCodeMap; Delegate LinuxPlatform::xEventRecieved; -const Window* mouseTrackingWindow; +Window* MouseTrackingWindow = nullptr; // Message boxes configuration #define LINUX_DIALOG_MIN_BUTTON_WIDTH 64 @@ -1917,9 +1917,6 @@ bool LinuxPlatform::Init() { if (PlatformBase::Init()) return true; - - mouseTrackingWindow = nullptr; - char fileNameBuffer[1024]; // Init timing @@ -2263,17 +2260,6 @@ bool LinuxPlatform::Init() return false; } -void LinuxPlatform::StartTrackingMouse(const Window* window) -{ - mouseTrackingWindow = window; -} - -void LinuxPlatform::EndTrackingMouse(const Window* window) -{ - if (mouseTrackingWindow == window) - mouseTrackingWindow = nullptr; -} - void LinuxPlatform::BeforeRun() { } @@ -2412,7 +2398,7 @@ void LinuxPlatform::Tick() // Update input context focus X11::XSetICFocus(IC); window = WindowsManager::GetByNativePtr((void*)event.xfocus.window); - if (window && mouseTrackingWindow == nullptr) + if (window && MouseTrackingWindow == nullptr) { window->OnGotFocus(); } @@ -2421,7 +2407,7 @@ void LinuxPlatform::Tick() // Update input context focus X11::XUnsetICFocus(IC); window = WindowsManager::GetByNativePtr((void*)event.xfocus.window); - if (window && mouseTrackingWindow == nullptr) + if (window && MouseTrackingWindow == nullptr) { window->OnLostFocus(); } @@ -2528,22 +2514,22 @@ void LinuxPlatform::Tick() break; case ButtonPress: window = WindowsManager::GetByNativePtr((void*)event.xbutton.window); - if (mouseTrackingWindow) - ((LinuxWindow*)mouseTrackingWindow)->OnButtonPress(&event.xbutton); + if (MouseTrackingWindow) + MouseTrackingWindow->OnButtonPress(&event.xbutton); else if (window) window->OnButtonPress(&event.xbutton); break; case ButtonRelease: window = WindowsManager::GetByNativePtr((void*)event.xbutton.window); - if (mouseTrackingWindow) - ((LinuxWindow*)mouseTrackingWindow)->OnButtonRelease(&event.xbutton); + if (MouseTrackingWindow) + MouseTrackingWindow->OnButtonRelease(&event.xbutton); else if (window) window->OnButtonRelease(&event.xbutton); break; case MotionNotify: window = WindowsManager::GetByNativePtr((void*)event.xmotion.window); - if (mouseTrackingWindow) - ((LinuxWindow*)mouseTrackingWindow)->OnMotionNotify(&event.xmotion); + if (MouseTrackingWindow) + MouseTrackingWindow->OnMotionNotify(&event.xmotion); else if (window) window->OnMotionNotify(&event.xmotion); break; @@ -2552,8 +2538,8 @@ void LinuxPlatform::Tick() break; case LeaveNotify: window = WindowsManager::GetByNativePtr((void*)event.xcrossing.window); - if (mouseTrackingWindow) - ((LinuxWindow*)mouseTrackingWindow)->OnLeaveNotify(&event.xcrossing); + if (MouseTrackingWindow) + MouseTrackingWindow->OnLeaveNotify(&event.xcrossing); if (window) window->OnLeaveNotify(&event.xcrossing); break; diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index 00e7743d7..54590adf2 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.h +++ b/Source/Engine/Platform/Linux/LinuxPlatform.h @@ -139,8 +139,6 @@ public: static String GetWorkingDirectory(); static bool SetWorkingDirectory(const String& path); static Window* CreateWindow(const CreateWindowSettings& settings); - static void StartTrackingMouse(const Window* window); - static void EndTrackingMouse(const Window *window); static void GetEnvironmentVariables(Dictionary& result); static bool GetEnvironmentVariable(const String& name, String& value); static bool SetEnvironmentVariable(const String& name, const String& value); diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index 9168beb29..9004ed84c 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -40,6 +40,7 @@ extern X11::Atom xAtomWmName; extern Dictionary KeyNameMap; extern Array KeyCodeMap; extern X11::Cursor Cursors[(int32)CursorType::MAX]; +extern Window* MouseTrackingWindow; static constexpr uint32 MouseDoubleClickTime = 500; static constexpr uint32 MaxDoubleClickDistanceSquared = 10; @@ -822,12 +823,13 @@ void LinuxWindow::SetTitle(const StringView& title) void LinuxWindow::StartTrackingMouse(bool useMouseScreenOffset) { - LinuxPlatform::StartTrackingMouse(this); + MouseTrackingWindow = this; } void LinuxWindow::EndTrackingMouse() { - LinuxPlatform::EndTrackingMouse(this); + if (MouseTrackingWindow == this) + MouseTrackingWindow = nullptr; } void LinuxWindow::SetCursor(CursorType type) From efebb29ac0479dfdea2e307687fbbdad3be3cc57 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 12:49:39 +0100 Subject: [PATCH 07/22] Minor tweak for #2003 --- Source/Editor/Content/Proxy/SceneProxy.cs | 6 +++++- Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs | 2 +- Source/Engine/Level/Level.cpp | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Content/Proxy/SceneProxy.cs b/Source/Editor/Content/Proxy/SceneProxy.cs index a232c672b..d959b524a 100644 --- a/Source/Editor/Content/Proxy/SceneProxy.cs +++ b/Source/Editor/Content/Proxy/SceneProxy.cs @@ -73,7 +73,11 @@ namespace FlaxEditor.Content /// public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item) { - menu.AddButton("Open additionally", () => { Editor.Instance.Scene.OpenScene(((SceneItem)item).ID, true); }); + var id = ((SceneItem)item).ID; + if (Level.FindScene(id) == null) + { + menu.AddButton("Open (additive)", () => { Editor.Instance.Scene.OpenScene(id, true); }); + } } } } diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 8b5318190..0c8e0f283 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -158,7 +158,7 @@ namespace FlaxEditor.Windows if (unloadedScenes.Count > 0) { contextMenu.AddSeparator(); - var childCM = contextMenu.GetOrAddChildMenu("Add Scene"); + var childCM = contextMenu.GetOrAddChildMenu("Open Scene"); foreach (var sceneGuid in unloadedScenes) { if (FlaxEngine.Content.GetAssetInfo(sceneGuid, out var unloadedScene)) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index cc91e48ca..41033876d 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -439,7 +439,7 @@ public: bool Do() const override { - auto scene = Scripting::FindObject(TargetScene); + auto scene = Level::FindScene(TargetScene); if (!scene) return true; return unloadScene(scene); From d614232f8d556947e37ce4ba799f2e48b6255e09 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Dec 2023 14:45:28 +0200 Subject: [PATCH 08/22] Fix VS build issues with C# projects when engine path has spaces --- .../VisualStudioProjectGenerator.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index b04531d9f..5cc1b2205 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -702,7 +702,7 @@ namespace Flax.Build.Projects.VisualStudio // Override MSBuild build tasks to run Flax.Build in C#-only projects { // Build command for the build tool - var buildToolPath = Path.ChangeExtension(Utilities.MakePathRelativeTo(typeof(Builder).Assembly.Location, Path.GetDirectoryName(solution.MainProject.Path)), null); + var buildToolPath = Path.ChangeExtension(typeof(Builder).Assembly.Location, null); var targetsFileContent = new StringBuilder(); targetsFileContent.AppendLine(""); @@ -724,16 +724,16 @@ namespace Flax.Build.Projects.VisualStudio { foreach (var configuration in solution.MainProject.Configurations) { - var cmdLine = string.Format("{0} -log -mutex -workspace={1} -arch={2} -configuration={3} -platform={4} -buildTargets={5}", - FixPath(buildToolPath), - FixPath(solution.MainProject.WorkspaceRootPath), + var cmdLine = string.Format("\"{0}\" -log -mutex -workspace=\"{1}\" -arch={2} -configuration={3} -platform={4} -buildTargets={5}", + buildToolPath, + solution.MainProject.WorkspaceRootPath, configuration.Architecture, configuration.Configuration, configuration.Platform, configuration.Target); Configuration.PassArgs(ref cmdLine); - str.AppendLine(string.Format(" ", cmdLine, extraArgs, configuration.Name)); + str.AppendLine(string.Format(" ", cmdLine, extraArgs, configuration.Name)); } } } @@ -774,14 +774,5 @@ namespace Flax.Build.Projects.VisualStudio projects.Add(project); } } - - private static string FixPath(string path) - { - if (path.Contains(' ')) - { - path = "\"" + path + "\""; - } - return path; - } } } From 84249b3b57a00a54ff099544510097465145d3bd Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Dec 2023 14:48:21 +0200 Subject: [PATCH 09/22] Skip building main C#-project in VS solution when C++-project is present Flax.Build is invoked twice, once for C++-project and one more time for C#-project. Skip the C#-project by using the custom .targets file to not break Rider's solution analysis feature. --- .../Projects/VisualStudio/CSSDKProjectGenerator.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs index c678b2521..2d4b0cc02 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs @@ -91,11 +91,6 @@ namespace Flax.Build.Projects.VisualStudio var baseConfiguration = project.Configurations.First(); var baseOutputDir = Utilities.MakePathRelativeTo(project.CSharp.OutputPath ?? baseConfiguration.TargetBuildOptions.OutputFolder, projectDirectory); var baseIntermediateOutputPath = Utilities.MakePathRelativeTo(project.CSharp.IntermediateOutputPath ?? Path.Combine(baseConfiguration.TargetBuildOptions.IntermediateFolder, "CSharp"), projectDirectory); - - bool isMainProject = Globals.Project.ProjectFolderPath == project.WorkspaceRootPath && project.Name != "BuildScripts" && (Globals.Project.Name != "Flax" || project.Name != "FlaxEngine"); - var flaxBuildTargetsFilename = isMainProject ? "Flax.Build.CSharp.targets" : "Flax.Build.CSharp.SkipBuild.targets"; - var cacheProjectsPath = Utilities.MakePathRelativeTo(Path.Combine(Globals.Root, "Cache", "Projects"), projectDirectory); - var flaxBuildTargetsPath = !string.IsNullOrEmpty(cacheProjectsPath) ? Path.Combine(cacheProjectsPath, flaxBuildTargetsFilename) : flaxBuildTargetsFilename; csProjectFileContent.AppendLine($" net{dotnetSdk.Version.Major}.{dotnetSdk.Version.Minor}"); csProjectFileContent.AppendLine(" disable"); @@ -114,7 +109,11 @@ namespace Flax.Build.Projects.VisualStudio //csProjectFileContent.AppendLine(" false"); // TODO: use it to reduce burden of framework libs - // Custom .targets file for overriding MSBuild build tasks + // Custom .targets file for overriding MSBuild build tasks, only invoke Flax.Build once per Flax project + bool isMainProject = Globals.Project.IsCSharpOnlyProject && Globals.Project.ProjectFolderPath == project.WorkspaceRootPath && project.Name != "BuildScripts" && (Globals.Project.Name != "Flax" || project.Name != "FlaxEngine"); + var flaxBuildTargetsFilename = isMainProject ? "Flax.Build.CSharp.targets" : "Flax.Build.CSharp.SkipBuild.targets"; + var cacheProjectsPath = Utilities.MakePathRelativeTo(Path.Combine(Globals.Root, "Cache", "Projects"), projectDirectory); + var flaxBuildTargetsPath = !string.IsNullOrEmpty(cacheProjectsPath) ? Path.Combine(cacheProjectsPath, flaxBuildTargetsFilename) : flaxBuildTargetsFilename; csProjectFileContent.AppendLine(string.Format(" $(MSBuildThisFileDirectory){0}", flaxBuildTargetsPath)); // Hide annoying warnings during build From 639803480ed4f78a5385decd2908054f66928f93 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Dec 2023 16:11:56 +0200 Subject: [PATCH 10/22] Improve .NET related errors during cooking process --- Source/Editor/Cooker/Steps/DeployDataStep.cpp | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index 80cb73c2b..013fa0e60 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -48,21 +48,21 @@ bool DeployDataStep::Perform(CookingData& data) } if (buildSettings.SkipDotnetPackaging && data.Tools->UseSystemDotnet()) { - // Use system-installed .Net Runtime + // Use system-installed .NET Runtime FileSystem::DeleteDirectory(dstDotnet); } else { - // Deploy .Net Runtime files + // Deploy .NET Runtime files FileSystem::CreateDirectory(dstDotnet); String srcDotnet = depsRoot / TEXT("Dotnet"); if (FileSystem::DirectoryExists(srcDotnet)) { - // Use prebuilt .Net installation for that platform - LOG(Info, "Using .Net Runtime {} at {}", data.Tools->GetName(), srcDotnet); + // Use prebuilt .NET installation for that platform + LOG(Info, "Using .NET Runtime {} at {}", data.Tools->GetName(), srcDotnet); if (EditorUtilities::CopyDirectoryIfNewer(dstDotnet, srcDotnet, true)) { - data.Error(TEXT("Failed to copy .Net runtime data files.")); + data.Error(TEXT("Failed to copy .NET runtime data files.")); return true; } } @@ -85,7 +85,7 @@ bool DeployDataStep::Perform(CookingData& data) } if (canUseSystemDotnet && (aotMode == DotNetAOTModes::None || aotMode == DotNetAOTModes::ILC)) { - // Ask Flax.Build to provide .Net SDK location for the current platform + // Ask Flax.Build to provide .NET SDK location for the current platform String sdks; bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), GAME_BUILD_DOTNET_VER), data.CacheDirectory); failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks); @@ -101,7 +101,7 @@ bool DeployDataStep::Perform(CookingData& data) } if (failed || !FileSystem::DirectoryExists(srcDotnet)) { - data.Error(TEXT("Failed to get .Net SDK location for a current platform.")); + data.Error(TEXT("Failed to get .NET SDK location for the current host platform.")); return true; } @@ -110,7 +110,7 @@ bool DeployDataStep::Perform(CookingData& data) FileSystem::GetChildDirectories(versions, srcDotnet / TEXT("host/fxr")); if (versions.Count() == 0) { - data.Error(TEXT("Failed to get .Net SDK location for a current platform.")); + data.Error(TEXT("Failed to find any .NET hostfxr versions for the current host platform.")); return true; } for (String& version : versions) @@ -121,8 +121,14 @@ bool DeployDataStep::Perform(CookingData& data) } Sorting::QuickSort(versions); const String version = versions.Last(); + if (version.IsEmpty()) + { + data.Error(TEXT("Failed to find supported .NET hostfxr version for the current host platform.")); + return true; + } + FileSystem::NormalizePath(srcDotnet); - LOG(Info, "Using .Net Runtime {} at {}", version, srcDotnet); + LOG(Info, "Using .NET Runtime {} at {}", version, srcDotnet); // Check if previously deployed files are valid (eg. system-installed .NET was updated from version 7.0.3 to 7.0.5) { @@ -158,13 +164,13 @@ bool DeployDataStep::Perform(CookingData& data) } if (failed) { - data.Error(TEXT("Failed to copy .Net runtime data files.")); + data.Error(TEXT("Failed to copy .NET runtime data files.")); return true; } } else { - // Ask Flax.Build to provide .Net Host Runtime location for the target platform + // Ask Flax.Build to provide .NET Host Runtime location for the target platform String sdks; const Char *platformName, *archName; data.GetBuildPlatformName(platformName, archName); @@ -180,11 +186,11 @@ bool DeployDataStep::Perform(CookingData& data) } if (failed || !FileSystem::DirectoryExists(srcDotnet)) { - data.Error(TEXT("Failed to get .Net SDK location for a current platform.")); + data.Error(TEXT("Failed to get .NET SDK location for the current host platform.")); return true; } FileSystem::NormalizePath(srcDotnet); - LOG(Info, "Using .Net Runtime {} at {}", TEXT("Host"), srcDotnet); + LOG(Info, "Using .NET Runtime {} at {}", TEXT("Host"), srcDotnet); // Deploy runtime files const Char* corlibPrivateName = TEXT("System.Private.CoreLib.dll"); @@ -249,7 +255,7 @@ bool DeployDataStep::Perform(CookingData& data) DEPLOY_NATIVE_FILE("libmonosgen-2.0.dylib"); DEPLOY_NATIVE_FILE("libSystem.IO.Compression.Native.dylib"); DEPLOY_NATIVE_FILE("libSystem.Native.dylib"); - DEPLOY_NATIVE_FILE("libSystem.Net.Security.Native.dylib"); + DEPLOY_NATIVE_FILE("libSystem.NET.Security.Native.dylib"); DEPLOY_NATIVE_FILE("libSystem.Security.Cryptography.Native.Apple.dylib"); break; #undef DEPLOY_NATIVE_FILE @@ -257,7 +263,7 @@ bool DeployDataStep::Perform(CookingData& data) } if (failed) { - data.Error(TEXT("Failed to copy .Net runtime data files.")); + data.Error(TEXT("Failed to copy .NET runtime data files.")); return true; } } @@ -278,7 +284,7 @@ bool DeployDataStep::Perform(CookingData& data) } if (ScriptsBuilder::RunBuildTool(args)) { - data.Error(TEXT("Failed to optimize .Net class library.")); + data.Error(TEXT("Failed to optimize .NET class library.")); return true; } } From f38245b834d243f555425082354f729dec81f811 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Dec 2023 16:13:49 +0200 Subject: [PATCH 11/22] Fix .NET runtime packaging with installed .NET 8 SDK --- Source/Editor/Cooker/Steps/DeployDataStep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index 013fa0e60..59ff0d744 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -116,7 +116,7 @@ bool DeployDataStep::Perform(CookingData& data) for (String& version : versions) { version = String(StringUtils::GetFileName(version)); - if (!version.StartsWith(TEXT("7."))) + if (!version.StartsWith(TEXT("7.")) && !version.StartsWith(TEXT("8."))) // .NET 7 or .NET 8 version.Clear(); } Sorting::QuickSort(versions); From 2bef880e214c2473248d72a7a413142a41a6c888 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 16:15:52 +0100 Subject: [PATCH 12/22] Fix render target pool over-allocation when changing render resolution frequently #2077 --- Source/Engine/Graphics/RenderBuffers.cpp | 3 + Source/Engine/Graphics/RenderTargetPool.cpp | 67 +++++++++------------ Source/Engine/Graphics/RenderTargetPool.h | 3 +- 3 files changed, 33 insertions(+), 40 deletions(-) diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index e0f4869ad..7ee900858 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -192,6 +192,9 @@ bool RenderBuffers::Init(int32 width, int32 height) _viewport = Viewport(0, 0, static_cast(width), static_cast(height)); LastEyeAdaptationTime = 0; + // Flush any pool render targets to prevent over-allocating GPU memory when resizing game viewport + RenderTargetPool::Flush(false, 4); + return result; } diff --git a/Source/Engine/Graphics/RenderTargetPool.cpp b/Source/Engine/Graphics/RenderTargetPool.cpp index fe22b4eaf..891610ab8 100644 --- a/Source/Engine/Graphics/RenderTargetPool.cpp +++ b/Source/Engine/Graphics/RenderTargetPool.cpp @@ -7,35 +7,31 @@ struct Entry { - bool IsOccupied; GPUTexture* RT; - uint64 LastFrameTaken; uint64 LastFrameReleased; uint32 DescriptionHash; + bool IsOccupied; }; namespace { - Array TemporaryRTs(64); + Array TemporaryRTs; } -void RenderTargetPool::Flush(bool force) +void RenderTargetPool::Flush(bool force, int32 framesOffset) { - const uint64 framesOffset = 3 * 60; + if (framesOffset < 0) + framesOffset = 3 * 60; // For how many frames RTs should be cached (by default) const uint64 maxReleaseFrame = Engine::FrameCount - framesOffset; force |= Engine::ShouldExit(); for (int32 i = 0; i < TemporaryRTs.Count(); i++) { - auto& tmp = TemporaryRTs[i]; - - if (!tmp.IsOccupied && (force || (tmp.LastFrameReleased < maxReleaseFrame))) + const auto& e = TemporaryRTs[i]; + if (!e.IsOccupied && (force || e.LastFrameReleased < maxReleaseFrame)) { - // Release - tmp.RT->DeleteObjectNow(); - TemporaryRTs.RemoveAt(i); - i--; - + e.RT->DeleteObjectNow(); + TemporaryRTs.RemoveAt(i--); if (TemporaryRTs.IsEmpty()) break; } @@ -48,19 +44,14 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc) const uint32 descHash = GetHash(desc); for (int32 i = 0; i < TemporaryRTs.Count(); i++) { - auto& tmp = TemporaryRTs[i]; - - if (!tmp.IsOccupied && tmp.DescriptionHash == descHash) + auto& e = TemporaryRTs[i]; + if (!e.IsOccupied && e.DescriptionHash == descHash) { - ASSERT(tmp.RT); - // Mark as used - tmp.IsOccupied = true; - tmp.LastFrameTaken = Engine::FrameCount; - return tmp.RT; + e.IsOccupied = true; + return e.RT; } } - #if !BUILD_RELEASE if (TemporaryRTs.Count() > 2000) { @@ -71,24 +62,23 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc) // Create new rt const String name = TEXT("TemporaryRT_") + StringUtils::ToString(TemporaryRTs.Count()); - auto newRenderTarget = GPUDevice::Instance->CreateTexture(name); - if (newRenderTarget->Init(desc)) + GPUTexture* rt = GPUDevice::Instance->CreateTexture(name); + if (rt->Init(desc)) { - Delete(newRenderTarget); + Delete(rt); LOG(Error, "Cannot create temporary render target. Description: {0}", desc.ToString()); return nullptr; } // Create temporary rt entry - Entry entry; - entry.IsOccupied = true; - entry.LastFrameReleased = 0; - entry.LastFrameTaken = Engine::FrameCount; - entry.RT = newRenderTarget; - entry.DescriptionHash = descHash; - TemporaryRTs.Add(entry); + Entry e; + e.IsOccupied = true; + e.LastFrameReleased = 0; + e.RT = rt; + e.DescriptionHash = descHash; + TemporaryRTs.Add(e); - return newRenderTarget; + return rt; } void RenderTargetPool::Release(GPUTexture* rt) @@ -98,14 +88,13 @@ void RenderTargetPool::Release(GPUTexture* rt) for (int32 i = 0; i < TemporaryRTs.Count(); i++) { - auto& tmp = TemporaryRTs[i]; - - if (tmp.RT == rt) + auto& e = TemporaryRTs[i]; + if (e.RT == rt) { // Mark as free - ASSERT(tmp.IsOccupied); - tmp.IsOccupied = false; - tmp.LastFrameReleased = Engine::FrameCount; + ASSERT(e.IsOccupied); + e.IsOccupied = false; + e.LastFrameReleased = Engine::FrameCount; return; } } diff --git a/Source/Engine/Graphics/RenderTargetPool.h b/Source/Engine/Graphics/RenderTargetPool.h index 345e5c81e..4ff69e2ea 100644 --- a/Source/Engine/Graphics/RenderTargetPool.h +++ b/Source/Engine/Graphics/RenderTargetPool.h @@ -15,7 +15,8 @@ public: /// Flushes the temporary render targets. /// /// True if release unused render targets by force, otherwise will use a few frames of delay. - static void Flush(bool force = false); + /// Amount of previous frames that should persist in the pool after flush. Resources used more than given value wil be freed. Use value of -1 to auto pick default duration. + static void Flush(bool force = false, int32 framesOffset = -1); /// /// Gets a temporary render target. From 6d5d61589423bad5489826970bceb59866b80585 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 16:38:16 +0100 Subject: [PATCH 13/22] Fix crash when drawing terrain without cached neighbor chunks #2087 --- Source/Engine/Terrain/TerrainChunk.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 737ce08f8..9bf1c1208 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -84,6 +84,8 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const DrawCall drawCall; if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod)) return; + if (!_neighbors[0]) + const_cast(this)->CacheNeighbors(); drawCall.InstanceCount = 1; drawCall.Material = _cachedDrawMaterial; renderContext.View.GetWorldMatrix(_transform, drawCall.World); From c145042f52f11630ecb8ecac44197ef6297c3678 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 17:08:54 +0100 Subject: [PATCH 14/22] Fix invalid BT node decorator linkage after removing it #2059 --- .../Editor/Surface/Archetypes/BehaviorTree.cs | 23 +++++++++++++++++++ .../Surface/Undo/AddRemoveNodeAction.cs | 8 ++++--- Source/Editor/Surface/VisjectSurface.cs | 2 +- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index f8cd7bb5a..07005df28 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -755,6 +755,29 @@ namespace FlaxEditor.Surface.Archetypes } } + /// + public override void OnDeleted(SurfaceNodeActions action) + { + // Unlink from the current parent (when deleted by user) + var node = Node; + if (node != null) + { + if (action == SurfaceNodeActions.User) + { + var decorators = node.DecoratorIds; + decorators.Remove(ID); + node.DecoratorIds = decorators; + } + else + { + node._decorators = null; + node.ResizeAuto(); + } + } + + base.OnDeleted(action); + } + public override void OnSurfaceCanEditChanged(bool canEdit) { base.OnSurfaceCanEditChanged(canEdit); diff --git a/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs b/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs index ba6770287..0deb66fbe 100644 --- a/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs +++ b/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs @@ -19,6 +19,7 @@ namespace FlaxEditor.Surface.Undo private ushort _typeId; private Float2 _nodeLocation; private object[] _nodeValues; + private SurfaceNodeActions _actionType = SurfaceNodeActions.User; // Action usage flow is first to apply user effect such as removing/adding node, then we use Undo type so node can react to this public AddRemoveNodeAction(SurfaceNode node, bool isAdd) { @@ -38,6 +39,7 @@ namespace FlaxEditor.Surface.Undo Add(); else Remove(); + _actionType = SurfaceNodeActions.Undo; } /// @@ -67,8 +69,8 @@ namespace FlaxEditor.Surface.Undo else if (_nodeValues != null && _nodeValues.Length != 0) throw new InvalidOperationException("Invalid node values."); node.Location = _nodeLocation; - context.OnControlLoaded(node, SurfaceNodeActions.Undo); - node.OnSurfaceLoaded(SurfaceNodeActions.Undo); + context.OnControlLoaded(node, _actionType); + node.OnSurfaceLoaded(_actionType); context.MarkAsModified(); } @@ -89,7 +91,7 @@ namespace FlaxEditor.Surface.Undo // Remove node context.Nodes.Remove(node); - context.OnControlDeleted(node, SurfaceNodeActions.Undo); + context.OnControlDeleted(node, _actionType); context.MarkAsModified(); } diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 3ac702549..530f3265f 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -837,7 +837,7 @@ namespace FlaxEditor.Surface actions.Add(action); } - Undo.AddAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes")); + AddBatchedUndoAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes")); } } From 64e391df24a5e3356f115126c4ba3d776a61ed1e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 17:58:18 +0100 Subject: [PATCH 15/22] Refactor Visual Script debugger apis to use bindings generator --- Source/Editor/Editor.cs | 117 ---------------- .../Editor/Managed/ManagedEditor.Internal.cpp | 127 ------------------ Source/Editor/Managed/ManagedEditor.cpp | 89 ++++++++++++ Source/Editor/Managed/ManagedEditor.h | 25 ++++ Source/Editor/Surface/VisualScriptSurface.cs | 2 +- .../Windows/Assets/VisualScriptWindow.cs | 13 +- 6 files changed, 122 insertions(+), 251 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index b384b6515..393bf564e 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1343,108 +1343,6 @@ namespace FlaxEditor public float AutoRebuildNavMeshTimeoutMs; } - [StructLayout(LayoutKind.Sequential)] - [NativeMarshalling(typeof(VisualScriptLocalMarshaller))] - internal struct VisualScriptLocal - { - public string Value; - public string ValueTypeName; - public uint NodeId; - public int BoxId; - } - - [CustomMarshaller(typeof(VisualScriptLocal), MarshalMode.Default, typeof(VisualScriptLocalMarshaller))] - internal static class VisualScriptLocalMarshaller - { - [StructLayout(LayoutKind.Sequential)] - internal struct VisualScriptLocalNative - { - public IntPtr Value; - public IntPtr ValueTypeName; - public uint NodeId; - public int BoxId; - } - - internal static VisualScriptLocal ConvertToManaged(VisualScriptLocalNative unmanaged) => ToManaged(unmanaged); - internal static VisualScriptLocalNative ConvertToUnmanaged(VisualScriptLocal managed) => ToNative(managed); - - internal static VisualScriptLocal ToManaged(VisualScriptLocalNative managed) - { - return new VisualScriptLocal() - { - Value = ManagedString.ToManaged(managed.Value), - ValueTypeName = ManagedString.ToManaged(managed.ValueTypeName), - NodeId = managed.NodeId, - BoxId = managed.BoxId, - }; - } - - internal static VisualScriptLocalNative ToNative(VisualScriptLocal managed) - { - return new VisualScriptLocalNative() - { - Value = ManagedString.ToNative(managed.Value), - ValueTypeName = ManagedString.ToNative(managed.ValueTypeName), - NodeId = managed.NodeId, - BoxId = managed.BoxId, - }; - } - - internal static void Free(VisualScriptLocalNative unmanaged) - { - ManagedString.Free(unmanaged.Value); - ManagedString.Free(unmanaged.ValueTypeName); - } - } - - [StructLayout(LayoutKind.Sequential)] - [NativeMarshalling(typeof(VisualScriptStackFrameMarshaller))] - internal struct VisualScriptStackFrame - { - public VisualScript Script; - public uint NodeId; - public int BoxId; - } - - [CustomMarshaller(typeof(VisualScriptStackFrame), MarshalMode.Default, typeof(VisualScriptStackFrameMarshaller))] - internal static class VisualScriptStackFrameMarshaller - { - [StructLayout(LayoutKind.Sequential)] - internal struct VisualScriptStackFrameNative - { - public IntPtr Script; - public uint NodeId; - public int BoxId; - } - - internal static VisualScriptStackFrame ConvertToManaged(VisualScriptStackFrameNative unmanaged) => ToManaged(unmanaged); - internal static VisualScriptStackFrameNative ConvertToUnmanaged(VisualScriptStackFrame managed) => ToNative(managed); - - internal static VisualScriptStackFrame ToManaged(VisualScriptStackFrameNative managed) - { - return new VisualScriptStackFrame() - { - Script = VisualScriptMarshaller.ConvertToManaged(managed.Script), - NodeId = managed.NodeId, - BoxId = managed.BoxId, - }; - } - - internal static VisualScriptStackFrameNative ToNative(VisualScriptStackFrame managed) - { - return new VisualScriptStackFrameNative() - { - Script = VisualScriptMarshaller.ConvertToUnmanaged(managed.Script), - NodeId = managed.NodeId, - BoxId = managed.BoxId, - }; - } - - internal static void Free(VisualScriptStackFrameNative unmanaged) - { - } - } - internal void BuildCommand(string arg) { if (TryBuildCommand(arg)) @@ -1723,21 +1621,6 @@ namespace FlaxEditor [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_RunVisualScriptBreakpointLoopTick", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_RunVisualScriptBreakpointLoopTick(float deltaTime); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptLocals", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "localsCount")] - internal static partial VisualScriptLocal[] Internal_GetVisualScriptLocals(out int localsCount); - - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptStackFrames", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "stackFrameCount")] - internal static partial VisualScriptStackFrame[] Internal_GetVisualScriptStackFrames(out int stackFrameCount); - - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptPreviousScopeFrame", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - internal static partial VisualScriptStackFrame Internal_GetVisualScriptPreviousScopeFrame(); - - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_EvaluateVisualScriptLocal", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalAs(UnmanagedType.U1)] - internal static partial bool Internal_EvaluateVisualScriptLocal(IntPtr script, ref VisualScriptLocal local); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_DeserializeSceneObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_DeserializeSceneObject(IntPtr sceneObject, string json); diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 8855ba58a..ae8b71ee2 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -526,133 +526,6 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa Engine::OnDraw(); } -struct VisualScriptLocalManaged -{ - MString* Value; - MString* ValueTypeName; - uint32 NodeId; - int32 BoxId; -}; - -DEFINE_INTERNAL_CALL(MArray*) EditorInternal_GetVisualScriptLocals(int* localsCount) -{ - MArray* result = nullptr; - *localsCount = 0; - const auto stack = VisualScripting::GetThreadStackTop(); - if (stack && stack->Scope) - { - const int32 count = stack->Scope->Parameters.Length() + stack->Scope->ReturnedValues.Count(); - const MClass* mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptLocal"); - ASSERT(mclass); - result = MCore::Array::New(mclass, count); - VisualScriptLocalManaged local; - local.NodeId = MAX_uint32; - if (stack->Scope->Parameters.Length() != 0) - { - auto s = stack; - while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope) - s = s->PreviousFrame; - if (s) - local.NodeId = s->Node->ID; - } - VisualScriptLocalManaged* resultPtr = MCore::Array::GetAddress(result); - for (int32 i = 0; i < stack->Scope->Parameters.Length(); i++) - { - auto& v = stack->Scope->Parameters[i]; - local.BoxId = i + 1; - local.Value = MUtils::ToString(v.ToString()); - local.ValueTypeName = MUtils::ToString(v.Type.GetTypeName()); - resultPtr[i] = local; - } - for (int32 i = 0; i < stack->Scope->ReturnedValues.Count(); i++) - { - auto& v = stack->Scope->ReturnedValues[i]; - local.NodeId = v.NodeId; - local.BoxId = v.BoxId; - local.Value = MUtils::ToString(v.Value.ToString()); - local.ValueTypeName = MUtils::ToString(v.Value.Type.GetTypeName()); - resultPtr[stack->Scope->Parameters.Length() + i] = local; - } - *localsCount = count; - } - return result; -} - -struct VisualScriptStackFrameManaged -{ - MObject* Script; - uint32 NodeId; - int32 BoxId; -}; - -DEFINE_INTERNAL_CALL(MArray*) EditorInternal_GetVisualScriptStackFrames(int* stackFramesCount) -{ - MArray* result = nullptr; - *stackFramesCount = 0; - const auto stack = VisualScripting::GetThreadStackTop(); - if (stack) - { - int32 count = 0; - auto s = stack; - while (s) - { - s = s->PreviousFrame; - count++; - } - const MClass* mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptStackFrame"); - ASSERT(mclass); - result = MCore::Array::New(mclass, count); - VisualScriptStackFrameManaged* resultPtr = MCore::Array::GetAddress(result); - s = stack; - count = 0; - while (s) - { - VisualScriptStackFrameManaged frame; - frame.Script = s->Script->GetOrCreateManagedInstance(); - frame.NodeId = s->Node->ID; - frame.BoxId = s->Box ? s->Box->ID : MAX_uint32; - resultPtr[count] = frame; - s = s->PreviousFrame; - count++; - } - *stackFramesCount = count; - } - return result; -} - -DEFINE_INTERNAL_CALL(VisualScriptStackFrameManaged) EditorInternal_GetVisualScriptPreviousScopeFrame() -{ - VisualScriptStackFrameManaged frame; - Platform::MemoryClear(&frame, sizeof(frame)); - const auto stack = VisualScripting::GetThreadStackTop(); - if (stack) - { - auto s = stack; - while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope) - s = s->PreviousFrame; - if (s && s->PreviousFrame) - { - s = s->PreviousFrame; - frame.Script = s->Script->GetOrCreateManagedInstance(); - frame.NodeId = s->Node->ID; - frame.BoxId = s->Box ? s->Box->ID : MAX_uint32; - } - } - return frame; -} - -DEFINE_INTERNAL_CALL(bool) EditorInternal_EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocalManaged* local) -{ - Variant v; - if (VisualScripting::Evaluate(script, VisualScripting::GetThreadStackTop()->Instance, local->NodeId, local->BoxId, v)) - { - local->Value = MUtils::ToString(v.ToString()); - local->ValueTypeName = MUtils::ToString(v.Type.GetTypeName()); - return true; - } - return false; -} - DEFINE_INTERNAL_CALL(void) EditorInternal_DeserializeSceneObject(SceneObject* sceneObject, MString* jsonObj) { PROFILE_CPU_NAMED("DeserializeSceneObject"); diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index bed742885..f7e0c1498 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -489,6 +489,95 @@ void ManagedEditor::RequestStartPlayOnEditMode() Internal_RequestStartPlayOnEditMode->Invoke(GetManagedInstance(), nullptr, nullptr); } +Array ManagedEditor::GetVisualScriptStackFrames() +{ + Array result; + const auto stack = VisualScripting::GetThreadStackTop(); + auto s = stack; + while (s) + { + VisualScriptStackFrame& frame = result.AddOne(); + frame.Script = s->Script; + frame.NodeId = s->Node->ID; + frame.BoxId = s->Box ? s->Box->ID : MAX_uint32; + s = s->PreviousFrame; + } + return result; +} + +ManagedEditor::VisualScriptStackFrame ManagedEditor::GetVisualScriptPreviousScopeFrame() +{ + VisualScriptStackFrame frame; + Platform::MemoryClear(&frame, sizeof(frame)); + const auto stack = VisualScripting::GetThreadStackTop(); + if (stack) + { + auto s = stack; + while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope) + s = s->PreviousFrame; + if (s && s->PreviousFrame) + { + s = s->PreviousFrame; + frame.Script = s->Script; + frame.NodeId = s->Node->ID; + frame.BoxId = s->Box ? s->Box->ID : MAX_uint32; + } + } + return frame; +} + +Array ManagedEditor::GetVisualScriptLocals() +{ + Array result; + const auto stack = VisualScripting::GetThreadStackTop(); + if (stack && stack->Scope) + { + const int32 count = stack->Scope->Parameters.Length() + stack->Scope->ReturnedValues.Count(); + result.Resize(count); + VisualScriptLocal local; + local.NodeId = MAX_uint32; + if (stack->Scope->Parameters.Length() != 0) + { + auto s = stack; + while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope) + s = s->PreviousFrame; + if (s) + local.NodeId = s->Node->ID; + } + for (int32 i = 0; i < stack->Scope->Parameters.Length(); i++) + { + auto& v = stack->Scope->Parameters[i]; + local.BoxId = i + 1; + local.Value = v.ToString(); + local.ValueTypeName = v.Type.GetTypeName(); + result[i] = local; + } + for (int32 i = 0; i < stack->Scope->ReturnedValues.Count(); i++) + { + auto& v = stack->Scope->ReturnedValues[i]; + local.NodeId = v.NodeId; + local.BoxId = v.BoxId; + local.Value = v.Value.ToString(); + local.ValueTypeName = v.Value.Type.GetTypeName(); + result[stack->Scope->Parameters.Length() + i] = local; + } + } + return result; +} + +bool ManagedEditor::EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocal& local) +{ + Variant v; + const auto stack = VisualScripting::GetThreadStackTop(); + if (stack && VisualScripting::Evaluate(script, stack->Instance, local.NodeId, local.BoxId, v)) + { + local.Value = v.ToString(); + local.ValueTypeName = v.Type.GetTypeName(); + return true; + } + return false; +} + void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly) { ASSERT(!HasManagedInstance()); diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 93d1723a3..8c9571cfd 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -210,6 +210,31 @@ public: API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath); #endif +public: + API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame + { + DECLARE_SCRIPTING_TYPE_MINIMAL(VisualScriptStackFrame); + + API_FIELD() class VisualScript* Script; + API_FIELD() uint32 NodeId; + API_FIELD() int32 BoxId; + }; + + API_STRUCT(Internal, NoDefault) struct VisualScriptLocal + { + DECLARE_SCRIPTING_TYPE_MINIMAL(VisualScriptLocal); + + API_FIELD() String Value; + API_FIELD() String ValueTypeName; + API_FIELD() uint32 NodeId; + API_FIELD() int32 BoxId; + }; + + API_FUNCTION(Internal) static Array GetVisualScriptStackFrames(); + API_FUNCTION(Internal) static VisualScriptStackFrame GetVisualScriptPreviousScopeFrame(); + API_FUNCTION(Internal) static Array GetVisualScriptLocals(); + API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local); + private: void OnEditorAssemblyLoaded(MAssembly* assembly); diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 2f8d4cda8..18a4695aa 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -123,7 +123,7 @@ namespace FlaxEditor.Surface BoxId = box.ID, }; var script = ((Windows.Assets.VisualScriptWindow)box.Surface.Owner).Asset; - if (Editor.Internal_EvaluateVisualScriptLocal(Object.GetUnmanagedPtr(script), ref local)) + if (Editor.EvaluateVisualScriptLocal(script, ref local)) { text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})"; return true; diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 79157f850..802269677 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -797,11 +797,12 @@ namespace FlaxEditor.Windows.Assets } // Check if any breakpoint was hit - for (int i = 0; i < Surface.Breakpoints.Count; i++) + var breakpoints = Surface.Breakpoints; + for (int i = 0; i < breakpoints.Count; i++) { - if (Surface.Breakpoints[i].ID == flowInfo.NodeId) + if (breakpoints[i].ID == flowInfo.NodeId) { - OnDebugBreakpointHit(ref flowInfo, Surface.Breakpoints[i]); + OnDebugBreakpointHit(ref flowInfo, breakpoints[i]); break; } } @@ -820,7 +821,7 @@ namespace FlaxEditor.Windows.Assets var state = (BreakpointHangState)Editor.Instance.Simulation.BreakpointHangTag; if (state.Locals == null) { - state.Locals = Editor.Internal_GetVisualScriptLocals(out var _); + state.Locals = Editor.GetVisualScriptLocals(); Editor.Instance.Simulation.BreakpointHangTag = state; } return state; @@ -831,7 +832,7 @@ namespace FlaxEditor.Windows.Assets var state = (BreakpointHangState)Editor.Instance.Simulation.BreakpointHangTag; if (state.StackFrames == null) { - state.StackFrames = Editor.Internal_GetVisualScriptStackFrames(out var _); + state.StackFrames = Editor.GetVisualScriptStackFrames(); Editor.Instance.Simulation.BreakpointHangTag = state; } return state; @@ -976,7 +977,7 @@ namespace FlaxEditor.Windows.Assets return; // Break on any of the output connects from the previous scope node - var frame = Editor.Internal_GetVisualScriptPreviousScopeFrame(); + var frame = Editor.GetVisualScriptPreviousScopeFrame(); if (frame.Script != null) { if (_debugStepOutNodesIds == null) From 0dd7e8653750453db58039be9c6ff00f633ffa58 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 18:14:16 +0100 Subject: [PATCH 16/22] Add better Visual Script debugger tooltips display --- Source/Editor/Surface/VisualScriptSurface.cs | 43 +++++++++++++++++-- Source/Engine/Content/Assets/VisualScript.cpp | 5 +++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 18a4695aa..90312c6b7 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -62,6 +62,19 @@ namespace FlaxEditor.Surface return true; } + private string GetBoxDebuggerTooltip(ref Editor.VisualScriptLocal local) + { + if (string.IsNullOrEmpty(local.ValueTypeName)) + { + if (string.IsNullOrEmpty(local.Value)) + return string.Empty; + return local.Value; + } + if (string.IsNullOrEmpty(local.Value)) + return $"({local.ValueTypeName})"; + return $"{local.Value}\n({local.ValueTypeName})"; + } + /// public override void OnNodeBreakpointEdited(SurfaceNode node) { @@ -95,7 +108,7 @@ namespace FlaxEditor.Surface ref var local = ref state.Locals[i]; if (local.BoxId == box.ID && local.NodeId == box.ParentNode.ID) { - text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})"; + text = GetBoxDebuggerTooltip(ref local); return true; } } @@ -107,7 +120,7 @@ namespace FlaxEditor.Surface ref var local = ref state.Locals[i]; if (local.BoxId == connectedBox.ID && local.NodeId == connectedBox.ParentNode.ID) { - text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})"; + text = GetBoxDebuggerTooltip(ref local); return true; } } @@ -125,7 +138,31 @@ namespace FlaxEditor.Surface var script = ((Windows.Assets.VisualScriptWindow)box.Surface.Owner).Asset; if (Editor.EvaluateVisualScriptLocal(script, ref local)) { - text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})"; + // Check if got no value (null) + if (string.IsNullOrEmpty(local.ValueTypeName) && string.Equals(local.Value, "null", StringComparison.Ordinal)) + { + var connections = box.Connections; + if (connections.Count == 0 && box.Archetype.ValueIndex >= 0 && box.ParentNode.Values != null && box.Archetype.ValueIndex < box.ParentNode.Values.Length) + { + // Special case when there is no value but the box has no connection and uses + var defaultValue = box.ParentNode.Values[box.Archetype.ValueIndex]; + if (defaultValue != null) + { + local.Value = defaultValue.ToString(); + local.ValueTypeName = defaultValue.GetType().FullName; + } + } + else if (connections.Count == 1) + { + // Special case when there is no value but the box has a connection with valid value to try to use it instead + box = connections[0]; + local.NodeId = box.ParentNode.ID; + local.BoxId = box.ID; + Editor.EvaluateVisualScriptLocal(script, ref local); + } + } + + text = GetBoxDebuggerTooltip(ref local); return true; } } diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 9748ba60c..320116d60 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -892,6 +892,11 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& PrintStack(LogType::Error); break; } + if (boxBase->ID == 1) + { + value = instance; + break; + } // TODO: check if instance is of event type (including inheritance) // Add Visual Script method to the event bindings table From 8418ca56e8cfeb6071264fbd5b991694590492cf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 18:16:00 +0100 Subject: [PATCH 17/22] Missing comment part --- Source/Editor/Surface/VisualScriptSurface.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 90312c6b7..4635e09d7 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Surface var connections = box.Connections; if (connections.Count == 0 && box.Archetype.ValueIndex >= 0 && box.ParentNode.Values != null && box.Archetype.ValueIndex < box.ParentNode.Values.Length) { - // Special case when there is no value but the box has no connection and uses + // Special case when there is no value but the box has no connection and uses default value var defaultValue = box.ParentNode.Values[box.Archetype.ValueIndex]; if (defaultValue != null) { From 272a147c2e12f049a7308030668054e815e4be67 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 16 Dec 2023 20:36:27 -0600 Subject: [PATCH 18/22] Add saved colors to color picker. --- .../Editor/GUI/Dialogs/ColorPickerDialog.cs | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs index 27878a763..61c60110b 100644 --- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs +++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs @@ -3,6 +3,8 @@ using FlaxEditor.GUI.Input; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Json; +using System.Collections.Generic; namespace FlaxEditor.GUI.Dialogs { @@ -30,6 +32,8 @@ namespace FlaxEditor.GUI.Dialogs private const float HSVMargin = 0.0f; private const float ChannelsMargin = 4.0f; private const float ChannelTextWidth = 12.0f; + private const float SavedColorButtonWidth = 20.0f; + private const float SavedColorButtonHeight = 20.0f; private Color _initialValue; private Color _value; @@ -52,6 +56,9 @@ namespace FlaxEditor.GUI.Dialogs private Button _cOK; private Button _cEyedropper; + private List _savedColors = new List(); + private List