diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index e0c460d0c..b6eb4ca31 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -8,7 +8,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using FlaxEditor.Content; -using FlaxEditor.Content.Import; using FlaxEditor.Content.Settings; using FlaxEditor.Content.Thumbnails; using FlaxEditor.Modules; @@ -154,12 +153,12 @@ namespace FlaxEditor public ContentFindingModule ContentFinding; /// - /// The scripts editing + /// The scripts editing. /// public CodeEditingModule CodeEditing; /// - /// The scripts documentation + /// The scripts documentation. /// public CodeDocsModule CodeDocs; @@ -179,7 +178,7 @@ namespace FlaxEditor public ProjectCacheModule ProjectCache; /// - /// The undo/redo + /// The undo/redo. /// public EditorUndo Undo; diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs index c61fad713..d7a638598 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; +using System.Text; using FlaxEditor.Options; using FlaxEditor.Scripting; using FlaxEngine; @@ -325,6 +327,78 @@ namespace FlaxEditor.Modules.SourceCodeEditing Editor.Instance.CodeEditing.SelectedEditor = editor; } + /// + /// Starts creating a new module + /// + internal void CreateModule(string path, string moduleName, bool editorModule, bool cpp) + { + if (string.IsNullOrEmpty(moduleName) || string.IsNullOrEmpty(path)) + { + Editor.LogWarning("Failed to create module due to no name"); + return; + } + + // Create folder + var moduleFolderPath = Path.Combine(path, moduleName); + Directory.CreateDirectory(moduleFolderPath); + + // Create module + var moduleText = "using Flax.Build;\n" + + "using Flax.Build.NativeCpp;\n" + + $"\npublic class {moduleName} : Game{(editorModule ? "Editor" : "")}Module\n" + + "{\n " + + "/// \n" + + " public override void Init()\n" + + " {\n" + + " base.Init();\n" + + "\n" + + " // C#-only scripting if false\n" + + $" BuildNativeCode = {(cpp ? "true" : "false")};\n" + + " }\n" + + "\n" + + " /// \n" + + " public override void Setup(BuildOptions options)\n" + + " {" + + "\n" + + " base.Setup(options);\n" + + "\n" + + " options.ScriptingAPI.IgnoreMissingDocumentationWarnings = true;\n" + + "\n" + + " // Here you can modify the build options for your game module\n" + + " // To reference another module use: options.PublicDependencies.Add(\"Audio\");\n" + + " // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n" + + " // To learn more see scripting documentation.\n" + + " }\n" + + "}"; + moduleText = Encoding.UTF8.GetString(Encoding.Default.GetBytes(moduleText)); + var modulePath = Path.Combine(moduleFolderPath, $"{moduleName}.Build.cs"); + File.WriteAllText(modulePath, moduleText); + Editor.Log($"Module created at {modulePath}"); + + // Get editor target and target files and add module + var files = Directory.GetFiles(path); + var targetModuleText = $"Modules.Add(\"{moduleName}\");\n "; + foreach (var file in files) + { + if (!file.Contains(".Build.cs", StringComparison.OrdinalIgnoreCase)) + continue; + var targetText = File.ReadAllText(file); + + // Skip game project if it is suppose to be an editor module + if (editorModule && targetText.Contains("GameProjectTarget", StringComparison.Ordinal)) + continue; + + // TODO: Handle edge case when there are no modules in a target + var index = targetText.IndexOf("Modules.Add"); + if (index != -1) + { + var newText = targetText.Insert(index, targetModuleText); + File.WriteAllText(file, newText); + Editor.Log($"Module added to Target: {file}"); + } + } + } + /// public override void OnUpdate() { diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 1dd26ecf5..03873df57 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -1,11 +1,13 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.IO; using FlaxEditor.Content; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.Assertions; +using FlaxEngine.GUI; using FlaxEngine.Json; namespace FlaxEditor.Windows @@ -145,9 +147,18 @@ namespace FlaxEditor.Windows cm.AddButton("Refresh all thumbnails", RefreshViewItemsThumbnails); } + cm.AddSeparator(); + + // Check if is source folder to add new module + if (folder?.ParentFolder?.Node is ProjectTreeNode parentFolderNode && folder.Node == parentFolderNode.Source) + { + var button = cm.AddButton("New module"); + button.CloseMenuOnClick = false; + button.Clicked += () => NewModule(button, parentFolderNode.Source.Path); + } + if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode)) { - cm.AddSeparator(); cm.AddButton("New folder", NewFolder); } @@ -311,5 +322,138 @@ namespace FlaxEditor.Windows return; } } + + private void NewModule(ContextMenuButton button, string path) + { + var popup = new ContextMenuBase + { + Size = new Float2(230, 125), + ClipChildren = false, + CullChildren = false, + }; + popup.Show(button, new Float2(button.Width, 0)); + + var nameLabel = new Label + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Name", + HorizontalAlignment = TextAlignment.Near, + }; + nameLabel.LocalX += 10; + nameLabel.LocalY += 10; + + var nameTextBox = new TextBox + { + Parent = popup, + WatermarkText = "Module Name", + AnchorPreset = AnchorPresets.TopLeft, + IsMultiline = false, + }; + nameTextBox.LocalX += 100; + nameTextBox.LocalY += 10; + var defaultTextBoxBorderColor = nameTextBox.BorderColor; + var defaultTextBoxBorderSelectedColor = nameTextBox.BorderSelectedColor; + nameTextBox.TextChanged += () => + { + if (string.IsNullOrEmpty(nameTextBox.Text)) + { + nameTextBox.BorderColor = defaultTextBoxBorderColor; + nameTextBox.BorderSelectedColor = defaultTextBoxBorderSelectedColor; + return; + } + + var pluginPath = Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text); + if (Directory.Exists(pluginPath)) + { + nameTextBox.BorderColor = Color.Red; + nameTextBox.BorderSelectedColor = Color.Red; + } + else + { + nameTextBox.BorderColor = defaultTextBoxBorderColor; + nameTextBox.BorderSelectedColor = defaultTextBoxBorderSelectedColor; + } + }; + + var editorLabel = new Label + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Editor", + HorizontalAlignment = TextAlignment.Near, + }; + editorLabel.LocalX += 10; + editorLabel.LocalY += 35; + + var editorCheckBox = new CheckBox + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + }; + editorCheckBox.LocalY += 35; + editorCheckBox.LocalX += 100; + + var cppLabel = new Label + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "C++", + HorizontalAlignment = TextAlignment.Near, + }; + cppLabel.LocalX += 10; + cppLabel.LocalY += 60; + + var cppCheckBox = new CheckBox + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + }; + cppCheckBox.LocalY += 60; + cppCheckBox.LocalX += 100; + + var submitButton = new Button + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Create", + Width = 70, + }; + submitButton.LocalX += 40; + submitButton.LocalY += 90; + submitButton.Clicked += () => + { + // TODO: Check all modules in project including plugins + if (Directory.Exists(Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text))) + { + Editor.LogWarning("Cannot create module due to name conflict."); + return; + } + Editor.CodeEditing.CreateModule(path, nameTextBox.Text, editorCheckBox.Checked, cppCheckBox.Checked); + nameTextBox.Clear(); + editorCheckBox.Checked = false; + cppCheckBox.Checked = false; + popup.Hide(); + button.ParentContextMenu.Hide(); + }; + + var cancelButton = new Button + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Cancel", + Width = 70, + }; + cancelButton.LocalX += 120; + cancelButton.LocalY += 90; + cancelButton.Clicked += () => + { + nameTextBox.Clear(); + editorCheckBox.Checked = false; + cppCheckBox.Checked = false; + popup.Hide(); + button.ParentContextMenu.Hide(); + }; + } } } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index b8e8a15ed..6b5855238 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -329,7 +329,7 @@ namespace FlaxEditor.Windows b.Checked = ShowPluginsFiles; b.CloseMenuOnClick = false; b.AutoCheck = true; - + b = show.ContextMenu.AddButton("Generated files", () => ShowGeneratedFiles = !ShowGeneratedFiles); b.TooltipText = "Shows generated files"; b.Checked = ShowGeneratedFiles; @@ -756,7 +756,7 @@ namespace FlaxEditor.Windows } /// - /// Stars creating the folder. + /// Starts creating the folder. /// public void NewFolder() {