Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite editor navigation model #1870

Merged
merged 68 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
b55afac
Avoid serializing default property values
glopesdev Jun 26, 2024
28e359f
Add explorer tree view control and path navigation
glopesdev Jul 1, 2024
52b5606
Add explorer support for highlighting node paths
glopesdev Jul 1, 2024
ce5381d
Add theme renderer support to custom tree views
glopesdev Jul 2, 2024
b60348d
Use state image list for flexible icon rendering
glopesdev Jul 2, 2024
ed38b28
Allow resolution to determine if path is read-only
glopesdev Jul 2, 2024
1fe2cb7
Rewrite editor navigation model
glopesdev Jul 2, 2024
b4f269e
Add navigation commands to undo stack
glopesdev Jul 2, 2024
7373fb6
Ensure explorer and status reset on workflow clear
glopesdev Jul 2, 2024
d5f976b
Add workflow editable status to explorer view
glopesdev Jul 14, 2024
252f7f9
Hide ready state from explorer view
glopesdev Jul 14, 2024
e5eb515
Ensure explorer view refreshes on build error
glopesdev Aug 3, 2024
5edb8f1
Ensure explorer view initializes on load
glopesdev Aug 3, 2024
31c147a
Prescale state image list size to system DPI
glopesdev Aug 3, 2024
09d4934
Save explorer splitter distance to editor settings
glopesdev Aug 3, 2024
d856fda
Size description boxes relative to each other
glopesdev Aug 3, 2024
74b1f3a
Fix description box sizes in netcore
glopesdev Aug 3, 2024
e394662
Avoid clipping breadcrumbs control
glopesdev Aug 3, 2024
93d4fb9
Auto-compress path breadcrumbs to fit control size
glopesdev Aug 4, 2024
5c3c519
Avoid renaming tab page on single tab mode
glopesdev Aug 4, 2024
d45df45
Size description box depending on editor settings
glopesdev Aug 4, 2024
c0875ef
Compress layout XML representation
glopesdev Aug 5, 2024
f98159e
Ensure project name is set before navigation reset
glopesdev Sep 3, 2024
58210ef
Clear workflow error highlight
glopesdev Sep 3, 2024
1c4ff3a
Prevent clipping of breadcrumbs button
glopesdev Sep 7, 2024
1ea00bf
Remove unnecessary unwrap call
glopesdev Sep 17, 2024
eb691af
Clarify condition purpose
glopesdev Sep 17, 2024
660ee47
Fix navigation to disabled group nodes
glopesdev Sep 17, 2024
c9fdd7c
Avoid relaunching closed visualizer
glopesdev Sep 17, 2024
6293a5a
Allow navigating to selected node by keyboard
glopesdev Sep 17, 2024
e911d06
Refactor explorer tree view as a user control
glopesdev Sep 17, 2024
5320821
Add single click navigation and context menu
glopesdev Sep 17, 2024
319af94
Add support for multi-panel navigation
glopesdev Oct 15, 2024
cbbd445
Allow restore docking state up to first level
glopesdev Dec 26, 2024
973d8ea
Dock in active document pane if available
glopesdev Dec 26, 2024
1d7bfd7
Ensure default window style for float windows
glopesdev Dec 26, 2024
39f1f59
Update layout of graph views with the same path
glopesdev Dec 26, 2024
5aebdfa
Ensure workflow path is set on restore
glopesdev Jan 4, 2025
20d41d7
Improve undo layout heuristics
glopesdev Jan 4, 2025
7162ec3
Ensure propagation of highlight and layout changes
glopesdev Jan 7, 2025
8dceede
Ensure validation when property grid loses focus
glopesdev Jan 7, 2025
997bf9c
Allow tab navigation across panels and controls
glopesdev Jan 7, 2025
e6b5e33
Add shortcuts and context menu to close pane tabs
glopesdev Jan 8, 2025
d76a4f0
Refresh explorer view before highlighting errors
glopesdev Jan 8, 2025
b405905
Close all float window panes as a single command
glopesdev Jan 8, 2025
e1a7c8b
Add support for changing dock panel theme
glopesdev Jan 8, 2025
1ef1d5e
Show read-only tab mark as a prefix symbol
glopesdev Feb 8, 2025
b1f6248
Move dock panel helper class
glopesdev Feb 8, 2025
750a0ee
Unify dock pane strip style
glopesdev Feb 10, 2025
54d8c32
Remove unused editor tab control
glopesdev Feb 10, 2025
a5affd7
Assign unique content ID to improve undo heuristic
glopesdev Feb 11, 2025
75f4b7d
Improvements for deterministic theme change
glopesdev Feb 11, 2025
e1a7b85
Move docking infrastructure to separate namespace
glopesdev Feb 11, 2025
1b1b8b9
Add docking layout serialization infrastructure
glopesdev Feb 12, 2025
2176ad4
Ensure content ID is propagated on pane change
glopesdev Feb 20, 2025
32d999f
Add support for persisting dock panel layout
glopesdev Feb 20, 2025
27fb6a0
Add support for new tab and window shortcut keys
glopesdev Feb 20, 2025
4d172b5
Avoid updating disposed dock contents
glopesdev Feb 20, 2025
dde4367
Ensure font change propagates to dock panel theme
glopesdev Feb 21, 2025
01da581
Ensure default workflow initializes consistently
glopesdev Feb 21, 2025
d0e9e78
Improve dock panel theme alignment
glopesdev Feb 21, 2025
277943a
Ensure successful initialization on mono
glopesdev Feb 21, 2025
c72a0a7
Allow navigation to include workflows
glopesdev Feb 23, 2025
c71679c
Allow tab and window navigation from single node
glopesdev Feb 23, 2025
65f2e95
Allow tab and window navigation for multiple nodes
glopesdev Feb 24, 2025
1679c0a
Update dock content text on selection refresh
glopesdev Feb 24, 2025
cc2428d
Force eager resolution of invalid contents
glopesdev Feb 25, 2025
bf13194
Ensure at least one root content is created
glopesdev Feb 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Bonsai.Design/CommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ public void EndCompositeCommand()

if (composite.Execute == null)
{
throw new InvalidOperationException("A composite command must have at least one action defined.");
composite = null;
return;
}

if (composite.Undo != null)
Expand Down
1 change: 1 addition & 0 deletions Bonsai.Editor.Tests/MockGraphView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 0 additions & 1 deletion Bonsai.Editor.Tests/WorkflowEditorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ static string ToString(IEnumerable<T> 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: () =>
Expand Down
1 change: 1 addition & 0 deletions Bonsai.Editor/Bonsai.Editor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="DockPanelSuite.ThemeVS2015" Version="3.1.1" />
<PackageReference Include="SvgNet" Version="3.3.8" />
<PackageReference Include="YamlDotNet" Version="16.0.0" />
<PackageReference Include="Markdig" Version="0.37.0" />
Expand Down
162 changes: 162 additions & 0 deletions Bonsai.Editor/Docking/DockPanelHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
using System;
using System.Drawing;
using System.IO;
using System.Text;
using System.Xml.Linq;
using Bonsai.Design;
using WeifenLuo.WinFormsUI.Docking;

namespace Bonsai.Editor.Docking
{
internal static class DockPanelHelper
{
public static TDockContent CreateDynamicContent<TDockContent>(
this DockPanel dockPanel,
Func<DockPanel, TDockContent> contentFactory,
Action<TDockContent> contentClosed,
DockState dockState,
CommandExecutor commandExecutor)
where TDockContent : DockContent
{
int contentIndex = -1;
bool singletonContent = false;
object contentId = new();
object dockPaneId = default;
object previousPaneId = default;
Rectangle? floatWindowBounds = null;
TDockContent dockContent = default;
DockAlignment? dockAlignment = default;
double dockProportion = default;
var isUndoAction = false;
void CloseContent()
{
try
{
isUndoAction = true;
dockContent.Close();
}
finally
{
isUndoAction = false;
}
}

void CreateAndShowContent()
{
dockContent = contentFactory(dockPanel);
dockContent.Tag = contentId;
dockContent.HideOnClose = false;
dockContent.FormClosed += (sender, e) =>
{
contentClosed(dockContent);
var nestedDockingStatus = dockContent.Pane.NestedDockingStatus;
dockState = dockContent.DockState;
dockAlignment = nestedDockingStatus.Alignment;
dockProportion = nestedDockingStatus.Proportion;
dockPaneId = dockContent.Pane.Tag;
previousPaneId = nestedDockingStatus.PreviousPane?.Tag;
singletonContent = dockContent.Pane.Contents.Count == 1;
floatWindowBounds = dockState == DockState.Float
? dockContent.Pane.FloatWindow.Bounds
: null;
contentIndex = dockContent.Pane.Contents.IndexOf(dockContent);
if (!isUndoAction)
{
var isRedoAction = false;
commandExecutor.Execute(
() =>
{
if (isRedoAction)
CloseContent();
else
isRedoAction = true;
},
CreateAndShowContent);
}
};

if (dockState == DockState.Unknown)
return;

var dockPane = dockPanel.GetPaneFromId(dockPaneId);
if (dockPane != null && !singletonContent)
{
dockContent.Show(dockPane, contentIndex);
return;
}

contentIndex = -1;
singletonContent = default;
dockPane = dockPanel.GetPaneFromId(previousPaneId);
if (dockPane is DockPane previousPane)
{
dockContent.Show(previousPane, dockAlignment.Value, dockProportion);
}
else if (floatWindowBounds.HasValue)
{
dockContent.Show(dockPanel, floatWindowBounds.GetValueOrDefault());
}
else if (dockPanel.ActiveDocumentPane != null && dockState == DockState.Document)
{
dockContent.Show(dockPanel.ActiveDocumentPane, contentIndex);
}
else
{
dockContent.Show(dockPanel, dockState);
}
}

if (dockState == DockState.Unknown)
CreateAndShowContent();
else
commandExecutor.Execute(
CreateAndShowContent,
CloseContent);
return dockContent;
}

public static void Show(this DockContent dockContent, DockPane pane, int contentIndex)
{
pane.DockPanel.SuspendLayout(allWindows: true);
dockContent.DockPanel = pane.DockPanel;
dockContent.Pane = pane;
if (contentIndex >= 0 && contentIndex < pane.Contents.Count)
{
pane.SetContentIndex(dockContent, contentIndex);
}
dockContent.Show();
pane.DockPanel.ResumeLayout(performLayout: true, allWindows: true);
}

static DockPane GetPaneFromId(this DockPanel dockPanel, object paneId)
{
for (int i = 0; i < dockPanel.Panes.Count; i++)
{
if (ReferenceEquals(paneId, dockPanel.Panes[i].Tag))
return dockPanel.Panes[i];
}

return null;
}

public static DockPane GetDocumentPane(this DockPanel dockPanel)
{
for (int i = 0; i < dockPanel.Panes.Count; i++)
{
var pane = dockPanel.Panes[i];
if (pane.IsActiveDocumentPane)
return pane;
}

return null;
}

public static XDocument SaveAsXml(this DockPanel dockPanel)
{
using var memoryStream = new MemoryStream();
dockPanel.SaveAsXml(memoryStream, Encoding.UTF8, upstream: true);
memoryStream.Position = 0;
return XDocument.Load(memoryStream);
}
}
}
65 changes: 65 additions & 0 deletions Bonsai.Editor/Docking/DockPanelSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@

using System;
using System.Text;
using Bonsai.Editor.GraphModel;
using Bonsai.Editor.GraphView;
using WeifenLuo.WinFormsUI.Docking;

namespace Bonsai.Editor.Docking
{
internal static class DockPanelSerializer
{
const char ContentTypeSeparator = ':';

static void ThrowNotRecognizedException(string contentString)
{
throw new ArgumentException(
$"The specified dock panel content was not recognized: {contentString}.",
nameof(contentString));
}

public static IDockContent DeserializeContent(
WorkflowEditorControl editorControl,
WorkflowBuilder workflowBuilder,
string contentString)
{
if (editorControl is null)
throw new ArgumentNullException(nameof(editorControl));

if (string.IsNullOrEmpty(contentString))
throw new ArgumentException("The dock panel content is null or empty.", nameof(contentString));

var contentElements = contentString.Split(new[] { ContentTypeSeparator });
if (contentElements.Length < 1 || contentElements.Length > 2)
ThrowNotRecognizedException(contentString);

switch (contentElements[0])
{
case nameof(WorkflowDockContent):
var workflowPath = contentElements.Length > 1
? WorkflowEditorPath.Parse(contentElements[1])
: null;
workflowPath?.Resolve(workflowBuilder);
return editorControl.CreateDockContent(workflowPath, DockState.Unknown);
default:
ThrowNotRecognizedException(contentString);
return default;
}
}

public static string SerializeContent(WorkflowDockContent dockContent)
{
if (dockContent is null)
throw new ArgumentNullException(nameof(dockContent));

var stringBuilder = new StringBuilder();
stringBuilder.Append(nameof(WorkflowDockContent));
if (dockContent.WorkflowGraphView.WorkflowPath != null)
{
stringBuilder.Append(ContentTypeSeparator);
stringBuilder.Append(dockContent.WorkflowGraphView.WorkflowPath);
}
return stringBuilder.ToString();
}
}
}
8 changes: 8 additions & 0 deletions Bonsai.Editor/Docking/InvalidDockContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using WeifenLuo.WinFormsUI.Docking;

namespace Bonsai.Editor.Docking
{
internal class InvalidDockContent : DockContent
{
}
}
123 changes: 123 additions & 0 deletions Bonsai.Editor/Docking/WorkflowDockContent.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading