mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-20 12:19:32 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
423 lines
10 KiB
C#
423 lines
10 KiB
C#
using Editor.NodeEditor;
|
|
using Facepunch.ActionGraphs;
|
|
using Sandbox;
|
|
using Sandbox.ActionGraphs;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Editor.ActionGraphs;
|
|
|
|
public partial class MainWindow : DockWindow
|
|
{
|
|
public event Action<object> SelectionChanged;
|
|
public event Action<Node.Input, int?> FocusedOnInput;
|
|
|
|
internal static List<MainWindow> AllWindows { get; } = new List<MainWindow>();
|
|
|
|
private static string FormatMessageContext( IMessageContext context )
|
|
{
|
|
return context switch
|
|
{
|
|
ActionGraph graph => graph.Title,
|
|
Node node => node.GetDisplayInfo().Name,
|
|
Link link => FormatMessageContext( link.Target ),
|
|
Node.IParameter parameter => FormatMessageContext( parameter.Node ),
|
|
Variable variable => variable.Name,
|
|
_ => $"{context}"
|
|
};
|
|
}
|
|
|
|
private static string GetTargetName( ActionGraph graph )
|
|
{
|
|
switch ( graph.GetEmbeddedTarget() )
|
|
{
|
|
case GameObject go:
|
|
return go.Name;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static string GetSourceName( ISourceLocation source )
|
|
{
|
|
return source switch
|
|
{
|
|
GameResourceSourceLocation { Resource: SceneFile { ResourcePath: null } } => "Play Mode",
|
|
GameResourceSourceLocation { Resource: SceneFile { ResourceName: { } name } } => name,
|
|
_ => source.ToString()
|
|
};
|
|
}
|
|
|
|
public static string GetFullPath( ActionGraph graph )
|
|
{
|
|
if ( graph.SourceLocation is null )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return GetTargetName( graph ) is { } targetName
|
|
? $"{GetSourceName( graph.SourceLocation )} - {targetName} - {graph.Title}"
|
|
: $"{GetSourceName( graph.SourceLocation )} - {graph.Title}";
|
|
}
|
|
|
|
[StackLineHandler( @"^at (.+?) in (.+/Facepunch.ActionGraphs/.+):line (.+)$", Order = 910 )]
|
|
public static StackRow ActionGraphLibraryStackLineHandler( Match match )
|
|
{
|
|
var row = new StackRow( match.Value, null ) { IsFromAction = true };
|
|
|
|
return row;
|
|
}
|
|
|
|
[StackLineHandler( @"at ActionGraph\.(?<guid>[a-fA-F0-9-]+)(?:\.Node(?<nodeid>[0-9]+))?(?:\.[^(]+)?(?:\(.*\))?", Order = 900 )]
|
|
public static StackRow ActionGraphStackLineHandler( Match match )
|
|
{
|
|
if ( !Guid.TryParse( match.Groups["guid"].Value, out var guid ) )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if ( !ActionGraphDebugger.TryGetGraph( guid, out var graph ) )
|
|
{
|
|
return new StackRow( $"Unknown Action Graph ({guid})", null );
|
|
}
|
|
|
|
IMessageContext context = graph;
|
|
|
|
if ( match.Groups["nodeid"].Success && int.TryParse( match.Groups["nodeid"].Value, out var nodeId ) && graph.Nodes.TryGetValue( nodeId, out var node ) )
|
|
{
|
|
context = node;
|
|
}
|
|
|
|
var row = new StackRow( FormatMessageContext( context ), GetFullPath( graph ) )
|
|
{
|
|
IsFromEngine = false,
|
|
IsFromAction = true
|
|
};
|
|
|
|
if ( graph.SourceLocation is not null )
|
|
{
|
|
row.MouseClick += () => EditorEvent.Run( "actiongraph.inspect", context );
|
|
}
|
|
|
|
return row;
|
|
}
|
|
|
|
private List<ActionGraphView> Views { get; } = new();
|
|
|
|
public IReadOnlyList<ActionGraphView> OpenViews => Views;
|
|
|
|
public ActionGraphView FocusedView => Views.LastOrDefault();
|
|
|
|
private Option _undoMenuOption;
|
|
private Option _redoMenuOption;
|
|
|
|
public bool CanOpenMultipleAssets => true;
|
|
|
|
public MainWindow( Window parent )
|
|
{
|
|
DeleteOnClose = true;
|
|
|
|
Size = new Vector2( 1280, 720 );
|
|
|
|
// Make this window stay on top of the editor, by making it a dialog
|
|
Parent = parent;
|
|
WindowFlags = WindowFlags.Dialog | WindowFlags.Customized | WindowFlags.CloseButton | WindowFlags.WindowSystemMenuHint | WindowFlags.WindowTitle | WindowFlags.MaximizeButton;
|
|
|
|
SetWindowIcon( "electrical_services" );
|
|
|
|
AllWindows.Add( this );
|
|
|
|
RebuildUI();
|
|
}
|
|
|
|
protected override void OnClosed()
|
|
{
|
|
base.OnClosed();
|
|
|
|
AllWindows.Remove( this );
|
|
}
|
|
|
|
protected override void OnFocus( FocusChangeReason reason )
|
|
{
|
|
base.OnFocus( reason );
|
|
|
|
// Move this window to the end of the list, so it has priority
|
|
// when opening a new graph
|
|
|
|
AllWindows.Remove( this );
|
|
AllWindows.Add( this );
|
|
}
|
|
|
|
public ActionGraphView Open( ActionGraph actionGraph )
|
|
{
|
|
var view = new ActionGraphView( actionGraph ) { Window = this };
|
|
var sibling = Views.LastOrDefault();
|
|
|
|
Views.Add( view );
|
|
|
|
if ( !sibling.IsValid() )
|
|
{
|
|
DockManager.AddDock( null, view, DockArea.RightOuter, split: 1f );
|
|
}
|
|
else
|
|
{
|
|
DockManager.AddDock( sibling, view, DockArea.Inside );
|
|
}
|
|
|
|
DockManager.Update();
|
|
|
|
return view;
|
|
}
|
|
|
|
internal void UpdateTitle( ActionGraphView view )
|
|
{
|
|
Title = $"{view.ActionGraph.Title} - Action Graph";
|
|
}
|
|
|
|
public void UpdateMenuOptions( UndoStack undoStack )
|
|
{
|
|
_undoMenuOption.Enabled = undoStack?.CanUndo ?? false;
|
|
_redoMenuOption.Enabled = undoStack?.CanRedo ?? false;
|
|
_undoMenuOption.Text = undoStack?.UndoName ?? "Undo";
|
|
_redoMenuOption.Text = undoStack?.RedoName ?? "Redo";
|
|
_undoMenuOption.StatusTip = undoStack?.UndoName;
|
|
_redoMenuOption.StatusTip = undoStack?.RedoName;
|
|
}
|
|
|
|
public void RebuildUI()
|
|
{
|
|
RestoreDefaultDockLayout();
|
|
|
|
MenuBar.Clear();
|
|
|
|
{
|
|
var file = MenuBar.AddMenu( "File" );
|
|
file.AddOption( new Option( "Save" ) { ShortcutName = "editor.save", Triggered = Save } );
|
|
file.AddSeparator();
|
|
file.AddOption( new Option( "Quit" ) { ShortcutName = "editor.quit", Triggered = Quit } );
|
|
}
|
|
|
|
{
|
|
var edit = MenuBar.AddMenu( "Edit" );
|
|
|
|
_undoMenuOption = edit.AddOption( "Undo", "undo", Undo, "editor.undo" );
|
|
_redoMenuOption = edit.AddOption( "Redo", "redo", Redo, "editor.redo" );
|
|
|
|
edit.AddSeparator();
|
|
edit.AddOption( "Cut", "common/cut.png", CutSelection, "editor.cut" );
|
|
edit.AddOption( "Copy", "common/copy.png", CopySelection, "editor.copy" );
|
|
edit.AddOption( "Paste", "common/paste.png", PasteSelection, "editor.paste" );
|
|
edit.AddOption( "Select All", "select_all", SelectAll, "editor.select-all" );
|
|
|
|
edit.AddSeparator();
|
|
|
|
edit.AddOption( "Clean Up", "cleaning_services", CleanUp );
|
|
}
|
|
|
|
{
|
|
var view = MenuBar.AddMenu( "View" );
|
|
|
|
view.AboutToShow += () => OnViewMenu( view );
|
|
}
|
|
|
|
{
|
|
var debug = MenuBar.AddMenu( "Debug" );
|
|
|
|
debug.AddOption( "Log Last Compiled", "code", LogLastCompiled );
|
|
}
|
|
|
|
UpdateMenuOptions( FocusedView?.UndoStack );
|
|
}
|
|
|
|
private void OnViewMenu( Menu view )
|
|
{
|
|
view.Clear();
|
|
view.AddOption( "Restore To Default", "settings_backup_restore", RestoreDefaultDockLayout );
|
|
view.AddSeparator();
|
|
|
|
foreach ( var dock in DockManager.DockTypes )
|
|
{
|
|
var o = view.AddOption( dock.Title, dock.Icon );
|
|
o.Checkable = true;
|
|
o.Checked = DockManager.IsDockOpen( dock.Title );
|
|
o.Toggled += ( b ) => DockManager.SetDockState( dock.Title, b );
|
|
}
|
|
|
|
view.AddSeparator();
|
|
|
|
var style = view.AddOption( "Grid-Aligned Wires", "turn_sharp_right" );
|
|
style.Checkable = true;
|
|
style.Checked = ActionGraphView.EnableGridAlignedWires;
|
|
style.Toggled += b => ActionGraphView.EnableGridAlignedWires = b;
|
|
}
|
|
|
|
protected override void RestoreDefaultDockLayout()
|
|
{
|
|
var openViews = Views
|
|
.Select( x => x.ActionGraph )
|
|
.ToArray();
|
|
|
|
var properties = new Properties( this );
|
|
var errorList = new ErrorList( null, this );
|
|
|
|
DockManager.Clear();
|
|
DockManager.RegisterDockType( "Inspector", "edit", () => new Properties( this ) );
|
|
DockManager.RegisterDockType( "ErrorList", "error", () => new ErrorList( null, this ) );
|
|
|
|
DockManager.AddDock( null, properties, DockArea.Left, DockManager.DockProperty.HideOnClose );
|
|
DockManager.AddDock( properties, errorList, DockArea.Bottom, DockManager.DockProperty.HideOnClose, split: 0.75f );
|
|
|
|
foreach ( var graph in openViews )
|
|
{
|
|
Open( graph );
|
|
}
|
|
|
|
DockManager.Update();
|
|
}
|
|
|
|
[Shortcut( "editor.quit", "CTRL+Q", ShortcutType.Window )]
|
|
private void Quit()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
[Shortcut( "editor.save", "Ctrl+S", ShortcutType.Window )]
|
|
private void Save()
|
|
{
|
|
FocusedView?.Save();
|
|
}
|
|
|
|
[Shortcut( "editor.undo", "Ctrl+Z", ShortcutType.Window )]
|
|
private void Undo()
|
|
{
|
|
FocusedView?.Undo();
|
|
}
|
|
|
|
[Shortcut( "editor.redo", "Ctrl+Y", ShortcutType.Window )]
|
|
private void Redo()
|
|
{
|
|
FocusedView?.Redo();
|
|
}
|
|
|
|
[Shortcut( "editor.cut", "Ctrl+X", ShortcutType.Window )]
|
|
private void CutSelection()
|
|
{
|
|
FocusedView?.CutSelection();
|
|
}
|
|
|
|
[Shortcut( "editor.copy", "Ctrl+C", ShortcutType.Window )]
|
|
private void CopySelection()
|
|
{
|
|
FocusedView?.CopySelection();
|
|
}
|
|
|
|
[Shortcut( "editor.paste", "Ctrl+V", ShortcutType.Window )]
|
|
private void PasteSelection()
|
|
{
|
|
FocusedView?.PasteSelection();
|
|
}
|
|
|
|
[Shortcut( "editor.duplicate", "CTRL+D", ShortcutType.Window )]
|
|
private void DuplicateSelection()
|
|
{
|
|
FocusedView?.DuplicateSelection();
|
|
}
|
|
|
|
[Shortcut( "editor.select-all", "Ctrl+A", ShortcutType.Window )]
|
|
private void SelectAll()
|
|
{
|
|
FocusedView?.SelectAll();
|
|
}
|
|
|
|
[Shortcut( "editor.clear-selection", "ESC", ShortcutType.Window )]
|
|
private void ClearSelection()
|
|
{
|
|
FocusedView?.ClearSelection();
|
|
}
|
|
|
|
[Shortcut( "gameObject.frame", "F", ShortcutType.Window )]
|
|
private void CenterOnSelection()
|
|
{
|
|
FocusedView?.CenterOnSelection();
|
|
}
|
|
|
|
private void CleanUp()
|
|
{
|
|
FocusedView?.CleanUp();
|
|
}
|
|
|
|
private void LogLastCompiled()
|
|
{
|
|
FocusedView?.LogLastCompiled();
|
|
}
|
|
|
|
internal void OnFocusView( ActionGraphView view )
|
|
{
|
|
Views.Remove( view );
|
|
Views.Add( view );
|
|
|
|
DispatchSelectionChanged( view );
|
|
|
|
UpdateTitle( view );
|
|
}
|
|
|
|
internal void OnRemoveView( ActionGraphView view )
|
|
{
|
|
Views.Remove( view );
|
|
}
|
|
|
|
internal void DispatchFocusedOnInput( Node.Input input, int? index )
|
|
{
|
|
FocusedOnInput?.Invoke( input, index );
|
|
}
|
|
|
|
internal void DispatchSelectionChanged( ActionGraphView view )
|
|
{
|
|
var node = view.SelectedItems
|
|
.OfType<NodeUI>()
|
|
.MaxBy( n => n is CommentUI );
|
|
|
|
SelectionChanged?.Invoke( (object)node ?? view.Graph );
|
|
}
|
|
|
|
public void SelectNode( Node node )
|
|
{
|
|
if ( Views.FirstOrDefault( x => x.ActionGraph == node.ActionGraph ) is not { } view )
|
|
{
|
|
return;
|
|
}
|
|
|
|
DockManager.RaiseDock( view.Name );
|
|
|
|
view.SelectNode( node );
|
|
}
|
|
|
|
public void SelectLinks( IEnumerable<Link> links )
|
|
{
|
|
var linkArray = links.ToArray();
|
|
|
|
if ( linkArray.FirstOrDefault()?.Target.Node.ActionGraph is not { } graph )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( Views.FirstOrDefault( x => x.ActionGraph == graph ) is not { } view )
|
|
{
|
|
return;
|
|
}
|
|
|
|
DockManager.RaiseDock( view.Name );
|
|
|
|
view.SelectLinks( linkArray );
|
|
}
|
|
|
|
public void SelectLink( Link link )
|
|
{
|
|
SelectLinks( new[] { link } );
|
|
}
|
|
}
|