// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using FlaxEditor.Actions; using FlaxEditor.Content; using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Drag; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; using Object = FlaxEngine.Object; namespace FlaxEditor.CustomEditors.Dedicated { /// /// Drag and drop scripts area control. /// /// public class DragAreaControl : ContainerControl { private DragHandlers _dragHandlers; private DragScriptItems _dragScripts; private DragAssets _dragAssets; /// /// The parent scripts editor. /// public ScriptsEditor ScriptsEditor; /// /// Initializes a new instance of the class. /// public DragAreaControl() : base(0, 0, 120, 40) { AutoFocus = false; // Add script button float addScriptButtonWidth = 60.0f; var addScriptButton = new Button { TooltipText = "Add new scripts to the actor", AnchorPreset = AnchorPresets.MiddleCenter, Text = "Add script", Parent = this, Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, 18), }; addScriptButton.ButtonClicked += OnAddScriptButtonClicked; } private void OnAddScriptButtonClicked(Button button) { var scripts = Editor.Instance.CodeEditing.Scripts.Get(); if (scripts.Count == 0) { // No scripts var cm1 = new ContextMenu(); cm1.AddButton("No scripts in project"); cm1.Show(this, button.BottomLeft); return; } // Show context menu with list of scripts to add var cm = new ItemsListContextMenu(180); for (int i = 0; i < scripts.Count; i++) { cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); } cm.ItemClicked += item => AddScript((ScriptType)item.Tag); cm.SortItems(); cm.Show(this, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0)); } /// public override void Draw() { var style = Style.Current; var size = Size; // Info Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, 22, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); // Check if drag is over if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag) { var area = new Rectangle(Float2.Zero, size); Render2D.FillRectangle(area, Color.Orange * 0.5f); Render2D.DrawRectangle(area, Color.Black); } base.Draw(); } private bool ValidateScript(ScriptItem scriptItem) { var scriptName = scriptItem.ScriptName; var scriptType = ScriptsBuilder.FindScript(scriptName); return scriptType != null; } private bool ValidateAsset(AssetItem assetItem) { if (assetItem is VisualScriptItem scriptItem) return scriptItem.ScriptType != ScriptType.Null; return false; } /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) { var result = base.OnDragEnter(ref location, data); if (result != DragDropEffect.None) return result; if (_dragHandlers == null) { _dragScripts = new DragScriptItems(ValidateScript); _dragAssets = new DragAssets(ValidateAsset); _dragHandlers = new DragHandlers { _dragScripts, _dragAssets, }; } return _dragHandlers.OnDragEnter(data); } /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { var result = base.OnDragMove(ref location, data); if (result != DragDropEffect.None) return result; return _dragHandlers.Effect; } /// public override void OnDragLeave() { _dragHandlers.OnDragLeave(); base.OnDragLeave(); } /// public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) { var result = base.OnDragDrop(ref location, data); if (result != DragDropEffect.None) return result; if (_dragHandlers.HasValidDrag) { if (_dragScripts.HasValidDrag) { result = _dragScripts.Effect; AddScripts(_dragScripts.Objects); } else if (_dragAssets.HasValidDrag) { result = _dragAssets.Effect; AddScripts(_dragAssets.Objects); } _dragHandlers.OnDragDrop(null); } return result; } private void AddScript(ScriptType item) { var list = new List(1) { item }; AddScripts(list); } private void AddScripts(List items) { var list = new List(items.Count); for (int i = 0; i < items.Count; i++) { var item = (VisualScriptItem)items[i]; var scriptType = item.ScriptType; if (scriptType == ScriptType.Null) { Editor.LogWarning("Invalid script type " + item.ShortName); } else { list.Add(scriptType); } } AddScripts(list); } private void AddScripts(List items) { var list = new List(items.Count); for (int i = 0; i < items.Count; i++) { var item = items[i]; var scriptName = item.ScriptName; var scriptType = ScriptsBuilder.FindScript(scriptName); if (scriptType == null) { Editor.LogWarning("Invalid script type " + scriptName); } else { list.Add(new ScriptType(scriptType)); } } AddScripts(list); } private void AddScripts(List items) { var actions = new List(4); for (int i = 0; i < items.Count; i++) { var scriptType = items[i]; var actors = ScriptsEditor.ParentEditor.Values; for (int j = 0; j < actors.Count; j++) { var actor = (Actor)actors[j]; actions.Add(AddRemoveScript.Add(actor, scriptType)); } } if (actions.Count == 0) { Editor.LogWarning("Failed to spawn scripts"); return; } var multiAction = new MultiUndoAction(actions); multiAction.Do(); var presenter = ScriptsEditor.Presenter; if (presenter != null) { presenter.Undo.AddAction(multiAction); presenter.Control.Focus(); } } } /// /// Small image control added per script group that allows to drag and drop a reference to it. Also used to reorder the scripts. /// /// internal class ScriptDragIcon : Image { private ScriptsEditor _editor; private bool _isMouseDown; private Float2 _mouseDownPos; /// /// Gets the target script. /// public Script Script => (Script)Tag; /// /// Initializes a new instance of the class. /// /// The script editor. /// The target script. public ScriptDragIcon(ScriptsEditor editor, Script script) { Tag = script; _editor = editor; } /// public override void OnMouseEnter(Float2 location) { _mouseDownPos = Float2.Minimum; base.OnMouseEnter(location); } /// public override void OnMouseLeave() { // Check if start drag drop if (_isMouseDown) { DoDrag(); _isMouseDown = false; } base.OnMouseLeave(); } /// public override void OnMouseMove(Float2 location) { // Check if start drag drop if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f) { DoDrag(); _isMouseDown = false; } base.OnMouseMove(location); } /// public override bool OnMouseUp(Float2 location, MouseButton button) { if (button == MouseButton.Left) { // Clear flag _isMouseDown = false; } return base.OnMouseUp(location, button); } /// public override bool OnMouseDown(Float2 location, MouseButton button) { if (button == MouseButton.Left) { // Set flag _isMouseDown = true; _mouseDownPos = location; } return base.OnMouseDown(location, button); } private void DoDrag() { var script = Script; _editor.OnScriptDragChange(true, script); DoDragDrop(DragScripts.GetDragData(script)); _editor.OnScriptDragChange(false, script); } } internal class ScriptArrangeBar : Control { private ScriptsEditor _editor; private int _index; private Script _script; private DragDropEffect _dragEffect; public ScriptArrangeBar() : base(0, 0, 120, 6) { AutoFocus = false; Visible = false; } public void Init(int index, ScriptsEditor editor) { _editor = editor; _index = index; _editor.ScriptDragChange += OnScriptDragChange; } private void OnScriptDragChange(bool start, Script script) { _script = start ? script : null; Visible = start; OnDragLeave(); } /// public override void Draw() { base.Draw(); var color = FlaxEngine.GUI.Style.Current.BackgroundSelected * (IsDragOver ? 0.9f : 0.1f); Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), color); } /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) { _dragEffect = DragDropEffect.None; var result = base.OnDragEnter(ref location, data); if (result != DragDropEffect.None) return result; if (data is DragDataText textData && DragScripts.IsValidData(textData)) return _dragEffect = DragDropEffect.Move; return result; } /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { return _dragEffect; } /// public override void OnDragLeave() { _dragEffect = DragDropEffect.None; base.OnDragLeave(); } /// public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) { var result = base.OnDragDrop(ref location, data); if (result != DragDropEffect.None) return result; if (_dragEffect != DragDropEffect.None) { result = _dragEffect; _dragEffect = DragDropEffect.None; _editor.ReorderScript(_script, _index); } return result; } } /// /// Custom editor for actor scripts collection. /// /// public sealed class ScriptsEditor : SyncPointEditor { private CheckBox[] _scriptToggles; /// /// Delegate for script drag start and event events. /// /// Set to true if drag started, otherwise false. /// The target script to reorder. public delegate void ScriptDragDelegate(bool start, Script script); /// /// Occurs when script drag changes (starts or ends). /// public event ScriptDragDelegate ScriptDragChange; /// /// The scripts collection. Undo operations are recorder for scripts. /// private readonly List