diff --git a/Source/Editor/Content/Proxy/ScriptProxy.cs b/Source/Editor/Content/Proxy/ScriptProxy.cs index f688f37df..d728dcc38 100644 --- a/Source/Editor/Content/Proxy/ScriptProxy.cs +++ b/Source/Editor/Content/Proxy/ScriptProxy.cs @@ -69,8 +69,7 @@ namespace FlaxEditor.Content /// public override bool IsFileNameValid(string filename) { - // Scripts cannot start with digit. - if (Char.IsDigit(filename[0])) + if (char.IsDigit(filename[0])) return false; if (filename.Equals("Script")) return false; diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index e03ccb331..63374361d 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -3,6 +3,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; +using System.Linq; using FlaxEditor.Actions; using FlaxEditor.Content; using FlaxEditor.GUI; @@ -16,6 +18,27 @@ using Object = FlaxEngine.Object; namespace FlaxEditor.CustomEditors.Dedicated { + internal class NewScriptItem : ItemsListContextMenu.Item + { + private string _scriptName; + + public string ScriptName + { + get => _scriptName; + set + { + _scriptName = value; + Name = $"Create script '{value}'"; + } + } + + public NewScriptItem(string scriptName) + { + ScriptName = scriptName; + TooltipText = "Create a new script"; + } + } + /// /// Drag and drop scripts area control. /// @@ -74,7 +97,43 @@ namespace FlaxEditor.CustomEditors.Dedicated { cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); } - cm.ItemClicked += item => AddScript((ScriptType)item.Tag); + cm.TextChanged += text => + { + if (!IsValidScriptName(text)) + return; + if (!cm.ItemsPanel.Children.Any(x => x.Visible && x is not NewScriptItem)) + { + // If there are no visible items, that means the search failed so we can find the create script button or create one if it's the first time + var newScriptItem = (NewScriptItem)cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem); + if (newScriptItem != null) + { + newScriptItem.Visible = true; + newScriptItem.ScriptName = text; + } + else + { + cm.AddItem(new NewScriptItem(text)); + } + } + else + { + // Make sure to hide the create script button if there + var newScriptItem = cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem); + if (newScriptItem != null) + newScriptItem.Visible = false; + } + }; + cm.ItemClicked += item => + { + if (item.Tag is ScriptType script) + { + AddScript(script); + } + else if (item is NewScriptItem newScriptItem) + { + CreateScript(newScriptItem); + } + }; cm.SortItems(); cm.Show(this, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0)); } @@ -113,6 +172,19 @@ namespace FlaxEditor.CustomEditors.Dedicated return false; } + private static bool IsValidScriptName(string text) + { + if (string.IsNullOrEmpty(text)) + return false; + if (text.Contains(' ')) + return false; + if (char.IsDigit(text[0])) + return false; + if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_')) + return false; + return Editor.Instance.ContentDatabase.GetProxy("cs").IsFileNameValid(text); + } + /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) { @@ -163,6 +235,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (_dragScripts.HasValidDrag) { result = _dragScripts.Effect; + AddScripts(_dragScripts.Objects); } else if (_dragAssets.HasValidDrag) @@ -177,7 +250,43 @@ namespace FlaxEditor.CustomEditors.Dedicated return result; } - private void AddScript(ScriptType item) + private void CreateScript(NewScriptItem item) + { + ScriptsEditor.NewScriptName = item.ScriptName; + var paths = Directory.GetFiles(Globals.ProjectSourceFolder, "*.Build.cs"); + + string moduleName = null; + foreach (var p in paths) + { + var file = File.ReadAllText(p); + if (!file.Contains("GameProjectTarget")) + continue; // Skip + + if (file.Contains("Modules.Add(\"Game\")")) + { + // Assume Game represents the main game module + moduleName = "Game"; + break; + } + } + + // Ensure the path slashes are correct for the OS + var correctedPath = Path.GetFullPath(Globals.ProjectSourceFolder); + if (string.IsNullOrEmpty(moduleName)) + { + var error = FileSystem.ShowBrowseFolderDialog(Editor.Instance.Windows.MainWindow, correctedPath, "Select a module folder to put the new script in", out moduleName); + if (error) + return; + } + var path = Path.Combine(Globals.ProjectSourceFolder, moduleName, item.ScriptName + ".cs"); + Editor.Instance.ContentDatabase.GetProxy("cs").Create(path, null); + } + + /// + /// Attach a script to the actor. + /// + /// The script. + public void AddScript(ScriptType item) { var list = new List(1) { item }; AddScripts(list); @@ -491,6 +600,11 @@ namespace FlaxEditor.CustomEditors.Dedicated /// public override IEnumerable UndoObjects => _scripts; + /// + /// Cached the newly created script name - used to add script after compilation. + /// + internal static string NewScriptName; + private void AddMissingScript(int index, LayoutElementsContainer layout) { var group = layout.Group("Missing script"); @@ -599,6 +713,21 @@ namespace FlaxEditor.CustomEditors.Dedicated var dragArea = layout.CustomContainer(); dragArea.CustomControl.ScriptsEditor = this; + // If the initialization is triggered by an editor recompilation, check if it was due to script generation from DragAreaControl + if (NewScriptName != null) + { + var script = Editor.Instance.CodeEditing.Scripts.Get().FirstOrDefault(x => x.Name == NewScriptName); + NewScriptName = null; + if (script != null) + { + dragArea.CustomControl.AddScript(script); + } + else + { + Editor.LogWarning("Failed to find newly created script."); + } + } + // No support for showing scripts from multiple actors that have different set of scripts var scripts = (Script[])Values[0]; _scripts.Clear(); @@ -636,7 +765,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var values = new ScriptsContainer(elementType, i, Values); var scriptType = TypeUtils.GetObjectType(script); var editor = CustomEditorsUtil.CreateEditor(scriptType, false); - + // Check if actor has all the required scripts bool hasAllRequirements = true; if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false)) diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 42d236991..c8c2a9c22 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -189,6 +189,11 @@ namespace FlaxEditor.GUI /// public event Action ItemClicked; + /// + /// Event fired when search text in this popup menu gets changed. + /// + public event Action TextChanged; + /// /// The panel control where you should add your items. /// @@ -263,6 +268,7 @@ namespace FlaxEditor.GUI UnlockChildrenRecursive(); PerformLayout(true); _searchBox.Focus(); + TextChanged?.Invoke(_searchBox.Text); } /// @@ -439,6 +445,7 @@ namespace FlaxEditor.GUI Hide(); return true; case KeyboardKeys.ArrowDown: + { if (RootWindow.FocusedControl == null) { // Focus search box if nothing is focused @@ -447,20 +454,19 @@ namespace FlaxEditor.GUI } // Focus the first visible item or then next one + var items = GetVisibleItems(); + var focusedIndex = items.IndexOf(focusedItem); + if (focusedIndex == -1) + focusedIndex = -1; + if (focusedIndex + 1 < items.Count) { - var items = GetVisibleItems(); - var focusedIndex = items.IndexOf(focusedItem); - if (focusedIndex == -1) - focusedIndex = -1; - if (focusedIndex + 1 < items.Count) - { - var item = items[focusedIndex + 1]; - item.Focus(); - _scrollPanel.ScrollViewTo(item); - return true; - } + var item = items[focusedIndex + 1]; + item.Focus(); + _scrollPanel.ScrollViewTo(item); + return true; } break; + } case KeyboardKeys.ArrowUp: if (focusedItem != null) {