diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 1a308e08a..efcd1bec3 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -103,11 +103,12 @@ namespace FlaxEditor.CustomEditors.Dedicated var actors = ScriptsEditor.ParentEditor.Values; foreach (var a in actors) { - if (a.GetType() != requireActor.RequiredType) - { - item.Enabled = false; - break; - } + if (a.GetType() == requireActor.RequiredType) + continue; + if (requireActor.IncludeInheritedTypes && a.GetType().IsSubclassOf(requireActor.RequiredType)) + continue; + item.Enabled = false; + break; } } cm.AddItem(item); @@ -824,7 +825,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (attribute != null) { var actor = script.Actor; - if (actor.GetType() != attribute.RequiredType && !actor.GetType().IsSubclassOf(attribute.RequiredType)) + if (actor.GetType() != attribute.RequiredType && (attribute.IncludeInheritedTypes && !actor.GetType().IsSubclassOf(attribute.RequiredType))) { Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Actor of type `{attribute.RequiredType}`."); hasAllRequirements = false; diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs index 3e0332b09..a8bfe4f5f 100644 --- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -18,7 +18,7 @@ namespace FlaxEditor.CustomEditors.Dedicated public class SplineEditor : ActorEditor { /// - /// Storage undo spline data + /// Stores undo spline data. /// private struct UndoData { @@ -83,7 +83,7 @@ namespace FlaxEditor.CustomEditors.Dedicated } /// - /// Edit curve options manipulate the curve as free mode + /// Edit curve options manipulate the curve as free mode. /// private sealed class FreeTangentMode : EditTangentOptionBase { @@ -98,7 +98,7 @@ namespace FlaxEditor.CustomEditors.Dedicated } /// - /// Edit curve options to set tangents to linear + /// Edit curve options to set tangents to linear. /// private sealed class LinearTangentMode : EditTangentOptionBase { @@ -107,13 +107,13 @@ namespace FlaxEditor.CustomEditors.Dedicated { SetKeyframeLinear(spline, index); - // change the selection to tangent parent (a spline point / keyframe) + // Change the selection to tangent parent (a spline point / keyframe) Editor.SetSelectSplinePointNode(spline, index); } } /// - /// Edit curve options to align tangents of selected spline + /// Edit curve options to align tangents of selected spline. /// private sealed class AlignedTangentMode : EditTangentOptionBase { @@ -168,8 +168,7 @@ namespace FlaxEditor.CustomEditors.Dedicated } /// - /// Edit curve options manipulate the curve setting selected point - /// tangent in as smoothed but tangent out as linear + /// Edit curve options manipulate the curve setting selected point tangent in as smoothed but tangent out as linear. /// private sealed class SmoothInTangentMode : EditTangentOptionBase { @@ -182,8 +181,7 @@ namespace FlaxEditor.CustomEditors.Dedicated } /// - /// Edit curve options manipulate the curve setting selected point - /// tangent in as linear but tangent out as smoothed + /// Edit curve options manipulate the curve setting selected point tangent in as linear but tangent out as smoothed. /// private sealed class SmoothOutTangentMode : EditTangentOptionBase { @@ -219,7 +217,8 @@ namespace FlaxEditor.CustomEditors.Dedicated var enabled = EnabledInHierarchy && tab.EnabledInHierarchy; var style = FlaxEngine.GUI.Style.Current; var size = Size; - var textHeight = 16.0f; + var textHeight = 30.0f; + // Make icons smaller when tabs get thinner var iconSize = size.Y - textHeight; var iconRect = new Rectangle((Width - iconSize) / 2, 0, iconSize, iconSize); if (tab._mirrorIcon) @@ -230,8 +229,11 @@ namespace FlaxEditor.CustomEditors.Dedicated var color = style.Foreground; if (!enabled) color *= 0.6f; + var textRect = new Rectangle(0, size.Y - textHeight, size.X, textHeight); + Render2D.PushClip(new Rectangle(Float2.Zero, Size)); Render2D.DrawSprite(tab._customIcon, iconRect, color); - Render2D.DrawText(style.FontMedium, tab._customText, new Rectangle(0, iconSize, size.X, textHeight), color, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, tab._customText, textRect, color, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords, 0.65f); + Render2D.PopClip(); } } @@ -291,18 +293,21 @@ namespace FlaxEditor.CustomEditors.Dedicated return; _selectedSpline = spline; - layout.Space(10); - var tabSize = 46; + //var tabSize = 46; + var tabSize = 60; var icons = Editor.Instance.Icons; - layout.Header("Selected spline point"); + var group = layout.Group("Selected Point"); _selectedPointsTabs = new Tabs { Height = tabSize, TabsSize = new Float2(tabSize), AutoTabsSize = true, - Parent = layout.ContainerControl, + Parent = group.ContainerControl, }; + // Move the group above the group containing spline points + group.Control.IndexInParent = 3; + _linearTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedLinear, "Linear", icons.SplineLinear64)); _freeTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedFree, "Free", icons.SplineFree64)); _alignedTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedAligned, "Aligned", icons.SplineAligned64)); @@ -310,13 +315,13 @@ namespace FlaxEditor.CustomEditors.Dedicated _smoothOutTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedSmoothOut, "Smooth Out", icons.SplineSmoothIn64, true)); _selectedPointsTabs.SelectedTabIndex = -1; - layout.Header("All spline points"); + group = layout.Group("All Points"); _allPointsTabs = new Tabs { Height = tabSize, TabsSize = new Float2(tabSize), AutoTabsSize = true, - Parent = layout.ContainerControl, + Parent = group.ContainerControl, }; _setLinearAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsLinear, "Set Linear Tangents", icons.SplineLinear64)); _setSmoothAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsSmooth, "Set Smooth Tangents", icons.SplineAligned64)); diff --git a/Source/Editor/CustomEditors/GUI/PropertiesList.cs b/Source/Editor/CustomEditors/GUI/PropertiesList.cs index 3847a8f36..f430bae06 100644 --- a/Source/Editor/CustomEditors/GUI/PropertiesList.cs +++ b/Source/Editor/CustomEditors/GUI/PropertiesList.cs @@ -70,9 +70,9 @@ namespace FlaxEditor.CustomEditors.GUI UpdateSplitRect(); } - private void AutoSizeSplitter() + private void AutoSizeSplitter(bool ignoreCustomSplitterValue = false) { - if (_hasCustomSplitterValue || !Editor.Instance.Options.Options.Interface.AutoSizePropertiesPanelSplitter) + if (_hasCustomSplitterValue && !ignoreCustomSplitterValue) return; Font font = Style.Current.FontMedium; @@ -178,6 +178,21 @@ namespace FlaxEditor.CustomEditors.GUI return base.OnMouseDown(location, button); } + /// + public override bool OnMouseDoubleClick(Float2 location, MouseButton button) + { + if (button == MouseButton.Left && _splitterRect.Contains(location)) + { + if (_splitterClicked) + EndTracking(); + + AutoSizeSplitter(true); + return true; + } + + return base.OnMouseDoubleClick(location, button); + } + /// public override bool OnMouseUp(Float2 location, MouseButton button) { @@ -220,7 +235,8 @@ namespace FlaxEditor.CustomEditors.GUI // Refresh UpdateSplitRect(); PerformLayout(true); - AutoSizeSplitter(); + if (Editor.Instance.Options.Options.Interface.AutoSizePropertiesPanelSplitter) + AutoSizeSplitter(); } /// diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index 58739917e..c08648505 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -526,6 +526,23 @@ int32 Editor::LoadProduct() return 12; } + // Get the last opened project path + String localCachePath; + FileSystem::GetSpecialFolderPath(SpecialFolder::AppData, localCachePath); + String editorConfigPath = localCachePath / TEXT("Flax"); + String lastProjectSettingPath = editorConfigPath / TEXT("LastProject.txt"); + if (!FileSystem::DirectoryExists(editorConfigPath)) + FileSystem::CreateDirectory(editorConfigPath); + String lastProjectPath; + if (FileSystem::FileExists(lastProjectSettingPath)) + File::ReadAllText(lastProjectSettingPath, lastProjectPath); + if (!FileSystem::DirectoryExists(lastProjectPath)) + lastProjectPath = String::Empty; + + // Try to open the last project when requested + if (projectPath.IsEmpty() && CommandLine::Options.LastProject.IsTrue() && !lastProjectPath.IsEmpty()) + projectPath = lastProjectPath; + // Missing project case if (projectPath.IsEmpty()) { @@ -541,7 +558,7 @@ int32 Editor::LoadProduct() Array files; if (FileSystem::ShowOpenFileDialog( nullptr, - StringView::Empty, + lastProjectPath, TEXT("Project files (*.flaxproj)\0*.flaxproj\0All files (*.*)\0*.*\0"), false, TEXT("Select project to open in Editor"), @@ -625,6 +642,10 @@ int32 Editor::LoadProduct() } } + // Update the last opened project path + if (lastProjectPath.Compare(Project->ProjectFolderPath) != 0) + File::WriteAllText(lastProjectSettingPath, Project->ProjectFolderPath, Encoding::UTF8); + return 0; } diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs index e379e911b..af0187248 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs @@ -325,7 +325,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing var codeEditor = options.SourceCode.SourceCodeEditor; if (codeEditor != "None") { - foreach (var e in Editor.Instance.CodeEditing.Editors) + foreach (var e in _editors) { if (string.Equals(codeEditor, e.Name, StringComparison.OrdinalIgnoreCase)) { @@ -334,7 +334,10 @@ namespace FlaxEditor.Modules.SourceCodeEditing } } } - Editor.Instance.CodeEditing.SelectedEditor = editor; + if (editor == null && _editors.Count != 0) + editor = _editors[0]; + + SelectedEditor = editor; } /// diff --git a/Source/Editor/Modules/SourceCodeEditing/DefaultSourceCodeEditor.cs b/Source/Editor/Modules/SourceCodeEditing/DefaultSourceCodeEditor.cs index 074dade6a..00057e9a7 100644 --- a/Source/Editor/Modules/SourceCodeEditing/DefaultSourceCodeEditor.cs +++ b/Source/Editor/Modules/SourceCodeEditing/DefaultSourceCodeEditor.cs @@ -41,9 +41,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing var vsCode = codeEditing.GetInBuildEditor(CodeEditorTypes.VSCode); var rider = codeEditing.GetInBuildEditor(CodeEditorTypes.Rider); -#if PLATFORM_WINDOW +#if PLATFORM_WINDOWS // Favor the newest Visual Studio - for (int i = (int)CodeEditorTypes.VS2019; i >= (int)CodeEditorTypes.VS2008; i--) + for (int i = (int)CodeEditorTypes.VS2026; i >= (int)CodeEditorTypes.VS2008; i--) { var visualStudio = codeEditing.GetInBuildEditor((CodeEditorTypes)i); if (visualStudio != null) @@ -74,7 +74,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing public string Name => "Default"; /// - public string GenerateProjectCustomArgs => null; + public string GenerateProjectCustomArgs => _currentEditor?.GenerateProjectCustomArgs; /// public void OpenSolution() diff --git a/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs b/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs index a2a333805..222a7a0bc 100644 --- a/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs +++ b/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs @@ -22,71 +22,14 @@ namespace FlaxEditor.Modules.SourceCodeEditing public InBuildSourceCodeEditor(CodeEditorTypes type) { Type = type; - switch (type) - { - case CodeEditorTypes.Custom: - Name = "Custom"; - break; - case CodeEditorTypes.SystemDefault: - Name = "System Default"; - break; - case CodeEditorTypes.VS2008: - Name = "Visual Studio 2008"; - break; - case CodeEditorTypes.VS2010: - Name = "Visual Studio 2010"; - break; - case CodeEditorTypes.VS2012: - Name = "Visual Studio 2012"; - break; - case CodeEditorTypes.VS2013: - Name = "Visual Studio 2013"; - break; - case CodeEditorTypes.VS2015: - Name = "Visual Studio 2015"; - break; - case CodeEditorTypes.VS2017: - Name = "Visual Studio 2017"; - break; - case CodeEditorTypes.VS2019: - Name = "Visual Studio 2019"; - break; - case CodeEditorTypes.VS2022: - Name = "Visual Studio 2022"; - break; - case CodeEditorTypes.VS2026: - Name = "Visual Studio 2026"; - break; - case CodeEditorTypes.VSCode: - Name = "Visual Studio Code"; - break; - case CodeEditorTypes.VSCodeInsiders: - Name = "Visual Studio Code - Insiders"; - break; - case CodeEditorTypes.Rider: - Name = "Rider"; - break; - default: throw new ArgumentOutOfRangeException(nameof(type), type, null); - } + Name = CodeEditingManager.GetName(type); } /// public string Name { get; set; } /// - public string GenerateProjectCustomArgs - { - get - { - switch (Type) - { - case CodeEditorTypes.VSCodeInsiders: - case CodeEditorTypes.VSCode: return "-vscode -vs2022"; - case CodeEditorTypes.Rider: return "-vs2022"; - default: return null; - } - } - } + public string GenerateProjectCustomArgs => CodeEditingManager.GetGenerateProjectCustomArgs(Type); /// public void OpenSolution() diff --git a/Source/Editor/Scripting/CodeEditor.cpp b/Source/Editor/Scripting/CodeEditor.cpp index b372da189..fcc8eef7c 100644 --- a/Source/Editor/Scripting/CodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditor.cpp @@ -139,6 +139,34 @@ CodeEditor* CodeEditingManager::GetCodeEditor(CodeEditorTypes editorType) return nullptr; } +String CodeEditingManager::GetName(CodeEditorTypes editorType) +{ + const auto editor = GetCodeEditor(editorType); + if (editor) + { + return editor->GetName(); + } + else + { + LOG(Warning, "Missing code editor type {0}", (int32)editorType); + return String::Empty; + } +} + +String CodeEditingManager::GetGenerateProjectCustomArgs(CodeEditorTypes editorType) +{ + const auto editor = GetCodeEditor(editorType); + if (editor) + { + return editor->GetGenerateProjectCustomArgs(); + } + else + { + LOG(Warning, "Missing code editor type {0}", (int32)editorType); + return String::Empty; + } +} + void CodeEditingManager::OpenFile(CodeEditorTypes editorType, const String& path, int32 line) { const auto editor = GetCodeEditor(editorType); diff --git a/Source/Editor/Scripting/CodeEditor.h b/Source/Editor/Scripting/CodeEditor.h index 9cc71977b..0baae21b0 100644 --- a/Source/Editor/Scripting/CodeEditor.h +++ b/Source/Editor/Scripting/CodeEditor.h @@ -109,9 +109,18 @@ public: /// /// Gets the name of the editor. /// - /// The name + /// The name. virtual String GetName() const = 0; + /// + /// Gets the custom arguments for the Flax.Build tool to add when generating project files for this code editor. + /// + /// The custom arguments to generate project files. + virtual String GetGenerateProjectCustomArgs() const + { + return String::Empty; + } + /// /// Opens the file. /// @@ -169,6 +178,20 @@ public: /// The editor object or null if not found. static CodeEditor* GetCodeEditor(CodeEditorTypes editorType); + /// + /// Gets the name of the editor. + /// + /// The code editor type. + /// The name. + API_FUNCTION() static String GetName(CodeEditorTypes editorType); + + /// + /// Gets the custom arguments for the Flax.Build tool to add when generating project files for this code editor. + /// + /// The code editor type. + /// The custom arguments to generate project files. + API_FUNCTION() static String GetGenerateProjectCustomArgs(CodeEditorTypes editorType); + /// /// Opens the file. Handles async opening. /// diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index 8884ca322..b63815dce 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -257,12 +257,17 @@ String RiderCodeEditor::GetName() const return TEXT("Rider"); } +String RiderCodeEditor::GetGenerateProjectCustomArgs() const +{ + return TEXT("-vs2022"); +} + void RiderCodeEditor::OpenFile(const String& path, int32 line) { // Generate project files if solution is missing if (!FileSystem::FileExists(_solutionPath)) { - ScriptsBuilder::GenerateProject(TEXT("-vs2022")); + ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs()); } // Open file @@ -290,7 +295,7 @@ void RiderCodeEditor::OpenSolution() // Generate project files if solution is missing if (!FileSystem::FileExists(_solutionPath)) { - ScriptsBuilder::GenerateProject(TEXT("-vs2022")); + ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs()); } // Open solution @@ -312,5 +317,5 @@ void RiderCodeEditor::OpenSolution() void RiderCodeEditor::OnFileAdded(const String& path) { - ScriptsBuilder::GenerateProject(); + ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs()); } diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.h b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.h index d9bf00947..f3c43b5b2 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.h +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.h @@ -35,6 +35,7 @@ public: // [CodeEditor] CodeEditorTypes GetType() const override; String GetName() const override; + String GetGenerateProjectCustomArgs() const override; void OpenFile(const String& path, int32 line) override; void OpenSolution() override; void OnFileAdded(const String& path) override; diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp index 5c06eec9c..2d79cc02b 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp @@ -145,7 +145,46 @@ CodeEditorTypes VisualStudioEditor::GetType() const String VisualStudioEditor::GetName() const { - return String(ToString(_version)); + const Char* name; + switch (_version) + { + case VisualStudioVersion::VS2008: + name = TEXT("Visual Studio 2008"); + break; + case VisualStudioVersion::VS2010: + name = TEXT("Visual Studio 2010"); + break; + case VisualStudioVersion::VS2012: + name = TEXT("Visual Studio 2012"); + break; + case VisualStudioVersion::VS2013: + name = TEXT("Visual Studio 2013"); + break; + case VisualStudioVersion::VS2015: + name = TEXT("Visual Studio 2015"); + break; + case VisualStudioVersion::VS2017: + name = TEXT("Visual Studio 2017"); + break; + case VisualStudioVersion::VS2019: + name = TEXT("Visual Studio 2019"); + break; + case VisualStudioVersion::VS2022: + name = TEXT("Visual Studio 2022"); + break; + case VisualStudioVersion::VS2026: + name = TEXT("Visual Studio 2026"); + break; + default: + name = ToString(_version); + break; + } + return String(name); +} + +String VisualStudioEditor::GetGenerateProjectCustomArgs() const +{ + return String::Format(TEXT("-{0}"), String(ToString(_version)).ToLower()); } void VisualStudioEditor::OpenFile(const String& path, int32 line) @@ -153,7 +192,7 @@ void VisualStudioEditor::OpenFile(const String& path, int32 line) // Generate project files if solution is missing if (!FileSystem::FileExists(_solutionPath)) { - ScriptsBuilder::GenerateProject(); + ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs()); } // Open file @@ -172,7 +211,7 @@ void VisualStudioEditor::OpenSolution() // Generate project files if solution is missing if (!FileSystem::FileExists(_solutionPath)) { - ScriptsBuilder::GenerateProject(); + ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs()); } // Open solution @@ -187,7 +226,7 @@ void VisualStudioEditor::OpenSolution() void VisualStudioEditor::OnFileAdded(const String& path) { // TODO: finish dynamic files adding to the project - for now just regenerate it - ScriptsBuilder::GenerateProject(); + ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs()); return; if (!FileSystem::FileExists(_solutionPath)) { diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h index 1bf1f1433..5c32a1171 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h +++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h @@ -56,6 +56,7 @@ public: // [CodeEditor] CodeEditorTypes GetType() const override; String GetName() const override; + String GetGenerateProjectCustomArgs() const override; void OpenFile(const String& path, int32 line) override; void OpenSolution() override; void OnFileAdded(const String& path) override; diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp index 5eba7f20c..bf2ef6bb6 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp @@ -128,6 +128,11 @@ String VisualStudioCodeEditor::GetName() const return _isInsiders ? TEXT("Visual Studio Code - Insiders") : TEXT("Visual Studio Code"); } +String VisualStudioCodeEditor::GetGenerateProjectCustomArgs() const +{ + return TEXT("-vs2022 -vscode"); +} + void VisualStudioCodeEditor::OpenFile(const String& path, int32 line) { // Generate VS solution files for intellisense diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h index 0212f207e..5091e1134 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h +++ b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h @@ -37,6 +37,7 @@ public: // [CodeEditor] CodeEditorTypes GetType() const override; String GetName() const override; + String GetGenerateProjectCustomArgs() const override; void OpenFile(const String& path, int32 line) override; void OpenSolution() override; bool UseAsyncForOpen() const override; diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 1e7d2295a..2182c2f55 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -247,8 +247,8 @@ namespace FlaxEditor.Viewport { _movementSpeed = value; - if (_cameraButton != null) - _cameraButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed); + if (_orthographicModeButton != null) + _orthographicModeButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed); } } @@ -581,7 +581,7 @@ namespace FlaxEditor.Viewport // Camera Settings Menu var cameraCM = new ContextMenu(); - _cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), _editor.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth) + _cameraButton = new ViewportWidgetButton("", _editor.Icons.Camera64, cameraCM) { Tag = this, TooltipText = "Camera Settings.", @@ -590,7 +590,7 @@ namespace FlaxEditor.Viewport _cameraWidget.Parent = this; // Orthographic/Perspective Mode Widget - _orthographicModeButton = new ViewportWidgetButton(string.Empty, _editor.Icons.CamSpeed32, null, true) + _orthographicModeButton = new OrthoCamToggleViewportWidgetButton(cameraSpeedTextWidth) { Checked = !_isOrtho, TooltipText = "Toggle Orthographic/Perspective Mode.", diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs index 55c3e9c71..dd09dc24c 100644 --- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs +++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs @@ -7,6 +7,100 @@ using FlaxEngine.GUI; namespace FlaxEditor.Viewport.Widgets { + /// + /// Otrhographic view toggle viewport Widget Button class. + /// Will draw a custom camera frustum to represent an orthographic or perspective camera. + /// + /// + [HideInEditor] + public class OrthoCamToggleViewportWidgetButton : ViewportWidgetButton + { + private const int iconPointCount = 4; + private const float iconRenderScale = 4.0f; + + private readonly Float2 iconDrawOffset = new Float2(3.0f, 3.0f); + + private readonly Float2[] iconPointsPerspective = new[] + { + new Float2(0.0f, 1.0f), // Top left + new Float2(4.0f, 0.0f), // Top right + new Float2(4.0f, 3.0f), // Bottom right + new Float2(0.0f, 2.0f), // Bottom left + }; + + private readonly Float2[] iconPointsOrtho = new[] + { + new Float2(0.0f, 0.0f), // Top left + new Float2(4.0f, 0.0f), // Top right + new Float2(4.0f, 3.0f), // Bottom right + new Float2(0.0f, 3.0f), // Bottom left + }; + + private bool wasChecked; + private float lerpWeight; + + /// + /// Initializes a new instance of the class. + /// + public OrthoCamToggleViewportWidgetButton(float speedTextWidth) + : base("00.0", SpriteHandle.Invalid, null, true, 20.0f + speedTextWidth) + { + wasChecked = Checked; + } + + /// + public override void Update(float deltaTime) + { + if (wasChecked != Checked) + { + lerpWeight = 0.0f; + wasChecked = Checked; + } + + if (lerpWeight <= 1.0f) + lerpWeight += deltaTime * 4.2f; + + base.Update(deltaTime); + } + + /// + public override void Draw() + { + // Cache data + var style = Style.Current; + var textRect = new Rectangle(0.0f, 0.0f, Width - 2.0f, Height); + var backgroundRect = textRect with { Width = Width - 1.0f }; + + // Check if is checked or mouse is over + if (Checked) + Render2D.FillRectangle(backgroundRect, style.BackgroundSelected * (IsMouseOver ? 0.9f : 0.6f)); + else if (IsMouseOver) + Render2D.FillRectangle(backgroundRect, style.BackgroundHighlighted); + + // Draw text + Render2D.DrawText(style.FontMedium, Text, textRect, style.ForegroundViewport * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Far, TextAlignment.Center); + + // Draw camera frustum icon + Float2[] currentStart = Checked ? iconPointsOrtho : iconPointsPerspective; + Float2[] currentTarget = Checked ? iconPointsPerspective : iconPointsOrtho; + + var features = Render2D.Features; + Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping; + for (int i = 1; i < iconPointCount + 1; i++) + { + int endPointIndex = Mathf.Wrap(i, 0, iconPointCount - 1); + Float2 lineStart = Float2.Lerp(currentStart[i - 1], currentTarget[i - 1], lerpWeight); + Float2 lineEnd = Float2.Lerp(currentStart[endPointIndex], currentTarget[endPointIndex], lerpWeight); + + lineStart = lineStart * iconRenderScale + iconDrawOffset; + lineEnd = lineEnd * iconRenderScale + iconDrawOffset; + + Render2D.DrawLine(lineStart, lineEnd, Color.White); + } + Render2D.Features = features; + } + } + /// /// Viewport Widget Button class. /// diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index 442051d73..5eee07dbf 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -109,7 +109,14 @@ void Behavior::UpdateAsync() const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context); if (result != BehaviorUpdateResult::Running) _result = result; - if (_result != BehaviorUpdateResult::Running) + if (_result != BehaviorUpdateResult::Running && tree->Graph.Root->Loop) + { + // Reset State + _result = BehaviorUpdateResult::Running; + _accumulatedTime = 0.0f; + _totalTime = 0; + } + else if (_result != BehaviorUpdateResult::Running) { Finished(); } diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 7aaab5f3c..05a472da7 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -96,6 +96,10 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTre // The target amount of the behavior logic updates per second. API_FIELD(Attributes="EditorOrder(100)") float UpdateFPS = 10.0f; + + // Whether to loop the root node. + API_FIELD(Attributes="EditorOrder(200)") + bool Loop = true; }; /// diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp index e0f9441eb..ad2c9c903 100644 --- a/Source/Engine/Engine/CommandLine.cpp +++ b/Source/Engine/Engine/CommandLine.cpp @@ -149,6 +149,7 @@ bool CommandLine::Parse(const Char* cmdLine) PARSE_BOOL_SWITCH("-clearcache ", ClearCache); PARSE_BOOL_SWITCH("-clearcooker ", ClearCookerCache); PARSE_ARG_SWITCH("-project ", Project); + PARSE_BOOL_SWITCH("-lastproject ", LastProject); PARSE_BOOL_SWITCH("-new ", NewProject); PARSE_BOOL_SWITCH("-genprojectfiles ", GenProjectFiles); PARSE_ARG_SWITCH("-build ", Build); diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h index ad49bee04..0464a478f 100644 --- a/Source/Engine/Engine/CommandLine.h +++ b/Source/Engine/Engine/CommandLine.h @@ -133,6 +133,11 @@ public: /// String Project; + /// + /// -lastproject (Opens the last project) + /// + Nullable LastProject; + /// /// -new (generates the project files inside the specified project folder or uses current workspace folder) /// diff --git a/Source/Engine/Input/Gamepad.cpp b/Source/Engine/Input/Gamepad.cpp index f4a2ef4ab..32856839b 100644 --- a/Source/Engine/Input/Gamepad.cpp +++ b/Source/Engine/Input/Gamepad.cpp @@ -2,6 +2,71 @@ #include "Gamepad.h" +namespace +{ + GamepadAxis GetButtonAxis(GamepadButton button, bool& positive) + { + positive = true; + switch (button) + { + case GamepadButton::LeftTrigger: + return GamepadAxis::LeftTrigger; + case GamepadButton::RightTrigger: + return GamepadAxis::RightTrigger; + case GamepadButton::LeftStickUp: + return GamepadAxis::LeftStickY; + case GamepadButton::LeftStickDown: + positive = false; + return GamepadAxis::LeftStickY; + case GamepadButton::LeftStickLeft: + positive = false; + return GamepadAxis::LeftStickX; + case GamepadButton::LeftStickRight: + return GamepadAxis::LeftStickX; + case GamepadButton::RightStickUp: + return GamepadAxis::RightStickY; + case GamepadButton::RightStickDown: + positive = false; + return GamepadAxis::RightStickY; + case GamepadButton::RightStickLeft: + positive = false; + return GamepadAxis::RightStickX; + case GamepadButton::RightStickRight: + return GamepadAxis::RightStickX; + default: + return GamepadAxis::None; + } + } + + bool GetButtonState(const Gamepad::State& state, GamepadButton button, float deadZone) + { + if (deadZone > 0.01f) + { + switch (button) + { + case GamepadButton::LeftTrigger: + case GamepadButton::RightTrigger: + case GamepadButton::LeftStickUp: + case GamepadButton::LeftStickDown: + case GamepadButton::LeftStickLeft: + case GamepadButton::LeftStickRight: + case GamepadButton::RightStickUp: + case GamepadButton::RightStickDown: + case GamepadButton::RightStickLeft: + case GamepadButton::RightStickRight: + { + bool positive; + float axis = state.Axis[(int32)GetButtonAxis(button, positive)]; + return positive ? axis >= deadZone : axis <= -deadZone; + } + default: + break; + } + } + return state.Buttons[(int32)button]; + } +} + void GamepadLayout::Init() { for (int32 i = 0; i < (int32)GamepadButton::MAX; i++) @@ -31,6 +96,21 @@ void Gamepad::ResetState() _mappedPrevState.Clear(); } +bool Gamepad::GetButton(GamepadButton button, float deadZone) const +{ + return GetButtonState(_mappedState, button, deadZone); +} + +bool Gamepad::GetButtonDown(GamepadButton button, float deadZone) const +{ + return GetButtonState(_mappedState, button, deadZone) && !GetButtonState(_mappedPrevState, button, deadZone); +} + +bool Gamepad::GetButtonUp(GamepadButton button, float deadZone) const +{ + return !GetButtonState(_mappedState, button, deadZone) && GetButtonState(_mappedPrevState, button, deadZone); +} + bool Gamepad::IsAnyButtonDown() const { // TODO: optimize with SIMD diff --git a/Source/Engine/Input/Gamepad.h b/Source/Engine/Input/Gamepad.h index 20994d85a..dc95c67ae 100644 --- a/Source/Engine/Input/Gamepad.h +++ b/Source/Engine/Input/Gamepad.h @@ -148,36 +148,30 @@ public: /// Gets the gamepad button state (true if being pressed during the current frame). /// /// Gamepad button to check + /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered. /// True if user holds down the button, otherwise false. - API_FUNCTION() FORCE_INLINE bool GetButton(const GamepadButton button) const - { - return _mappedState.Buttons[static_cast(button)]; - } + API_FUNCTION() bool GetButton(GamepadButton button, float deadZone = 0.0f) const; /// /// Gets the gamepad button down state (true if was pressed during the current frame). /// /// Gamepad button to check + /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered. /// True if user starts pressing down the button, otherwise false. - API_FUNCTION() FORCE_INLINE bool GetButtonDown(const GamepadButton button) const - { - return _mappedState.Buttons[static_cast(button)] && !_mappedPrevState.Buttons[static_cast(button)]; - } - - /// - /// Checks if any gamepad button is currently pressed. - /// - API_PROPERTY() bool IsAnyButtonDown() const; + API_FUNCTION() bool GetButtonDown(GamepadButton button, float deadZone = 0.0f) const; /// /// Gets the gamepad button up state (true if was released during the current frame). /// /// Gamepad button to check + /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered. /// True if user releases the button, otherwise false. - API_FUNCTION() FORCE_INLINE bool GetButtonUp(const GamepadButton button) const - { - return !_mappedState.Buttons[static_cast(button)] && _mappedPrevState.Buttons[static_cast(button)]; - } + API_FUNCTION() bool GetButtonUp(GamepadButton button, float deadZone = 0.0f) const; + + /// + /// Checks if any gamepad button is currently pressed. + /// + API_PROPERTY() bool IsAnyButtonDown() const; public: /// diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index 7048140ef..c95d7b03e 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -120,6 +120,7 @@ void InputSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* m config.MouseButton = JsonTools::GetEnum(v, "MouseButton", MouseButton::None); config.GamepadButton = JsonTools::GetEnum(v, "GamepadButton", GamepadButton::None); config.Gamepad = JsonTools::GetEnum(v, "Gamepad", InputGamepadIndex::All); + config.DeadZone = JsonTools::GetFloat(v, "DeadZone", 0.5f); } } else @@ -499,24 +500,24 @@ float Input::GetGamepadAxis(int32 gamepadIndex, GamepadAxis axis) return 0.0f; } -bool Input::GetGamepadButton(int32 gamepadIndex, GamepadButton button) +bool Input::GetGamepadButton(int32 gamepadIndex, GamepadButton button, float deadZone) { if (gamepadIndex >= 0 && gamepadIndex < Gamepads.Count()) - return Gamepads[gamepadIndex]->GetButton(button); + return Gamepads[gamepadIndex]->GetButton(button, deadZone); return false; } -bool Input::GetGamepadButtonDown(int32 gamepadIndex, GamepadButton button) +bool Input::GetGamepadButtonDown(int32 gamepadIndex, GamepadButton button, float deadZone) { if (gamepadIndex >= 0 && gamepadIndex < Gamepads.Count()) - return Gamepads[gamepadIndex]->GetButtonDown(button); + return Gamepads[gamepadIndex]->GetButtonDown(button, deadZone); return false; } -bool Input::GetGamepadButtonUp(int32 gamepadIndex, GamepadButton button) +bool Input::GetGamepadButtonUp(int32 gamepadIndex, GamepadButton button, float deadZone) { if (gamepadIndex >= 0 && gamepadIndex < Gamepads.Count()) - return Gamepads[gamepadIndex]->GetButtonUp(button); + return Gamepads[gamepadIndex]->GetButtonUp(button, deadZone); return false; } @@ -542,13 +543,13 @@ float Input::GetGamepadAxis(InputGamepadIndex gamepad, GamepadAxis axis) return false; } -bool Input::GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button) +bool Input::GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button, float deadZone) { if (gamepad == InputGamepadIndex::All) { for (auto g : Gamepads) { - if (g->GetButton(button)) + if (g->GetButton(button, deadZone)) return true; } } @@ -556,18 +557,18 @@ bool Input::GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button) { const auto index = static_cast(gamepad); if (index < Gamepads.Count()) - return Gamepads[index]->GetButton(button); + return Gamepads[index]->GetButton(button, deadZone); } return false; } -bool Input::GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button) +bool Input::GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button, float deadZone) { if (gamepad == InputGamepadIndex::All) { for (auto g : Gamepads) { - if (g->GetButtonDown(button)) + if (g->GetButtonDown(button, deadZone)) return true; } } @@ -575,18 +576,18 @@ bool Input::GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button { const auto index = static_cast(gamepad); if (index < Gamepads.Count()) - return Gamepads[index]->GetButtonDown(button); + return Gamepads[index]->GetButtonDown(button, deadZone); } return false; } -bool Input::GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button) +bool Input::GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button, float deadZone) { if (gamepad == InputGamepadIndex::All) { for (auto g : Gamepads) { - if (g->GetButtonUp(button)) + if (g->GetButtonUp(button, deadZone)) return true; } } @@ -594,7 +595,7 @@ bool Input::GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button) { const auto index = static_cast(gamepad); if (index < Gamepads.Count()) - return Gamepads[index]->GetButtonUp(button); + return Gamepads[index]->GetButtonUp(button, deadZone); } return false; } @@ -1065,26 +1066,26 @@ void InputService::Update() bool isActive; if (config.Mode == InputActionMode::Pressing) { - isActive = Input::GetKey(config.Key) || Input::GetMouseButton(config.MouseButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadButton); + isActive = Input::GetKey(config.Key) || Input::GetMouseButton(config.MouseButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadButton, config.DeadZone); } else if (config.Mode == InputActionMode::Press) { - isActive = Input::GetKeyDown(config.Key) || Input::GetMouseButtonDown(config.MouseButton) || Input::GetGamepadButtonDown(config.Gamepad, config.GamepadButton); + isActive = Input::GetKeyDown(config.Key) || Input::GetMouseButtonDown(config.MouseButton) || Input::GetGamepadButtonDown(config.Gamepad, config.GamepadButton, config.DeadZone); } else { - isActive = Input::GetKeyUp(config.Key) || Input::GetMouseButtonUp(config.MouseButton) || Input::GetGamepadButtonUp(config.Gamepad, config.GamepadButton); + isActive = Input::GetKeyUp(config.Key) || Input::GetMouseButtonUp(config.MouseButton) || Input::GetGamepadButtonUp(config.Gamepad, config.GamepadButton, config.DeadZone); } - if (Input::GetKeyDown(config.Key) || Input::GetMouseButtonDown(config.MouseButton) || Input::GetGamepadButtonDown(config.Gamepad, config.GamepadButton)) + if (Input::GetKeyDown(config.Key) || Input::GetMouseButtonDown(config.MouseButton) || Input::GetGamepadButtonDown(config.Gamepad, config.GamepadButton, config.DeadZone)) { data.State = InputActionState::Press; } - else if (Input::GetKey(config.Key) || Input::GetMouseButton(config.MouseButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadButton)) + else if (Input::GetKey(config.Key) || Input::GetMouseButton(config.MouseButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadButton, config.DeadZone)) { data.State = InputActionState::Pressing; } - else if (Input::GetKeyUp(config.Key) || Input::GetMouseButtonUp(config.MouseButton) || Input::GetGamepadButtonUp(config.Gamepad, config.GamepadButton)) + else if (Input::GetKeyUp(config.Key) || Input::GetMouseButtonUp(config.MouseButton) || Input::GetGamepadButtonUp(config.Gamepad, config.GamepadButton, config.DeadZone)) { data.State = InputActionState::Release; } diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h index 73e87f5f0..dca26a5f4 100644 --- a/Source/Engine/Input/Input.h +++ b/Source/Engine/Input/Input.h @@ -238,24 +238,27 @@ public: /// /// The gamepad index /// Gamepad button to check + /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered. /// True if user holds down the button, otherwise false. - API_FUNCTION() static bool GetGamepadButton(int32 gamepadIndex, GamepadButton button); + API_FUNCTION() static bool GetGamepadButton(int32 gamepadIndex, GamepadButton button, float deadZone = 0.0f); /// /// Gets the gamepad button down state (true if was pressed during the current frame). /// /// The gamepad index /// Gamepad button to check + /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered. /// True if user starts pressing down the button, otherwise false. - API_FUNCTION() static bool GetGamepadButtonDown(int32 gamepadIndex, GamepadButton button); + API_FUNCTION() static bool GetGamepadButtonDown(int32 gamepadIndex, GamepadButton button, float deadZone = 0.0f); /// /// Gets the gamepad button up state (true if was released during the current frame). /// /// The gamepad index /// Gamepad button to check + /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered. /// True if user releases the button, otherwise false. - API_FUNCTION() static bool GetGamepadButtonUp(int32 gamepadIndex, GamepadButton button); + API_FUNCTION() static bool GetGamepadButtonUp(int32 gamepadIndex, GamepadButton button, float deadZone = 0.0f); /// /// Gets the gamepad axis value. @@ -270,24 +273,27 @@ public: /// /// The gamepad /// Gamepad button to check + /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered. /// True if user holds down the button, otherwise false. - API_FUNCTION() static bool GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button); + API_FUNCTION() static bool GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button, float deadZone = 0.0f); /// /// Gets the gamepad button down state (true if was pressed during the current frame). /// /// The gamepad /// Gamepad button to check + /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered. /// True if user starts pressing down the button, otherwise false. - API_FUNCTION() static bool GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button); + API_FUNCTION() static bool GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button, float deadZone = 0.0f); /// /// Gets the gamepad button up state (true if was released during the current frame). /// /// The gamepad /// Gamepad button to check + /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered. /// True if user releases the button, otherwise false. - API_FUNCTION() static bool GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button); + API_FUNCTION() static bool GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button, float deadZone = 0.0f); public: /// diff --git a/Source/Engine/Input/VirtualInput.h b/Source/Engine/Input/VirtualInput.h index 817d7014c..a4975aaef 100644 --- a/Source/Engine/Input/VirtualInput.h +++ b/Source/Engine/Input/VirtualInput.h @@ -48,6 +48,12 @@ API_STRUCT() struct ActionConfig /// API_FIELD(Attributes="EditorOrder(40)") InputGamepadIndex Gamepad; + + /// + /// Threshold for non-binary value inputs such as gamepad stick position to decide if action was triggered. Can be sued to activate action only if input value is higher than specified number. + /// + API_FIELD(Attributes = "EditorOrder(50)") + float DeadZone = 0.5f; }; /// diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index f52fab600..abf62aef0 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -178,21 +178,20 @@ void Actor::OnDeleteObject() _scene = nullptr; } } - else if (_parent) + else { - // Unlink from the parent - _parent->Children.RemoveKeepOrder(this); - _parent->_isHierarchyDirty = true; - _parent = nullptr; - _scene = nullptr; + if (_isEnabled) + OnDisable(); + if (_parent) + { + // Unlink from the parent + _parent->Children.RemoveKeepOrder(this); + _parent->_isHierarchyDirty = true; + _parent = nullptr; + _scene = nullptr; + } } - // Ensure to exit gameplay in a valid way - ASSERT(!IsDuringPlay()); -#if BUILD_DEBUG || BUILD_DEVELOPMENT - ASSERT(!_isEnabled); -#endif - // Fire event Deleted(this); diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 9cf6794be..956005f57 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1123,6 +1123,32 @@ SceneResult SceneLoader::OnBegin(Args& args) _lastSceneLoadTime = DateTime::Now(); StartFrame = Engine::UpdateCount; + // Validate arguments + if (!args.Data.IsArray()) + { + LOG(Error, "Invalid Data member."); + CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, Guid::Empty); + return SceneResult::Failed; + } + + // Peek scene node value (it's the first actor serialized) + SceneId = JsonTools::GetGuid(args.Data[0], "ID"); + if (!SceneId.IsValid()) + { + LOG(Error, "Invalid scene id."); + CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId); + return SceneResult::Failed; + } + + // Peek meta + if (args.EngineBuild < 6000) + { + LOG(Error, "Invalid serialized engine build."); + CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId); + return SceneResult::Failed; + } + Modifier->EngineBuild = args.EngineBuild; + // Scripting backend should be loaded for the current project before loading scene if (!Scripting::HasGameModulesLoaded()) { @@ -1136,27 +1162,7 @@ SceneResult SceneLoader::OnBegin(Args& args) MessageBox::Show(TEXT("Failed to load scripts.\n\nCannot load scene without game script modules.\n\nSee logs for more info."), TEXT("Missing game modules"), MessageBoxButtons::OK, MessageBoxIcon::Error); } #endif - return SceneResult::Failed; - } - - // Peek meta - if (args.EngineBuild < 6000) - { - LOG(Error, "Invalid serialized engine build."); - return SceneResult::Failed; - } - if (!args.Data.IsArray()) - { - LOG(Error, "Invalid Data member."); - return SceneResult::Failed; - } - Modifier->EngineBuild = args.EngineBuild; - - // Peek scene node value (it's the first actor serialized) - SceneId = JsonTools::GetGuid(args.Data[0], "ID"); - if (!SceneId.IsValid()) - { - LOG(Error, "Invalid scene id."); + CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId); return SceneResult::Failed; } @@ -1164,6 +1170,7 @@ SceneResult SceneLoader::OnBegin(Args& args) if (Level::FindScene(SceneId) != nullptr) { LOG(Info, "Scene {0} is already loaded.", SceneId); + CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId); return SceneResult::Failed; } diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.cpp b/Source/Engine/Platform/Apple/AppleFileSystem.cpp index 450507009..a38269d69 100644 --- a/Source/Engine/Platform/Apple/AppleFileSystem.cpp +++ b/Source/Engine/Platform/Apple/AppleFileSystem.cpp @@ -83,17 +83,19 @@ void AppleFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& res switch (type) { case SpecialFolder::Desktop: - result = home / TEXT("/Desktop"); + result = home / TEXT("/Desktop"); // TODO: should be NSDesktopDirectory break; case SpecialFolder::Documents: - result = home / TEXT("/Documents"); + result = home / TEXT("/Documents"); // TODO: should be NSDocumentDirectory break; case SpecialFolder::Pictures: - result = home / TEXT("/Pictures"); + result = home / TEXT("/Pictures"); // TODO: should be NSPicturesDirectory break; case SpecialFolder::AppData: + result = home / TEXT("/Library/Application Support"); // TODO: should be NSApplicationSupportDirectory + break; case SpecialFolder::LocalAppData: - result = home / TEXT("/Library/Caches"); + result = home / TEXT("/Library/Caches"); // TODO: should be NSApplicationSupportDirectory break; case SpecialFolder::ProgramData: result = home / TEXT("/Library/Application Support"); diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index 9b31dc28f..4293985fa 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -338,17 +338,35 @@ void LinuxFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& res switch (type) { case SpecialFolder::Desktop: - result = home / TEXT("Desktop"); + { + String desktopDir; + if (!Platform::GetEnvironmentVariable(TEXT("XDG_DESKTOP_DIR"), desktopDir)) + result = desktopDir; + else + result = home / TEXT("Desktop"); break; + } case SpecialFolder::Documents: result = String::Empty; break; case SpecialFolder::Pictures: - result = home / TEXT("Pictures"); + { + String picturesDir; + if (!Platform::GetEnvironmentVariable(TEXT("XDG_PICTURES_DIR"), picturesDir)) + result = picturesDir; + else + result = home / TEXT("Pictures"); break; + } case SpecialFolder::AppData: - result = TEXT("/usr/share"); + { + String configHome; + if (!Platform::GetEnvironmentVariable(TEXT("XDG_CONFIG_HOME"), configHome)) + result = configHome; + else + result = home / TEXT(".config"); break; + } case SpecialFolder::LocalAppData: { String dataHome; diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 1881aac3b..e62970f83 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -158,6 +158,7 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTexture* lightBuffer) renderContext.Buffers->GBuffer3->View(), }; renderContext.View.Pass = DrawPass::GBuffer; + context->SetViewportAndScissors(renderContext.Buffers->GetViewport()); // Clear GBuffer { diff --git a/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs index 6e08217bf..3e5ad7f73 100644 --- a/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs +++ b/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs @@ -13,13 +13,20 @@ public class RequireActorAttribute : Attribute /// The required type. /// public Type RequiredType; + + /// + /// Whether to include inherited types. + /// + public bool IncludeInheritedTypes; /// /// Initializes a new instance of the class. /// /// The required type. - public RequireActorAttribute(Type type) + /// Whether to include inherited types. + public RequireActorAttribute(Type type, bool includeInheritedTypes = false) { RequiredType = type; + IncludeInheritedTypes = includeInheritedTypes; } } diff --git a/Source/Engine/UI/GUI/CanvasRootControl.cs b/Source/Engine/UI/GUI/CanvasRootControl.cs index 4697c5051..db6722090 100644 --- a/Source/Engine/UI/GUI/CanvasRootControl.cs +++ b/Source/Engine/UI/GUI/CanvasRootControl.cs @@ -25,12 +25,12 @@ namespace FlaxEngine.GUI /// /// Gets a value indicating whether canvas is 2D (screen-space). /// - public bool Is2D => _canvas.RenderMode == CanvasRenderMode.ScreenSpace; + public bool Is2D => _canvas.Is2D; /// /// Gets a value indicating whether canvas is 3D (world-space or camera-space). /// - public bool Is3D => _canvas.RenderMode != CanvasRenderMode.ScreenSpace; + public bool Is3D => _canvas.Is3D; /// /// Initializes a new instance of the class. diff --git a/Source/Engine/UI/GUI/CanvasScaler.cs b/Source/Engine/UI/GUI/CanvasScaler.cs index 1e30fd22f..8dc1fcd4d 100644 --- a/Source/Engine/UI/GUI/CanvasScaler.cs +++ b/Source/Engine/UI/GUI/CanvasScaler.cs @@ -298,6 +298,7 @@ namespace FlaxEngine.GUI { case CanvasRenderMode.WorldSpace: case CanvasRenderMode.WorldSpaceFaceCamera: + case CanvasRenderMode.GPUTexture: scale = 1.0f; break; default: diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index 20c81ce1c..df542aacf 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -33,6 +33,11 @@ namespace FlaxEngine /// The world space rendering mode that places Canvas as any other object in the scene and orients it to face the camera. The size of the Canvas can be set manually using its Transform, and UI elements will render in front of or behind other objects in the scene based on 3D placement. This is useful for UIs that are meant to be a part of the world. This is also known as a 'diegetic interface'. /// WorldSpaceFaceCamera = 3, + + /// + /// The off-screen rendering mode that draws the contents of the canvas into a GPU texture that can be used in the scene or by other systems. The size of the canvas is automatically set to the size of the texture. + /// + GPUTexture = 4, } /// @@ -105,7 +110,7 @@ namespace FlaxEngine private CanvasRenderMode _renderMode; private readonly CanvasRootControl _guiRoot; private CanvasRenderer _renderer; - private bool _isLoading, _isRegisteredForTick; + private bool _isLoading, _isRegisteredForTick, _isRegisteredForOnDraw; /// /// Gets or sets the canvas rendering mode. @@ -169,6 +174,8 @@ namespace FlaxEngine private bool Editor_IsCameraSpace => _renderMode == CanvasRenderMode.CameraSpace; + private bool Editor_IsGPUTexture => _renderMode == CanvasRenderMode.GPUTexture; + private bool Editor_UseRenderCamera => _renderMode == CanvasRenderMode.CameraSpace || _renderMode == CanvasRenderMode.WorldSpaceFaceCamera; #endif @@ -206,6 +213,12 @@ namespace FlaxEngine [EditorOrder(60), Limit(0.01f), EditorDisplay("Canvas"), VisibleIf("Editor_IsCameraSpace"), Tooltip("Distance from the RenderCamera to place the plane with GUI. If the screen is resized, changes resolution, or the camera frustum changes, the Canvas will automatically change size to match as well.")] public float Distance { get; set; } = 500; + /// + /// Gets or sets the output texture for the canvas when render mode is set to . The size of the canvas will be automatically set to the size of the texture. The canvas will render its content into this texture. + /// + [EditorOrder(70), NoSerialize, EditorDisplay("Canvas"), VisibleIf("Editor_IsGPUTexture")] + public GPUTexture OutputTexture { get; set; } + /// /// Gets the canvas GUI root control. /// @@ -329,6 +342,11 @@ namespace FlaxEngine _isRegisteredForTick = false; Scripting.Update -= OnUpdate; } + if (_isRegisteredForOnDraw) + { + _isRegisteredForOnDraw = false; + Scripting.Draw -= OnDraw; + } } /// @@ -358,7 +376,7 @@ namespace FlaxEngine /// /// Gets a value indicating whether canvas is 3D (world-space or camera-space). /// - public bool Is3D => _renderMode != CanvasRenderMode.ScreenSpace; + public bool Is3D => _renderMode != CanvasRenderMode.ScreenSpace && _renderMode != CanvasRenderMode.GPUTexture; /// /// Gets the world matrix used to transform the GUI from the local space to the world space. Handles canvas rendering mode @@ -491,6 +509,11 @@ namespace FlaxEngine { if (_isLoading) return; + if (_isRegisteredForOnDraw) + { + _isRegisteredForOnDraw = false; + Scripting.Draw -= OnDraw; + } switch (_renderMode) { @@ -563,7 +586,32 @@ namespace FlaxEngine } break; } + case CanvasRenderMode.GPUTexture: + { + if (!_isRegisteredForOnDraw) + { + _isRegisteredForOnDraw = true; + Scripting.Draw += OnDraw; + } + break; } + } + } + + private void OnDraw() + { + var outputTexture = OutputTexture; + if (!outputTexture || !outputTexture.IsAllocated) + return; + var context = GPUDevice.Instance.MainContext; + _guiRoot.Size = outputTexture.Size; + + Profiler.BeginEvent("UI Canvas"); + Profiler.BeginEventGPU("UI Canvas"); + context.Clear(outputTexture.View(), Color.Transparent); + Render2D.CallDrawing(GUI, context, outputTexture); + Profiler.EndEvent(); + Profiler.EndEventGPU(); } private void OnUpdate()