diff --git a/src/DynamoCoreWpf/Commands/NoteCommands.cs b/src/DynamoCoreWpf/Commands/NoteCommands.cs
index c325645c409..910527efd06 100644
--- a/src/DynamoCoreWpf/Commands/NoteCommands.cs
+++ b/src/DynamoCoreWpf/Commands/NoteCommands.cs
@@ -1,9 +1,11 @@
-using Dynamo.UI.Commands;
+using Dynamo.Controls;
+using Dynamo.UI.Commands;
+using Dynamo.Utilities;
using Newtonsoft.Json;
namespace Dynamo.ViewModels
{
- public partial class NoteViewModel
+ public partial class NoteViewModel: IWorkspaceElement
{
private DelegateCommand _selectCommand;
[JsonIgnore]
@@ -93,5 +95,22 @@ public DelegateCommand UnpinFromNodeCommand
return unpinFromNodeCommand;
}
}
+
+ ///
+ /// This signifies if the note should be rendered
+ ///
+ [JsonIgnore]
+ public bool IsVisibleInCanvas
+ {
+ get => isVisibleInCanvas;
+ set
+ {
+ isVisibleInCanvas = value;
+ RaisePropertyChanged(nameof(isVisibleInCanvas));
+ }
+ }
+ private bool isVisibleInCanvas = false;
+
+ public Rect2D Rect => Model.Rect;
}
}
diff --git a/src/DynamoCoreWpf/Controls/DeferredContent.cs b/src/DynamoCoreWpf/Controls/DeferredContent.cs
new file mode 100644
index 00000000000..6e308f755cf
--- /dev/null
+++ b/src/DynamoCoreWpf/Controls/DeferredContent.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Threading;
+using Dynamo.ViewModels;
+
+namespace Dynamo.Controls
+{
+ ///
+ /// https://stackoverflow.com/a/26543731
+ ///
+ public class DeferredContent : ContentPresenter
+ {
+ public static void Focus(double x, double y) => Priority.Focus(x, y);
+ private static DeferredContentRenderPriority Priority = new();
+
+ public DataTemplate DeferredContentTemplate
+ {
+ get => (DataTemplate)GetValue(DeferredContentTemplateProperty);
+ set => SetValue(DeferredContentTemplateProperty, value);
+ }
+
+ public static readonly DependencyProperty DeferredContentTemplateProperty =
+ DependencyProperty.Register(nameof(DeferredContentTemplate),
+ typeof(DataTemplate), typeof(DeferredContent), null);
+
+ public DeferredContent()
+ {
+ IsVisibleChanged += DeferredContent_IsVisibleChanged;
+ }
+
+ private void DeferredContent_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
+ {
+ if (true.Equals(e.NewValue))
+ {
+ IsVisibleChanged -= DeferredContent_IsVisibleChanged;
+ if (DataContext == null)
+ {
+ DataContextChanged += DeferredContent_DataContextChanged;
+ return;
+ }
+ //Dispatcher.BeginInvoke(ShowDeferredContent, DispatcherPriority.Background);
+ Priority.Enqueue(this);
+ }
+ }
+
+ private void DeferredContent_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
+ {
+ DataContextChanged -= DeferredContent_DataContextChanged;
+ // new node added,jump the queue
+ Dispatcher.BeginInvoke(ShowDeferredContent, DispatcherPriority.Render);
+ }
+
+ public void ShowDeferredContent()
+ {
+ if (DeferredContentTemplate != null)
+ {
+ base.Content = DeferredContentTemplate.LoadContent();
+ RaiseDeferredContentLoaded();
+ }
+ }
+
+ private void RaiseDeferredContentLoaded()
+ {
+ DeferredContentLoaded?.Invoke(this, new RoutedEventArgs());
+ }
+
+ public event EventHandler DeferredContentLoaded;
+
+ private class DeferredContentRenderPriority
+ {
+ private bool isRunning = false;
+ private Point canvasCenter = default;
+ private readonly List<(DeferredContent, DispatcherOperation)> scheduled = [];
+ private readonly PriorityQueue pending =
+ new PriorityQueue();
+
+ public void Enqueue(DeferredContent control)
+ {
+ if (control.DataContext is not NodeViewModel nvm) return;
+
+ var dist = (canvasCenter - new Point(nvm.X, nvm.Y)).Length;
+ pending.Enqueue(control, dist);
+
+ if (!isRunning)
+ {
+ isRunning = true;
+ control.Dispatcher.BeginInvoke(SchedulePending, DispatcherPriority.ContextIdle);
+ }
+ }
+
+ public void Focus(double x, double y)
+ {
+ isRunning = false;
+ canvasCenter = new Point(x, y);
+
+ var toReschedule = new List();
+ if (pending.Count > 0)
+ {
+ toReschedule.AddRange(pending.UnorderedItems.Select(i => i.Element));
+ pending.Clear();
+ }
+
+ foreach (var (control, task) in scheduled)
+ {
+ if (task.Status != DispatcherOperationStatus.Completed && task.Abort())
+ {
+ toReschedule.Add(control);
+ }
+ }
+ scheduled.Clear();
+
+ foreach (var control in toReschedule)
+ {
+ Enqueue(control);
+ }
+ }
+
+ public void SchedulePending()
+ {
+ while (isRunning && pending.TryDequeue(out var control, out _))
+ {
+ scheduled.Add((control, control.Dispatcher.BeginInvoke(control.ShowDeferredContent, DispatcherPriority.Background)));
+ }
+
+ isRunning = false;
+ }
+ }
+ }
+}
diff --git a/src/DynamoCoreWpf/Controls/IWorkspaceElement.cs b/src/DynamoCoreWpf/Controls/IWorkspaceElement.cs
new file mode 100644
index 00000000000..4f52c12fb0b
--- /dev/null
+++ b/src/DynamoCoreWpf/Controls/IWorkspaceElement.cs
@@ -0,0 +1,10 @@
+using Dynamo.Utilities;
+
+namespace Dynamo.Controls
+{
+ internal interface IWorkspaceElement
+ {
+ bool IsVisibleInCanvas { get; set; }
+ Rect2D Rect { get; }
+ }
+}
diff --git a/src/DynamoCoreWpf/DynamoCoreWpf.csproj b/src/DynamoCoreWpf/DynamoCoreWpf.csproj
index 25a013533bf..c159cc6e434 100644
--- a/src/DynamoCoreWpf/DynamoCoreWpf.csproj
+++ b/src/DynamoCoreWpf/DynamoCoreWpf.csproj
@@ -224,6 +224,7 @@
InstalledPackagesControl.xaml
+
NodeAutoCompleteSearchControl.xaml
@@ -456,6 +457,7 @@
AboutWindow.xaml
+
ConnectorAnchorView.xaml
diff --git a/src/DynamoCoreWpf/UI/Themes/Modern/DataTemplates.xaml b/src/DynamoCoreWpf/UI/Themes/Modern/DataTemplates.xaml
index efbad42a4aa..c504727e6fa 100644
--- a/src/DynamoCoreWpf/UI/Themes/Modern/DataTemplates.xaml
+++ b/src/DynamoCoreWpf/UI/Themes/Modern/DataTemplates.xaml
@@ -1,4 +1,4 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -27,4 +46,4 @@
-
\ No newline at end of file
+
diff --git a/src/DynamoCoreWpf/ViewModels/Core/AnnotationViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/AnnotationViewModel.cs
index 6d8f87c6fee..02de5ac870e 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/AnnotationViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/AnnotationViewModel.cs
@@ -6,6 +6,7 @@
using System.Windows.Input;
using System.Windows.Media;
using Dynamo.Configuration;
+using Dynamo.Controls;
using Dynamo.Graph;
using Dynamo.Graph.Annotations;
using Dynamo.Graph.Nodes;
@@ -20,7 +21,7 @@
namespace Dynamo.ViewModels
{
- public class AnnotationViewModel : ViewModelBase
+ public class AnnotationViewModel : ViewModelBase, IWorkspaceElement
{
private AnnotationModel annotationModel;
private IEnumerable originalInPorts;
@@ -371,6 +372,24 @@ public ObservableCollection GroupStyleList
RaisePropertyChanged(nameof(GroupStyleList));
}
}
+
+ ///
+ /// This signifies if the node should be rendered
+ ///
+ [JsonIgnore]
+ public bool IsVisibleInCanvas
+ {
+ get => isVisibleInCanvas;
+ set
+ {
+ isVisibleInCanvas = value;
+ RaisePropertyChanged(nameof(isVisibleInCanvas));
+ }
+ }
+ private bool isVisibleInCanvas = false;
+
+ public Rect2D Rect => AnnotationModel.Rect;
+
#endregion
#region Commands
diff --git a/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs
index e45034ac7e0..c21c98a253d 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs
@@ -11,6 +11,7 @@
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Dynamo.Configuration;
+using Dynamo.Controls;
using Dynamo.Engine.CodeGeneration;
using Dynamo.Graph;
using Dynamo.Graph.Nodes;
@@ -20,6 +21,7 @@
using Dynamo.Logging;
using Dynamo.Models;
using Dynamo.Selection;
+using Dynamo.Utilities;
using Dynamo.Wpf.ViewModels.Core;
using Newtonsoft.Json;
using Point = System.Windows.Point;
@@ -31,7 +33,7 @@ namespace Dynamo.ViewModels
/// Interaction logic for dynControl.xaml
///
- public partial class NodeViewModel : ViewModelBase
+ public partial class NodeViewModel : ViewModelBase, IWorkspaceElement
{
#region delegates
public delegate void SetToolTipDelegate(string message);
@@ -573,6 +575,23 @@ public bool NodeHoveringState
}
}
+ ///
+ /// This signifies if the node should be rendered
+ ///
+ [JsonIgnore]
+ public bool IsVisibleInCanvas
+ {
+ get => isVisibleInCanvas;
+ set
+ {
+ isVisibleInCanvas = value;
+ RaisePropertyChanged(nameof(isVisibleInCanvas));
+ }
+ }
+ private bool isVisibleInCanvas = false;
+
+ public Rect2D Rect => NodeModel.Rect;
+
private bool isNodeNewlyAdded;
private ImageSource imageSource;
private string imgGlyphOneSource;
diff --git a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs
index 209a89ea37b..1456679c7bd 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs
@@ -10,6 +10,7 @@
using System.Windows.Data;
using System.Windows.Input;
using Dynamo.Configuration;
+using Dynamo.Controls;
using Dynamo.Engine;
using Dynamo.Graph;
using Dynamo.Graph.Annotations;
@@ -560,6 +561,8 @@ internal GeometryScalingViewModel GeoScalingViewModel
}
}
+ private bool FinishedLoading = false;
+
#endregion
public WorkspaceViewModel(WorkspaceModel model, DynamoViewModel dynamoViewModel)
@@ -634,6 +637,13 @@ public WorkspaceViewModel(WorkspaceModel model, DynamoViewModel dynamoViewModel)
foreach (AnnotationModel annotation in Model.Annotations) Model_AnnotationAdded(annotation);
foreach (ConnectorModel connector in Model.Connectors) Connectors_ConnectorAdded(connector);
+ FinishedLoading = true;
+
+ NodeAutoCompleteSearchViewModel = new NodeAutoCompleteSearchViewModel(DynamoViewModel)
+ {
+ Visible = true
+ };
+
geoScalingViewModel = new GeometryScalingViewModel(this.DynamoViewModel);
geoScalingViewModel.ScaleValue = Convert.ToInt32(Math.Log10(Model.ScaleFactor));
@@ -879,6 +889,11 @@ private void Model_NoteAdded(NoteModel note)
{
var viewModel = new NoteViewModel(this, note);
Notes.Add(viewModel);
+
+ if (FinishedLoading)
+ {
+ viewModel.IsVisibleInCanvas = true;
+ }
}
private void Model_NoteRemoved(NoteModel note)
@@ -901,6 +916,11 @@ private void Model_AnnotationAdded(AnnotationModel annotation)
{
var viewModel = new AnnotationViewModel(this, annotation);
Annotations.Add(viewModel);
+
+ if (FinishedLoading)
+ {
+ viewModel.IsVisibleInCanvas = true;
+ }
}
private void Model_AnnotationRemoved(AnnotationModel annotation)
@@ -967,12 +987,21 @@ void Model_NodeAdded(NodeModel node)
var nodeViewModel = new NodeViewModel(this, node);
nodeViewModel.SnapInputEvent += nodeViewModel_SnapInputEvent;
nodeViewModel.NodeLogic.Modified += OnNodeModified;
+
lock (Nodes)
{
Nodes.Add(nodeViewModel);
}
if (nodeViewModel.ErrorBubble != null)
+ {
+ nodeViewModel.ErrorBubble.IsVisibleInCanvas = FinishedLoading;
Errors.Add(nodeViewModel.ErrorBubble);
+ }
+
+ if (FinishedLoading)
+ {
+ nodeViewModel.IsVisibleInCanvas = true;
+ }
PostNodeChangeActions();
@@ -1923,7 +1952,38 @@ internal IEnumerable GetViewModelsInternal(IEnumerable mode
return foundModels;
}
-
+ internal void UpdateWorkspaceElementVisibility(double visibleWidth, double visibleHeight)
+ {
+ var width = visibleWidth / Zoom;
+ var height = visibleHeight / Zoom;
+ var left = -X / Zoom;
+ var top = -Y / Zoom;
+
+ DeferredContent.Focus(left + width/2, top + height/2);
+
+ var visibleRect = new Rect2D(left, top, width, height);
+ var itemsToTest = Nodes.Cast()
+ .Concat(Notes)
+ .Concat(Annotations);
+
+ foreach (var item in itemsToTest)
+ {
+ item.IsVisibleInCanvas = visibleRect.IntersectsWith(item.Rect);
+
+ if (item is NodeViewModel nvm && item.IsVisibleInCanvas && nvm.ErrorBubble != null)
+ {
+ nvm.ErrorBubble.IsVisibleInCanvas = true;
+ }
+ }
+
+#if DEBUG
+ var total = Nodes.Count + Notes.Count + Annotations.Count;
+ var hidden = Nodes.Count(n => !n.IsVisibleInCanvas) +
+ Notes.Count(n => !n.IsVisibleInCanvas) +
+ Annotations.Count(a => !a.IsVisibleInCanvas);
+ Debug.WriteLine($"{hidden}/{total} workspace elements hidden");
+#endif
+ }
}
public class ViewModelEventArgs : EventArgs
diff --git a/src/DynamoCoreWpf/ViewModels/Preview/InfoBubbleViewModel.cs b/src/DynamoCoreWpf/ViewModels/Preview/InfoBubbleViewModel.cs
index 7961145c27c..daa420d1f1c 100644
--- a/src/DynamoCoreWpf/ViewModels/Preview/InfoBubbleViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Preview/InfoBubbleViewModel.cs
@@ -4,7 +4,9 @@
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
+using Dynamo.Controls;
using Dynamo.Logging;
+using Dynamo.Utilities;
using Dynamo.Wpf.ViewModels.Core;
namespace Dynamo.ViewModels
@@ -26,7 +28,7 @@ public InfoBubbleEventArgs(Request request)
public delegate void InfoBubbleEventHandler(object sender, InfoBubbleEventArgs e);
- public partial class InfoBubbleViewModel : ViewModelBase
+ public partial class InfoBubbleViewModel : ViewModelBase, IWorkspaceElement
{
public enum Style
{
@@ -444,7 +446,23 @@ public bool NodeWarningsIteratorVisible
/// and cannot have their Width (or Visibility) set manually.
///
public bool NodeErrorsIteratorVisible => GetMessagesOfStyle(NodeMessages, Style.Error).Count > 1;
-
+
+ ///
+ /// This signifies if the node should be rendered
+ ///
+ public bool IsVisibleInCanvas
+ {
+ get => isVisibleInCanvas;
+ set
+ {
+ isVisibleInCanvas = value;
+ RaisePropertyChanged(nameof(isVisibleInCanvas));
+ }
+ }
+ private bool isVisibleInCanvas = false;
+
+ public Rect2D Rect => default;
+
#endregion
#region Event Handlers
diff --git a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml
index 7d3f61daa72..35c86b4419a 100644
--- a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml
+++ b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml
@@ -185,6 +185,9 @@
Value="{Binding Top}" />
+
diff --git a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs
index a64660908c0..3afabced695 100644
--- a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs
+++ b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs
@@ -149,6 +149,8 @@ private void RemoveViewModelsubscriptions(WorkspaceViewModel ViewModel)
ViewModel.Model.CurrentOffsetChanged -= vm_CurrentOffsetChanged;
DynamoSelection.Instance.Selection.CollectionChanged -= OnSelectionCollectionChanged;
infiniteGridView.DetachFromZoomBorder(zoomBorder);
+
+ zoomBorder.ViewSettingsChanged -= CallUpdateWorkspaceElementVisibility;
}
///
@@ -178,6 +180,24 @@ private void AttachViewModelsubscriptions(WorkspaceViewModel ViewModel)
ViewModel.Model.CurrentOffsetChanged += vm_CurrentOffsetChanged;
DynamoSelection.Instance.Selection.CollectionChanged += OnSelectionCollectionChanged;
infiniteGridView.AttachToZoomBorder(zoomBorder);
+
+ zoomBorder.ViewSettingsChanged += CallUpdateWorkspaceElementVisibility;
+ // load the initially visible nodes as soon as the context is loaded
+ Dispatcher.BeginInvoke(UpdateWorkspaceElementVisibility, System.Windows.Threading.DispatcherPriority.ContextIdle);
+ }
+
+ private void CallUpdateWorkspaceElementVisibility(ViewSettingsChangedEventArgs _) => UpdateWorkspaceElementVisibility();
+
+ private void UpdateWorkspaceElementVisibility()
+ {
+ double visibleWidth = outerCanvas?.ActualWidth ?? 0;
+ double visibleHeight = outerCanvas?.ActualHeight ?? 0;
+ if (visibleWidth <= 0 || visibleHeight <= 0)
+ {
+ return;
+ }
+
+ ViewModel.UpdateWorkspaceElementVisibility(visibleWidth, visibleHeight);
}
private void ShowHideNodeAutoCompleteControl(ShowHideFlags flag)