diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 1dd26ecf5..f10175cce 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,152 @@ namespace FlaxEditor.Windows cm.AddButton("Refresh all thumbnails", RefreshViewItemsThumbnails); } + cm.AddSeparator(); + + // Check if is source folder to add new module + if (item is ContentFolder sourceFolder && sourceFolder.ParentFolder.Node is ProjectTreeNode node) + { + if (sourceFolder.Node == node.Source) + { + var button = cm.AddButton("New Module"); + button.CloseMenuOnClick = false; + button.Clicked += () => + { + 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; + } + CreateModule(node.Source.Path, nameTextBox.Text, editorCheckBox.Checked, cppCheckBox.Checked); + nameTextBox.Clear(); + editorCheckBox.Checked = false; + cppCheckBox.Checked = false; + popup.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(); + }; + }; + } + } + if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode)) { - cm.AddSeparator(); cm.AddButton("New folder", NewFolder); } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index b8e8a15ed..1210b021c 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Xml; using FlaxEditor.Content; using FlaxEditor.Content.GUI; @@ -756,7 +757,80 @@ namespace FlaxEditor.Windows } /// - /// Stars creating the folder. + /// Starts creating a new module + /// + private async 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"); + await File.WriteAllTextAsync(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 = await File.ReadAllTextAsync(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); + await File.WriteAllTextAsync(file, newText); + Editor.Log($"Module added to Target: {file}"); + } + } + } + + /// + /// Starts creating the folder. /// public void NewFolder() {