Files
FlaxEngine/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
2024-04-22 23:17:56 +02:00

1037 lines
37 KiB
C#

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
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
{
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";
}
}
/// <summary>
/// Drag and drop scripts area control.
/// </summary>
/// <seealso cref="FlaxEngine.GUI.Control" />
public class DragAreaControl : ContainerControl
{
private DragHandlers _dragHandlers;
private DragScriptItems _dragScripts;
private DragAssets _dragAssets;
private Button _addScriptsButton;
/// <summary>
/// The parent scripts editor.
/// </summary>
public ScriptsEditor ScriptsEditor;
/// <summary>
/// Initializes a new instance of the <see cref="DragAreaControl"/> class.
/// </summary>
public DragAreaControl()
: base(0, 0, 120, 40)
{
AutoFocus = false;
// Add script button
var buttonText = "Add script";
var textSize = Style.Current.FontMedium.MeasureText(buttonText);
float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4;
var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4;
_addScriptsButton = new Button
{
TooltipText = "Add new scripts to the actor",
AnchorPreset = AnchorPresets.MiddleCenter,
Text = buttonText,
Parent = this,
Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, buttonHeight),
};
_addScriptsButton.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.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));
}
/// <inheritdoc />
public override void Draw()
{
var style = Style.Current;
var size = Size;
// Info
Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, _addScriptsButton.Height + 4, 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, style.Selection);
Render2D.DrawRectangle(area, style.SelectionBorder);
}
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;
}
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);
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None || _dragHandlers == null)
return result;
return _dragHandlers.Effect;
}
/// <inheritdoc />
public override void OnDragLeave()
{
_dragHandlers?.OnDragLeave();
base.OnDragLeave();
}
/// <inheritdoc />
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 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);
}
/// <summary>
/// Attach a script to the actor.
/// </summary>
/// <param name="item">The script.</param>
public void AddScript(ScriptType item)
{
var list = new List<ScriptType>(1) { item };
AddScripts(list);
}
private void AddScripts(List<AssetItem> items)
{
var list = new List<ScriptType>(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<ScriptItem> items)
{
var list = new List<ScriptType>(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<ScriptType> items)
{
var actions = new List<IUndoAction>();
for (int i = 0; i < items.Count; i++)
{
var scriptType = items[i];
RequireScriptAttribute scriptAttribute = null;
if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false))
{
foreach (var e in scriptType.GetAttributes(false))
{
if (e is not RequireScriptAttribute requireScriptAttribute)
continue;
scriptAttribute = requireScriptAttribute;
break;
}
}
// See if script requires a specific actor type
RequireActorAttribute actorAttribute = null;
if (scriptType.HasAttribute(typeof(RequireActorAttribute), false))
{
foreach (var e in scriptType.GetAttributes(false))
{
if (e is not RequireActorAttribute requireActorAttribute)
continue;
actorAttribute = requireActorAttribute;
break;
}
}
var actors = ScriptsEditor.ParentEditor.Values;
for (int j = 0; j < actors.Count; j++)
{
var actor = (Actor)actors[j];
// If required actor exists but is not this actor type then skip adding to actor
if (actorAttribute != null)
{
if (actor.GetType() != actorAttribute.RequiredType && !actor.GetType().IsSubclassOf(actorAttribute.RequiredType))
{
Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` not added to `{actor}` due to script requiring an Actor type of `{actorAttribute.RequiredType}`.");
continue;
}
}
actions.Add(AddRemoveScript.Add(actor, scriptType));
// Check if actor has required scripts and add them if the actor does not.
if (scriptAttribute != null)
{
foreach (var type in scriptAttribute.RequiredTypes)
{
if (!type.IsSubclassOf(typeof(Script)))
{
Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(type.Name)}` not added to `{actor}` due to the class not being a subclass of Script.");
continue;
}
if (actor.GetScript(type) != null)
continue;
actions.Add(AddRemoveScript.Add(actor, new ScriptType(type)));
}
}
}
}
if (actions.Count == 0)
{
Editor.LogWarning("Failed to spawn scripts");
return;
}
var multiAction = new MultiUndoAction(actions);
multiAction.Do();
var presenter = ScriptsEditor.Presenter;
ScriptsEditor.ParentEditor?.RebuildLayout();
if (presenter != null)
{
presenter.Undo.AddAction(multiAction);
presenter.Control.Focus();
}
}
}
/// <summary>
/// Small image control added per script group that allows to drag and drop a reference to it. Also used to reorder the scripts.
/// </summary>
/// <seealso cref="FlaxEngine.GUI.Image" />
internal class DragImage : Image
{
private bool _isMouseDown;
private Float2 _mouseDownPos;
/// <summary>
/// Action called when drag event should start.
/// </summary>
public Action<DragImage> Drag;
/// <inheritdoc />
public override void OnMouseEnter(Float2 location)
{
_mouseDownPos = Float2.Minimum;
base.OnMouseEnter(location);
}
/// <inheritdoc />
public override void OnMouseLeave()
{
if (_isMouseDown)
{
_isMouseDown = false;
Drag(this);
}
base.OnMouseLeave();
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f)
{
_isMouseDown = false;
Drag(this);
}
base.OnMouseMove(location);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left)
{
_isMouseDown = false;
return true;
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left)
{
_isMouseDown = true;
_mouseDownPos = location;
return true;
}
return base.OnMouseDown(location, button);
}
}
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();
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
var color = Style.Current.BackgroundSelected * (IsDragOver ? 0.9f : 0.1f);
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), color);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
return _dragEffect;
}
/// <inheritdoc />
public override void OnDragLeave()
{
_dragEffect = DragDropEffect.None;
base.OnDragLeave();
}
/// <inheritdoc />
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;
}
}
/// <summary>
/// Custom editor for actor scripts collection.
/// </summary>
/// <seealso cref="CustomEditor" />
public sealed class ScriptsEditor : SyncPointEditor
{
private CheckBox[] _scriptToggles;
/// <summary>
/// Delegate for script drag start and event events.
/// </summary>
/// <param name="start">Set to true if drag started, otherwise false.</param>
/// <param name="script">The target script to reorder.</param>
public delegate void ScriptDragDelegate(bool start, Script script);
/// <summary>
/// Occurs when script drag changes (starts or ends).
/// </summary>
public event ScriptDragDelegate ScriptDragChange;
/// <summary>
/// The scripts collection. Undo operations are recorder for scripts.
/// </summary>
private readonly List<Script> _scripts = new List<Script>();
/// <inheritdoc />
public override IEnumerable<object> UndoObjects => _scripts;
/// <summary>
/// Cached the newly created script name - used to add script after compilation.
/// </summary>
internal static string NewScriptName;
private void AddMissingScript(int index, LayoutElementsContainer layout)
{
var group = layout.Group("Missing script");
// Add settings button to the group
var settingsButton = group.AddSettingsButton();
settingsButton.Tag = index;
settingsButton.Clicked += MissingSettingsButtonOnClicked;
}
private void MissingSettingsButtonOnClicked(Image image, MouseButton mouseButton)
{
if (mouseButton != MouseButton.Left)
return;
var index = (int)image.Tag;
var cm = new ContextMenu
{
Tag = index
};
cm.AddButton("Remove", OnClickMissingRemove);
cm.Show(image, image.Size);
}
private void OnClickMissingRemove(ContextMenuButton button)
{
var index = (int)button.ParentContextMenu.Tag;
// TODO: support undo
var actors = ParentEditor.Values;
for (int i = 0; i < actors.Count; i++)
{
var actor = (Actor)actors[i];
var script = actor.GetScript(index);
if (script)
{
script.Parent = null;
Object.Destroy(script);
}
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
}
}
/// <summary>
/// Values container for the collection of the scripts. Helps with prefab linkage and reference value usage (uses Prefab Instance ID rather than index in array).
/// </summary>
public sealed class ScriptsContainer : ListValueContainer
{
private readonly Guid _prefabObjectId;
/// <summary>
/// Gets the prefab object identifier used by the container scripts. Empty if there is no valid linkage to the prefab object.
/// </summary>
public Guid PrefabObjectId => _prefabObjectId;
/// <summary>
/// Initializes a new instance of the <see cref="ScriptsContainer"/> class.
/// </summary>
/// <param name="elementType">Type of the collection elements (script type).</param>
/// <param name="index">The script index in the actor scripts collection.</param>
/// <param name="values">The collection values (scripts array).</param>
public ScriptsContainer(ScriptType elementType, int index, ValueContainer values)
: base(elementType, index)
{
Capacity = values.Count;
for (int i = 0; i < values.Count; i++)
{
var v = (IList)values[i];
Add(v[index]);
}
if (values.HasReferenceValue && Count > 0 && this[0] is Script script && script.HasPrefabLink)
{
_prefabObjectId = script.PrefabObjectID;
RefreshReferenceValue(values.ReferenceValue);
}
}
/// <inheritdoc />
public override void RefreshReferenceValue(object instanceValue)
{
// Clear
_referenceValue = null;
_hasReferenceValue = false;
if (instanceValue is IList v)
{
// Get the reference value if script with the given link id exists in the reference values collection
for (int i = 0; i < v.Count; i++)
{
if (v[i] is Script script && script.PrefabObjectID == _prefabObjectId)
{
_referenceValue = script;
_hasReferenceValue = true;
break;
}
}
}
}
}
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
// Area for drag&drop scripts
var dragArea = layout.CustomContainer<DragAreaControl>();
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();
_scripts.AddRange(scripts);
for (int i = 1; i < Values.Count; i++)
{
var e = (Script[])Values[i];
if (scripts.Length != e.Length)
return;
for (int j = 0; j < e.Length; j++)
{
var t1 = scripts[j] != null ? scripts[j].TypeName : null;
var t2 = e[j] != null ? e[j].TypeName : null;
if (t1 != t2)
return;
}
}
// Scripts arrange bar
var dragBar = layout.Custom<ScriptArrangeBar>();
dragBar.CustomControl.Init(0, this);
// Scripts
var elementType = new ScriptType(typeof(Script));
_scriptToggles = new CheckBox[scripts.Length];
for (int i = 0; i < scripts.Length; i++)
{
var script = scripts[i];
if (script == null)
{
AddMissingScript(i, layout);
continue;
}
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))
{
RequireScriptAttribute scriptAttribute = null;
foreach (var e in scriptType.GetAttributes(false))
{
if (e is not RequireScriptAttribute requireScriptAttribute)
continue;
scriptAttribute = requireScriptAttribute;
}
if (scriptAttribute != null)
{
foreach (var type in scriptAttribute.RequiredTypes)
{
if (!type.IsSubclassOf(typeof(Script)))
continue;
var requiredScript = script.Actor.GetScript(type);
if (requiredScript == null)
{
Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Script of type `{type}`.");
hasAllRequirements = false;
}
}
}
}
if (scriptType.HasAttribute(typeof(RequireActorAttribute), false))
{
RequireActorAttribute attribute = null;
foreach (var e in scriptType.GetAttributes(false))
{
if (e is not RequireActorAttribute requireActorAttribute)
continue;
attribute = requireActorAttribute;
break;
}
if (attribute != null)
{
var actor = script.Actor;
if (actor.GetType() != attribute.RequiredType && !actor.GetType().IsSubclassOf(attribute.RequiredType))
{
Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Actor of type `{attribute.RequiredType}`.");
hasAllRequirements = false;
// Maybe call to remove script here?
}
}
}
// Create group
var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name);
var group = layout.Group(title, editor);
if (!hasAllRequirements)
group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.Statusbar.Failed;
if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{
if (Editor.Instance.ProjectCache.IsGroupToggled(title))
group.Panel.Close();
else
group.Panel.Open();
group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(panel.HeaderText, panel.IsClosed);
}
else
group.Panel.Open();
// Customize
group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType);
if (script.HasPrefabLink)
group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.ProgressNormal;
// Add toggle button to the group
var headerHeight = group.Panel.HeaderHeight;
var scriptToggle = new CheckBox
{
TooltipText = "If checked, script will be enabled.",
IsScrollable = false,
Checked = script.Enabled,
Parent = group.Panel,
Size = new Float2(headerHeight),
Bounds = new Rectangle(headerHeight, 0, headerHeight, headerHeight),
BoxSize = headerHeight - 4.0f,
Tag = script,
};
scriptToggle.StateChanged += OnScriptToggleCheckChanged;
_scriptToggles[i] = scriptToggle;
// Add drag button to the group
var scriptDrag = new DragImage
{
TooltipText = "Script reference",
AutoFocus = true,
IsScrollable = false,
Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
Parent = group.Panel,
Bounds = new Rectangle(scriptToggle.Right, 0.5f, headerHeight, headerHeight),
Margin = new Margin(1),
Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12),
Tag = script,
Drag = img =>
{
var s = (Script)img.Tag;
OnScriptDragChange(true, s);
img.DoDragDrop(DragScripts.GetDragData(s));
OnScriptDragChange(false, s);
}
};
// Add settings button to the group
var settingsButton = group.AddSettingsButton();
settingsButton.Tag = script;
settingsButton.Clicked += OnSettingsButtonClicked;
group.Panel.HeaderTextMargin = new Margin(scriptDrag.Right - 12, 15, 2, 2);
group.Object(values, editor);
// Scripts arrange bar
dragBar = layout.Custom<ScriptArrangeBar>();
dragBar.CustomControl.Init(i + 1, this);
}
base.Initialize(layout);
}
/// <summary>
/// Called when script drag changes.
/// </summary>
/// <param name="start">if set to <c>true</c> drag just started, otherwise ended.</param>
/// <param name="script">The target script.</param>
public void OnScriptDragChange(bool start, Script script)
{
ScriptDragChange.Invoke(start, script);
}
/// <summary>
/// Changes the script order (with undo).
/// </summary>
/// <param name="script">The script to reorder.</param>
/// <param name="targetIndex">The target index to move script.</param>
public void ReorderScript(Script script, int targetIndex)
{
// Skip if no change
if (script.OrderInParent == targetIndex)
return;
var action = ChangeScriptAction.ChangeOrder(script, targetIndex);
action.Do();
Presenter?.Undo.AddAction(action);
}
private void OnScriptToggleCheckChanged(CheckBox box)
{
var script = (Script)box.Tag;
if (script.Enabled == box.Checked)
return;
var action = ChangeScriptAction.ChangeEnabled(script, box.Checked);
action.Do();
Presenter?.Undo.AddAction(action);
}
private void OnSettingsButtonClicked(Image image, MouseButton mouseButton)
{
if (mouseButton != MouseButton.Left)
return;
var script = (Script)image.Tag;
var scriptType = TypeUtils.GetType(script.TypeName);
var item = scriptType.ContentItem;
var cm = new ContextMenu
{
Tag = script
};
cm.AddButton("Remove", OnClickRemove).Icon = Editor.Instance.Icons.Cross12;
cm.AddButton("Move up", OnClickMoveUp).Enabled = script.OrderInParent > 0;
cm.AddButton("Move down", OnClickMoveDown).Enabled = script.OrderInParent < script.Actor.Scripts.Length - 1;
// TODO: copy script
// TODO: paste script values
// TODO: paste script as new
// TODO: copy script reference
cm.AddSeparator();
cm.AddButton("Copy type name", OnClickCopyTypeName);
cm.AddButton("Edit script", OnClickEditScript).Enabled = item != null;
var showButton = cm.AddButton("Show in content window", OnClickShowScript);
showButton.Enabled = item != null;
showButton.Icon = Editor.Instance.Icons.Search12;
cm.Show(image, image.Size);
}
private void OnClickRemove(ContextMenuButton button)
{
var script = (Script)button.ParentContextMenu.Tag;
var action = AddRemoveScript.Remove(script);
action.Do();
Presenter.Undo?.AddAction(action);
}
private void OnClickMoveUp(ContextMenuButton button)
{
var script = (Script)button.ParentContextMenu.Tag;
var action = ChangeScriptAction.ChangeOrder(script, script.OrderInParent - 1);
action.Do();
Presenter.Undo?.AddAction(action);
}
private void OnClickMoveDown(ContextMenuButton button)
{
var script = (Script)button.ParentContextMenu.Tag;
var action = ChangeScriptAction.ChangeOrder(script, script.OrderInParent + 1);
action.Do();
Presenter.Undo?.AddAction(action);
}
private void OnClickCopyTypeName(ContextMenuButton button)
{
var script = (Script)button.ParentContextMenu.Tag;
Clipboard.Text = script.TypeName;
}
private void OnClickEditScript(ContextMenuButton button)
{
var script = (Script)button.ParentContextMenu.Tag;
var scriptType = TypeUtils.GetType(script.TypeName);
var item = scriptType.ContentItem;
if (item != null)
Editor.Instance.ContentEditing.Open(item);
}
private void OnClickShowScript(ContextMenuButton button)
{
var script = (Script)button.ParentContextMenu.Tag;
var scriptType = TypeUtils.GetType(script.TypeName);
var item = scriptType.ContentItem;
if (item != null)
Editor.Instance.Windows.ContentWin.Select(item);
}
/// <inheritdoc />
public override void Refresh()
{
if (Values.Count == 1)
{
var scripts = ((Actor)ParentEditor.Values[0]).Scripts;
if (!Utils.ArraysEqual(scripts, _scripts))
{
ParentEditor.RebuildLayout();
return;
}
for (int i = 0; i < _scriptToggles.Length; i++)
{
if (_scriptToggles[i] != null)
_scriptToggles[i].Checked = scripts[i].Enabled;
}
}
base.Refresh();
}
/// <inheritdoc />
protected override void Deinitialize()
{
_scriptToggles = null;
base.Deinitialize();
}
}
}