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()
{