Replicated the weapon flying tick setup using resources
This commit is contained in:
425
addons/forge/editor/statescript/StatescriptAddNodeDialog.cs
Normal file
425
addons/forge/editor/statescript/StatescriptAddNodeDialog.cs
Normal file
@@ -0,0 +1,425 @@
|
||||
// Copyright © Gamesmiths Guild.
|
||||
|
||||
#if TOOLS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Gamesmiths.Forge.Godot.Resources.Statescript;
|
||||
using Gamesmiths.Forge.Statescript.Nodes;
|
||||
using Godot;
|
||||
|
||||
namespace Gamesmiths.Forge.Godot.Editor.Statescript;
|
||||
|
||||
/// <summary>
|
||||
/// A popup dialog for adding Statescript nodes to a graph. Features a search bar, categorized tree view, description
|
||||
/// panel, and Create/Cancel buttons.
|
||||
/// </summary>
|
||||
[Tool]
|
||||
internal sealed partial class StatescriptAddNodeDialog : ConfirmationDialog, ISerializationListener
|
||||
{
|
||||
private const int DialogWidth = 244;
|
||||
private const int DialogHeight = 400;
|
||||
|
||||
private static readonly string _exitNodeDescription = new ExitNode().Description;
|
||||
|
||||
private LineEdit? _searchBar;
|
||||
private MenuButton? _expandCollapseButton;
|
||||
private PopupMenu? _expandCollapsePopup;
|
||||
private Tree? _tree;
|
||||
private Label? _descriptionHeader;
|
||||
private RichTextLabel? _descriptionLabel;
|
||||
|
||||
private bool _isFiltering;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the user confirms node creation. The first argument is the selected
|
||||
/// <see cref="StatescriptNodeDiscovery.NodeTypeInfo"/> (null for Exit node), the second is the
|
||||
/// <see cref="StatescriptNodeType"/>, and the third is the graph-local position to place the node.
|
||||
/// </summary>
|
||||
public event Action<StatescriptNodeDiscovery.NodeTypeInfo?, StatescriptNodeType, Vector2>? NodeCreationRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the graph-local position where the new node should be placed.
|
||||
/// </summary>
|
||||
public Vector2 SpawnPosition { get; set; }
|
||||
|
||||
public StatescriptAddNodeDialog()
|
||||
{
|
||||
Title = "Add Statescript Node";
|
||||
Exclusive = true;
|
||||
Unresizable = false;
|
||||
MinSize = new Vector2I(DialogWidth, DialogHeight);
|
||||
Size = new Vector2I(DialogWidth, DialogHeight);
|
||||
OkButtonText = "Create";
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
|
||||
Transient = true;
|
||||
TransientToFocused = true;
|
||||
|
||||
BuildUI();
|
||||
PopulateTree();
|
||||
|
||||
GetOkButton().Disabled = true;
|
||||
|
||||
Confirmed += OnConfirmed;
|
||||
Canceled += OnCanceled;
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
base._ExitTree();
|
||||
DisconnectSignals();
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
DisconnectSignals();
|
||||
NodeCreationRequested = null;
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
ConnectSignals();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the dialog at the specified screen position, resets search and selection state.
|
||||
/// </summary>
|
||||
/// <param name="spawnPosition">The graph-local position where the node should be created.</param>
|
||||
/// <param name="screenPosition">The screen position to show the dialog at.</param>
|
||||
public void ShowAtPosition(Vector2 spawnPosition, Vector2I screenPosition)
|
||||
{
|
||||
SpawnPosition = spawnPosition;
|
||||
|
||||
if (_isFiltering)
|
||||
{
|
||||
_searchBar?.Clear();
|
||||
PopulateTree();
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBar?.Clear();
|
||||
}
|
||||
|
||||
_tree?.DeselectAll();
|
||||
GetOkButton().Disabled = true;
|
||||
UpdateDescription(null);
|
||||
|
||||
Position = screenPosition;
|
||||
Size = new Vector2I(DialogWidth, DialogHeight);
|
||||
Popup();
|
||||
|
||||
_searchBar?.GrabFocus();
|
||||
}
|
||||
|
||||
private static void SetAllCollapsed(TreeItem root, bool collapsed)
|
||||
{
|
||||
TreeItem? child = root.GetFirstChild();
|
||||
while (child is not null)
|
||||
{
|
||||
child.Collapsed = collapsed;
|
||||
SetAllCollapsed(child, collapsed);
|
||||
child = child.GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildUI()
|
||||
{
|
||||
var vBox = new VBoxContainer
|
||||
{
|
||||
SizeFlagsVertical = Control.SizeFlags.ExpandFill,
|
||||
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
|
||||
};
|
||||
|
||||
AddChild(vBox);
|
||||
|
||||
var searchHBox = new HBoxContainer();
|
||||
vBox.AddChild(searchHBox);
|
||||
|
||||
_searchBar = new LineEdit
|
||||
{
|
||||
PlaceholderText = "Search...",
|
||||
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
|
||||
ClearButtonEnabled = true,
|
||||
RightIcon = EditorInterface.Singleton.GetEditorTheme().GetIcon("Search", "EditorIcons"),
|
||||
};
|
||||
|
||||
_searchBar.TextChanged += OnSearchTextChanged;
|
||||
searchHBox.AddChild(_searchBar);
|
||||
|
||||
_expandCollapseButton = new MenuButton
|
||||
{
|
||||
Flat = true,
|
||||
Icon = EditorInterface.Singleton.GetEditorTheme().GetIcon("Tools", "EditorIcons"),
|
||||
TooltipText = "Options",
|
||||
};
|
||||
|
||||
_expandCollapsePopup = _expandCollapseButton.GetPopup();
|
||||
_expandCollapsePopup.AddItem("Expand All", 0);
|
||||
_expandCollapsePopup.AddItem("Collapse All", 1);
|
||||
_expandCollapsePopup.IdPressed += OnExpandCollapseMenuPressed;
|
||||
searchHBox.AddChild(_expandCollapseButton);
|
||||
|
||||
_tree = new Tree
|
||||
{
|
||||
SizeFlagsVertical = Control.SizeFlags.ExpandFill,
|
||||
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
|
||||
HideRoot = true,
|
||||
SelectMode = Tree.SelectModeEnum.Single,
|
||||
};
|
||||
|
||||
_tree.ItemSelected += OnTreeItemSelected;
|
||||
_tree.ItemActivated += OnTreeItemActivated;
|
||||
vBox.AddChild(_tree);
|
||||
|
||||
_descriptionHeader = new Label
|
||||
{
|
||||
Text = "Description:",
|
||||
};
|
||||
|
||||
vBox.AddChild(_descriptionHeader);
|
||||
|
||||
_descriptionLabel = new RichTextLabel
|
||||
{
|
||||
BbcodeEnabled = true,
|
||||
ScrollActive = true,
|
||||
CustomMinimumSize = new Vector2(0, 70),
|
||||
};
|
||||
|
||||
vBox.AddChild(_descriptionLabel);
|
||||
}
|
||||
|
||||
private void PopulateTree(string filter = "")
|
||||
{
|
||||
if (_tree is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isFiltering = !string.IsNullOrWhiteSpace(filter);
|
||||
_tree.Clear();
|
||||
TreeItem root = _tree.CreateItem();
|
||||
|
||||
IReadOnlyList<StatescriptNodeDiscovery.NodeTypeInfo> discoveredTypes =
|
||||
StatescriptNodeDiscovery.GetDiscoveredNodeTypes();
|
||||
|
||||
var filterLower = filter.ToLowerInvariant();
|
||||
|
||||
TreeItem? actionCategory = null;
|
||||
TreeItem? conditionCategory = null;
|
||||
TreeItem? stateCategory = null;
|
||||
|
||||
foreach (StatescriptNodeDiscovery.NodeTypeInfo typeInfo in discoveredTypes)
|
||||
{
|
||||
if (_isFiltering && !typeInfo.DisplayName.Contains(filterLower, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TreeItem categoryItem;
|
||||
|
||||
switch (typeInfo.NodeType)
|
||||
{
|
||||
case StatescriptNodeType.Action:
|
||||
actionCategory ??= CreateCategoryItem(root, "Action");
|
||||
categoryItem = actionCategory;
|
||||
break;
|
||||
case StatescriptNodeType.Condition:
|
||||
conditionCategory ??= CreateCategoryItem(root, "Condition");
|
||||
categoryItem = conditionCategory;
|
||||
break;
|
||||
case StatescriptNodeType.State:
|
||||
stateCategory ??= CreateCategoryItem(root, "State");
|
||||
categoryItem = stateCategory;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
TreeItem item = _tree.CreateItem(categoryItem);
|
||||
item.SetText(0, typeInfo.DisplayName);
|
||||
item.SetMetadata(0, typeInfo.RuntimeTypeName);
|
||||
}
|
||||
|
||||
if (!_isFiltering || "exit".Contains(filterLower, StringComparison.OrdinalIgnoreCase)
|
||||
|| "exit node".Contains(filterLower, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
TreeItem exitItem = _tree.CreateItem(root);
|
||||
exitItem.SetText(0, "Exit");
|
||||
exitItem.SetMetadata(0, "__exit__");
|
||||
}
|
||||
|
||||
SetAllCollapsed(root, !_isFiltering);
|
||||
|
||||
UpdateDescription(null);
|
||||
}
|
||||
|
||||
private TreeItem CreateCategoryItem(TreeItem parent, string name)
|
||||
{
|
||||
TreeItem item = _tree!.CreateItem(parent);
|
||||
item.SetText(0, name);
|
||||
item.SetSelectable(0, false);
|
||||
return item;
|
||||
}
|
||||
|
||||
private void OnSearchTextChanged(string newText)
|
||||
{
|
||||
PopulateTree(newText);
|
||||
GetOkButton().Disabled = true;
|
||||
}
|
||||
|
||||
private void OnExpandCollapseMenuPressed(long id)
|
||||
{
|
||||
if (_tree is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem? root = _tree.GetRoot();
|
||||
if (root is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetAllCollapsed(root, id != 0);
|
||||
}
|
||||
|
||||
private void OnTreeItemSelected()
|
||||
{
|
||||
if (_tree is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem? selected = _tree.GetSelected();
|
||||
if (selected?.IsSelectable(0) != true)
|
||||
{
|
||||
GetOkButton().Disabled = true;
|
||||
UpdateDescription(null);
|
||||
return;
|
||||
}
|
||||
|
||||
GetOkButton().Disabled = false;
|
||||
|
||||
var metadata = selected.GetMetadata(0).AsString();
|
||||
UpdateDescription(metadata);
|
||||
}
|
||||
|
||||
private void OnTreeItemActivated()
|
||||
{
|
||||
if (_tree?.GetSelected() is not null && !GetOkButton().Disabled)
|
||||
{
|
||||
OnConfirmed();
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConfirmed()
|
||||
{
|
||||
if (_tree is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem? selected = _tree.GetSelected();
|
||||
if (selected?.IsSelectable(0) != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var metadata = selected.GetMetadata(0).AsString();
|
||||
|
||||
if (metadata == "__exit__")
|
||||
{
|
||||
NodeCreationRequested?.Invoke(null, StatescriptNodeType.Exit, SpawnPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
StatescriptNodeDiscovery.NodeTypeInfo? typeInfo =
|
||||
StatescriptNodeDiscovery.FindByRuntimeTypeName(metadata);
|
||||
|
||||
if (typeInfo is not null)
|
||||
{
|
||||
NodeCreationRequested?.Invoke(typeInfo, typeInfo.NodeType, SpawnPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCanceled()
|
||||
{
|
||||
// Method intentionally left blank, no action needed on cancel.
|
||||
}
|
||||
|
||||
private void UpdateDescription(string? runtimeTypeName)
|
||||
{
|
||||
if (_descriptionLabel is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (runtimeTypeName is null)
|
||||
{
|
||||
_descriptionLabel.Text = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
if (runtimeTypeName == "__exit__")
|
||||
{
|
||||
_descriptionLabel.Text = _exitNodeDescription;
|
||||
return;
|
||||
}
|
||||
|
||||
StatescriptNodeDiscovery.NodeTypeInfo? typeInfo =
|
||||
StatescriptNodeDiscovery.FindByRuntimeTypeName(runtimeTypeName);
|
||||
|
||||
_descriptionLabel.Text = typeInfo?.Description ?? string.Empty;
|
||||
}
|
||||
|
||||
private void DisconnectSignals()
|
||||
{
|
||||
Confirmed -= OnConfirmed;
|
||||
Canceled -= OnCanceled;
|
||||
|
||||
if (_searchBar is not null)
|
||||
{
|
||||
_searchBar.TextChanged -= OnSearchTextChanged;
|
||||
}
|
||||
|
||||
if (_expandCollapsePopup is not null)
|
||||
{
|
||||
_expandCollapsePopup.IdPressed -= OnExpandCollapseMenuPressed;
|
||||
}
|
||||
|
||||
if (_tree is not null)
|
||||
{
|
||||
_tree.ItemSelected -= OnTreeItemSelected;
|
||||
_tree.ItemActivated -= OnTreeItemActivated;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectSignals()
|
||||
{
|
||||
Confirmed += OnConfirmed;
|
||||
Canceled += OnCanceled;
|
||||
|
||||
if (_searchBar is not null)
|
||||
{
|
||||
_searchBar.TextChanged += OnSearchTextChanged;
|
||||
}
|
||||
|
||||
if (_expandCollapsePopup is not null)
|
||||
{
|
||||
_expandCollapsePopup.IdPressed += OnExpandCollapseMenuPressed;
|
||||
}
|
||||
|
||||
if (_tree is not null)
|
||||
{
|
||||
_tree.ItemSelected += OnTreeItemSelected;
|
||||
_tree.ItemActivated += OnTreeItemActivated;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user