diff --git a/Bonsai.Editor.Tests/MockGraphView.cs b/Bonsai.Editor.Tests/MockGraphView.cs index 296a11c2c..e67e10448 100644 --- a/Bonsai.Editor.Tests/MockGraphView.cs +++ b/Bonsai.Editor.Tests/MockGraphView.cs @@ -18,6 +18,7 @@ public MockGraphView(ExpressionBuilderGraph workflow = null) Workflow = workflow ?? new ExpressionBuilderGraph(); CommandExecutor = new CommandExecutor(); var serviceContainer = new ServiceContainer(); + serviceContainer.AddService(typeof(WorkflowBuilder), new WorkflowBuilder(Workflow)); serviceContainer.AddService(typeof(CommandExecutor), CommandExecutor); ServiceProvider = serviceContainer; } diff --git a/Bonsai.Editor.Tests/WorkflowEditorTests.cs b/Bonsai.Editor.Tests/WorkflowEditorTests.cs index 4b4930d04..b181e19d5 100644 --- a/Bonsai.Editor.Tests/WorkflowEditorTests.cs +++ b/Bonsai.Editor.Tests/WorkflowEditorTests.cs @@ -51,7 +51,6 @@ static string ToString(IEnumerable sequence) var editor = new WorkflowEditor(graphView.ServiceProvider, graphView); editor.UpdateLayout.Subscribe(graphView.UpdateGraphLayout); editor.UpdateSelection.Subscribe(graphView.UpdateSelection); - editor.Workflow = graphView.Workflow; var nodeSequence = editor.GetGraphValues().ToArray(); return (editor, assertIsReversible: () => diff --git a/Bonsai.Editor/EditorForm.Designer.cs b/Bonsai.Editor/EditorForm.Designer.cs index 3c99e6e7b..239402f72 100644 --- a/Bonsai.Editor/EditorForm.Designer.cs +++ b/Bonsai.Editor/EditorForm.Designer.cs @@ -113,9 +113,9 @@ private void InitializeComponent() this.editExtensionsToolStripButton = new System.Windows.Forms.ToolStripButton(); this.reloadExtensionsToolStripButton = new System.Windows.Forms.ToolStripButton(); this.statusStrip = new System.Windows.Forms.StatusStrip(); + this.statusImageLabel = new System.Windows.Forms.ToolStripStatusLabel(); this.statusContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components); this.statusCopyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.statusImageLabel = new System.Windows.Forms.ToolStripStatusLabel(); this.toolboxSplitContainer = new Bonsai.Editor.SelectableSplitContainer(); this.toolboxTableLayoutPanel = new Bonsai.Editor.TableLayoutPanel(); this.searchTextBox = new Bonsai.Editor.CueBannerTextBox(); @@ -151,6 +151,10 @@ private void InitializeComponent() this.commandExecutor = new Bonsai.Design.CommandExecutor(); this.workflowFileWatcher = new System.IO.FileSystemWatcher(); this.exportImageDialog = new System.Windows.Forms.SaveFileDialog(); + this.explorerSplitContainer = new System.Windows.Forms.SplitContainer(); + this.explorerLayoutPanel = new Bonsai.Editor.TableLayoutPanel(); + this.explorerLabel = new Bonsai.Editor.Label(); + this.explorerTreeView = new Bonsai.Editor.ExplorerTreeView(); this.menuStrip.SuspendLayout(); this.toolStrip.SuspendLayout(); this.statusStrip.SuspendLayout(); @@ -178,6 +182,11 @@ private void InitializeComponent() this.propertiesLayoutPanel.SuspendLayout(); this.toolboxContextMenuStrip.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.workflowFileWatcher)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.explorerSplitContainer)).BeginInit(); + this.explorerSplitContainer.Panel1.SuspendLayout(); + this.explorerSplitContainer.Panel2.SuspendLayout(); + this.explorerSplitContainer.SuspendLayout(); + this.explorerLayoutPanel.SuspendLayout(); this.SuspendLayout(); // // menuStrip @@ -606,7 +615,7 @@ private void InitializeComponent() // welcomeToolStripMenuItem // this.welcomeToolStripMenuItem.Name = "welcomeToolStripMenuItem"; - this.welcomeToolStripMenuItem.Size = new System.Drawing.Size(152, 22); + this.welcomeToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.welcomeToolStripMenuItem.Text = "&Welcome..."; this.welcomeToolStripMenuItem.Click += new System.EventHandler(this.welcomeToolStripMenuItem_Click); // @@ -615,7 +624,7 @@ private void InitializeComponent() this.docsToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("docsToolStripMenuItem.Image"))); this.docsToolStripMenuItem.Name = "docsToolStripMenuItem"; this.docsToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.F1; - this.docsToolStripMenuItem.Size = new System.Drawing.Size(152, 22); + this.docsToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.docsToolStripMenuItem.Text = "&View Help"; this.docsToolStripMenuItem.Click += new System.EventHandler(this.docsToolStripMenuItem_Click); // @@ -623,7 +632,7 @@ private void InitializeComponent() // this.forumToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("forumToolStripMenuItem.Image"))); this.forumToolStripMenuItem.Name = "forumToolStripMenuItem"; - this.forumToolStripMenuItem.Size = new System.Drawing.Size(152, 22); + this.forumToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.forumToolStripMenuItem.Text = "Bonsai &Forums"; this.forumToolStripMenuItem.Click += new System.EventHandler(this.forumToolStripMenuItem_Click); // @@ -631,19 +640,19 @@ private void InitializeComponent() // this.reportBugToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("reportBugToolStripMenuItem.Image"))); this.reportBugToolStripMenuItem.Name = "reportBugToolStripMenuItem"; - this.reportBugToolStripMenuItem.Size = new System.Drawing.Size(152, 22); + this.reportBugToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.reportBugToolStripMenuItem.Text = "&Report a Bug"; this.reportBugToolStripMenuItem.Click += new System.EventHandler(this.reportBugToolStripMenuItem_Click); // // toolStripSeparator5 // this.toolStripSeparator5.Name = "toolStripSeparator5"; - this.toolStripSeparator5.Size = new System.Drawing.Size(149, 6); + this.toolStripSeparator5.Size = new System.Drawing.Size(177, 6); // // aboutToolStripMenuItem // this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; - this.aboutToolStripMenuItem.Size = new System.Drawing.Size(152, 22); + this.aboutToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.aboutToolStripMenuItem.Text = "&About..."; this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click); // @@ -880,6 +889,14 @@ private void InitializeComponent() this.statusStrip.TabIndex = 2; this.statusStrip.Text = "statusStrip"; // + // statusImageLabel + // + this.statusImageLabel.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.statusImageLabel.Image = global::Bonsai.Editor.Properties.Resources.StatusReadyImage; + this.statusImageLabel.Name = "statusImageLabel"; + this.statusImageLabel.Size = new System.Drawing.Size(16, 17); + this.statusImageLabel.Text = "statusImageLabel"; + // // statusContextMenuStrip // this.statusContextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -897,14 +914,6 @@ private void InitializeComponent() this.statusCopyToolStripMenuItem.Text = "&Copy"; this.statusCopyToolStripMenuItem.Click += new System.EventHandler(this.statusCopyToolStripMenuItem_Click); // - // statusImageLabel - // - this.statusImageLabel.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.statusImageLabel.Image = global::Bonsai.Editor.Properties.Resources.StatusReadyImage; - this.statusImageLabel.Name = "statusImageLabel"; - this.statusImageLabel.Size = new System.Drawing.Size(16, 17); - this.statusImageLabel.Text = "statusImageLabel"; - // // toolboxSplitContainer // this.toolboxSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill; @@ -922,8 +931,8 @@ private void InitializeComponent() // this.toolboxSplitContainer.Panel2.Controls.Add(this.toolboxDescriptionPanel); this.toolboxSplitContainer.Selectable = true; - this.toolboxSplitContainer.Size = new System.Drawing.Size(197, 308); - this.toolboxSplitContainer.SplitterDistance = 245; + this.toolboxSplitContainer.Size = new System.Drawing.Size(197, 138); + this.toolboxSplitContainer.SplitterDistance = 75; this.toolboxSplitContainer.TabIndex = 1; this.toolboxSplitContainer.TabStop = false; // @@ -939,7 +948,7 @@ private void InitializeComponent() this.toolboxTableLayoutPanel.RowCount = 2; this.toolboxTableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 23F)); this.toolboxTableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.toolboxTableLayoutPanel.Size = new System.Drawing.Size(197, 245); + this.toolboxTableLayoutPanel.Size = new System.Drawing.Size(197, 75); this.toolboxTableLayoutPanel.TabIndex = 2; // // searchTextBox @@ -980,15 +989,13 @@ private void InitializeComponent() treeNode4, treeNode5, treeNode6}); - this.toolboxTreeView.Size = new System.Drawing.Size(197, 222); + this.toolboxTreeView.Size = new System.Drawing.Size(197, 52); this.toolboxTreeView.TabIndex = 0; - this.toolboxTreeView.ItemDrag += new System.Windows.Forms.ItemDragEventHandler(this.toolboxTreeView_ItemDrag); this.toolboxTreeView.AfterLabelEdit += new System.Windows.Forms.NodeLabelEditEventHandler(this.toolboxTreeView_AfterLabelEdit); + this.toolboxTreeView.ItemDrag += new System.Windows.Forms.ItemDragEventHandler(this.toolboxTreeView_ItemDrag); this.toolboxTreeView.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.toolboxTreeView_AfterSelect); this.toolboxTreeView.NodeMouseDoubleClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.toolboxTreeView_NodeMouseDoubleClick); - this.toolboxTreeView.Enter += new System.EventHandler(this.toolboxTreeView_Enter); this.toolboxTreeView.KeyDown += new System.Windows.Forms.KeyEventHandler(this.toolboxTreeView_KeyDown); - this.toolboxTreeView.Leave += new System.EventHandler(this.toolboxTreeView_Leave); this.toolboxTreeView.MouseUp += new System.Windows.Forms.MouseEventHandler(this.toolboxTreeView_MouseUp); // // toolboxDescriptionPanel @@ -1068,9 +1075,9 @@ private void InitializeComponent() this.propertyGrid.Name = "propertyGrid"; this.propertyGrid.Size = new System.Drawing.Size(193, 245); this.propertyGrid.TabIndex = 0; + this.propertyGrid.Refreshed += new System.EventHandler(this.propertyGrid_Refreshed); this.propertyGrid.DragDrop += new System.Windows.Forms.DragEventHandler(this.propertyGrid_DragDrop); this.propertyGrid.DragEnter += new System.Windows.Forms.DragEventHandler(this.propertyGrid_DragEnter); - this.propertyGrid.Refreshed += new System.EventHandler(this.propertyGrid_Refreshed); this.propertyGrid.Validated += new System.EventHandler(this.propertyGrid_Validated); // // propertyGridContextMenuStrip @@ -1108,7 +1115,7 @@ private void InitializeComponent() // // panelSplitContainer.Panel1 // - this.panelSplitContainer.Panel1.Controls.Add(this.toolboxLayoutPanel); + this.panelSplitContainer.Panel1.Controls.Add(this.explorerSplitContainer); // // panelSplitContainer.Panel2 // @@ -1131,7 +1138,7 @@ private void InitializeComponent() this.toolboxLayoutPanel.RowCount = 2; this.toolboxLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 23F)); this.toolboxLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.toolboxLayoutPanel.Size = new System.Drawing.Size(200, 340); + this.toolboxLayoutPanel.Size = new System.Drawing.Size(200, 170); this.toolboxLayoutPanel.TabIndex = 1; // // toolboxLabel @@ -1207,7 +1214,7 @@ private void InitializeComponent() this.findPreviousToolStripMenuItem, this.goToDefinitionToolStripMenuItem}); this.toolboxContextMenuStrip.Name = "toolboxContextMenuStrip"; - this.toolboxContextMenuStrip.Size = new System.Drawing.Size(207, 290); + this.toolboxContextMenuStrip.Size = new System.Drawing.Size(207, 268); // // toolboxDocsToolStripMenuItem // @@ -1327,6 +1334,63 @@ private void InitializeComponent() "*.tiff)|*.tif;*.tiff|PNG (*.png)|*.png|SVG (*.svg)|*.svg"; this.exportImageDialog.FilterIndex = 6; // + // explorerSplitContainer + // + this.explorerSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.explorerSplitContainer.Location = new System.Drawing.Point(0, 0); + this.explorerSplitContainer.Name = "explorerSplitContainer"; + this.explorerSplitContainer.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // explorerSplitContainer.Panel1 + // + this.explorerSplitContainer.Panel1.Controls.Add(this.toolboxLayoutPanel); + // + // explorerSplitContainer.Panel2 + // + this.explorerSplitContainer.Panel2.Controls.Add(this.explorerLayoutPanel); + this.explorerSplitContainer.Size = new System.Drawing.Size(200, 340); + this.explorerSplitContainer.SplitterDistance = 170; + this.explorerSplitContainer.TabIndex = 2; + // + // explorerLayoutPanel + // + this.explorerLayoutPanel.ColumnCount = 1; + this.explorerLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.explorerLayoutPanel.Controls.Add(this.explorerTreeView, 0, 1); + this.explorerLayoutPanel.Controls.Add(this.explorerLabel, 0, 0); + this.explorerLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.explorerLayoutPanel.Location = new System.Drawing.Point(0, 0); + this.explorerLayoutPanel.Name = "explorerLayoutPanel"; + this.explorerLayoutPanel.Padding = new System.Windows.Forms.Padding(0, 6, 0, 0); + this.explorerLayoutPanel.RowCount = 2; + this.explorerLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 23F)); + this.explorerLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.explorerLayoutPanel.Size = new System.Drawing.Size(200, 166); + this.explorerLayoutPanel.TabIndex = 2; + // + // explorerLabel + // + this.explorerLabel.AutoSize = true; + this.explorerLabel.BackColor = System.Drawing.SystemColors.ScrollBar; + this.explorerLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.explorerLabel.Location = new System.Drawing.Point(3, 6); + this.explorerLabel.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); + this.explorerLabel.Name = "explorerLabel"; + this.explorerLabel.Size = new System.Drawing.Size(197, 23); + this.explorerLabel.TabIndex = 2; + this.explorerLabel.Text = "Explorer"; + this.explorerLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // explorerTreeView + // + this.explorerTreeView.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.explorerTreeView.Dock = System.Windows.Forms.DockStyle.Fill; + this.explorerTreeView.Location = new System.Drawing.Point(0, 29); + this.explorerTreeView.Margin = new System.Windows.Forms.Padding(3, 0, 0, 3); + this.explorerTreeView.Name = "explorerTreeView"; + this.explorerTreeView.Size = new System.Drawing.Size(200, 137); + this.explorerTreeView.TabIndex = 3; + // // EditorForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1378,6 +1442,12 @@ private void InitializeComponent() this.propertiesLayoutPanel.PerformLayout(); this.toolboxContextMenuStrip.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.workflowFileWatcher)).EndInit(); + this.explorerSplitContainer.Panel1.ResumeLayout(false); + this.explorerSplitContainer.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.explorerSplitContainer)).EndInit(); + this.explorerSplitContainer.ResumeLayout(false); + this.explorerLayoutPanel.ResumeLayout(false); + this.explorerLayoutPanel.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); @@ -1500,6 +1570,10 @@ private void InitializeComponent() private System.Windows.Forms.ContextMenuStrip statusContextMenuStrip; private System.Windows.Forms.ToolStripMenuItem statusCopyToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem toolboxDocsToolStripMenuItem; + private System.Windows.Forms.SplitContainer explorerSplitContainer; + private Bonsai.Editor.TableLayoutPanel explorerLayoutPanel; + private Bonsai.Editor.Label explorerLabel; + private Bonsai.Editor.ExplorerTreeView explorerTreeView; } } diff --git a/Bonsai.Editor/EditorForm.cs b/Bonsai.Editor/EditorForm.cs index 5e6071f87..f62d7862f 100644 --- a/Bonsai.Editor/EditorForm.cs +++ b/Bonsai.Editor/EditorForm.cs @@ -43,6 +43,7 @@ public partial class EditorForm : Form static readonly char[] ToolboxArgumentSeparator = new[] { ' ' }; static readonly object ExtensionsDirectoryChanged = new object(); static readonly object WorkflowValidating = new object(); + static readonly object WorkflowValidated = new object(); int version; int saveVersion; @@ -69,6 +70,7 @@ public partial class EditorForm : Form readonly BehaviorSubject updatesAvailable; readonly FormScheduler formScheduler; readonly TypeVisualizerMap typeVisualizers; + readonly VisualizerLayoutMap visualizerSettings; readonly List workflowElements; readonly List workflowExtensions; readonly WorkflowRuntimeExceptionCache exceptionCache; @@ -76,6 +78,7 @@ public partial class EditorForm : Form AttributeCollection browsableAttributes; DirectoryInfo extensionsPath; WorkflowBuilder workflowBuilder; + VisualizerDialogMap visualizerDialogs; WorkflowException workflowError; IDisposable running; bool debugging; @@ -154,6 +157,7 @@ public EditorForm( regularFont = new Font(toolboxDescriptionTextBox.Font, FontStyle.Regular); selectionFont = new Font(toolboxDescriptionTextBox.Font, FontStyle.Bold); typeVisualizers = new TypeVisualizerMap(); + visualizerSettings = new VisualizerLayoutMap(typeVisualizers); workflowElements = new List(); workflowExtensions = new List(); exceptionCache = new WorkflowRuntimeExceptionCache(); @@ -168,7 +172,7 @@ public EditorForm( definitionsPath = Path.Combine(Path.GetTempPath(), DefinitionsDirectory + "." + GuidHelper.GetProcessGuid().ToString()); editorControl = new WorkflowEditorControl(editorSite); editorControl.Enter += new EventHandler(editorControl_Enter); - editorControl.Workflow = workflowBuilder.Workflow; + editorControl.WorkflowPath = null; editorControl.Dock = DockStyle.Fill; workflowSplitContainer.Panel1.Controls.Add(editorControl); propertyGrid.BrowsableAttributes = browsableAttributes = DesignTimeAttributes; @@ -187,6 +191,14 @@ public EditorForm( editorControl.Leave += delegate { menuStrip.Enabled = true; }; } components.Add(editorControl); + + explorerTreeView.NodeMouseDoubleClick += ExplorerTreeView_NodeMouseDoubleClick; + } + + private void ExplorerTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e) + { + var workflowPath = (WorkflowEditorPath)e.Node?.Tag; + editorControl.WorkflowGraphView.WorkflowPath = workflowPath; } #region Loading @@ -294,6 +306,7 @@ protected override void OnLoad(EventArgs e) handler => FormClosed -= handler); InitializeSubjectSources().TakeUntil(formClosed).Subscribe(); InitializeWorkflowFileWatcher().TakeUntil(formClosed).Subscribe(); + InitializeWorkflowExplorerWatcher().TakeUntil(formClosed).Subscribe(); updatesAvailable.TakeUntil(formClosed).ObserveOn(formScheduler).Subscribe(HandleUpdatesAvailable); var currentDirectory = Path.GetFullPath(Environment.CurrentDirectory).TrimEnd('\\'); @@ -322,7 +335,10 @@ protected override void OnLoad(EventArgs e) InitializeEditorToolboxTypes(); var shutdown = ShutdownSequence(); - var initialization = InitializeToolbox().Merge(InitializeTypeVisualizers()).TakeLast(1).Finally(shutdown.Dispose).ObserveOn(Scheduler.Default); + var initialization = InitializeToolbox().Merge(InitializeTypeVisualizers()) + .TakeLast(1) + .Finally(shutdown.Dispose) + .ObserveOn(Scheduler.Default); if (validFileName && OpenWorkflow(initialFileName, false)) { foreach (var assignment in propertyAssignments) @@ -436,7 +452,7 @@ IEnumerable EditorToolboxTypes() IObservable InitializeSubjectSources() { - var selectionChanged = Observable.FromEventPattern( + var selectedViewChanged = Observable.FromEventPattern( handler => selectionModel.SelectionChanged += handler, handler => selectionModel.SelectionChanged -= handler) .Select(evt => selectionModel.SelectedView) @@ -446,7 +462,7 @@ IObservable InitializeSubjectSources() handler => Events.RemoveHandler(WorkflowValidating, handler)) .Select(evt => selectionModel.SelectedView); return Observable - .Merge(selectionChanged, workflowValidating) + .Merge(selectedViewChanged, workflowValidating) .Do(view => { toolboxTreeView.BeginUpdate(); @@ -477,6 +493,34 @@ IObservable InitializeSubjectSources() .Select(xs => Unit.Default); } + IObservable InitializeWorkflowExplorerWatcher() + { + var selectedViewChanged = Observable.FromEventPattern( + handler => selectionModel.SelectionChanged += handler, + handler => selectionModel.SelectionChanged -= handler) + .Select(evt => selectionModel.SelectedView.WorkflowPath) + .DistinctUntilChanged() + .Do(view => explorerTreeView.SelectNode(editorControl.WorkflowGraphView.WorkflowPath)) + .IgnoreElements() + .Select(xs => Unit.Default); + + var workflowValidated = Observable.FromEventPattern( + handler => Events.AddHandler(WorkflowValidated, handler), + handler => Events.RemoveHandler(WorkflowValidated, handler)) + .Select(evt => selectionModel.SelectedView); + return Observable.Merge(selectedViewChanged, workflowValidated.Do(view => + { + if (workflowBuilder.Workflow == null) + return; + + explorerTreeView.UpdateWorkflow( + GetProjectDisplayName(), + workflowBuilder); + }) + .IgnoreElements() + .Select(xs => Unit.Default)); + } + IObservable InitializeWorkflowFileWatcher() { var extensionsDirectoryChanged = Observable.FromEventPattern( @@ -799,8 +843,8 @@ void ClearWorkflow() ClearWorkflowError(); saveWorkflowDialog.FileName = null; workflowBuilder.Workflow.Clear(); - editorControl.VisualizerLayout = null; - editorControl.Workflow = workflowBuilder.Workflow; + editorControl.WorkflowPath = null; + visualizerSettings.Clear(); ResetProjectStatus(); UpdateTitle(); } @@ -831,7 +875,7 @@ bool OpenWorkflow(string fileName, bool setWorkingDirectory) UpdateWorkflowDirectory(fileName, setWorkingDirectory); if (EditorResult == EditorResult.ReloadEditor) return false; - editorControl.Workflow = workflowBuilder.Workflow; + editorControl.WorkflowPath = null; if (workflowBuilder.Workflow.Count > 0 && !editorControl.WorkflowGraphView.GraphView.Nodes.Any()) { try { workflowBuilder.Workflow.Build(); } @@ -845,8 +889,7 @@ bool OpenWorkflow(string fileName, bool setWorkingDirectory) } workflowBuilder = PrepareWorkflow(workflowBuilder, workflowVersion, out bool upgraded); - editorControl.VisualizerLayout = null; - editorControl.Workflow = workflowBuilder.Workflow; + editorControl.WorkflowPath = null; editorSite.ValidateWorkflow(); var layoutPath = LayoutHelper.GetLayoutPath(fileName); @@ -854,7 +897,11 @@ bool OpenWorkflow(string fileName, bool setWorkingDirectory) { using (var reader = XmlReader.Create(layoutPath)) { - try { editorControl.VisualizerLayout = (VisualizerLayout)VisualizerLayout.Serializer.Deserialize(reader); } + try + { + var visualizerLayout = (VisualizerLayout)VisualizerLayout.Serializer.Deserialize(reader); + visualizerSettings.SetVisualizerLayout(workflowBuilder, visualizerLayout); + } catch (InvalidOperationException) { } } } @@ -908,11 +955,11 @@ bool SaveWorkflow(string fileName) if (!SaveWorkflowBuilder(fileName, serializerWorkflowBuilder)) return false; saveVersion = version; - editorControl.UpdateVisualizerLayout(); - if (editorControl.VisualizerLayout != null) + var visualizerLayout = visualizerSettings.GetVisualizerLayout(workflowBuilder); + if (visualizerLayout != null) { var layoutPath = LayoutHelper.GetLayoutPath(fileName); - SaveVisualizerLayout(layoutPath, editorControl.VisualizerLayout); + SaveVisualizerLayout(layoutPath, visualizerLayout); } UpdateWorkflowDirectory(fileName); @@ -975,6 +1022,11 @@ void OnWorkflowValidating(EventArgs e) (Events[WorkflowValidating] as EventHandler)?.Invoke(this, e); } + void OnWorkflowValidated(EventArgs e) + { + (Events[WorkflowValidated] as EventHandler)?.Invoke(this, e); + } + void OnExtensionsDirectoryChanged(EventArgs e) { (Events[ExtensionsDirectoryChanged] as EventHandler)?.Invoke(this, e); @@ -1192,7 +1244,11 @@ IDisposable ShutdownSequence() running = null; building = false; - editorControl.UpdateVisualizerLayout(); + if (visualizerDialogs != null) + { + visualizerSettings.Update(visualizerDialogs); + visualizerDialogs = null; + } UpdateTitle(); })); } @@ -1204,10 +1260,11 @@ void StartWorkflow(bool debug) building = true; debugging = debug; ClearWorkflowError(); + visualizerDialogs = visualizerSettings.CreateVisualizerDialogs(workflowBuilder); LayoutHelper.SetWorkflowNotifications(workflowBuilder.Workflow, debug); - if (!debug && editorControl.VisualizerLayout != null) + if (!debug) { - LayoutHelper.SetLayoutNotifications(editorControl.VisualizerLayout); + LayoutHelper.SetLayoutNotifications(workflowBuilder.Workflow, visualizerDialogs); } running = Observable.Using( @@ -1221,6 +1278,7 @@ void StartWorkflow(bool debug) { statusTextLabel.Text = Resources.RunningStatus; statusImageLabel.Image = statusRunningImage; + visualizerDialogs.Show(visualizerSettings, editorSite, this); editorSite.OnWorkflowStarted(EventArgs.Empty); Activate(); })); @@ -1295,7 +1353,10 @@ void ClearWorkflowError() { if (workflowError != null) { - ClearExceptionBuilderNode(editorControl.WorkflowGraphView, workflowError); + statusStrip.ContextMenuStrip = null; + statusTextLabel.Text = Resources.ReadyStatus; + statusImageLabel.Image = Resources.StatusReadyImage; + explorerTreeView.SetNodeStatus(ExplorerNodeStatus.Ready); } exceptionCache.Clear(); @@ -1310,91 +1371,22 @@ void HighlightWorkflowError() } } - void ClearExceptionBuilderNode(WorkflowGraphView workflowView, WorkflowException e) - { - GraphNode graphNode = null; - if (workflowView != null) - { - graphNode = workflowView.FindGraphNode(e.Builder); - if (graphNode != null) - { - workflowView.GraphView.Invalidate(graphNode); - graphNode.Highlight = false; - } - } - - if (e.InnerException is WorkflowException nestedException) - { - WorkflowGraphView nestedEditor = null; - if (workflowView != null) - { - var editorLauncher = workflowView.GetWorkflowEditorLauncher(graphNode); - nestedEditor = editorLauncher != null && editorLauncher.Visible ? editorLauncher.WorkflowGraphView : null; - } - - ClearExceptionBuilderNode(nestedEditor, nestedException); - } - else - { - statusStrip.ContextMenuStrip = null; - statusTextLabel.Text = Resources.ReadyStatus; - statusImageLabel.Image = Resources.StatusReadyImage; - } - } - void HighlightExceptionBuilderNode(WorkflowException ex, bool showMessageBox) { - HighlightExceptionBuilderNode(editorControl.WorkflowGraphView, ex, showMessageBox); - } + var workflowPath = WorkflowEditorPath.GetExceptionPath(workflowBuilder, ex); + var pathElements = workflowPath.GetPathElements(); + var selectedView = selectionModel.SelectedView; + selectedView.HighlightGraphNode(workflowPath, showMessageBox); - void HighlightExceptionBuilderNode(WorkflowGraphView workflowView, WorkflowException ex, bool showMessageBox) - { - GraphNode graphNode = null; - if (workflowView != null) + var buildException = ex is WorkflowBuildException; + var errorCaption = buildException ? Resources.BuildError_Caption : Resources.RuntimeError_Caption; + statusTextLabel.Text = ex.Message; + statusStrip.ContextMenuStrip = statusContextMenuStrip; + statusImageLabel.Image = buildException ? Resources.StatusBlockedImage : Resources.StatusCriticalImage; + explorerTreeView.SetNodeStatus(pathElements, ExplorerNodeStatus.Blocked); + if (showMessageBox) { - graphNode = workflowView.FindGraphNode(ex.Builder); - if (graphNode == null) - { - throw new InvalidOperationException(Resources.ExceptionNodeNotFound_Error); - } - - workflowView.GraphView.Invalidate(graphNode); - if (showMessageBox) workflowView.GraphView.SelectedNode = graphNode; - graphNode.Highlight = true; - } - - var nestedException = ex.InnerException as WorkflowException; - if (nestedException != null) - { - WorkflowGraphView nestedEditor = null; - if (workflowView != null) - { - var editorLauncher = workflowView.GetWorkflowEditorLauncher(graphNode); - if (editorLauncher != null) - { - if (building && editorLauncher.Visible) workflowView.LaunchWorkflowView(graphNode); - nestedEditor = editorLauncher.WorkflowGraphView; - } - } - - HighlightExceptionBuilderNode(nestedEditor, nestedException, showMessageBox); - } - else - { - if (workflowView != null) - { - workflowView.GraphView.Select(); - } - - var buildException = ex is WorkflowBuildException; - var errorCaption = buildException ? Resources.BuildError_Caption : Resources.RuntimeError_Caption; - statusTextLabel.Text = ex.Message; - statusStrip.ContextMenuStrip = statusContextMenuStrip; - statusImageLabel.Image = buildException ? Resources.StatusBlockedImage : Resources.StatusCriticalImage; - if (showMessageBox) - { - editorSite.ShowError(ex.Message, errorCaption); - } + editorSite.ShowError(ex.Message, errorCaption); } } @@ -1429,33 +1421,21 @@ void HandleWorkflowCompleted() else clearErrors(); } - void HighlightExpression(WorkflowGraphView workflowView, ExpressionScope scope) + void SelectBuilderNode(ExpressionBuilder builder) { - if (workflowView == null) + var builderPath = WorkflowEditorPath.GetBuilderPath(workflowBuilder, builder); + if (builderPath != null) { - throw new ArgumentNullException(nameof(workflowView)); - } + var selectedView = selectionModel.SelectedView; + selectedView.WorkflowPath = builderPath.Parent; - var graphNode = workflowView.FindGraphNode(scope.Value); - if (graphNode != null) - { - workflowView.GraphView.SelectedNode = graphNode; - var innerScope = scope.InnerScope; - if (innerScope != null) - { - workflowView.LaunchWorkflowView(graphNode); - var editorLauncher = workflowView.GetWorkflowEditorLauncher(graphNode); - if (editorLauncher != null) - { - HighlightExpression(editorLauncher.WorkflowGraphView, innerScope); - } - } - else + var graphNode = selectedView.FindGraphNode(builderPath.Resolve(workflowBuilder)); + if (graphNode == null) { - var ownerForm = workflowView.EditorControl.ParentForm; - if (ownerForm != null) ownerForm.Activate(); - workflowView.SelectGraphNode(graphNode); + throw new InvalidOperationException(Resources.ExceptionNodeNotFound_Error); } + + selectedView.SelectGraphNode(graphNode); } } @@ -1552,13 +1532,13 @@ selectedNode.Tag is not null && void editorControl_Enter(object sender, EventArgs e) { var selectedView = selectionModel.SelectedView; - if (selectedView != null && selectedView.Launcher != null) + if (selectedView != null) { var container = selectedView.EditorControl; if (container != null && container != editorControl && hotKeys.TabState) { container.ParentForm.Activate(); - var forward = Form.ModifierKeys.HasFlag(Keys.Shift); + var forward = ModifierKeys.HasFlag(Keys.Shift); container.SelectNextControl(container.ActiveControl, forward, true, true, false); } } @@ -1618,18 +1598,16 @@ private void UpdatePropertyGrid() if (!hasSelectedObjects && selectedView != null) { // Select externalized properties - var launcher = selectedView.Launcher; - if (launcher != null) + if (selectedView.WorkflowPath != null) { - displayName = ElementHelper.GetElementName(launcher.Builder); - description = ElementHelper.GetElementDescription(launcher.Builder); + var builder = ExpressionBuilder.Unwrap(selectedView.WorkflowPath.Resolve(workflowBuilder)); + displayName = ElementHelper.GetElementName(builder); + description = ElementHelper.GetElementDescription(builder); } else { description = workflowBuilder.Description ?? Resources.WorkflowPropertiesDescription; - displayName = !string.IsNullOrEmpty(saveWorkflowDialog.FileName) - ? Path.GetFileNameWithoutExtension(saveWorkflowDialog.FileName) - : editorControl.ActiveTab.TabPage.Text; + displayName = GetProjectDisplayName(); } propertyGrid.SelectedObject = selectedView.Workflow; @@ -1878,8 +1856,7 @@ void FindNextMatch(Func predicate, ExpressionBuilder cu var match = workflowBuilder.Find(predicate, current, findPrevious); if (match != null) { - var scope = workflowBuilder.GetExpressionScope(match); - HighlightExpression(editorControl.WorkflowGraphView, scope); + SelectBuilderNode(match); } } @@ -1925,8 +1902,7 @@ private void toolboxTreeView_KeyDown(object sender, KeyEventArgs e) } else { - var scope = workflowBuilder.GetExpressionScope(definition.Subject); - HighlightExpression(editorControl.WorkflowGraphView, scope); + SelectBuilderNode(definition.Subject); } } } @@ -2504,6 +2480,16 @@ public object GetService(Type serviceType) return siteForm.typeVisualizers; } + if (serviceType == typeof(VisualizerLayoutMap)) + { + return siteForm.visualizerSettings; + } + + if (serviceType == typeof(VisualizerDialogMap)) + { + return siteForm.visualizerDialogs; + } + if (serviceType == typeof(ThemeRenderer)) { return siteForm.themeRenderer; @@ -2516,11 +2502,10 @@ public object GetService(Type serviceType) if (serviceType == typeof(DialogTypeVisualizer)) { - var selectedView = siteForm.selectionModel.SelectedView; var selectedNode = siteForm.selectionModel.SelectedNodes.FirstOrDefault(); - if (selectedNode != null) + if (selectedNode != null && selectedNode.Value is InspectBuilder builder && + siteForm.visualizerDialogs.TryGetValue(builder, out VisualizerDialogLauncher visualizerDialog)) { - var visualizerDialog = selectedView.GetVisualizerDialogLauncher(selectedNode); var visualizer = visualizerDialog.Visualizer; if (visualizer.IsValueCreated) { @@ -2671,6 +2656,11 @@ public void SelectNextControl(bool forward) siteForm.Activate(); } + public void SelectBuilderNode(ExpressionBuilder builder) + { + siteForm.SelectBuilderNode(builder); + } + public bool ValidateWorkflow() { if (siteForm.running == null) @@ -2680,6 +2670,7 @@ public bool ValidateWorkflow() siteForm.OnWorkflowValidating(EventArgs.Empty); siteForm.ClearWorkflowError(); siteForm.workflowBuilder.Workflow.Build(); + siteForm.OnWorkflowValidated(EventArgs.Empty); } catch (WorkflowBuildException ex) { @@ -2745,8 +2736,7 @@ public void ShowDefinition(object component) var definition = siteForm.workflowBuilder.GetSubjectDefinition(model.Workflow, namedElement.Name); if (definition != null) { - var scope = siteForm.workflowBuilder.GetExpressionScope(definition.Subject); - siteForm.HighlightExpression(siteForm.editorControl.WorkflowGraphView, scope); + siteForm.SelectBuilderNode(definition.Subject); return; } } @@ -3023,6 +3013,9 @@ private void InitializeTheme() toolboxTreeView.Renderer = themeRenderer.ToolStripRenderer; toolboxDescriptionTextBox.BackColor = panelColor; toolboxDescriptionTextBox.ForeColor = ForeColor; + explorerTreeView.Renderer = themeRenderer.ToolStripRenderer; + explorerLabel.BackColor = colorTable.SeparatorDark; + explorerLabel.ForeColor = ForeColor; propertiesDescriptionTextBox.BackColor = panelColor; propertiesDescriptionTextBox.ForeColor = ForeColor; menuStrip.ForeColor = SystemColors.ControlText; @@ -3039,6 +3032,7 @@ private void InitializeTheme() } propertiesLayoutPanel.RowStyles[0].Height -= labelOffset; toolboxLayoutPanel.RowStyles[0].Height -= labelOffset; + explorerLayoutPanel.RowStyles[0].Height -= labelOffset; propertyGrid.Refresh(); } diff --git a/Bonsai.Editor/GraphModel/WorkflowBuilderExtensions.cs b/Bonsai.Editor/GraphModel/WorkflowBuilderExtensions.cs index b6b8b8f0a..3b3750a6b 100644 --- a/Bonsai.Editor/GraphModel/WorkflowBuilderExtensions.cs +++ b/Bonsai.Editor/GraphModel/WorkflowBuilderExtensions.cs @@ -91,47 +91,6 @@ public static IEnumerable GetDependentExpressions(this SubjectD } } } - - public static ExpressionScope GetExpressionScope(this WorkflowBuilder source, ExpressionBuilder target) - { - return GetExpressionScope(source.Workflow, target); - } - - static ExpressionScope GetExpressionScope(ExpressionBuilderGraph source, ExpressionBuilder target) - { - foreach (var node in source) - { - var builder = ExpressionBuilder.Unwrap(node.Value); - if (builder == target) - { - return new ExpressionScope(node.Value, innerScope: null); - } - - if (builder is IWorkflowExpressionBuilder workflowBuilder) - { - var innerScope = GetExpressionScope(workflowBuilder.Workflow, target); - if (innerScope != null) - { - return new ExpressionScope(node.Value, innerScope); - } - } - } - - return null; - } - } - - class ExpressionScope - { - public ExpressionScope(ExpressionBuilder value, ExpressionScope innerScope) - { - Value = value; - InnerScope = innerScope; - } - - public ExpressionBuilder Value { get; } - - public ExpressionScope InnerScope { get; } } class SubjectDefinition diff --git a/Bonsai.Editor/GraphModel/WorkflowEditor.cs b/Bonsai.Editor/GraphModel/WorkflowEditor.cs index 8453da7a6..c7fd40b15 100644 --- a/Bonsai.Editor/GraphModel/WorkflowEditor.cs +++ b/Bonsai.Editor/GraphModel/WorkflowEditor.cs @@ -20,10 +20,10 @@ class WorkflowEditor readonly IGraphView graphView; readonly Subject error; readonly Subject updateLayout; - readonly Subject updateParentLayout; readonly Subject invalidateLayout; readonly Subject> updateSelection; readonly Subject closeWorkflowEditor; + WorkflowEditorPath workflowPath; public WorkflowEditor(IServiceProvider provider, IGraphView view) { @@ -32,20 +32,47 @@ public WorkflowEditor(IServiceProvider provider, IGraphView view) commandExecutor = (CommandExecutor)provider.GetService(typeof(CommandExecutor)); error = new Subject(); updateLayout = new Subject(); - updateParentLayout = new Subject(); invalidateLayout = new Subject(); updateSelection = new Subject>(); closeWorkflowEditor = new Subject(); + WorkflowPath = null; } - public ExpressionBuilderGraph Workflow { get; set; } + public ExpressionBuilderGraph Workflow { get; private set; } + + public bool IsReadOnly { get; private set; } + + public WorkflowEditorPath WorkflowPath + { + get { return workflowPath; } + set + { + workflowPath = value; + var workflowBuilder = (WorkflowBuilder)serviceProvider.GetService(typeof(WorkflowBuilder)); + if (workflowPath != null) + { + var builder = ExpressionBuilder.Unwrap(workflowPath.Resolve(workflowBuilder, out bool isReadOnly)); + if (builder is not IWorkflowExpressionBuilder workflowExpressionBuilder) + { + throw new ArgumentException(Resources.InvalidWorkflowPath_Error, nameof(value)); + } + + Workflow = workflowExpressionBuilder.Workflow; + IsReadOnly = isReadOnly; + } + else + { + Workflow = workflowBuilder.Workflow; + IsReadOnly = false; + } + updateLayout.OnNext(false); + } + } public IObservable Error => error; public IObservable UpdateLayout => updateLayout; - public IObservable UpdateParentLayout => updateParentLayout; - public IObservable InvalidateLayout => invalidateLayout; public IObservable> UpdateSelection => updateSelection; @@ -170,8 +197,6 @@ private void AddWorkflowInput(ExpressionBuilderGraph workflow, Node layer).FirstOrDefault(n => n.Value == value); } + + public void NavigateTo(WorkflowEditorPath path) + { + if (path == workflowPath) + return; + + var previousPath = workflowPath; + var selectedNodes = graphView.SelectedNodes.ToArray(); + var restoreSelectedNodes = CreateUpdateSelectionDelegate(selectedNodes); + commandExecutor.Execute( + () => WorkflowPath = path, + () => + { + WorkflowPath = previousPath; + restoreSelectedNodes(); + }); + } } enum CreateGraphNodeType diff --git a/Bonsai.Editor/GraphView/WorkflowEditorControl.cs b/Bonsai.Editor/GraphView/WorkflowEditorControl.cs index 36c5234e8..b3344be6a 100644 --- a/Bonsai.Editor/GraphView/WorkflowEditorControl.cs +++ b/Bonsai.Editor/GraphView/WorkflowEditorControl.cs @@ -3,7 +3,6 @@ using System.Drawing; using System.Windows.Forms; using Bonsai.Expressions; -using Bonsai.Design; using Bonsai.Editor.Themes; using Bonsai.Editor.GraphModel; @@ -18,17 +17,12 @@ partial class WorkflowEditorControl : UserControl Padding? adjustMargin; public WorkflowEditorControl(IServiceProvider provider) - : this(provider, false) - { - } - - public WorkflowEditorControl(IServiceProvider provider, bool readOnly) { InitializeComponent(); serviceProvider = provider ?? throw new ArgumentNullException(nameof(provider)); editorService = (IWorkflowEditorService)provider.GetService(typeof(IWorkflowEditorService)); themeRenderer = (ThemeRenderer)provider.GetService(typeof(ThemeRenderer)); - workflowTab = InitializeTab(workflowTabPage, readOnly, null); + workflowTab = InitializeTab(workflowTabPage); annotationPanel.ThemeRenderer = themeRenderer; annotationPanel.LinkClicked += (sender, e) => { EditorDialog.OpenUrl(e.LinkText); }; annotationPanel.CloseRequested += delegate { CollapseAnnotationPanel(); }; @@ -60,16 +54,15 @@ public int AnnotationPanelSize } } - public VisualizerLayout VisualizerLayout + public WorkflowEditorPath WorkflowPath { - get { return WorkflowGraphView.VisualizerLayout; } - set { WorkflowGraphView.VisualizerLayout = value; } + get { return WorkflowGraphView.WorkflowPath; } + set { WorkflowGraphView.WorkflowPath = value; } } public ExpressionBuilderGraph Workflow { get { return WorkflowGraphView.Workflow; } - set { WorkflowGraphView.Workflow = value; } } public void ExpandAnnotationPanel(ExpressionBuilder builder) @@ -91,11 +84,6 @@ public void CollapseAnnotationPanel() annotationPanel.Tag = null; } - public void UpdateVisualizerLayout() - { - WorkflowGraphView.UpdateVisualizerLayout(); - } - public TabPageController ActiveTab { get; private set; } public int ItemHeight @@ -103,9 +91,9 @@ public int ItemHeight get { return tabControl.DisplayRectangle.Y; } } - TabPageController InitializeTab(TabPage tabPage, bool readOnly, Control container) + TabPageController InitializeTab(TabPage tabPage) { - var workflowGraphView = new WorkflowGraphView(serviceProvider, this, readOnly); + var workflowGraphView = new WorkflowGraphView(serviceProvider, this); workflowGraphView.BackColorChanged += (sender, e) => { tabPage.BackColor = workflowGraphView.BackColor; @@ -118,42 +106,43 @@ TabPageController InitializeTab(TabPage tabPage, bool readOnly, Control containe var tabState = new TabPageController(tabPage, workflowGraphView); tabPage.Tag = tabState; tabPage.SuspendLayout(); - if (container != null) + + var breadcrumbs = new WorkflowPathNavigationControl(serviceProvider); + breadcrumbs.WorkflowPath = null; + breadcrumbs.WorkflowPathMouseClick += (sender, e) => workflowGraphView.WorkflowPath = e.Path; + workflowGraphView.WorkflowPathChanged += (sender, e) => { - container.TextChanged += (sender, e) => tabState.Text = container.Text; - container.Controls.Add(workflowGraphView); - tabPage.Controls.Add(container); - } - else tabPage.Controls.Add(workflowGraphView); + breadcrumbs.WorkflowPath = workflowGraphView.WorkflowPath; + tabState.Text = breadcrumbs.DisplayName; + }; + + var navigationPanel = new TableLayoutPanel(); + navigationPanel.Dock = DockStyle.Fill; + navigationPanel.ColumnCount = 1; + navigationPanel.RowCount = 2; + navigationPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, breadcrumbs.Height)); + navigationPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + navigationPanel.Controls.Add(breadcrumbs); + navigationPanel.Controls.Add(workflowGraphView); + tabPage.Controls.Add(navigationPanel); tabPage.BackColor = workflowGraphView.BackColor; tabPage.ResumeLayout(false); tabPage.PerformLayout(); return tabState; } - public TabPageController CreateTab(IWorkflowExpressionBuilder builder, bool readOnly, Control owner) + public TabPageController CreateTab(WorkflowEditorPath workflowPath) { var tabPage = new TabPage(); tabPage.Padding = workflowTabPage.Padding; tabPage.UseVisualStyleBackColor = workflowTabPage.UseVisualStyleBackColor; - var tabState = InitializeTab(tabPage, readOnly || builder is IncludeWorkflowBuilder, owner); - tabState.Text = ExpressionBuilder.GetElementDisplayName(builder); - tabState.WorkflowGraphView.Workflow = builder.Workflow; - tabState.Builder = builder; + var tabState = InitializeTab(tabPage); + tabState.WorkflowGraphView.WorkflowPath = workflowPath; tabControl.TabPages.Add(tabPage); return tabState; } - public void SelectTab(IWorkflowExpressionBuilder builder) - { - var tabPage = FindTab(builder); - if (tabPage != null) - { - tabControl.SelectTab(tabPage); - } - } - public void SelectTab(WorkflowGraphView workflowGraphView) { var tabPage = (TabPage)workflowGraphView.Tag; @@ -164,23 +153,10 @@ public void SelectTab(WorkflowGraphView workflowGraphView) } } - public void CloseTab(IWorkflowExpressionBuilder builder) - { - var tabPage = FindTab(builder); - if (tabPage != null) - { - var tabState = (TabPageController)tabPage.Tag; - CloseTab(tabState); - } - } - void CloseTab(TabPage tabPage) { var tabState = (TabPageController)tabPage.Tag; - if (tabState.Builder != null) - { - CloseTab(tabState); - } + CloseTab(tabState); } void CloseTab(TabPageController tabState) @@ -202,47 +178,12 @@ void CloseTab(TabPageController tabState) } } - public void RefreshTab(IWorkflowExpressionBuilder builder) - { - var tabPage = FindTab(builder); - if (tabPage != null) - { - var tabState = (TabPageController)tabPage.Tag; - RefreshTab(tabState); - } - } - - void RefreshTab(TabPageController tabState) - { - var builder = tabState.Builder; - var workflowGraphView = tabState.WorkflowGraphView; - if (builder != null && builder.Workflow != workflowGraphView.Workflow) - { - CloseTab(tabState); - } - } - - TabPage FindTab(IWorkflowExpressionBuilder builder) - { - foreach (TabPage tabPage in tabControl.TabPages) - { - var tabState = (TabPageController)tabPage.Tag; - if (tabState.Builder == builder) - { - return tabPage; - } - } - - return null; - } - void ActivateTab(TabPage tabPage) { var tabState = tabPage != null ? (TabPageController)tabPage.Tag : null; if (tabState != null && ActiveTab != tabState) { ActiveTab = tabState; - RefreshTab(ActiveTab); ActiveTab.UpdateSelection(); } } @@ -285,8 +226,6 @@ public TabPageController(TabPage tabPage, WorkflowGraphView graphView) public TabPage TabPage { get; private set; } - public IWorkflowExpressionBuilder Builder { get; set; } - public WorkflowGraphView WorkflowGraphView { get; private set; } public string Text @@ -301,7 +240,8 @@ public string Text void UpdateDisplayText() { - TabPage.Text = displayText + (WorkflowGraphView.ReadOnly ? ReadOnlySuffix : string.Empty) + CloseSuffix; + //TabPage.Text = displayText + (WorkflowGraphView.ReadOnly ? ReadOnlySuffix : string.Empty) + CloseSuffix; + TabPage.Text = displayText + (WorkflowGraphView.IsReadOnly ? ReadOnlySuffix : string.Empty); } public void UpdateSelection() @@ -361,7 +301,7 @@ void tabControl_MouseUp(object sender, MouseEventArgs e) var tabState = (TabPageController)selectedTab.Tag; var tabRect = tabControl.GetTabRect(tabControl.SelectedIndex); - if (tabState.Builder != null && tabRect.Contains(e.Location)) + if (selectedTab != workflowTabPage && tabRect.Contains(e.Location)) { using (var graphics = selectedTab.CreateGraphics()) { diff --git a/Bonsai.Editor/GraphView/WorkflowGraphView.cs b/Bonsai.Editor/GraphView/WorkflowGraphView.cs index 539aacea4..fb59d0468 100644 --- a/Bonsai.Editor/GraphView/WorkflowGraphView.cs +++ b/Bonsai.Editor/GraphView/WorkflowGraphView.cs @@ -21,10 +21,10 @@ namespace Bonsai.Editor.GraphView { partial class WorkflowGraphView : UserControl { - static readonly Action EmptyAction = () => { }; static readonly Cursor InvalidSelectionCursor = Cursors.No; static readonly Cursor MoveSelectionCursor = Cursors.SizeAll; static readonly Cursor AlternateSelectionCursor = Cursors.UpArrow; + static readonly object WorkflowPathChangedEvent = new(); const int RightMouseButton = 0x2; const int ShiftModifier = 0x4; @@ -36,7 +36,6 @@ partial class WorkflowGraphView : UserControl public const string BonsaiExtension = ".bonsai"; int dragKeyState; - bool editorLaunching; bool isContextMenuSource; GraphNode dragHighlight; IEnumerable dragSelection; @@ -45,31 +44,17 @@ partial class WorkflowGraphView : UserControl readonly IWorkflowEditorState editorState; readonly IWorkflowEditorService editorService; readonly TypeVisualizerMap typeVisualizerMap; - readonly Dictionary workflowEditorMapping; + readonly VisualizerLayoutMap visualizerSettings; readonly IServiceProvider serviceProvider; readonly IUIService uiService; readonly ThemeRenderer themeRenderer; readonly IDefinitionProvider definitionProvider; - Dictionary visualizerMapping; - VisualizerLayout visualizerLayout; - ExpressionBuilderGraph workflow; - public WorkflowGraphView(IServiceProvider provider, WorkflowEditorControl owner, bool readOnly) + public WorkflowGraphView(IServiceProvider provider, WorkflowEditorControl owner) { - if (provider == null) - { - throw new ArgumentNullException(nameof(provider)); - } - - if (owner == null) - { - throw new ArgumentNullException(nameof(owner)); - } - + EditorControl = owner ?? throw new ArgumentNullException(nameof(owner)); + serviceProvider = provider ?? throw new ArgumentNullException(nameof(provider)); InitializeComponent(); - EditorControl = owner; - serviceProvider = provider; - ReadOnly = readOnly; Editor = new WorkflowEditor(provider, graphView); uiService = (IUIService)provider.GetService(typeof(IUIService)); themeRenderer = (ThemeRenderer)provider.GetService(typeof(ThemeRenderer)); @@ -79,11 +64,10 @@ public WorkflowGraphView(IServiceProvider provider, WorkflowEditorControl owner, selectionModel = (WorkflowSelectionModel)provider.GetService(typeof(WorkflowSelectionModel)); editorService = (IWorkflowEditorService)provider.GetService(typeof(IWorkflowEditorService)); typeVisualizerMap = (TypeVisualizerMap)provider.GetService(typeof(TypeVisualizerMap)); + visualizerSettings = (VisualizerLayoutMap)provider.GetService(typeof(VisualizerLayoutMap)); editorState = (IWorkflowEditorState)provider.GetService(typeof(IWorkflowEditorState)); - workflowEditorMapping = new Dictionary(); graphView.HandleDestroyed += graphView_HandleDestroyed; - editorState.WorkflowStarted += editorService_WorkflowStarted; themeRenderer.ThemeChanged += themeRenderer_ThemeChanged; InitializeTheme(); InitializeViewBindings(); @@ -91,15 +75,16 @@ public WorkflowGraphView(IServiceProvider provider, WorkflowEditorControl owner, internal WorkflowEditor Editor { get; } - internal WorkflowEditorLauncher Launcher { get; set; } - internal WorkflowEditorControl EditorControl { get; } - internal bool ReadOnly { get; } + internal bool IsReadOnly + { + get { return Editor.IsReadOnly; } + } internal bool CanEdit { - get { return !ReadOnly && !editorState.WorkflowRunning; } + get { return !Editor.IsReadOnly && !editorState.WorkflowRunning; } } public GraphViewControl GraphView @@ -107,138 +92,46 @@ public GraphViewControl GraphView get { return graphView; } } - public VisualizerLayout VisualizerLayout + public WorkflowEditorPath WorkflowPath { - get { return visualizerLayout; } - set { SetVisualizerLayout(value); } - } - - public ExpressionBuilderGraph Workflow - { - get { return workflow; } + get { return Editor.WorkflowPath; } set { - ClearEditorMapping(); - workflow = value; - Editor.Workflow = value; - UpdateEditorWorkflow(); + Editor.WorkflowPath = value; + UpdateSelection(forceUpdate: true); + OnWorkflowPathChanged(EventArgs.Empty); } } - public static ElementCategory GetToolboxElementCategory(TreeNode typeNode) + public event EventHandler WorkflowPathChanged { - var elementCategories = (ElementCategory[])typeNode.Tag; - for (int i = 0; i < elementCategories.Length; i++) - { - if (elementCategories[i] == ElementCategory.Nested) continue; - return elementCategories[i]; - } - - return ElementCategory.Combinator; + add { Events.AddHandler(WorkflowPathChangedEvent, value); } + remove { Events.RemoveHandler(WorkflowPathChangedEvent, value); } } - private Func CreateWindowOwnerSelectorDelegate() - { - return Launcher != null ? (Func)(() => Launcher.Owner) : () => graphView; - } - - private Action CreateUpdateEditorMappingDelegate(Action> action) + public ExpressionBuilderGraph Workflow { - return Launcher != null - ? (Action)(() => action(Launcher.WorkflowGraphView.workflowEditorMapping)) - : () => action(workflowEditorMapping); - } - - #region Model - - private void HideWorkflowEditorLauncher(WorkflowEditorLauncher editorLauncher) - { - var visible = editorLauncher.Visible; - var serviceProvider = this.serviceProvider; - var windowSelector = CreateWindowOwnerSelectorDelegate(); - var activeTabClosing = editorLauncher.Container != null && - editorLauncher.Container.ActiveTab != null && - editorLauncher.Container.ActiveTab.WorkflowGraphView == editorLauncher.WorkflowGraphView; - commandExecutor.Execute( - editorLauncher.Hide, - () => - { - if (visible && editorLauncher.Builder.Workflow != null) - { - editorLauncher.Show(windowSelector(), serviceProvider); - if (editorLauncher.Container != null && activeTabClosing) - { - editorLauncher.Container.SelectTab(editorLauncher.WorkflowGraphView); - } - } - }); + get { return Editor.Workflow; } } - private void UpdateEditorWorkflow() + private void OnWorkflowPathChanged(EventArgs e) { - UpdateGraphLayout(validateWorkflow: false); - if (editorState.WorkflowRunning) - { - InitializeVisualizerMapping(); - } + (Events[WorkflowPathChangedEvent] as EventHandler)?.Invoke(this, e); } - internal void HideEditorMapping() + public static ElementCategory GetToolboxElementCategory(TreeNode typeNode) { - foreach (var mapping in workflowEditorMapping) + var elementCategories = (ElementCategory[])typeNode.Tag; + for (int i = 0; i < elementCategories.Length; i++) { - mapping.Value.Hide(); + if (elementCategories[i] == ElementCategory.Nested) continue; + return elementCategories[i]; } - } - private void ClearEditorMapping() - { - HideEditorMapping(); - workflowEditorMapping.Clear(); - } - - private void InitializeVisualizerMapping() - { - if (workflow == null) return; - visualizerMapping = LayoutHelper.CreateVisualizerMapping( - workflow, - visualizerLayout, - typeVisualizerMap, - serviceProvider, - graphView, - this); - } - - private void CloseWorkflowEditorLauncher(IWorkflowExpressionBuilder workflowExpressionBuilder) - { - CloseWorkflowEditorLauncher(workflowExpressionBuilder, true); + return ElementCategory.Combinator; } - private void CloseWorkflowEditorLauncher(IWorkflowExpressionBuilder workflowExpressionBuilder, bool removeEditorMapping) - { - if (workflowEditorMapping.TryGetValue(workflowExpressionBuilder, out WorkflowEditorLauncher editorLauncher)) - { - if (editorLauncher.Visible) - { - var workflowGraphView = editorLauncher.WorkflowGraphView; - foreach (var node in workflowGraphView.workflow) - { - var nestedBuilder = ExpressionBuilder.Unwrap(node.Value) as IWorkflowExpressionBuilder; - if (nestedBuilder != null) - { - workflowGraphView.CloseWorkflowEditorLauncher(nestedBuilder, removeEditorMapping); - } - } - } - - HideWorkflowEditorLauncher(editorLauncher); - var removeMapping = removeEditorMapping - ? CreateUpdateEditorMappingDelegate(editorMapping => editorMapping.Remove(workflowExpressionBuilder)) - : EmptyAction; - var addMapping = CreateUpdateEditorMappingDelegate(editorMapping => editorMapping[workflowExpressionBuilder] = editorLauncher); - commandExecutor.Execute(removeMapping, addMapping); - } - } + #region Model private void InsertWorkflow(ExpressionBuilderGraph workflow) { @@ -385,6 +278,32 @@ internal void SelectGraphNode(GraphNode node) UpdateSelection(); } + internal void HighlightGraphNode(WorkflowEditorPath path, bool selectNode) + { + if (selectNode) + WorkflowPath = path?.Parent; + + while (path != null) + { + if (path.Parent == WorkflowPath) + { + var builder = Workflow[path.Index].Value; + var graphNode = FindGraphNode(builder); + if (graphNode == null) + { + throw new InvalidOperationException(Resources.ExceptionNodeNotFound_Error); + } + + GraphView.Invalidate(graphNode); + if (selectNode) GraphView.SelectedNode = graphNode; + graphNode.Highlight = true; + break; + } + + path = path.Parent; + } + } + private bool HasDefaultEditor(ExpressionBuilder builder) { if (builder is IWorkflowExpressionBuilder) return true; @@ -496,10 +415,18 @@ private void LaunchVisualizer(GraphNode node) return; } - var visualizerLauncher = GetVisualizerDialogLauncher(node); - if (visualizerLauncher != null) + var builder = (InspectBuilder)Workflow[node.Index].Value; + var visualizerDialogs = (VisualizerDialogMap)serviceProvider.GetService(typeof(VisualizerDialogMap)); + if (visualizerDialogs != null) { - visualizerLauncher.Show(graphView, serviceProvider); + if (!visualizerDialogs.TryGetValue(builder, out VisualizerDialogLauncher visualizerLauncher)) + { + visualizerSettings.TryGetValue(builder, out VisualizerDialogSettings dialogSettings); + visualizerLauncher = visualizerDialogs.Add(builder, Workflow, dialogSettings); + } + + var ownerWindow = uiService.GetDialogOwnerWindow(); + visualizerLauncher.Show(ownerWindow, serviceProvider); } } @@ -541,85 +468,22 @@ private void LaunchDefinition(GraphNode node) } public void LaunchWorkflowView(GraphNode node) - { - CreateWorkflowView(node, null, Rectangle.Empty, launch: true, activate: true); - } - - private void CreateWorkflowView(GraphNode node, VisualizerLayout editorLayout, Rectangle bounds, bool launch, bool activate) { var builder = WorkflowEditor.GetGraphNodeBuilder(node); var disableBuilder = builder as DisableBuilder; var workflowExpressionBuilder = (disableBuilder != null ? disableBuilder.Builder : builder) as IWorkflowExpressionBuilder; - if (workflowExpressionBuilder == null || editorLaunching) return; - - editorLaunching = true; - var parentLaunching = Launcher != null && Launcher.ParentView.editorLaunching; - var compositeExecutor = new Lazy(() => - { - if (!parentLaunching) commandExecutor.BeginCompositeCommand(); - return commandExecutor; - }, false); - - try - { - if (!workflowEditorMapping.TryGetValue(workflowExpressionBuilder, out WorkflowEditorLauncher editorLauncher)) - { - Func parentSelector; - Func containerSelector; - if (workflowExpressionBuilder is IncludeWorkflowBuilder || - workflowExpressionBuilder is GroupWorkflowBuilder) - { - containerSelector = () => Launcher != null ? Launcher.WorkflowGraphView.EditorControl : EditorControl; - } - else containerSelector = () => null; - parentSelector = () => Launcher != null ? Launcher.WorkflowGraphView : this; - - editorLauncher = new WorkflowEditorLauncher(workflowExpressionBuilder, parentSelector, containerSelector); - editorLauncher.VisualizerLayout = editorLayout; - editorLauncher.Bounds = bounds; - var addEditorMapping = CreateUpdateEditorMappingDelegate(editorMapping => editorMapping.Add(workflowExpressionBuilder, editorLauncher)); - var removeEditorMapping = CreateUpdateEditorMappingDelegate(editorMapping => editorMapping.Remove(workflowExpressionBuilder)); - compositeExecutor.Value.Execute(addEditorMapping, removeEditorMapping); - } - - if (launch && (!editorLauncher.Visible || activate)) - { - var highlight = node.Highlight; - var visible = editorLauncher.Visible; - var editorService = this.editorService; - var serviceProvider = this.serviceProvider; - var windowSelector = CreateWindowOwnerSelectorDelegate(); - Action launchEditor = () => - { - if (editorLauncher.Builder.Workflow != null) - { - editorLauncher.Show(windowSelector(), serviceProvider); - if (editorLauncher.Container != null && !parentLaunching && activate) - { - editorLauncher.Container.SelectTab(editorLauncher.WorkflowGraphView); - } - - if (highlight && !visible) - { - editorService.RefreshEditor(); - } - } - }; - - if (visible) launchEditor(); - else compositeExecutor.Value.Execute(launchEditor, editorLauncher.Hide); - } - } - finally + if (workflowExpressionBuilder != null) { - if (compositeExecutor.IsValueCreated && !parentLaunching) - { - compositeExecutor.Value.EndCompositeCommand(); - } - editorLaunching = false; + var newPath = new WorkflowEditorPath(node.Index, WorkflowPath); + LaunchWorkflowPath(newPath); } } + private void LaunchWorkflowPath(WorkflowEditorPath path) + { + Editor.NavigateTo(path); + } + internal void UpdateSelection() { UpdateSelection(forceUpdate: false); @@ -635,166 +499,6 @@ internal void UpdateSelection(bool forceUpdate) } } - internal void CloseWorkflowView(IWorkflowExpressionBuilder workflowExpressionBuilder) - { - commandExecutor.BeginCompositeCommand(); - CloseWorkflowEditorLauncher(workflowExpressionBuilder, false); - commandExecutor.EndCompositeCommand(); - } - - public VisualizerDialogLauncher GetVisualizerDialogLauncher(GraphNode node) - { - VisualizerDialogLauncher visualizerDialog = null; - if (visualizerMapping != null && node?.Value is InspectBuilder inspectBuilder) - { - visualizerMapping.TryGetValue(inspectBuilder, out visualizerDialog); - } - - return visualizerDialog; - } - - public WorkflowEditorLauncher GetWorkflowEditorLauncher(GraphNode node) - { - var builder = WorkflowEditor.GetGraphNodeBuilder(node); - var disableBuilder = builder as DisableBuilder; - if (disableBuilder != null) builder = disableBuilder.Builder; - - var workflowExpressionBuilder = builder as IWorkflowExpressionBuilder; - if (workflowExpressionBuilder != null) - { - workflowEditorMapping.TryGetValue(workflowExpressionBuilder, out WorkflowEditorLauncher editorLauncher); - return editorLauncher; - } - - return null; - } - - private VisualizerDialogSettings CreateLayoutSettings(ExpressionBuilder builder) - { - VisualizerDialogSettings dialogSettings; - if (ExpressionBuilder.GetWorkflowElement(builder) is IWorkflowExpressionBuilder workflowExpressionBuilder && - workflowEditorMapping.TryGetValue(workflowExpressionBuilder, out WorkflowEditorLauncher editorLauncher)) - { - if (editorLauncher.Visible) editorLauncher.UpdateEditorLayout(); - dialogSettings = new WorkflowEditorSettings - { - EditorVisualizerLayout = editorLauncher.Visible ? editorLauncher.VisualizerLayout : null, - EditorDialogSettings = new VisualizerDialogSettings - { - Visible = editorLauncher.Visible, - Bounds = editorLauncher.Bounds, - Tag = editorLauncher - } - }; - } - else dialogSettings = new VisualizerDialogSettings(); - dialogSettings.Tag = builder; - return dialogSettings; - } - - private void SetVisualizerLayout(VisualizerLayout layout) - { - if (workflow == null) - { - throw new InvalidOperationException(Resources.VisualizerLayoutOnNullWorkflow_Error); - } - - visualizerLayout = layout ?? new VisualizerLayout(); - foreach (var node in workflow) - { - var layoutSettings = visualizerLayout.GetLayoutSettings(node.Value); - if (layoutSettings == null) - { - layoutSettings = CreateLayoutSettings(node.Value); - visualizerLayout.DialogSettings.Add(layoutSettings); - } - else layoutSettings.Tag = node.Value; - - var graphNode = graphView.Nodes.SelectMany(layer => layer).First(n => n.Value == node.Value); - if (layoutSettings is WorkflowEditorSettings workflowEditorSettings && - workflowEditorSettings.EditorDialogSettings.Tag == null) - { - var editorLayout = workflowEditorSettings.EditorVisualizerLayout; - var editorVisible = workflowEditorSettings.EditorDialogSettings.Visible; - var editorBounds = workflowEditorSettings.EditorDialogSettings.Bounds; - CreateWorkflowView(graphNode, - editorLayout, - editorBounds, - launch: editorVisible, - activate: false); - } - } - } - - public void UpdateVisualizerLayout() - { - var updatedLayout = new VisualizerLayout(); - var topologicalOrder = workflow.TopologicalSort(); - foreach (var node in topologicalOrder) - { - var builder = node.Value; - VisualizerDialogSettings dialogSettings; - if (visualizerMapping != null && - visualizerMapping.TryGetValue(builder as InspectBuilder, out VisualizerDialogLauncher visualizerDialog)) - { - var visible = visualizerDialog.Visible; - if (!editorState.WorkflowRunning) - { - visualizerDialog.Hide(); - } - - var visualizer = visualizerDialog.Visualizer; - dialogSettings = CreateLayoutSettings(builder); - dialogSettings.Visible = visible; - dialogSettings.Bounds = visualizerDialog.Bounds; - dialogSettings.WindowState = visualizerDialog.WindowState; - - if (visualizer.IsValueCreated) - { - var visualizerType = visualizer.Value.GetType(); - if (visualizerType.IsPublic) - { - dialogSettings.VisualizerTypeName = visualizerType.FullName; - dialogSettings.VisualizerSettings = LayoutHelper.SerializeVisualizerSettings( - visualizer.Value, - topologicalOrder); - } - } - } - else - { - dialogSettings = visualizerLayout.GetLayoutSettings(builder); - if (dialogSettings == null) dialogSettings = CreateLayoutSettings(builder); - else - { - if (ExpressionBuilder.Unwrap(builder) is IWorkflowExpressionBuilder workflowExpressionBuilder) - { - var updatedEditorSettings = CreateLayoutSettings(builder); - updatedEditorSettings.Bounds = dialogSettings.Bounds; - updatedEditorSettings.Visible = dialogSettings.Visible; - updatedEditorSettings.WindowState = dialogSettings.WindowState; - updatedEditorSettings.VisualizerTypeName = dialogSettings.VisualizerTypeName; - updatedEditorSettings.VisualizerSettings = dialogSettings.VisualizerSettings; - foreach (var mashup in dialogSettings.Mashups) - { - updatedEditorSettings.Mashups.Add(mashup); - } - - dialogSettings = updatedEditorSettings; - } - } - } - - updatedLayout.DialogSettings.Add(dialogSettings); - } - - visualizerLayout = updatedLayout; - if (!editorState.WorkflowRunning) - { - visualizerMapping = null; - } - } - public void RefreshSelection() { foreach (var node in graphView.SelectedNodes) @@ -812,12 +516,6 @@ void RefreshEditorNode(GraphNode node) { LaunchVisualizer(node); } - - var editor = GetWorkflowEditorLauncher(node); - if (editor != null && editor.Visible) - { - editor.UpdateEditorText(); - } } private void UpdateGraphLayout() @@ -827,14 +525,13 @@ private void UpdateGraphLayout() private void UpdateGraphLayout(bool validateWorkflow) { - graphView.Nodes = workflow.ConnectedComponentLayering(); + graphView.Nodes = Workflow.ConnectedComponentLayering(); graphView.Invalidate(); if (validateWorkflow) { editorService.ValidateWorkflow(); } - UpdateVisualizerLayout(); if (validateWorkflow) { EditorControl.SelectTab(this); @@ -848,16 +545,13 @@ private void UpdateGraphLayout(bool validateWorkflow) } } UpdateSelection(); + editorService.RefreshEditor(); } private void InvalidateGraphLayout(bool validateWorkflow) { graphView.Refresh(); - if (Launcher != null) - { - Launcher.ParentView.InvalidateGraphLayout(validateWorkflow); - } - else if (validateWorkflow) + if (validateWorkflow) { editorService.ValidateWorkflow(); } @@ -928,8 +622,8 @@ private void graphView_DragEnter(object sender, DragEventArgs e) else if (e.Data.GetDataPresent(typeof(GraphNode))) { var graphViewNode = (GraphNode)e.Data.GetData(typeof(GraphNode)); - var node = WorkflowEditor.GetGraphNodeTag(workflow, graphViewNode, false); - if (node != null && workflow.Contains(node)) + var node = WorkflowEditor.GetGraphNodeTag(Workflow, graphViewNode, false); + if (node != null && Workflow.Contains(node)) { dragSelection = graphView.SelectedNodes; dragHighlight = graphViewNode; @@ -1125,22 +819,7 @@ private void graphView_KeyDown(object sender, KeyEventArgs e) if (e.KeyCode == Keys.Back && e.Modifiers == Keys.Control) { - if (Launcher != null && Launcher.ParentView != null) - { - var parentView = Launcher.ParentView; - var parentEditor = parentView.EditorControl; - var parentEditorForm = parentEditor.ParentForm; - if (EditorControl.ParentForm != parentEditorForm) - { - parentEditorForm.Activate(); - } - - var parentNode = parentView.Workflow.FirstOrDefault(node => ExpressionBuilder.Unwrap(node.Value) == Launcher.Builder); - if (parentNode != null) - { - parentView.SelectBuilderNode(parentNode.Value); - } - } + LaunchWorkflowPath(WorkflowPath?.Parent); } if (CanEdit) @@ -1266,15 +945,9 @@ private void graphView_NodeMouseLeave(object sender, GraphNodeMouseEventArgs e) private void graphView_HandleDestroyed(object sender, EventArgs e) { - editorState.WorkflowStarted -= editorService_WorkflowStarted; themeRenderer.ThemeChanged -= themeRenderer_ThemeChanged; } - private void editorService_WorkflowStarted(object sender, EventArgs e) - { - InitializeVisualizerMapping(); - } - private void themeRenderer_ThemeChanged(object sender, EventArgs e) { InitializeTheme(); @@ -1288,31 +961,12 @@ private void InitializeTheme() private void InitializeViewBindings() { - Editor.CloseWorkflowEditor.Subscribe(CloseWorkflowEditorLauncher); Editor.Error.Subscribe(ex => uiService.ShowError(ex)); - Editor.UpdateLayout.Subscribe(validateWorkflow => - { - if (Launcher != null) Launcher.WorkflowGraphView.UpdateGraphLayout(); - else UpdateGraphLayout(); - }); - - Editor.UpdateParentLayout.Subscribe(validateWorkflow => - { - if (Launcher != null) - { - Launcher.ParentView.UpdateGraphLayout(validateWorkflow); - } - }); - - Editor.InvalidateLayout.Subscribe(validateWorkflow => - { - if (Launcher != null) Launcher.WorkflowGraphView.InvalidateGraphLayout(validateWorkflow); - else InvalidateGraphLayout(validateWorkflow); - }); - + Editor.UpdateLayout.Subscribe(UpdateGraphLayout); + Editor.InvalidateLayout.Subscribe(InvalidateGraphLayout); Editor.UpdateSelection.Subscribe(selection => { - var activeView = Launcher != null ? Launcher.WorkflowGraphView.GraphView : graphView; + var activeView = graphView; activeView.SelectedNodes = activeView.Nodes.LayeredNodes() .Where(node => { @@ -1589,7 +1243,7 @@ private ToolStripMenuItem CreateSubjectTypeMenuItem( private HashSet FindMappedProperties(GraphNode node) { var mappedProperties = new HashSet(); - foreach (var predecessor in workflow.Predecessors(WorkflowEditor.GetGraphNodeTag(workflow, node))) + foreach (var predecessor in Workflow.Predecessors(WorkflowEditor.GetGraphNodeTag(Workflow, node))) { var builder = ExpressionBuilder.Unwrap(predecessor.Value); if (builder is ExternalizedProperty externalizedProperty) @@ -1637,7 +1291,7 @@ private ToolStripMenuItem CreateExternalizeMenuItem( var menuItem = new ToolStripMenuItem(text, null, delegate { var mapping = new ExternalizedMapping { Name = memberName, DisplayName = externalizedName }; - var mappingNode = (from predecessor in workflow.Predecessors(WorkflowEditor.GetGraphNodeTag(workflow, selectedNode)) + var mappingNode = (from predecessor in Workflow.Predecessors(WorkflowEditor.GetGraphNodeTag(Workflow, selectedNode)) let builder = ExpressionBuilder.Unwrap(predecessor.Value) as ExternalizedMappingBuilder where builder != null && predecessor.Successors.Count == 1 select new { node = FindGraphNode(predecessor.Value), builder }) @@ -1700,7 +1354,7 @@ private ToolStripMenuItem CreatePropertySourceMenuItem( return menuItem; } - private ToolStripMenuItem CreateVisualizerMenuItem(string typeName, VisualizerDialogSettings layoutSettings, GraphNode selectedNode) + private ToolStripMenuItem CreateVisualizerMenuItem(string typeName, GraphNode selectedNode) { ToolStripMenuItem menuItem = null; var emptyVisualizer = string.IsNullOrEmpty(typeName); @@ -1721,37 +1375,37 @@ private ToolStripMenuItem CreateVisualizerMenuItem(string typeName, VisualizerDi } else if (!menuItem.Checked) { - layoutSettings.VisualizerTypeName = typeName; - layoutSettings.VisualizerSettings = null; - layoutSettings.Visible = !emptyVisualizer; - if (!editorState.WorkflowRunning) + var dialogSettings = emptyVisualizer ? default : new VisualizerDialogSettings { - layoutSettings.Size = Size.Empty; - } - else + Tag = inspectBuilder, + VisualizerTypeName = typeName, + Visible = true, + Bounds = Rectangle.Empty + }; + + if (editorState.WorkflowRunning) { - var visualizerLauncher = visualizerMapping[inspectBuilder]; - var visualizerVisible = visualizerLauncher.Visible; - if (visualizerVisible) + var visualizerDialogs = (VisualizerDialogMap)serviceProvider.GetService(typeof(VisualizerDialogMap)); + if (visualizerDialogs.TryGetValue(inspectBuilder, out VisualizerDialogLauncher visualizerDialog)) { - visualizerLauncher.Hide(); + visualizerDialog.Hide(); + visualizerDialogs.Remove(visualizerDialog); } - var visualizerBounds = visualizerLauncher.Bounds; - visualizerLauncher = LayoutHelper.CreateVisualizerLauncher( - inspectBuilder, - visualizerLayout, - typeVisualizerMap, - workflow, - visualizerLauncher.VisualizerFactory.MashupSources, - workflowGraphView: this); - visualizerLauncher.Bounds = new Rectangle(visualizerBounds.Location, Size.Empty); - visualizerMapping[inspectBuilder] = visualizerLauncher; - if (layoutSettings.Visible) + if (!emptyVisualizer) { - visualizerLauncher.Show(graphView, serviceProvider); + var dialogLauncher = visualizerDialogs.Add(inspectBuilder, Workflow, dialogSettings); + var ownerWindow = uiService.GetDialogOwnerWindow(); + visualizerDialog.Show(ownerWindow, serviceProvider); } } + else + { + if (emptyVisualizer) + visualizerSettings.Remove(inspectBuilder); + else + visualizerSettings[inspectBuilder] = dialogSettings; + } } }); return menuItem; @@ -1860,44 +1514,31 @@ private void contextMenuStrip_Opening(object sender, CancelEventArgs e) createPropertySourceToolStripMenuItem.Enabled = createPropertySourceToolStripMenuItem.DropDownItems.Count > 0; } - var layoutSettings = visualizerLayout.GetLayoutSettings(selectedNode.Value); - if (layoutSettings != null) - { - var activeVisualizer = layoutSettings.VisualizerTypeName; - if (workflowElement is VisualizerMappingBuilder mappingBuilder && - mappingBuilder.VisualizerType != null) - { - activeVisualizer = mappingBuilder.VisualizerType.GetType().GetGenericArguments()[0].FullName; - } + var activeVisualizer = visualizerSettings.TryGetValue(inspectBuilder, out var dialogSettings) + ? dialogSettings.VisualizerTypeName + : null; - if (editorState.WorkflowRunning) - { - if (visualizerMapping.TryGetValue(inspectBuilder, out VisualizerDialogLauncher visualizerLauncher)) - { - var visualizer = visualizerLauncher.Visualizer; - if (visualizer.IsValueCreated) - { - activeVisualizer = visualizer.Value.GetType().FullName; - } - } - } + if (workflowElement is VisualizerMappingBuilder mappingBuilder && + mappingBuilder.VisualizerType != null) + { + activeVisualizer = mappingBuilder.VisualizerType.GetType().GetGenericArguments()[0].FullName; + } - var visualizerElement = ExpressionBuilder.GetVisualizerElement(inspectBuilder); - if (visualizerElement.ObservableType != null && - (!editorState.WorkflowRunning || visualizerElement.PublishNotifications)) + var visualizerElement = ExpressionBuilder.GetVisualizerElement(inspectBuilder); + if (visualizerElement.ObservableType != null && + (!editorState.WorkflowRunning || visualizerElement.PublishNotifications)) + { + var visualizerTypes = Enumerable.Repeat(null, 1); + visualizerTypes = visualizerTypes.Concat(typeVisualizerMap.GetTypeVisualizers(visualizerElement)); + visualizerToolStripMenuItem.Enabled = true; + foreach (var type in visualizerTypes) { - var visualizerTypes = Enumerable.Repeat(null, 1); - visualizerTypes = visualizerTypes.Concat(typeVisualizerMap.GetTypeVisualizers(visualizerElement)); - visualizerToolStripMenuItem.Enabled = true; - foreach (var type in visualizerTypes) - { - var typeName = type != null ? type.FullName : string.Empty; - var menuItem = CreateVisualizerMenuItem(typeName, layoutSettings, selectedNode); - visualizerToolStripMenuItem.DropDownItems.Add(menuItem); - menuItem.Checked = type == null - ? string.IsNullOrEmpty(activeVisualizer) - : typeName == activeVisualizer; - } + var typeName = type?.FullName ?? string.Empty; + var menuItem = CreateVisualizerMenuItem(typeName, selectedNode); + visualizerToolStripMenuItem.DropDownItems.Add(menuItem); + menuItem.Checked = type is null + ? activeVisualizer is null + : typeName == activeVisualizer; } } } diff --git a/Bonsai.Editor/IWorkflowEditorService.cs b/Bonsai.Editor/IWorkflowEditorService.cs index e831ae9dd..823f42788 100644 --- a/Bonsai.Editor/IWorkflowEditorService.cs +++ b/Bonsai.Editor/IWorkflowEditorService.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Windows.Forms; +using Bonsai.Expressions; namespace Bonsai.Editor { @@ -24,6 +25,8 @@ interface IWorkflowEditorService void SelectNextControl(bool forward); + void SelectBuilderNode(ExpressionBuilder builder); + bool ValidateWorkflow(); void RefreshEditor(); diff --git a/Bonsai.Editor/Layout/LayoutHelper.cs b/Bonsai.Editor/Layout/LayoutHelper.cs index 3047766f4..491724b4a 100644 --- a/Bonsai.Editor/Layout/LayoutHelper.cs +++ b/Bonsai.Editor/Layout/LayoutHelper.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Windows.Forms; using System.Xml.Linq; using System.Xml.Serialization; @@ -20,40 +19,11 @@ static class LayoutHelper const string MashupSettingsElement = "MashupSettings"; const string MashupSourceElement = "Source"; - public static VisualizerDialogSettings GetLayoutSettings(this VisualizerLayout visualizerLayout, object key) - { - return visualizerLayout?.DialogSettings.FirstOrDefault(xs => xs.Tag == key || xs.Tag == null); - } - public static string GetLayoutPath(string fileName) { return Path.ChangeExtension(fileName, Path.GetExtension(fileName) + LayoutExtension); } - public static void SetLayoutTags(ExpressionBuilderGraph source, VisualizerLayout layout) - { - foreach (var node in source) - { - var builder = node.Value; - var layoutSettings = layout.GetLayoutSettings(builder); - if (layoutSettings == null) - { - layoutSettings = new VisualizerDialogSettings(); - layout.DialogSettings.Add(layoutSettings); - } - layoutSettings.Tag = builder; - - if (layoutSettings is WorkflowEditorSettings editorSettings && - ExpressionBuilder.Unwrap(builder) is IWorkflowExpressionBuilder workflowBuilder && - editorSettings.EditorVisualizerLayout != null && - editorSettings.EditorDialogSettings.Visible && - workflowBuilder.Workflow != null) - { - SetLayoutTags(workflowBuilder.Workflow, editorSettings.EditorVisualizerLayout); - } - } - } - public static void SetWorkflowNotifications(ExpressionBuilderGraph source, bool publishNotifications) { foreach (var builder in from node in source @@ -70,41 +40,15 @@ public static void SetWorkflowNotifications(ExpressionBuilderGraph source, bool } } - public static void SetLayoutNotifications(VisualizerLayout root) + public static void SetLayoutNotifications(ExpressionBuilderGraph source, VisualizerDialogMap lookup) { - foreach (var settings in root.DialogSettings) + foreach (var builder in source.Descendants()) { - SetLayoutNotifications(settings, root, forcePublish: false); - } - } - - static void SetLayoutNotifications(VisualizerDialogSettings settings, VisualizerLayout root, bool forcePublish = false) - { - var inspectBuilder = settings.Tag as InspectBuilder; - while (inspectBuilder != null && !inspectBuilder.PublishNotifications) - { - if (string.IsNullOrEmpty(settings.VisualizerTypeName) && !forcePublish) - { - break; - } - - SetVisualizerNotifications(inspectBuilder); - foreach (var index in settings.Mashups.Concat(settings.VisualizerSettings? - .Descendants(MashupSourceElement) - .Select(m => int.Parse(m.Value)) - .Distinct() ?? Enumerable.Empty())) + var inspectBuilder = (InspectBuilder)builder; + if (lookup.TryGetValue((InspectBuilder)builder, out VisualizerDialogLauncher _)) { - if (index < 0 || index >= root.DialogSettings.Count) continue; - var mashupSource = root.DialogSettings[index]; - SetLayoutNotifications(mashupSource, root, forcePublish: true); + SetVisualizerNotifications(inspectBuilder); } - - inspectBuilder = ExpressionBuilder.GetVisualizerElement(inspectBuilder); - } - - if (settings is WorkflowEditorSettings editorSettings && editorSettings.EditorVisualizerLayout != null) - { - SetLayoutNotifications(editorSettings.EditorVisualizerLayout); } } @@ -117,18 +61,6 @@ static void SetVisualizerNotifications(InspectBuilder inspectBuilder) } } - static IEnumerable GetMashupSources(this VisualizerFactory visualizerFactory) - { - yield return visualizerFactory; - foreach (var source in visualizerFactory.MashupSources) - { - foreach (var nestedSource in source.GetMashupSources()) - { - yield return nestedSource; - } - } - } - internal static Type GetMashupSourceType(Type mashupVisualizerType, Type visualizerType, TypeVisualizerMap typeVisualizerMap) { Type mashupSource = default; @@ -153,11 +85,9 @@ internal static Type GetMashupSourceType(Type mashupVisualizerType, Type visuali public static VisualizerDialogLauncher CreateVisualizerLauncher( InspectBuilder source, - VisualizerLayout visualizerLayout, + VisualizerDialogSettings layoutSettings, TypeVisualizerMap typeVisualizerMap, - ExpressionBuilderGraph workflow, - IReadOnlyList mashupArguments, - Editor.GraphView.WorkflowGraphView workflowGraphView = null) + ExpressionBuilderGraph workflow) { var inspectBuilder = ExpressionBuilder.GetVisualizerElement(source); if (inspectBuilder.ObservableType == null || !inspectBuilder.PublishNotifications || @@ -166,23 +96,23 @@ public static VisualizerDialogLauncher CreateVisualizerLauncher( return null; } - var layoutSettings = visualizerLayout.GetLayoutSettings(source); - var visualizerType = typeVisualizerMap.GetVisualizerType(layoutSettings?.VisualizerTypeName ?? string.Empty) - ?? typeVisualizerMap.GetTypeVisualizers(inspectBuilder).FirstOrDefault(); - if (visualizerType == null) + var visualizerType = typeVisualizerMap.GetVisualizerType(layoutSettings?.VisualizerTypeName ?? string.Empty); + visualizerType ??= typeVisualizerMap.GetTypeVisualizers(inspectBuilder).FirstOrDefault(); + if (visualizerType is null) { return null; } + var mashupArguments = GetMashupArguments(inspectBuilder, typeVisualizerMap); var visualizerFactory = new VisualizerFactory(inspectBuilder, visualizerType, mashupArguments); var visualizer = new Lazy(() => DeserializeVisualizerSettings( visualizerType, layoutSettings, - visualizerLayout, + workflow, visualizerFactory, typeVisualizerMap)); - var launcher = new VisualizerDialogLauncher(visualizer, visualizerFactory, workflow, source, workflowGraphView); + var launcher = new VisualizerDialogLauncher(visualizer, visualizerFactory, workflow, source); launcher.Text = source != null ? ExpressionBuilder.GetElementDisplayName(source) : null; return launcher; } @@ -203,48 +133,6 @@ static IReadOnlyList GetMashupArguments(InspectBuilder builde }).ToList(); } - public static Dictionary CreateVisualizerMapping( - ExpressionBuilderGraph workflow, - VisualizerLayout visualizerLayout, - TypeVisualizerMap typeVisualizerMap, - IServiceProvider provider = null, - IWin32Window owner = null, - Editor.GraphView.WorkflowGraphView graphView = null) - { - if (workflow == null) return null; - var visualizerMapping = (from node in workflow.TopologicalSort() - let source = (InspectBuilder)node.Value - let mashupArguments = GetMashupArguments(source, typeVisualizerMap) - let visualizerLauncher = CreateVisualizerLauncher( - source, - visualizerLayout, - typeVisualizerMap, - workflow, - mashupArguments, - graphView) - where visualizerLauncher != null - select new { source, visualizerLauncher }) - .ToDictionary(mapping => mapping.source, - mapping => mapping.visualizerLauncher); - foreach (var mapping in visualizerMapping) - { - var key = mapping.Key; - var visualizerLauncher = mapping.Value; - var layoutSettings = visualizerLayout.GetLayoutSettings(key); - if (layoutSettings != null) - { - visualizerLauncher.Bounds = layoutSettings.Bounds; - visualizerLauncher.WindowState = layoutSettings.WindowState; - if (layoutSettings.Visible) - { - visualizerLauncher.Show(owner, provider); - } - } - } - - return visualizerMapping; - } - public static XElement SerializeVisualizerSettings( DialogTypeVisualizer visualizer, IEnumerable> topologicalOrder) @@ -311,11 +199,11 @@ static XElement SerializeMashupSource( public static DialogTypeVisualizer DeserializeVisualizerSettings( Type visualizerType, VisualizerDialogSettings layoutSettings, - VisualizerLayout visualizerLayout, + ExpressionBuilderGraph workflow, VisualizerFactory visualizerFactory, TypeVisualizerMap typeVisualizerMap) { - if (layoutSettings?.VisualizerTypeName != visualizerType.FullName) + if (layoutSettings?.VisualizerTypeName != visualizerType?.FullName) { layoutSettings = default; } @@ -338,7 +226,7 @@ public static DialogTypeVisualizer DeserializeVisualizerSettings( layoutSettings.Mashups.Clear(); } - return visualizerFactory.CreateVisualizer(layoutSettings?.VisualizerSettings, visualizerLayout, typeVisualizerMap); + return visualizerFactory.CreateVisualizer(layoutSettings?.VisualizerSettings, workflow, typeVisualizerMap); } static int? GetMashupSourceIndex( @@ -353,7 +241,7 @@ public static DialogTypeVisualizer DeserializeVisualizerSettings( public static DialogTypeVisualizer CreateVisualizer( this VisualizerFactory visualizerFactory, XElement visualizerSettings, - VisualizerLayout visualizerLayout, + ExpressionBuilderGraph workflow, TypeVisualizerMap typeVisualizerMap) { DialogTypeVisualizer visualizer; @@ -386,19 +274,19 @@ public static DialogTypeVisualizer CreateVisualizer( if (mashupSourceElement == null) continue; var mashupSourceIndex = int.Parse(mashupSourceElement.Value); - var mashupSource = (InspectBuilder)visualizerLayout.DialogSettings[mashupSourceIndex]?.Tag; + var mashupSource = (InspectBuilder)workflow[mashupSourceIndex].Value; var mashupVisualizerTypeName = mashup.Element(nameof(VisualizerDialogSettings.VisualizerTypeName))?.Value; var mashupVisualizerType = typeVisualizerMap.GetVisualizerType(mashupVisualizerTypeName); mashupFactory = new VisualizerFactory(mashupSource, mashupVisualizerType); } - CreateMashupVisualizer(mashupVisualizer, visualizerFactory, mashupFactory, visualizerLayout, typeVisualizerMap, mashup); + CreateMashupVisualizer(mashupVisualizer, visualizerFactory, mashupFactory, workflow, typeVisualizerMap, mashup); } for (int i = index; i < visualizerFactory.MashupSources.Count; i++) { var mashupFactory = visualizerFactory.MashupSources[i]; - CreateMashupVisualizer(mashupVisualizer, visualizerFactory, mashupFactory, visualizerLayout, typeVisualizerMap); + CreateMashupVisualizer(mashupVisualizer, visualizerFactory, mashupFactory, workflow, typeVisualizerMap); } } @@ -409,7 +297,7 @@ static void CreateMashupVisualizer( MashupVisualizer mashupVisualizer, VisualizerFactory visualizerFactory, VisualizerFactory mashupFactory, - VisualizerLayout visualizerLayout, + ExpressionBuilderGraph workflow, TypeVisualizerMap typeVisualizerMap, XElement mashup = null) { @@ -434,7 +322,7 @@ static void CreateMashupVisualizer( } } - var nestedVisualizer = mashupFactory.CreateVisualizer(mashupVisualizerSettings, visualizerLayout, typeVisualizerMap); + var nestedVisualizer = mashupFactory.CreateVisualizer(mashupVisualizerSettings, workflow, typeVisualizerMap); mashupVisualizer.MashupSources.Add(mashupFactory.Source, nestedVisualizer); } } diff --git a/Bonsai.Editor/Layout/VisualizerDialogLauncher.cs b/Bonsai.Editor/Layout/VisualizerDialogLauncher.cs index 6cb186b21..32b6c4d14 100644 --- a/Bonsai.Editor/Layout/VisualizerDialogLauncher.cs +++ b/Bonsai.Editor/Layout/VisualizerDialogLauncher.cs @@ -3,39 +3,38 @@ using Bonsai.Expressions; using System.Reactive.Linq; using System.Windows.Forms; -using Bonsai.Editor.GraphView; using Bonsai.Editor.GraphModel; +using System.Linq; +using Bonsai.Editor; namespace Bonsai.Design { class VisualizerDialogLauncher : DialogLauncher, ITypeVisualizerContext { - readonly ExpressionBuilderGraph workflow; - readonly WorkflowGraphView graphView; ServiceContainer visualizerContext; IDisposable visualizerObserver; public VisualizerDialogLauncher( Lazy visualizer, VisualizerFactory visualizerFactory, - ExpressionBuilderGraph visualizerWorkflow, - InspectBuilder workflowSource, - WorkflowGraphView workflowGraphView) + ExpressionBuilderGraph workflow, + InspectBuilder workflowSource) { Visualizer = visualizer ?? throw new ArgumentNullException(nameof(visualizer)); VisualizerFactory = visualizerFactory ?? throw new ArgumentNullException(nameof(visualizerFactory)); Source = workflowSource ?? throw new ArgumentNullException(nameof(workflowSource)); - workflow = visualizerWorkflow; - graphView = workflowGraphView; + Workflow = workflow ?? throw new ArgumentNullException(nameof(workflow)); } public string Text { get; set; } public InspectBuilder Source { get; } + public ExpressionBuilderGraph Workflow { get; } + public Lazy Visualizer { get; } - public VisualizerFactory VisualizerFactory { get; } + private VisualizerFactory VisualizerFactory { get; } static IDisposable SubscribeDialog(IObservable source, TypeVisualizerDialog visualizerDialog) { @@ -55,7 +54,7 @@ protected override void InitializeComponents(TypeVisualizerDialog visualizerDial visualizerContext.AddService(typeof(ITypeVisualizerContext), this); visualizerContext.AddService(typeof(TypeVisualizerDialog), visualizerDialog); visualizerContext.AddService(typeof(IDialogTypeVisualizerService), visualizerDialog); - visualizerContext.AddService(typeof(ExpressionBuilderGraph), workflow); + visualizerContext.AddService(typeof(ExpressionBuilderGraph), Workflow); Visualizer.Value.Load(visualizerContext); var visualizerOutput = Visualizer.Value.Visualize(VisualizerFactory.Source.Output, visualizerContext); @@ -90,10 +89,10 @@ protected override void InitializeComponents(TypeVisualizerDialog visualizerDial void visualizerDialog_KeyDown(object sender, KeyEventArgs e) { - if (graphView != null && e.KeyCode == Keys.Back && e.Control) + if (e.KeyCode == Keys.Back && e.Control) { - graphView.SelectBuilderNode(Source); - graphView.EditorControl.ParentForm.Activate(); + var editorService = (IWorkflowEditorService)visualizerContext.GetService(typeof(IWorkflowEditorService)); + editorService.SelectBuilderNode(Source); } if (e.KeyCode == Keys.Delete && e.Control) @@ -148,36 +147,40 @@ MashupVisualizer GetMashupContainer(int x, int y, bool allowEmpty = true) return visualizer; } - public void CreateMashup(MashupVisualizer dialogMashup, VisualizerDialogLauncher visualizerDialog, TypeVisualizerMap typeVisualizerMap) + public void CreateMashup(MashupVisualizer dialogMashup, InspectBuilder source, Type visualizerType, TypeVisualizerMap typeVisualizerMap) { - if (visualizerDialog != null) + if (visualizerType == null) + throw new ArgumentNullException(nameof(visualizerType)); + + var dialogMashupType = dialogMashup.GetType(); + var mashupSourceType = LayoutHelper.GetMashupSourceType(dialogMashupType, visualizerType, typeVisualizerMap); + if (mashupSourceType != null) { - var dialogMashupType = dialogMashup.GetType(); - var visualizerFactory = visualizerDialog.VisualizerFactory; - var mashupSourceType = LayoutHelper.GetMashupSourceType(dialogMashupType, visualizerFactory.VisualizerType, typeVisualizerMap); - if (mashupSourceType != null) + UnloadMashups(); + if (mashupSourceType == typeof(DialogTypeVisualizer)) { - UnloadMashups(); - if (mashupSourceType == typeof(DialogTypeVisualizer)) - { - mashupSourceType = visualizerFactory.VisualizerType; - } - var visualizerMashup = (DialogTypeVisualizer)Activator.CreateInstance(mashupSourceType); - dialogMashup.MashupSources.Add(visualizerFactory.Source, visualizerMashup); - ReloadMashups(); + mashupSourceType = visualizerType; } + var visualizerMashup = (DialogTypeVisualizer)Activator.CreateInstance(mashupSourceType); + dialogMashup.MashupSources.Add(source, visualizerMashup); + ReloadMashups(); } } void visualizerDialog_DragDrop(object sender, DragEventArgs e) { - if (graphView != null && visualizerContext != null && e.Data.GetDataPresent(typeof(GraphNode))) + if (visualizerContext != null && e.Data.GetDataPresent(typeof(GraphNode))) { var graphNode = (GraphNode)e.Data.GetData(typeof(GraphNode)); + var inspectBuilder = (InspectBuilder)graphNode.Value; var typeVisualizerMap = (TypeVisualizerMap)visualizerContext.GetService(typeof(TypeVisualizerMap)); - var visualizerDialog = graphView.GetVisualizerDialogLauncher(graphNode); - var visualizer = GetMashupContainer(e.X, e.Y); - CreateMashup(visualizer, visualizerDialog, typeVisualizerMap); + var visualizerDialogMap = (VisualizerDialogMap)visualizerContext.GetService(typeof(VisualizerDialogMap)); + var visualizerType = GetVisualizerType(inspectBuilder, visualizerDialogMap, typeVisualizerMap); + if (visualizerType != null) + { + var visualizer = GetMashupContainer(e.X, e.Y); + CreateMashup(visualizer, inspectBuilder, visualizerType, typeVisualizerMap); + } } } @@ -187,17 +190,17 @@ void visualizerDialog_DragOver(object sender, DragEventArgs e) void visualizerDialog_DragEnter(object sender, DragEventArgs e) { - if (graphView != null && visualizerContext != null && e.Data.GetDataPresent(typeof(GraphNode))) + if (visualizerContext != null && e.Data.GetDataPresent(typeof(GraphNode))) { var graphNode = (GraphNode)e.Data.GetData(typeof(GraphNode)); - var visualizerDialog = graphView.GetVisualizerDialogLauncher(graphNode); - if (visualizerDialog != null && visualizerDialog != this) + var typeVisualizerMap = (TypeVisualizerMap)visualizerContext.GetService(typeof(TypeVisualizerMap)); + var visualizerDialogMap = (VisualizerDialogMap)visualizerContext.GetService(typeof(VisualizerDialogMap)); + var visualizerType = GetVisualizerType((InspectBuilder)graphNode.Value, visualizerDialogMap, typeVisualizerMap); + if (visualizerType != null) { var dialogMashupType = VisualizerFactory.VisualizerType; if (dialogMashupType.IsSubclassOf(typeof(MashupVisualizer))) { - var visualizerType = visualizerDialog.VisualizerFactory.VisualizerType; - var typeVisualizerMap = (TypeVisualizerMap)visualizerContext.GetService(typeof(TypeVisualizerMap)); var mashupVisualizerType = LayoutHelper.GetMashupSourceType(dialogMashupType, visualizerType, typeVisualizerMap); if (mashupVisualizerType != null) { @@ -207,5 +210,16 @@ void visualizerDialog_DragEnter(object sender, DragEventArgs e) } } } + + Type GetVisualizerType(InspectBuilder source, VisualizerDialogMap visualizerDialogMap, TypeVisualizerMap typeVisualizerMap) + { + if (visualizerDialogMap.TryGetValue(source, out VisualizerDialogLauncher dialogLauncher)) + { + if (dialogLauncher == this) + return null; + else return dialogLauncher.VisualizerFactory.VisualizerType; + } + else return typeVisualizerMap.GetTypeVisualizers(source).FirstOrDefault(); + } } } diff --git a/Bonsai.Editor/Layout/VisualizerDialogMap.cs b/Bonsai.Editor/Layout/VisualizerDialogMap.cs new file mode 100644 index 000000000..b4aa0c133 --- /dev/null +++ b/Bonsai.Editor/Layout/VisualizerDialogMap.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Windows.Forms; +using Bonsai.Expressions; + +namespace Bonsai.Design +{ + internal class VisualizerDialogMap : IEnumerable + { + readonly TypeVisualizerMap typeVisualizerMap; + readonly Dictionary lookup; + + public VisualizerDialogMap(TypeVisualizerMap typeVisualizers) + { + typeVisualizerMap = typeVisualizers ?? throw new ArgumentNullException(nameof(typeVisualizers)); + lookup = new(); + } + + public VisualizerDialogLauncher this[InspectBuilder key] + { + get => lookup[key]; + } + + public bool TryGetValue(InspectBuilder key, out VisualizerDialogLauncher value) + { + return lookup.TryGetValue(key, out value); + } + + public void Show(VisualizerLayoutMap visualizerSettings, IServiceProvider provider = null, IWin32Window owner = null) + { + foreach (var dialogLauncher in lookup.Values) + { + var dialogSettings = visualizerSettings[dialogLauncher.Source]; + dialogLauncher.Bounds = dialogSettings.Bounds; + dialogLauncher.WindowState = dialogSettings.WindowState; + if (dialogSettings.Visible) + { + dialogLauncher.Show(owner, provider); + } + } + } + + public VisualizerDialogLauncher Add(InspectBuilder source, ExpressionBuilderGraph workflow, VisualizerDialogSettings dialogSettings) + { + var dialogLauncher = LayoutHelper.CreateVisualizerLauncher( + source, + dialogSettings, + typeVisualizerMap, + workflow); + Add(dialogLauncher); + return dialogLauncher; + } + + public void Add(VisualizerDialogLauncher item) + { + lookup.Add(item.Source, item); + } + + public bool Remove(VisualizerDialogLauncher item) + { + return lookup.Remove(item.Source); + } + + public IEnumerator GetEnumerator() + { + return lookup.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Bonsai.Editor/Layout/VisualizerDialogSettings.cs b/Bonsai.Editor/Layout/VisualizerDialogSettings.cs index 9b8f99795..af85354d7 100644 --- a/Bonsai.Editor/Layout/VisualizerDialogSettings.cs +++ b/Bonsai.Editor/Layout/VisualizerDialogSettings.cs @@ -6,9 +6,13 @@ namespace Bonsai.Design { +#pragma warning disable CS0612 // Type or member is obsolete [XmlInclude(typeof(WorkflowEditorSettings))] +#pragma warning restore CS0612 // Type or member is obsolete public class VisualizerDialogSettings { + public int? Index { get; set; } + [XmlIgnore] public object Tag { get; set; } @@ -35,6 +39,8 @@ public Rectangle Bounds public XElement VisualizerSettings { get; set; } + public VisualizerLayout NestedLayout { get; set; } + // [Obsolete] public Collection Mashups { get; } = new Collection(); diff --git a/Bonsai.Editor/Layout/VisualizerLayoutMap.cs b/Bonsai.Editor/Layout/VisualizerLayoutMap.cs new file mode 100644 index 000000000..172fa418c --- /dev/null +++ b/Bonsai.Editor/Layout/VisualizerLayoutMap.cs @@ -0,0 +1,201 @@ +using System.Collections; +using System.Collections.Generic; +using Bonsai.Expressions; + +namespace Bonsai.Design +{ + internal class VisualizerLayoutMap : IEnumerable + { + readonly TypeVisualizerMap typeVisualizerMap; + readonly Dictionary lookup; + + public VisualizerLayoutMap(TypeVisualizerMap typeVisualizers) + { + typeVisualizerMap = typeVisualizers; + lookup = new(); + } + + public VisualizerDialogSettings this[InspectBuilder key] + { + get => lookup[key]; + set => lookup[key] = value; + } + + public bool TryGetValue(InspectBuilder key, out VisualizerDialogSettings value) + { + return lookup.TryGetValue(key, out value); + } + + private void CreateVisualizerDialogs(ExpressionBuilderGraph workflow, VisualizerDialogMap visualizerDialogs) + { + for (int i = 0; i < workflow.Count; i++) + { + var builder = (InspectBuilder)workflow[i].Value; + if (lookup.TryGetValue(builder, out VisualizerDialogSettings dialogSettings)) + { + visualizerDialogs.Add(builder, workflow, dialogSettings); + } + + if (ExpressionBuilder.Unwrap(builder) is IWorkflowExpressionBuilder workflowBuilder) + { + CreateVisualizerDialogs(workflowBuilder.Workflow, visualizerDialogs); + } + } + } + + public VisualizerDialogMap CreateVisualizerDialogs(WorkflowBuilder workflowBuilder) + { + var visualizerDialogs = new VisualizerDialogMap(typeVisualizerMap); + CreateVisualizerDialogs(workflowBuilder.Workflow, visualizerDialogs); + return visualizerDialogs; + } + + public void Update(IEnumerable visualizerDialogs) + { + var unused = new HashSet(lookup.Keys); + foreach (var dialog in visualizerDialogs) + { + unused.Remove(dialog.Source); + if (!lookup.TryGetValue(dialog.Source, out VisualizerDialogSettings dialogSettings)) + { + dialogSettings = new VisualizerDialogSettings(); + dialogSettings.Tag = dialog.Source; + lookup.Add(dialog.Source, dialogSettings); + } + + var visible = dialog.Visible; + dialog.Hide(); + dialogSettings.Visible = visible; + dialogSettings.Bounds = dialog.Bounds; + dialogSettings.WindowState = dialog.WindowState; + + var visualizer = dialog.Visualizer.Value; + var visualizerType = visualizer.GetType(); + if (visualizerType.IsPublic) + { + dialogSettings.VisualizerTypeName = visualizerType.FullName; + dialogSettings.VisualizerSettings = LayoutHelper.SerializeVisualizerSettings( + visualizer, + dialog.Workflow); + } + } + + foreach (var builder in unused) + { + lookup.Remove(builder); + } + } + + public VisualizerLayout GetVisualizerLayout(WorkflowBuilder workflowBuilder) + { + return GetVisualizerLayout(workflowBuilder.Workflow); + } + + private VisualizerLayout GetVisualizerLayout(ExpressionBuilderGraph workflow) + { + var layout = new VisualizerLayout(); + for (int i = 0; i < workflow.Count; i++) + { + var builder = (InspectBuilder)workflow[i].Value; + var layoutSettings = new VisualizerDialogSettings { Index = i }; + + if (lookup.TryGetValue(builder, out VisualizerDialogSettings dialogSettings)) + { + layoutSettings.Visible = dialogSettings.Visible; + layoutSettings.Bounds = dialogSettings.Bounds; + layoutSettings.WindowState = dialogSettings.WindowState; + layoutSettings.VisualizerTypeName = dialogSettings.VisualizerTypeName; + layoutSettings.VisualizerSettings = dialogSettings.VisualizerSettings; + } + + if (ExpressionBuilder.Unwrap(builder) is IWorkflowExpressionBuilder workflowBuilder) + { + layoutSettings.NestedLayout = GetVisualizerLayout(workflowBuilder.Workflow); + } + + if (!layoutSettings.Bounds.IsEmpty || + layoutSettings.VisualizerTypeName != null || + layoutSettings.NestedLayout?.DialogSettings.Count > 0) + { + layout.DialogSettings.Add(layoutSettings); + } + } + + return layout; + } + + public static VisualizerLayoutMap FromVisualizerLayout( + WorkflowBuilder workflowBuilder, + VisualizerLayout layout, + TypeVisualizerMap typeVisualizers) + { + var visualizerSettings = new VisualizerLayoutMap(typeVisualizers); + visualizerSettings.SetVisualizerLayout(workflowBuilder.Workflow, layout); + return visualizerSettings; + } + + public void SetVisualizerLayout(WorkflowBuilder workflowBuilder, VisualizerLayout layout) + { + Clear(); + SetVisualizerLayout(workflowBuilder.Workflow, layout); + } + + private void SetVisualizerLayout(ExpressionBuilderGraph workflow, VisualizerLayout layout) + { + for (int i = 0; i < layout.DialogSettings.Count; i++) + { + var layoutSettings = layout.DialogSettings[i]; + var index = layoutSettings.Index.GetValueOrDefault(i); + if (index < workflow.Count) + { + var builder = (InspectBuilder)workflow[index].Value; + var dialogSettings = new VisualizerDialogSettings(); + dialogSettings.Tag = builder; + dialogSettings.Bounds = layoutSettings.Bounds; + dialogSettings.WindowState = layoutSettings.WindowState; + dialogSettings.Visible = layoutSettings.Visible; + dialogSettings.VisualizerTypeName = layoutSettings.VisualizerTypeName; + dialogSettings.VisualizerSettings = layoutSettings.VisualizerSettings; + Add(dialogSettings); + + if (layoutSettings.NestedLayout != null && + ExpressionBuilder.Unwrap(builder) is IWorkflowExpressionBuilder workflowBuilder) + { + SetVisualizerLayout(workflowBuilder.Workflow, layoutSettings.NestedLayout); + } + } + } + } + + public void Add(VisualizerDialogSettings item) + { + var builder = (InspectBuilder)item.Tag; + lookup.Add(builder, item); + } + + public bool ContainsKey(InspectBuilder builder) + { + return lookup.ContainsKey(builder); + } + + public bool Remove(InspectBuilder builder) + { + return lookup.Remove(builder); + } + + public void Clear() + { + lookup.Clear(); + } + + public IEnumerator GetEnumerator() + { + return lookup.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Bonsai.Editor/Layout/WorkflowEditorLauncher.cs b/Bonsai.Editor/Layout/WorkflowEditorLauncher.cs deleted file mode 100644 index 87bb1d83a..000000000 --- a/Bonsai.Editor/Layout/WorkflowEditorLauncher.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Windows.Forms; -using Bonsai.Expressions; -using System.ComponentModel; -using Bonsai.Editor.GraphView; -using Bonsai.Editor; - -namespace Bonsai.Design -{ - class WorkflowEditorLauncher : DialogLauncher - { - bool userClosing; - readonly Func parentSelector; - readonly Func containerSelector; - - public WorkflowEditorLauncher(IWorkflowExpressionBuilder builder, Func parentSelector, Func containerSelector) - { - Builder = builder ?? throw new ArgumentNullException(nameof(builder)); - this.parentSelector = parentSelector ?? throw new ArgumentNullException(nameof(parentSelector)); - this.containerSelector = containerSelector ?? throw new ArgumentNullException(nameof(containerSelector)); - } - - internal IWorkflowExpressionBuilder Builder { get; } - - internal WorkflowGraphView ParentView - { - get { return parentSelector(); } - } - - internal WorkflowEditorControl Container - { - get { return containerSelector(); } - } - - internal IWin32Window Owner - { - get { return VisualizerDialog; } - } - - public VisualizerLayout VisualizerLayout { get; set; } - - public WorkflowGraphView WorkflowGraphView { get; private set; } - - public void UpdateEditorLayout() - { - if (WorkflowGraphView != null) - { - WorkflowGraphView.UpdateVisualizerLayout(); - VisualizerLayout = WorkflowGraphView.VisualizerLayout; - if (VisualizerDialog != null) - { - Bounds = VisualizerDialog.LayoutBounds; - } - } - } - - public void UpdateEditorText() - { - if (VisualizerDialog != null) - { - VisualizerDialog.Text = ExpressionBuilder.GetElementDisplayName(Builder); - if (VisualizerDialog.TopLevel == false) - { - Container.RefreshTab(Builder); - } - } - } - - public override void Show(IWin32Window owner, IServiceProvider provider) - { - if (VisualizerDialog != null && VisualizerDialog.TopLevel == false) - { - Container.SelectTab(Builder); - } - else base.Show(owner, provider); - } - - public override void Hide() - { - if (VisualizerDialog != null) - { - userClosing = false; - if (VisualizerDialog.TopLevel == false) - { - Container.CloseTab(Builder); - } - else base.Hide(); - } - } - - void EditorClosing(object sender, CancelEventArgs e) - { - if (userClosing) - { - e.Cancel = true; - ParentView.CloseWorkflowView(Builder); - ParentView.UpdateSelection(); - } - else - { - UpdateEditorLayout(); - WorkflowGraphView.HideEditorMapping(); - } - } - - protected override LauncherDialog CreateVisualizerDialog(IServiceProvider provider) - { - return new NestedEditorDialog(provider); - } - - protected override void InitializeComponents(TypeVisualizerDialog visualizerDialog, IServiceProvider provider) - { - var workflowEditor = Container; - if (workflowEditor == null) - { - workflowEditor = new WorkflowEditorControl(provider, ParentView.ReadOnly); - workflowEditor.SuspendLayout(); - workflowEditor.Dock = DockStyle.Fill; - workflowEditor.Font = ParentView.Font; - workflowEditor.Workflow = Builder.Workflow; - WorkflowGraphView = workflowEditor.WorkflowGraphView; - workflowEditor.ResumeLayout(false); - visualizerDialog.AddControl(workflowEditor); - visualizerDialog.Icon = Editor.Properties.Resources.Icon; - visualizerDialog.ShowIcon = true; - visualizerDialog.Activated += (sender, e) => workflowEditor.ActiveTab.UpdateSelection(); - visualizerDialog.FormClosing += (sender, e) => - { - if (e.CloseReason == CloseReason.UserClosing) - { - EditorClosing(sender, e); - } - }; - } - else - { - visualizerDialog.FormBorderStyle = FormBorderStyle.None; - visualizerDialog.Dock = DockStyle.Fill; - visualizerDialog.TopLevel = false; - visualizerDialog.Visible = true; - var tabState = workflowEditor.CreateTab(Builder, ParentView.ReadOnly, visualizerDialog); - WorkflowGraphView = tabState.WorkflowGraphView; - tabState.TabClosing += EditorClosing; - } - - userClosing = true; - visualizerDialog.BackColor = ParentView.ParentForm.BackColor; - WorkflowGraphView.BackColorChanged += (sender, e) => visualizerDialog.BackColor = ParentView.ParentForm.BackColor; - WorkflowGraphView.Launcher = this; - WorkflowGraphView.VisualizerLayout = VisualizerLayout; - WorkflowGraphView.SelectFirstGraphNode(); - WorkflowGraphView.Select(); - UpdateEditorText(); - } - - class NestedEditorDialog : LauncherDialog - { - IWorkflowEditorService editorService; - - public NestedEditorDialog(IServiceProvider provider) - { - editorService = (IWorkflowEditorService)provider.GetService(typeof(IWorkflowEditorService)); - } - - protected override void OnKeyDown(KeyEventArgs e) - { - if (e.KeyCode == Keys.Escape) - { - e.Handled = true; - } - base.OnKeyDown(e); - } - - protected override bool ProcessTabKey(bool forward) - { - var selected = SelectNextControl(ActiveControl, forward, true, true, false); - if (!selected) - { - var parent = Parent; - if (parent != null) return parent.SelectNextControl(this, forward, true, true, false); - else editorService.SelectNextControl(forward); - } - - return selected; - } - } - } -} diff --git a/Bonsai.Editor/Layout/WorkflowEditorSettings.cs b/Bonsai.Editor/Layout/WorkflowEditorSettings.cs index fa8337182..41dd89b07 100644 --- a/Bonsai.Editor/Layout/WorkflowEditorSettings.cs +++ b/Bonsai.Editor/Layout/WorkflowEditorSettings.cs @@ -1,5 +1,8 @@ -namespace Bonsai.Design +using System; + +namespace Bonsai.Design { + [Obsolete] public class WorkflowEditorSettings : VisualizerDialogSettings { public VisualizerDialogSettings EditorDialogSettings { get; set; } diff --git a/Bonsai.Editor/Properties/Resources.Designer.cs b/Bonsai.Editor/Properties/Resources.Designer.cs index 295eb181f..f9ef2ac92 100644 --- a/Bonsai.Editor/Properties/Resources.Designer.cs +++ b/Bonsai.Editor/Properties/Resources.Designer.cs @@ -286,6 +286,15 @@ internal static string InvalidReplaceGroupNode_Error { } } + /// + /// Looks up a localized string similar to The specified workflow path does not resolve to a workflow expression builder node.. + /// + internal static string InvalidWorkflowPath_Error { + get { + return ResourceManager.GetString("InvalidWorkflowPath_Error", resourceCulture); + } + } + /// /// Looks up a localized string similar to There was an error opening the workflow {0}: ///{1}. diff --git a/Bonsai.Editor/Properties/Resources.resx b/Bonsai.Editor/Properties/Resources.resx index d5b31a13b..86579078e 100644 --- a/Bonsai.Editor/Properties/Resources.resx +++ b/Bonsai.Editor/Properties/Resources.resx @@ -134,7 +134,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vwAADr8BOAVTJAAAAK5JREFUOE9jQAffvn1LAOL9QPwfDYPEEqDKMAFQUgGIz3/at/P/q2jf/09FGFAw + vAAADrwBlbxySQAAAK5JREFUOE9jQAffvn1LAOL9QPwfDYPEEqDKMAFQUgGIz3/at/P/q2jf/09FGFAw SAwkB1IDUgvVBgFQze9fZ8ZgaETHb8pzQIa8RzEEyDkPksCmARuGGnIepjkB5DRsCvFhqHcSQAbsx+Zn QhikB6QXZABWBcRgkF4UA4gFtDOAVAwzgOJApCwaoWnhPDGpEIZREhIIADngpExMasSalEEAagh5mQkZ ACVJyM4MDAD69UvNH5WBiAAAAABJRU5ErkJggg== @@ -143,7 +143,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vwAADr8BOAVTJAAAANdJREFUOE+dU7kNwzAM1CjZwQtpBA/gwqVX8CKpAggI4FojuHNrwIVahZeQhh7a + vAAADrwBlbxySQAAANdJREFUOE+dU7kNwzAM1CjZwQtpBA/gwqVX8CKpAggI4FojuHNrwIVahZeQhh7a cXLAFSJ5Rz2UKRFCsERHjAURs1xWg5I3op/mKbb3NjZjkxEx5FCDWpZ9wOK1e3SVsOTwHGCyZia08Eho Ao1s4kVssTWtUNi7PgLLtuwxPo6FgdPOnBJCAEYSgwZaGGTFJbXuQmi/GmjdhapBKjjrDqoGqeisOygG 1SWKEDjqnl5i9YyyC+Co+/6MPAv+yhQKs0ECaPEe5SvTqI4ywCb/faYUlPzhOxvzAkO1WA01cJaNAAAA @@ -153,7 +153,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wAAADsABataJCQAAANRJREFUOE+1U8ENwjAMzBAMwAiMwAJMAI98kfgzGx86QqbgU5CCeOSHgi/YlZNY + vQAADr0BR/uQrQAAANRJREFUOE+1U8ENwjAMzBAMwAiMwAJMAI98kfgzGx86QqbgU5CCeOSHgi/YlZNY SFBx0qmNfXdtWse1SCl54kDMDVHzLOtBzSUxPM6nPG43+bJwFVFDDxpo2fYGm+N1v+uMLW/HA0JiFUKL gAYE43pV2Bp1nUOCmD1eTUTPeyzUIVadt+MRMMieRQiI2KoVLXngRcD0JCvEMgvh7QJAHQJYZvA/AdqM q75vQyRg9kec9xt5FoJMIQTaLNT1apAAWpRRlmn8RHOUAQ757TBpUPOL4+zcCzIffKHxkkn8AAAAAElF @@ -162,11 +162,11 @@ - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAADBSURBVDhPY0AH3759SwDi/UD8Hw2DxBKgyjABUFIBiM8f - vX/0f8G2gv/GM41RMEgMJAdSA1IL1QYBUM3va/bUYGhExx2HOkCGvEcxBMg5D5LApgEbhhpyHqY5AeQ0 - dEU339z8H7kmEkMchqHeSQAZsB+bn2Fg5pmZGHIgDNID0gsyAKsCZIDLNSC9RBkAA+iuoZ8BhLxAcSBS - Fo3QtHCemFQIwygJCQSAHHBSJiY1Yk3KIAA1hLzMhAyAkiRkZwYGAEcIWvs/bCjHAAAAAElFTkSuQmCC + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + wQAADsEBuJFr7QAAAMFJREFUOE9jQAffvn1LAOL9QPwfDYPEEqDKMAFQUgGIzx+9f/R/wbaC/8YzjVEw + SAwkB1IDUgvVBgFQze9r9tRgaETHHYc6QIa8RzEEyDkPksCmARuGGnIepjkB5DR0RTff3PwfuSYSQxyG + od5JABmwH5ufYWDmmZkYciAM0gPSCzIAqwJkgMs1IL1EGQAD6K6hnwGEvEBxIFIWjdC0cJ6YVAjDKAkJ + BIAccFImJjViTcogADWEvMyEDICSJGRnBgYARwha+z9sKMcAAAAASUVORK5CYII= @@ -219,7 +219,7 @@ Copyright (c) .NET Foundation and Contributors iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC41ZYUyZQAAAKdJREFUOE+lkMEN + wAAADsABataJCQAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC41ZYUyZQAAAKdJREFUOE+lkMEN wyAQBCktooV8U4KLyTuklnxSkJ0P300WaZ3jwDaSLY18HLeDTQDQkHPG7f5CuD4LrNnzc6Rp+PCRpFps hUVP8i9+Dzc1PD3eVVD1kEAn2ZAkjYANi8LECvyeKIKUEmKM5V1tOgHxs0ENiw1Y/Byz1S/0ZB6dLNbL UINDy/wpKGTXmlsv0QguowJlSFewx5Dg9BecFuxxKBhBGQDhC/DB5AQ227rCAAAAAElFTkSuQmCC @@ -341,4 +341,7 @@ NOTE: You will have to restart Bonsai for any changes to take effect. Help + + The specified workflow path does not resolve to a workflow expression builder node. + \ No newline at end of file diff --git a/Bonsai.Editor/WorkflowRunner.cs b/Bonsai.Editor/WorkflowRunner.cs index e3ed7e0a4..5d6f651e6 100644 --- a/Bonsai.Editor/WorkflowRunner.cs +++ b/Bonsai.Editor/WorkflowRunner.cs @@ -32,9 +32,11 @@ static void RunLayout( workflowBuilder = new WorkflowBuilder(workflowBuilder.Workflow.ToInspectableGraph()); BuildAssignProperties(workflowBuilder, propertyAssignments); + + var visualizerSettings = VisualizerLayoutMap.FromVisualizerLayout(workflowBuilder, layout, typeVisualizers); + var visualizerDialogs = visualizerSettings.CreateVisualizerDialogs(workflowBuilder); LayoutHelper.SetWorkflowNotifications(workflowBuilder.Workflow, publishNotifications: false); - LayoutHelper.SetLayoutTags(workflowBuilder.Workflow, layout); - LayoutHelper.SetLayoutNotifications(layout); + LayoutHelper.SetLayoutNotifications(workflowBuilder.Workflow, visualizerDialogs); var services = new System.ComponentModel.Design.ServiceContainer(); services.AddService(typeof(WorkflowBuilder), workflowBuilder); @@ -42,31 +44,14 @@ static void RunLayout( var cts = new CancellationTokenSource(); var contextMenu = new ContextMenuStrip(); - void CreateVisualizerMapping(ExpressionBuilderGraph workflow, VisualizerLayout layout) + foreach (var launcher in visualizerDialogs) { - var mapping = LayoutHelper.CreateVisualizerMapping(workflow, layout, typeVisualizers, services); - foreach (var launcher in mapping.Values.Where(launcher => launcher.Visualizer.IsValueCreated)) + var activeLauncher = launcher; + contextMenu.Items.Add(new ToolStripMenuItem(launcher.Text, null, (sender, e) => { - var activeLauncher = launcher; - contextMenu.Items.Add(new ToolStripMenuItem(launcher.Text, null, (sender, e) => - { - activeLauncher.Show(services); - })); - } - - foreach (var settings in layout.DialogSettings) - { - if (settings is WorkflowEditorSettings editorSettings && - editorSettings.Tag is ExpressionBuilder builder && - ExpressionBuilder.Unwrap(builder) is IWorkflowExpressionBuilder workflowBuilder && - editorSettings.EditorVisualizerLayout != null && - editorSettings.EditorDialogSettings.Visible) - { - CreateVisualizerMapping(workflowBuilder.Workflow, editorSettings.EditorVisualizerLayout); - } - } + activeLauncher.Show(services); + })); } - CreateVisualizerMapping(workflowBuilder.Workflow, layout); contextMenu.Items.Add(new ToolStripSeparator()); contextMenu.Items.Add(new ToolStripMenuItem("Stop", null, (sender, e) => cts.Cancel())); @@ -76,6 +61,7 @@ editorSettings.Tag is ExpressionBuilder builder && notifyIcon.ContextMenuStrip = contextMenu; notifyIcon.Visible = true; + visualizerDialogs.Show(visualizerSettings, services); using var synchronizationContext = new WindowsFormsSynchronizationContext(); runtimeWorkflow.Finally(() => { diff --git a/Bonsai/DependencyInspector.cs b/Bonsai/DependencyInspector.cs index 7076fb1a5..69be47211 100644 --- a/Bonsai/DependencyInspector.cs +++ b/Bonsai/DependencyInspector.cs @@ -34,10 +34,9 @@ static IEnumerable GetVisualizerSettings(VisualizerLay foreach (var settings in layout.DialogSettings) { yield return settings; - var editorSettings = settings as WorkflowEditorSettings; - if (editorSettings != null && editorSettings.EditorVisualizerLayout != null) + if (settings.NestedLayout != null) { - stack.Push(editorSettings.EditorVisualizerLayout); + stack.Push(settings.NestedLayout); } } }