Editor/ShaderGraphPlus/MainWindow.cs
using Editor;
using Sandbox.Rendering;
using ShaderGraphPlus.Internal;
using ShaderGraphPlus.Nodes;
using System.Text;
namespace ShaderGraphPlus;
[EditorForAssetType( ShaderGraphPlusGlobals.SubgraphAssetTypeExtension )]
public class MainWindowShaderFunc : MainWindow, IAssetEditor
{
public override bool IsSubgraph => true;
public override string FileType => $"{ShaderGraphPlusGlobals.AssetTypeName} Sub-Graph";
public override string FileExtension => ShaderGraphPlusGlobals.SubgraphAssetTypeExtension;
void IAssetEditor.SelectMember( string memberName )
{
throw new NotImplementedException();
}
}
[EditorForAssetType( ShaderGraphPlusGlobals.AssetTypeExtension )]
[EditorApp( ShaderGraphPlusGlobals.AssetTypeName, "gradient", "edit shaders" )]
public class MainWindowShader : MainWindow, IAssetEditor
{
public override bool IsSubgraph => false;
public override string FileType => ShaderGraphPlusGlobals.AssetTypeName;
public override string FileExtension => ShaderGraphPlusGlobals.AssetTypeExtension;
void IAssetEditor.SelectMember( string memberName )
{
throw new NotImplementedException();
}
}
public class MainWindow : DockWindow
{
public virtual bool IsSubgraph => false;
public virtual string FileType => ShaderGraphPlusGlobals.AssetTypeName;
public virtual string FileExtension => ShaderGraphPlusGlobals.AssetTypeExtension;
private ShaderGraphPlus _graph;
private ShaderGraphPlusView _graphView;
private BlackboardView _blackboardView;
private Asset _asset;
public string AssetPath => _asset?.Path;
private Widget _graphCanvas;
private Widget _blackboardCanvas;
private Properties _properties;
private PreviewPanel _preview;
private Output _output;
private UndoHistory _undoHistory;
private PaletteWidget _palette;
private TextView _generatedCodeTextView;
private readonly UndoStack _undoStack = new();
private Option _undoOption;
private Option _redoOption;
private Option _undoMenuOption;
private Option _redoMenuOption;
private Option _autoCompileOption;
public UndoStack UndoStack => _undoStack;
private bool _dirty = false;
private bool _autoCompile = true;
private string _generatedCode;
private readonly Dictionary<string, bool> _boolAttributes = new();
private readonly Dictionary<string, int> _intAttributes = new();
private readonly Dictionary<string, float> _floatAttributes = new();
private readonly Dictionary<string, Vector2> _float2Attributes = new();
private readonly Dictionary<string, Vector3> _float3Attributes = new();
private readonly Dictionary<string, Color> _float4Attributes = new();
private readonly Dictionary<string, SamplerState> _samplerStateAttributes = new();
private readonly Dictionary<string, Texture> _textureAttributes = new();
private readonly Dictionary<string, int> _dynamicComboIntAttributes = new();
private readonly Dictionary<string, int> _shaderFeatures = new();
//private readonly List<BaseNodePlus> _compiledNodes = new();
private bool _isCompiling = false;
private bool _isPendingCompile = false;
private RealTimeSince _timeSinceCompile;
private Menu _recentFilesMenu;
private readonly List<string> _recentFiles = new();
private Option _fileHistoryBack;
private Option _fileHistoryForward;
private Option _fileHistoryHome;
private List<string> _fileHistory = new();
private int _fileHistoryPosition = 0;
private string _defaultDockState;
public bool CanOpenMultipleAssets => true;
public bool EnableNodePreview => _preview.Preview.EnableNodePreview;
private ProjectCreator ProjectCreator { get; set; }
private Dictionary<string, ShaderFeatureBase> ShaderFeatures = new();
private List<GraphCompiler.GraphIssue> BlackboardIssues { get; set; } = new();
public MainWindow()
{
DeleteOnClose = true;
Title = FileType;
Size = new Vector2( 1700, 1050 );
_graph = new();
_graph.IsSubgraph = IsSubgraph;
CreateToolBar();
_recentFiles = Editor.FileSystem.Temporary.ReadJsonOrDefault( "shadergraphplus_recentfiles.json", _recentFiles )
.Where( x => System.IO.File.Exists( x ) ).ToList();
CreateUI();
Show();
CreateNew();
EditorEvent.Run( "shadergraphplus.created" );
OpenProjectCreationDialog();
}
private void OpenProjectCreationDialog()
{
ProjectCreator = new ProjectCreator
{
DeleteOnClose = true
};
var initialPath = $"{Project.Current.GetAssetsPath().Replace( "\\", "/" )}/Shaders";
if ( !Directory.Exists( initialPath ) )
{
Directory.CreateDirectory( initialPath );
}
ProjectCreator.FolderEditPath = initialPath;
ProjectCreator.Show();
ProjectCreator.OnProjectCreated += OpenProject;
}
public void AssetOpen( Asset asset )
{
if ( asset == null || string.IsNullOrWhiteSpace( asset.AbsolutePath ) )
return;
// We dont need the project creator when opening an existing asset. So lets forceably close it.
ProjectCreator?.Close();
ProjectCreator = null;
Open( asset.AbsolutePath );
}
private void RestoreShader()
{
if ( !_preview.IsValid() )
return;
_preview.Material = Material.Load( "materials/core/shader_editor.vmat" );
}
public void OnSelected( object selection )
{
if ( selection == null )
{
_blackboardView.ClearSelection();
_properties.Target = _graph;
return;
}
var oldTarget = _properties.Target;
if ( selection is BlackboardParameter parameter )
{
if ( oldTarget is BaseNodePlus )
{
_graphView.ClearSelection();
}
_properties.Target = parameter;
}
else if ( selection is ShaderGraphPlus )
{
if ( oldTarget is BlackboardParameter )
{
_blackboardView.ClearSelection();
}
_properties.Target = _graph;
}
else if ( selection is BaseNodePlus node )
{
if ( oldTarget is BlackboardParameter )
{
_blackboardView.ClearSelection();
}
if ( node is IBlackboardNode blackboardNode )
{
// For now only select a blackboard parameter when _graphView only has 1 selection.
if ( _graphView.SelectedItems.Count() == 1 )
{
if ( blackboardNode.ParameterIdentifier != default )
{
var blackboardParameter = _graph.FindParameter( blackboardNode.ParameterIdentifier );
_blackboardView.SetSelection( blackboardParameter );
_properties.Target = blackboardParameter;
}
}
else
{
_properties.Target = _graph;
}
}
else
{
_properties.Target = node;
}
if ( EnableNodePreview && (node != null && node.CanPreview) )
{
_preview.SetStage( node.PreviewID );
}
else
{
_preview.SetStage( ShaderGraphPlusGlobals.GraphCompiler.NoNodePreviewID );
}
}
}
internal void OnGraphViewClicked()
{
// Fixes not being able to select the graph in the GraphView when the latest target was a BlackboardParameter.
if ( _properties.Target is BlackboardParameter )
{
OnSelected( null );
_blackboardView.ClearSelection();
}
}
private void OpenGeneratedShader()
{
if ( _asset is null )
{
Save();
}
else
{
var path = System.IO.Path.ChangeExtension( _asset.AbsolutePath, ".shader" );
var asset = AssetSystem.FindByPath( path );
asset?.OpenInEditor();
}
}
private void OpenTempGeneratedShader()
{
if ( !_dirty )
{
SetDirty();
}
var assetPath = $"shadergraphplus/{_asset?.Name ?? "untitled"}_shadergraphplus.generated.shader";
var resourcePath = System.IO.Path.Combine( Editor.FileSystem.Temporary.GetFullPath( "/temp" ), assetPath );
var asset = AssetSystem.FindByPath( resourcePath );
asset?.OpenInEditor();
}
private void OpenShaderGraphProjectTxt()
{
if ( _asset is null )
{
Save();
}
else
{
var path = _asset.AbsolutePath;
Utilities.Path.OpenInNotepad( path );
}
}
private void Screenshot()
{
if ( _asset is null )
return;
var path = Editor.FileSystem.Root.GetFullPath( $"/screenshots/shadergraphplus/{_asset.Name}.png" );
System.IO.Directory.CreateDirectory( System.IO.Path.GetDirectoryName( path ) );
_graphView.Capture( $"screenshots/shadergraphplus/{_asset.Name}.png" );
EditorUtility.OpenFileFolder( path );
}
protected virtual void Compile()
{
_shaderCompileErrors.Clear();
if ( string.IsNullOrWhiteSpace( _generatedCode ) )
{
RestoreShader();
return;
}
if ( _isCompiling )
{
_isPendingCompile = true;
return;
}
var assetPath = $"shadergraphplus/{_asset?.Name ?? "untitled"}_shadergraphplus.generated.shader";
var resourcePath = System.IO.Path.Combine( ".source2/temp", assetPath );
Editor.FileSystem.Root.CreateDirectory( ".source2/temp/shadergraphplus" );
Editor.FileSystem.Root.WriteAllText( resourcePath, _generatedCode );
_isCompiling = true;
_preview.IsCompiling = _isCompiling;
RestoreShader();
_timeSinceCompile = 0;
CompileAsync( resourcePath );
}
private async void CompileAsync( string path )
{
var options = new Sandbox.Engine.Shaders.ShaderCompileOptions
{
ConsoleOutput = true
};
var result = await EditorUtility.CompileShader( Editor.FileSystem.Root, path, options );
if ( result.Success )
{
var asset = AssetSystem.RegisterFile( Editor.FileSystem.Root.GetFullPath( path ) );
while ( !asset.IsCompiledAndUpToDate )
{
await Task.Yield();
}
}
MainThread.Queue( () => OnCompileFinished( result.Success ? 0 : 1 ) );
}
private readonly List<string> _shaderCompileErrors = new();
private struct StatusMessage
{
public string Status { get; set; }
public string Message { get; set; }
}
internal void OnOutputData( object sender, DataReceivedEventArgs e )
{
var str = e.Data;
if ( str == null )
return;
MainThread.Queue( () =>
{
var trimmed = str.Trim();
if ( trimmed.StartsWith( '{' ) && trimmed.EndsWith( '}' ) )
{
var status = Json.Deserialize<StatusMessage>( trimmed );
if ( status.Status == "Error" )
{
if ( !string.IsNullOrWhiteSpace( status.Message ) )
{
var lines = status.Message.Split( '\n' );
foreach ( var line in lines.Where( x => !string.IsNullOrWhiteSpace( x ) ) )
{
_shaderCompileErrors.Add( line );
}
}
}
}
} );
}
internal void OnErrorData( object sender, DataReceivedEventArgs e )
{
if ( e == null || e.Data == null )
return;
MainThread.Queue( () =>
{
var error = $"{e.Data}";
if ( !string.IsNullOrWhiteSpace( error ) )
_shaderCompileErrors.Add( error );
} );
}
private void OnCompileFinished( int exitCode )
{
_isCompiling = false;
if ( _isPendingCompile )
{
_isPendingCompile = false;
Compile();
return;
}
if ( exitCode == 0 && _shaderCompileErrors.Count == 0 )
{
SGPLogger.Info( $"Compile finished in {_timeSinceCompile}" );
var shaderPath = $"shadergraphplus/{_asset?.Name ?? "untitled"}_shadergraphplus.generated.shader";
// Reload the shader otherwise it's gonna be the old wank
// Alternatively Material.Create could be made to force reload the shader
Editor.ConsoleSystem.Run( $"mat_reloadshaders {shaderPath}" );
var material = Material.Create( $"{_asset?.Name ?? "untitled"}_shadergraphplus_generated", shaderPath );
_preview.Material = material;
}
else
{
SGPLogger.Error( $"Compile failed in {_timeSinceCompile}" );
_output.GraphIssues = _shaderCompileErrors.Select( x => new GraphCompiler.GraphIssue { Message = x } ).ToList();
DockManager.RaiseDock( "Output" );
RestoreShader();
ClearAttributes();
}
_preview.IsCompiling = _isCompiling;
_preview.PostProcessing = _graph.Domain == ShaderDomain.PostProcess;
_shaderCompileErrors.Clear();
}
private void OnAttribute( string name, object value )
{
if ( value == null )
return;
switch ( value )
{
case Color v:
if ( _float4Attributes.TryAdd( name, v ) )
{
_preview?.SetAttribute( name, v );
}
break;
case Vector4 v:
if ( _float4Attributes.TryAdd( name, v ) )
{
_preview?.SetAttribute( name, (Color)v );
}
break;
case Vector3 v:
if ( _float3Attributes.TryAdd( name, v ) )
{
_preview?.SetAttribute( name, v );
}
break;
case Vector2 v:
if ( _float2Attributes.TryAdd( name, v ) )
{
_preview?.SetAttribute( name, v );
}
break;
case float v:
if ( _floatAttributes.TryAdd( name, v ) )
{
_preview?.SetAttribute( name, v );
}
break;
case int v:
if ( _intAttributes.TryAdd( name, v ) )
{
_preview?.SetAttribute( name, v );
}
break;
case bool v:
if ( _boolAttributes.TryAdd( name, v ) )
{
_preview?.SetAttribute( name, v );
}
break;
case Texture v:
if ( _textureAttributes.TryAdd( name, v ) )
{
_preview?.SetAttribute( name, v );
}
break;
case Sampler v:
if ( _samplerStateAttributes.TryAdd( name, (SamplerState)v ) )
{
_preview?.SetAttribute( name, (SamplerState)v );
}
break;
case ShaderFeatureWrapper v:
if ( _shaderFeatures.TryAdd( v.FeatureName, v.Value ) )
{
_preview?.SetFeature( v.FeatureName, v.Value );
}
break;
case DynamicComboWrapper v:
if ( _dynamicComboIntAttributes.TryAdd( v.ComboName, v.Value ) )
{
_preview?.SetDynamicCombo( v.ComboName, v.Value );
}
break;
default:
throw new InvalidOperationException( $"Unsupported attribute type: \"{value.GetType()}\"" );
}
}
private void RegisterShaderFeatures( out List<GraphCompiler.GraphIssue> registrationIssues )
{
ShaderFeatures.Clear();
registrationIssues = new();
var features = _graph.Parameters.OfType<IBlackboardShaderFeatureParameter>();
foreach ( var feature in features )
{
ShaderFeatureBase shaderFeature = default;
if ( feature is ShaderFeatureBooleanParameter boolFeatureParam )
{
shaderFeature = new ShaderFeatureBoolean()
{
Name = boolFeatureParam.Name,
HeaderName = boolFeatureParam.HeaderName,
Description = boolFeatureParam.Description,
};
}
else if ( feature is ShaderFeatureEnumParameter enumFeatureParam )
{
shaderFeature = new ShaderFeatureEnum()
{
Name = enumFeatureParam.Name,
HeaderName = enumFeatureParam.HeaderName,
Description = enumFeatureParam.Description,
Options = enumFeatureParam.Options,
};
}
if ( shaderFeature.IsValid && !ShaderFeatures.ContainsKey( shaderFeature.Name ) )
{
ShaderFeatures.Add( shaderFeature.Name, shaderFeature );
}
}
}
private string GeneratePreviewCode()
{
if ( BlackboardIssues.Any() )
{
_output.GraphIssues = BlackboardIssues;
DockManager.RaiseDock( "Output" );
_generatedCode = null;
_generatedCodeTextView.SetTextContents( "" );
RestoreShader();
return null;
}
if ( _autoCompile )
{
ClearAttributes();
}
// Go ahead preregister anything before iterating over all the nodes in the graph.
RegisterShaderFeatures( out List<GraphCompiler.GraphIssue> registrationIssues );
if ( registrationIssues.Any() )
{
_output.GraphIssues = registrationIssues;
DockManager.RaiseDock( "Output" );
_generatedCode = null;
_generatedCodeTextView.SetTextContents( "" );
RestoreShader();
return null;
}
var resultNode = _graph.Nodes.OfType<BaseResult>().FirstOrDefault();
var compiler = new GraphCompiler( _graph, ShaderFeatures, true );
var nodeErrors = new List<GraphCompiler.GraphIssue>();
var nodeWarnings = new List<GraphCompiler.GraphIssue>();
var evaluatedCustomFunctions = new List<string>();
if ( _autoCompile )
{
compiler.OnAttribute = OnAttribute;
}
foreach ( var node in _graph.Nodes.OfType<BaseNodePlus>() )
{
node.ClearError();
// Assign a PreviewID to any Previewable node.
if ( node.CanPreview )
{
node.PreviewID = compiler.PreviewID++;
}
if ( node is IWarningNode warningNode )
{
node.HasWarning = false;
var warnings = warningNode.GetWarnings();
if ( warnings.Count > 0 )
{
node.HasWarning = true;
foreach ( var warning in warnings )
{
nodeWarnings.Add( new() { Message = warning, Node = node, IsWarning = true } );
}
}
}
if ( node is IErroringNode erroringNode )
{
node.ClearError();
var errors = erroringNode.GetErrors();
if ( errors.Count > 0 )
{
node.HasError = true;
var errorSb = new StringBuilder();
foreach ( var error in errors )
{
nodeErrors.Add( new() { Message = error, Node = node, IsWarning = false } );
errorSb.AppendLine( error );
}
node.ErrorMessage = errorSb.ToString();
}
}
if ( node is PreviewableNode previewableNode )
{
previewableNode.EvaluatePreview( compiler );
}
if ( node is CustomFunctionNode customFunctionNode )
{
if ( !string.IsNullOrWhiteSpace( customFunctionNode.Name ) )
{
node.ClearError();
if ( evaluatedCustomFunctions.Contains( customFunctionNode.Name ) )
{
node.HasError = true;
node.ErrorMessage = "Duplicate Custom Function node";
nodeErrors.Add( new() { Message = node.ErrorMessage, Node = node, IsWarning = false } );
}
else
{
evaluatedCustomFunctions.Add( customFunctionNode.Name );
}
}
}
var property = node.GetType().GetProperties( BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static )
.FirstOrDefault( x => x.GetGetMethod() != null && x.PropertyType == typeof( NodeResult.Func ) );
if ( property == null )
continue;
var output = property.GetCustomAttribute<BaseNodePlus.OutputAttribute>();
if ( output == null )
continue;
var result = compiler.Result( new NodeInput { Identifier = node.Identifier, Output = property.Name } );
if ( !result.IsValid() )
continue;
Type componentType = null;
if ( result.ResultType == ResultType.VoidFunction )
{
componentType = result.Components switch
{
int r when r == 1 => typeof( float ),
int r when r == 2 => typeof( Vector2 ),
int r when r == 3 => typeof( Vector3 ),
int r when r == 4 => typeof( Vector4 ),
_ => throw new NotImplementedException(),
};
}
else
{
componentType = result.ResultType switch
{
ResultType.Bool => typeof( bool ),
ResultType.Int => typeof( int ),
ResultType.Float => typeof( float ),
ResultType.Vector2 => typeof( Vector2 ),
ResultType.Vector3 => typeof( Vector3 ),
ResultType.Vector4 => typeof( Vector4 ),
ResultType.Float2x2 => typeof( Float2x2 ),
ResultType.Float3x3 => typeof( Float3x3 ),
ResultType.Float4x4 => typeof( Float4x4 ),
ResultType.Sampler => typeof( Sampler ),
ResultType.Texture2D => typeof( Texture ),
ResultType.TextureCube => typeof( Texture ),
ResultType.Gradient => typeof( Gradient ),
_ => throw new Exception( $"Unsupported ResultType \"{result.ResultType}\"" ),
};
}
if ( componentType == null )
continue;
// While we're here, let's check the output plugs and update their handle configs to the result type
var nodeUI = _graphView.FindNode( node );
if ( !nodeUI.IsValid() )
continue;
var plugOut = nodeUI.Outputs.FirstOrDefault( x => ((BasePlug)x.Inner).Info.Property == property );
if ( !plugOut.IsValid() )
continue;
plugOut.PropertyType = componentType;
// We also have to update everything so they get repainted
nodeUI.Update();
foreach ( var input in nodeUI.Inputs )
{
if ( !input.IsValid() || !input.Connection.IsValid() )
continue;
input.Connection.Update();
}
}
if ( _properties.IsValid() && _properties.Target is BaseNodePlus targetNode && targetNode.CanPreview )
{
_preview.SetStage( targetNode.PreviewID );
}
else
{
_preview.SetStage( ShaderGraphPlusGlobals.GraphCompiler.NoNodePreviewID );
}
if ( resultNode != null && !IsSubgraph )
{
UpdateNodeUI( resultNode );
}
else if ( IsSubgraph )
{
foreach ( var subgraphOutput in _graph.Nodes.OfType<SubgraphOutput>() )
{
UpdateNodeUI( subgraphOutput );
}
}
var code = compiler.Generate();
#region Errors & Warnings
nodeErrors.AddRange( compiler.Errors );
nodeWarnings.AddRange( compiler.Warnings );
if ( nodeWarnings.Any() ) //&& iErroringNodeErrors.Any() )
{
// Add any iErroringNodeErrors to the end of the iWarningNodeWarning list.
if ( nodeErrors.Any() )
{
nodeWarnings.AddRange( nodeErrors );
_output.GraphIssues = nodeWarnings;
DockManager.RaiseDock( "Output" );
_generatedCode = null;
_generatedCodeTextView.SetTextContents( "" );
RestoreShader();
return null;
}
else // No Errors to add :) not great not terrible...
{
_output.GraphIssues = nodeWarnings;
DockManager.RaiseDock( "Output" );
}
}
else // No warnings? clear em.
{
_output.ClearWarnings();
}
if ( nodeErrors.Any() )
{
_output.GraphIssues = nodeErrors;
DockManager.RaiseDock( "Output" );
_generatedCode = null;
//_generatedCodeTextView.SetTextContents( "" );
RestoreShader();
return null;
}
else // No errors :o? clear em :D.
{
_output.ClearErrors();
}
#endregion Errors & Warnings
if ( _generatedCode != code )
{
_generatedCode = code;
//_generatedCodeTextView.SetTextContents( code );
if ( _autoCompile )
{
Compile();
}
}
return code;
}
private void UpdateNodeUI( BaseNodePlus node )
{
var nodeUI = _graphView.FindNode( node );
if ( nodeUI.IsValid() )
{
nodeUI.Update();
foreach ( var input in nodeUI.Inputs )
{
if ( !input.IsValid() || !input.Connection.IsValid() )
continue;
input.Connection.Update();
}
}
}
private string GenerateShaderCode()
{
// Go ahead preregister anything before iterating over all the nodes in the graph.
RegisterShaderFeatures( out _ );
var compiler = new GraphCompiler( _graph, ShaderFeatures, false );
return compiler.Generate();
}
public void OnUndoPushed()
{
_undoHistory.History = _undoStack.Names;
}
public void SetDirty( bool evaluate = true )
{
Update();
_dirty = true;
_graphCanvas.WindowTitle = $"{_asset?.Name ?? "untitled"}*";
if ( evaluate )
GeneratePreviewCode();
}
[EditorEvent.Frame]
protected void Frame()
{
_undoOption.Enabled = _undoStack.CanUndo;
_redoOption.Enabled = _undoStack.CanRedo;
_undoMenuOption.Enabled = _undoStack.CanUndo;
_redoMenuOption.Enabled = _undoStack.CanRedo;
_undoOption.Text = _undoStack.UndoName;
_redoOption.Text = _undoStack.RedoName;
_undoMenuOption.Text = _undoStack.UndoName ?? "Undo";
_redoMenuOption.Text = _undoStack.RedoName ?? "Redo";
_undoOption.StatusTip = _undoStack.UndoName;
_redoOption.StatusTip = _undoStack.RedoName;
_undoMenuOption.StatusTip = _undoStack.UndoName;
_redoMenuOption.StatusTip = _undoStack.RedoName;
if ( _undoHistory is not null )
{
_undoHistory.UndoLevel = _undoStack.UndoLevel;
}
CheckForChanges();
}
private void CheckForChanges()
{
bool wasDirty = false;
foreach ( var node in _graph.Nodes )
{
if ( node is ShaderNodePlus shaderNode && shaderNode.IsDirty )
{
shaderNode.IsDirty = false;
wasDirty = true;
}
}
if ( wasDirty )
{
_graphView.ChildValuesChanged( null );
}
}
[Shortcut( "editor.undo", "CTRL+Z", ShortcutType.Window )]
private void Undo()
{
if ( _undoStack.Undo() is UndoOp op )
{
SGPLogger.Info( $"Undo ({op.name})" );
_redoOption.Enabled = _undoStack.CanUndo;
_graph.ClearNodes();
_graph.ClearParameters();
_graph.DeserializeNodes( op.undoBuffer, true );
_graph.DeserializeParameters( op.undoBuffer );
_graphView.RebuildFromGraph();
_blackboardView.RebuildFromGraph();
SetDirty();
}
}
[Shortcut( "editor.redo", "CTRL+Y", ShortcutType.Window )]
private void Redo()
{
if ( _undoStack.Redo() is UndoOp op )
{
SGPLogger.Info( $"Redo ({op.name})" );
_redoOption.Enabled = _undoStack.CanRedo;
_graph.ClearNodes();
_graph.ClearParameters();
_graph.DeserializeNodes( op.redoBuffer, true );
_graph.DeserializeParameters( op.redoBuffer );
_graphView.RebuildFromGraph();
_blackboardView.RebuildFromGraph();
SetDirty();
}
}
private void SetUndoLevel( int level )
{
if ( _undoStack.SetUndoLevel( level ) is UndoOp op )
{
SGPLogger.Info( $"SetUndoLevel ({op.name})" );
_graph.ClearNodes();
_graph.ClearParameters();
_graph.DeserializeNodes( op.redoBuffer, true );
_graph.DeserializeParameters( op.redoBuffer );
_graphView.RebuildFromGraph();
_blackboardView.RebuildFromGraph();
SetDirty();
}
}
[Shortcut( "editor.cut", "CTRL+X", ShortcutType.Window )]
private void CutSelection()
{
_graphView.CutSelection();
}
[Shortcut( "editor.copy", "CTRL+C", ShortcutType.Window )]
private void CopySelection()
{
_graphView.CopySelection();
}
[Shortcut( "editor.paste", "CTRL+V", ShortcutType.Window )]
private void PasteSelection()
{
_graphView.PasteSelection();
}
[Shortcut( "editor.duplicate", "CTRL+D", ShortcutType.Window )]
private void DuplicateSelection()
{
_graphView.DuplicateSelection();
}
[Shortcut( "editor.select-all", "CTRL+A", ShortcutType.Window )]
private void SelectAll()
{
_graphView.SelectAll();
}
[Shortcut( "editor.clear-selection", "ESC", ShortcutType.Window )]
private void ClearSelection()
{
_graphView.ClearSelection();
}
[Shortcut( "gameObject.frame", "F", ShortcutType.Window )]
private void CenterOnSelection()
{
_graphView.CenterOnSelection();
}
private void CreateToolBar()
{
var toolBar = new ToolBar( this, "ShaderGraphPlusToolbar" );
AddToolBar( toolBar, ToolbarPosition.Top );
toolBar.AddOption( "New", "common/new.png", New ).StatusTip = "New Graph";
toolBar.AddOption( "Open", "common/open.png", Open ).StatusTip = "Open Graph";
toolBar.AddOption( "Save", "common/save.png", () => Save() ).StatusTip = "Save Graph";
toolBar.AddSeparator();
_undoOption = toolBar.AddOption( "Undo", "undo", Undo );
_redoOption = toolBar.AddOption( "Redo", "redo", Redo );
toolBar.AddSeparator();
toolBar.AddOption( "Cut", "common/cut.png", CutSelection );
toolBar.AddOption( "Copy", "common/copy.png", CopySelection );
toolBar.AddOption( "Paste", "common/paste.png", PasteSelection );
toolBar.AddOption( "Select All", "select_all", SelectAll );
toolBar.AddSeparator();
toolBar.AddOption( "Compile", "refresh", () => Compile() ).StatusTip = "Compile Graph";
_autoCompileOption = toolBar.AddOption( "Toggle Auto Compile", "model_editor/auto_recompile.png", () =>
{
_autoCompile = !_autoCompile;
if ( _autoCompile )
{
Compile();
//SetDirty();
}
_autoCompileOption.Icon = $"{(_autoCompile ? "model_editor/auto_recompile.png" : "model_editor/supress_auto_recompile.png")}";
} );
_autoCompileOption.StatusTip = "Enable or Disable graph auto compile.";
toolBar.AddOption( "Open Generated Shader", "common/edit.png", () => OpenGeneratedShader() ).StatusTip = "Open Generated Shader";
toolBar.AddOption( "Take Screenshot", "photo_camera", Screenshot ).StatusTip = "Take Screenshot";
_undoOption.Enabled = false;
_redoOption.Enabled = false;
}
public void BuildMenuBar()
{
var file = MenuBar.AddMenu( "File" );
file.AddOption( "New", "common/new.png", New, "editor.new" ).StatusTip = "New Graph";
file.AddOption( "Open", "common/open.png", Open, "editor.open" ).StatusTip = "Open Graph";
file.AddOption( "Save", "common/save.png", Save, "editor.save" ).StatusTip = "Save Graph";
file.AddOption( "Save As...", "common/save.png", SaveAs, "editor.save-as" ).StatusTip = "Save Graph As...";
file.AddSeparator();
_recentFilesMenu = file.AddMenu( "Recent Files" );
file.AddSeparator();
file.AddOption( "Quit", null, Quit, "editor.quit" ).StatusTip = "Quit";
var edit = MenuBar.AddMenu( "Edit" );
_undoMenuOption = edit.AddOption( "Undo", "undo", Undo, "editor.undo" );
_redoMenuOption = edit.AddOption( "Redo", "redo", Redo, "editor.redo" );
_undoMenuOption.Enabled = _undoStack.CanUndo;
_redoMenuOption.Enabled = _undoStack.CanRedo;
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" );
var debug = MenuBar.AddMenu( "Debug" );
debug.AddSeparator();
debug.AddOption( "Open Temp Shader", "common/edit.png", OpenTempGeneratedShader );
debug.AddOption( "Open ShaderGraph Project in text editor", "common/edit.png", OpenShaderGraphProjectTxt );
debug.AddSeparator();
RefreshRecentFiles();
var view = MenuBar.AddMenu( "View" );
view.AboutToShow += () => OnViewMenu( view );
}
[Shortcut( "editor.quit", "CTRL+Q" )]
void Quit()
{
Close();
}
void RefreshRecentFiles()
{
_recentFilesMenu.Enabled = _recentFiles.Count > 0;
_recentFilesMenu.Clear();
_recentFilesMenu.AddOption( "Clear recent files", null, ClearRecentFiles )
.StatusTip = "Clear recent files";
_recentFilesMenu.AddSeparator();
const int maxFilesToDisplay = 10;
int fileCount = 0;
for ( int i = _recentFiles.Count - 1; i >= 0; i-- )
{
if ( fileCount >= maxFilesToDisplay )
break;
var filePath = _recentFiles[i];
_recentFilesMenu.AddOption( $"{++fileCount} - {filePath}", null, () => PromptSave( () => Open( filePath ) ) )
.StatusTip = $"Open {filePath}";
}
}
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 = ShaderGraphPlusView.EnableGridAlignedWires;
style.Toggled += b => ShaderGraphPlusView.EnableGridAlignedWires = b;
}
private void ClearRecentFiles()
{
if ( _recentFiles.Count == 0 )
return;
_recentFiles.Clear();
RefreshRecentFiles();
SaveRecentFiles();
}
private void AddToRecentFiles( string filePath )
{
filePath = filePath.ToLower();
// If file is already recent, remove it so it'll become the most recent
if ( _recentFiles.Contains( filePath ) )
{
_recentFiles.RemoveAll( x => x == filePath );
}
_recentFiles.Add( filePath );
RefreshRecentFiles();
SaveRecentFiles();
}
private void SaveRecentFiles()
{
Editor.FileSystem.Temporary.WriteJson( "shadergraphplus_recentfiles.json", _recentFiles );
}
private void PromptSave( Action action )
{
if ( !_dirty )
{
action?.Invoke();
return;
}
var confirm = new PopupWindow(
"Save Current Graph", "The open graph has unsaved changes. Would you like to save now?", "Cancel",
new Dictionary<string, Action>()
{
{ "No", () => action?.Invoke() },
{ "Yes", () => { if ( SaveInternal( false ) ) action?.Invoke(); } }
}
);
confirm.Show();
}
[Shortcut( "editor.new", "CTRL+N" )]
public void New()
{
PromptSave( CreateNew );
}
public void CreateNew()
{
_asset = null;
_graph = new();
_dirty = false;
_graphView.Graph = _graph;
_blackboardView.Graph = _graph;
_graphCanvas.WindowTitle = "untitled";
_preview.Model = null;
_preview.Tint = Color.White;
_undoStack.Clear();
_undoHistory.History = _undoStack.Names;
_generatedCode = "";
_generatedCodeTextView.SetTextContents( "" );
_properties.Target = _graph;
_blackboardView.Rebuild();
_output.ClearErrors();
_output.ClearWarnings();
if ( !IsSubgraph )
{
var result = _graphView.CreateNewNode( _graphView.FindNodeType( typeof( Result ) ), 0 );
_graphView.Scale = 1;
_graphView.CenterOn( result.Size * 0.5f );
}
else
{
var result = _graphView.CreateNewNode( _graphView.FindNodeType( typeof( SubgraphOutput ) ), 0 );
var parameter = _blackboardView.CreateNewParameter( _graphView.FindParameterType( typeof( Float3SubgraphOutputParameter ) ) ) as Float3SubgraphOutputParameter;
parameter.Preview = SubgraphOutputPreviewType.Albedo;
var subgraphOutput = result.Node as SubgraphOutput;
subgraphOutput.ParameterIdentifier = parameter.Identifier;
_graphView.Scale = 1;
_graphView.CenterOn( result.Size * 0.5f );
}
ClearAttributes();
RestoreShader();
}
private void ClearAttributes()
{
_boolAttributes.Clear();
_intAttributes.Clear();
_floatAttributes.Clear();
_float2Attributes.Clear();
_float3Attributes.Clear();
_float4Attributes.Clear();
_textureAttributes.Clear();
_samplerStateAttributes.Clear();
_dynamicComboIntAttributes.Clear();
_shaderFeatures.Clear();
//_compiledNodes.Clear();
_preview?.ClearAttributes();
}
public void Open()
{
var fd = new FileDialog( null )
{
Title = $"Open {FileType}",
DefaultSuffix = $".{FileExtension}"
};
fd.SetNameFilter( $"{FileType} ( *.{FileExtension})" );
if ( !fd.Execute() )
return;
PromptSave( () => Open( fd.SelectedFile ) );
}
private void OpenProject( string path )
{
Open( path );
}
public void Open( string path, bool addToPath = true )
{
var asset = AssetSystem.FindByPath( path );
if ( asset == null )
return;
if ( asset == _asset )
{
Focus();
return;
}
var graph = new ShaderGraphPlus();
graph.Deserialize( System.IO.File.ReadAllText( path ), null, Path.GetFileName( path ) );
graph.Path = asset.RelativePath;
graph.IsSubgraph = IsSubgraph;
_preview.Model = string.IsNullOrWhiteSpace( graph.Model ) ? null : Model.Load( graph.Model );
_preview.LoadSettings( graph.PreviewSettings );
_asset = asset;
_graph = graph;
_dirty = false;
_graphView.Graph = _graph;
_blackboardView.Graph = _graph;
_graphCanvas.WindowTitle = _asset.Name;
_undoStack.Clear();
_undoHistory.History = _undoStack.Names;
_generatedCode = "";
_generatedCodeTextView.SetTextContents( "" );
_properties.Target = _graph;
_blackboardView.Rebuild();
if ( addToPath )
AddFileHistory( path );
_output.ClearErrors();
_output.ClearWarnings();
var center = Vector2.Zero;
var resultNode = graph.Nodes.OfType<BaseResult>().FirstOrDefault();
if ( resultNode != null )
{
var nodeUI = _graphView.FindNode( resultNode );
if ( nodeUI.IsValid() )
{
center = nodeUI.Position + nodeUI.Size * 0.5f;
}
}
_graphView.Scale = 1;
_graphView.CenterOn( center );
_graphView.RestoreViewFromCookie();
ClearAttributes();
AddToRecentFiles( path );
GeneratePreviewCode();
var generatedShaderPath = System.IO.Path.ChangeExtension( _asset.AbsolutePath, ".shader" );
if ( System.IO.Path.Exists( generatedShaderPath ) )
{
_generatedCodeTextView.SetTextContents( System.IO.File.ReadAllText( generatedShaderPath ) );
}
else
{
DockManager.RaiseDock( "Output" );
}
}
[Shortcut( "editor.save-as", "CTRL+SHIFT+S" )]
public void SaveAs()
{
SaveInternal( true );
}
[Shortcut( "editor.save", "CTRL+S" )]
public void Save()
{
SaveInternal( false );
}
private void AddFileHistory( string path )
{
var lastFileHistory = _fileHistory.LastOrDefault();
if ( _fileHistoryPosition < _fileHistory.Count - 1 )
{
_fileHistory.RemoveRange( _fileHistoryPosition + 1, _fileHistory.Count - _fileHistoryPosition - 1 );
}
if ( path != lastFileHistory )
_fileHistory.Add( path );
_fileHistoryPosition = _fileHistory.Count - 1;
UpdateFileHistoryButtons();
}
private void FileHistoryForward()
{
if ( _fileHistoryPosition < _fileHistory.Count - 1 )
{
_fileHistoryPosition++;
PromptSave( () =>
{
Open( _fileHistory[_fileHistoryPosition], false );
UpdateFileHistoryButtons();
} );
}
}
private void FileHistoryBack()
{
if ( _fileHistoryPosition > 0 )
{
_fileHistoryPosition--;
PromptSave( () =>
{
Open( _fileHistory[_fileHistoryPosition], false );
UpdateFileHistoryButtons();
} );
}
}
private void FileHistoryHome()
{
if ( _fileHistory.Count == 0 ) return;
PromptSave( () =>
{
Open( _fileHistory.First() );
UpdateFileHistoryButtons();
} );
}
private void UpdateFileHistoryButtons()
{
_fileHistoryForward.Enabled = _fileHistoryPosition < _fileHistory.Count - 1;
_fileHistoryBack.Enabled = _fileHistoryPosition > 0;
_fileHistoryHome.Enabled = _asset.Path != _fileHistory.FirstOrDefault();
}
private bool SaveInternal( bool saveAs )
{
var savePath = _asset == null || saveAs ? GetSavePath() : _asset.AbsolutePath;
if ( string.IsNullOrWhiteSpace( savePath ) )
return false;
_preview.SaveSettings( _graph.PreviewSettings );
// Write serialized graph to asset file
System.IO.File.WriteAllText( savePath, _graph.Serialize() );
if ( saveAs )
{
// If we're saving as, we want to register the new asset
_asset = null;
}
// Register asset if we haven't already
_asset ??= AssetSystem.RegisterFile( savePath );
if ( _asset == null )
{
SGPLogger.Warning( $"Unable to register asset {savePath}" );
return false;
}
MainAssetBrowser.Instance?.Local.UpdateAssetList();
_dirty = false;
_graphCanvas.WindowTitle = _asset.Name;
if ( IsSubgraph )
{
Compile();
_generatedCodeTextView.SetTextContents( _generatedCode );
}
else
{
var shaderPath = System.IO.Path.ChangeExtension( savePath, ".shader" );
var code = GenerateShaderCode();
if ( string.IsNullOrWhiteSpace( code ) )
return false;
_generatedCodeTextView.SetTextContents( code );
// Write generated shader to file
if ( System.IO.File.Exists( shaderPath ) )
{
FileInfo fileInfo = new FileInfo( shaderPath );
if ( !fileInfo.IsReadOnly )
{
System.IO.File.WriteAllText( shaderPath, code );
}
else
{
SGPLogger.Warning( $"Asset at path \"{_asset.Path}\" is read only!" );
}
}
else
{
System.IO.File.WriteAllText( shaderPath, code );
}
}
// Write generated post processing class to file within the current projects code folder.
//if (_graph.MaterialDomain is MaterialDomain.PostProcess)
//{
// // If the post processing class code is blank, dont bother generating the class.
// if ( !string.IsNullOrWhiteSpace( code.Item2 ) )
// {
// WritePostProcessingShaderClass( code.Item2 );
// }
//
//}
//
AddToRecentFiles( savePath );
EditorEvent.Run( "shadergraphplus.update.subgraph", _asset.RelativePath );
return true;
}
private void WritePostProcessingShaderClass( string classCode )
{
var path = System.IO.Directory.CreateDirectory( $"{Utilities.Path.GetProjectCodePath()}/Components/PostProcessing" );
File.WriteAllText( Path.Combine( path.FullName, $"{_asset.Name}_PostProcessing.cs" ), classCode );
}
private string GetSavePath()
{
var fd = new FileDialog( null )
{
Title = $"Save {FileType}",
DefaultSuffix = $".{FileExtension}"
};
fd.SelectFile( $"untitled.{FileExtension}" );
fd.SetFindFile();
fd.SetModeSave();
fd.SetNameFilter( $"{FileType} (*.{FileExtension})" );
if ( !fd.Execute() )
return null;
return fd.SelectedFile;
}
public void CreateUI()
{
BuildMenuBar();
DockManager.RegisterDockType( "Graph", "account_tree", null, false );
DockManager.RegisterDockType( "Preview", "photo", null, false );
DockManager.RegisterDockType( "Properties", "edit", null, false );
DockManager.RegisterDockType( "Blackboard", "edit", null, false );
DockManager.RegisterDockType( "Output", "notes", null, false );
DockManager.RegisterDockType( "Console", "text_snippet", null, false );
DockManager.RegisterDockType( "Undo History", "history", null, false );
DockManager.RegisterDockType( "Palette", "palette", null, false );
DockManager.RegisterDockType( "Generated Code", "", null, false );
_graphCanvas = new Widget( this ) { WindowTitle = $"{(_asset != null ? _asset.Name : "untitled")}{(_dirty ? "*" : "")}" };
_graphCanvas.Name = "Graph";
_graphCanvas.SetWindowIcon( "account_tree" );
_graphCanvas.Layout = Layout.Column();
var graphToolBar = new ToolBar( _graphCanvas, "GraphToolBar" );
graphToolBar.SetIconSize( 16 );
_fileHistoryBack = graphToolBar.AddOption( null, "arrow_back" );
_fileHistoryForward = graphToolBar.AddOption( null, "arrow_forward" );
graphToolBar.AddSeparator();
_fileHistoryHome = graphToolBar.AddOption( null, "common/home.png" );
_fileHistoryBack.Triggered += FileHistoryBack;
_fileHistoryForward.Triggered += FileHistoryForward;
_fileHistoryHome.Triggered += FileHistoryHome;
_fileHistoryBack.Enabled = false;
_fileHistoryForward.Enabled = false;
_fileHistoryHome.Enabled = false;
var stretcher = new Widget( graphToolBar );
stretcher.Layout = Layout.Row();
stretcher.Layout.AddStretchCell( 1 );
graphToolBar.AddWidget( stretcher );
graphToolBar.AddWidget( new GamePerformanceBar( () => (1000.0f / PerformanceStats.LastSecond.FrameAvg).ToString( "n0" ) + "fps" ) { ToolTip = "Frames Per Second Average" } );
graphToolBar.AddWidget( new GamePerformanceBar( () => PerformanceStats.LastSecond.FrameAvg.ToString( "0.00" ) + "ms" ) { ToolTip = "Frame Time Average (milliseconds)" } );
graphToolBar.AddWidget( new GamePerformanceBar( () => PerformanceStats.ApproximateProcessMemoryUsage.FormatBytes() ) { ToolTip = "Approximate Memory Usage" } );
_graphCanvas.Layout.Add( graphToolBar );
_blackboardCanvas = new Widget( this ) { WindowTitle = $"Blackboard" };
_blackboardCanvas.Name = "Blackboard";
_blackboardCanvas.SetWindowIcon( "list" );
_blackboardCanvas.Layout = Layout.Row();
_blackboardCanvas.Layout.Spacing = 8;
_blackboardCanvas.Layout.Margin = 4;
_blackboardView = new BlackboardView( _blackboardCanvas, this );
_blackboardView.Graph = _graph;
_blackboardView.OnDirty += () => SetDirty();
_blackboardView.OnParameterNodeDeleted += () =>
{
_graphView.RebuildFromGraph();
};
_blackboardCanvas.Layout.Add( _blackboardView, 1 );
_graphView = new ShaderGraphPlusView( _graphCanvas, this, _blackboardView );
_graphView.BilinearFiltering = false;
var nodeTypes = EditorTypeLibrary.GetTypes<ShaderNodePlus>()
.Where( x => !x.IsAbstract && !x.HasAttribute<HideAttribute>() ).OrderBy( x => x.Name );
var blackboardParameterTypes = EditorTypeLibrary.GetTypes<BlackboardParameter>()
.Where( x => !x.IsAbstract ).OrderBy( x => x.Name );
foreach ( var type in nodeTypes )
{
_graphView.AddNodeType( type );
}
foreach ( var type in blackboardParameterTypes )
{
_graphView.AddParameterType( type );
_blackboardView.AddParameterType( type );
}
var subgraphs = AssetSystem.All.Where( x => x.Path.EndsWith( $".{ShaderGraphPlusGlobals.SubgraphAssetTypeExtension}", StringComparison.OrdinalIgnoreCase ) );
foreach ( var subgraph in subgraphs )
{
// Skip any _c compiled subgraph files.
if ( subgraph.CanRecompile )
{
_graphView.AddNodeType( subgraph.Path );
}
}
_graphView.Graph = _graph;
_graphView.OnChildValuesChanged += ( w ) => SetDirty();
_graphCanvas.Layout.Add( _graphView, 1 );
_output = new Output( this );
_output.OnNodeSelected += ( node ) =>
{
var nodeUI = _graphView.SelectNode( node );
_graphView.Scale = 1;
_graphView.CenterOn( nodeUI.Center );
};
_preview = new PreviewPanel( this, _graph.Model )
{
OnModelChanged = ( model ) => _graph.Model = model?.Name
};
foreach ( var value in _samplerStateAttributes )
{
_preview.SetAttribute( value.Key, value.Value );
}
foreach ( var value in _textureAttributes )
{
_preview.SetAttribute( value.Key, value.Value );
}
foreach ( var value in _float4Attributes )
{
_preview.SetAttribute( value.Key, value.Value );
}
foreach ( var value in _float3Attributes )
{
_preview.SetAttribute( value.Key, value.Value );
}
foreach ( var value in _float2Attributes )
{
_preview.SetAttribute( value.Key, value.Value );
}
foreach ( var value in _floatAttributes )
{
_preview.SetAttribute( value.Key, value.Value );
}
foreach ( var value in _boolAttributes )
{
_preview.SetAttribute( value.Key, value.Value );
}
foreach ( var value in _dynamicComboIntAttributes )
{
_preview.SetDynamicCombo( value.Key, value.Value );
}
foreach ( var value in _shaderFeatures )
{
_preview.SetFeature( value.Key, value.Value );
}
_properties = new Properties( this );
_properties.Target = _graph;
_properties.PropertyUpdated += OnPropertyUpdated;
_undoHistory = new UndoHistory( this, _undoStack );
_undoHistory.OnUndo = Undo;
_undoHistory.OnRedo = Redo;
_undoHistory.OnHistorySelected = SetUndoLevel;
_palette = new PaletteWidget( this, IsSubgraph );
_generatedCodeTextView = new TextView( this, "Generated Code", "" );
DockManager.AddDock( null, _preview, DockArea.Inside, DockManager.DockProperty.HideOnClose );
DockManager.AddDock( null, _graphCanvas, DockArea.Right, DockManager.DockProperty.HideCloseButton | DockManager.DockProperty.HideOnClose, 0.7f );
DockManager.AddDock( _graphCanvas, _output, DockArea.Bottom, DockManager.DockProperty.HideOnClose, 0.25f );
DockManager.AddDock( _preview, _properties, DockArea.Bottom, DockManager.DockProperty.HideOnClose, 0.5f );
// Yuck, console is internal but i want it, what is the correct way?
var console = EditorTypeLibrary.Create( "ConsoleWidget", typeof( Widget ), new[] { this } ) as Widget;
DockManager.AddDock( _output, console, DockArea.Inside, DockManager.DockProperty.HideOnClose );
DockManager.AddDock( _output, _blackboardCanvas, DockArea.Right, DockManager.DockProperty.HideOnClose, -0.5f );
DockManager.AddDock( _output, _undoHistory, DockArea.Inside, DockManager.DockProperty.HideOnClose );
DockManager.AddDock( _output, _palette, DockArea.Inside, DockManager.DockProperty.HideOnClose );
DockManager.AddDock( _output, _generatedCodeTextView, DockArea.Inside, DockManager.DockProperty.HideOnClose );
DockManager.RaiseDock( "Output" );
DockManager.Update();
_defaultDockState = DockManager.State;
if ( StateCookie != "ShaderGraphPlus" )
{
StateCookie = "ShaderGraphPlus";
}
else
{
RestoreFromStateCookie();
}
Compile();
}
private void CheckParameterTarget( BlackboardParameter parameter )
{
BlackboardIssues.Clear();
if ( !parameter.CheckParameter( out var parameterIssues ) )
{
foreach ( var issue in parameterIssues )
{
BlackboardIssues.Add( new() { Node = null, Message = issue, IsWarning = false } );
}
}
}
private void OnPropertyUpdated( SerializedProperty serializedProperty )
{
BlackboardIssues.Clear();
_preview.PostProcessing = _graphView.Graph.Domain == ShaderDomain.PostProcess;
if ( _properties.Target is BlackboardParameter parameter )
{
CheckParameterTarget( parameter );
}
if ( _properties.Target is BaseNodePlus node )
{
_graphView.UpdateNode( node );
}
var shouldEvaluate = _properties.Target is not CommentNode;
SetDirty( shouldEvaluate );
}
protected override void RestoreDefaultDockLayout()
{
DockManager.State = _defaultDockState;
SaveToStateCookie();
}
protected override bool OnClose()
{
if ( !_dirty )
{
return true;
}
var confirm = new PopupWindow(
"Save Current Graph", "The open graph has unsaved changes. Would you like to save now?", "Cancel",
new Dictionary<string, Action>()
{
{ "No", () => { _dirty = false; Close(); } },
{ "Yes", () => { if ( SaveInternal( false ) ) Close(); } }
}
);
confirm.Show();
return false;
}
// For Development iteration only
/*
[EditorEvent.Hotload]
public void OnHotload()
{
DockManager.Clear();
MenuBar.Clear();
CreateUI();
Compile();
_preview3D.LoadSettings( _graph.PreviewSettings );
}
*/
[Event( "shadergraphplus.update.subgraph" )]
public void OnSubgraphUpdate( string updatedPath )
{
foreach ( var node in _graph.Nodes )
{
if ( node is SubgraphNode subgraphNode )
{
subgraphNode.Subgraph = null;
subgraphNode.OnNodeCreated();
}
}
GeneratePreviewCode();
}
}