Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,9 @@ public NodeAutoCompleteSearchControl()

private void NodeAutoCompleteSearchControl_Loaded(object sender, RoutedEventArgs e)
{
if (ViewModel != null && ViewModel.PortViewModel != null)
{
ViewModel.PortViewModel.PlaceNodeAutocompleteWindow(this, e);
Analytics.TrackEvent(
Analytics.TrackEvent(
Dynamo.Logging.Actions.Open,
Dynamo.Logging.Categories.NodeAutoCompleteOperations);
}
}

private void NodeAutoCompleteSearchControl_Unloaded(object sender, RoutedEventArgs e)
Expand Down
3 changes: 3 additions & 0 deletions src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,9 @@ public double Y
}
}

internal double ActualHeight { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, can you explain why these were needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These were needed to get the exact height and width from the node view and exposed to the portviewmodel via the nodeviewmodel. The width and height are needed to compute the popup placement positioning.

internal double ActualWidth { get; set; }

#endregion

#region events
Expand Down
42 changes: 22 additions & 20 deletions src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public partial class PortViewModel : ViewModelBase
private readonly NodeViewModel _node;
private DelegateCommand _useLevelsCommand;
private DelegateCommand _keepListStructureCommand;
private const double autocompleteUISpacing = 2.5;
private const double autocompletePopupSpacing = 2.5;

/// <summary>
/// Port model.
Expand Down Expand Up @@ -230,38 +230,40 @@ public override void Dispose()
}

/// <summary>
/// Places the node autocomplete window relative to the respective port
/// once the control is loaded and when its actual width is known.
/// The UI is first placed w.r.t to the X, Y position of the node (to which the port belongs),
/// then offset from that based on the port, the width of the UI itself and in some cases, the node width.
/// Sets up the node autocomplete window to be placed relative to the node.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
internal void PlaceNodeAutocompleteWindow(object sender, EventArgs e)
/// <param name="popup">Node autocomplete popup.</param>
internal void SetupNodeAutocompleteWindowPlacement(Popup popup)
{
var control = sender as NodeAutoCompleteSearchControl;
var popup = control.Parent as Popup;

_node.OnRequestAutoCompletePopupPlacementTarget(popup);
popup.CustomPopupPlacementCallback = PlaceAutocompletePopup;
}

private CustomPopupPlacement[] PlaceAutocompletePopup(Size popupSize, Size targetSize, Point offset)
{
var zoom = _node.WorkspaceViewModel.Zoom;

double x;
var scaledSpacing = autocompletePopupSpacing * targetSize.Width / _node.ActualWidth;
if (PortModel.PortType == PortType.Input)
{
// Offset popup to the left by its width from left edge of node and constant spacing.
// Note: MinWidth property of the control is set to a constant value in the XAML
// for the ActualWidth to return a consistent value.
x = -autocompleteUISpacing - control.ActualWidth / zoom;
// Offset popup to the left by its width from left edge of node and spacing.
x = -scaledSpacing - popupSize.Width;
}
else
{
// Offset popup to the right by node width from left edge of node.
x = autocompleteUISpacing + PortModel.Owner.Width;
// Offset popup to the right by node width and spacing from left edge of node.
x = scaledSpacing + targetSize.Width;
}
// Offset popup down from the upper edge of the node by the node header and corresponding to the respective port.
var y = NodeModel.HeaderHeight + PortModel.Index * PortModel.Height;
popup.PlacementRectangle = new Rect(x, y, 0, 0);
// Scale the absolute heights by the target height (passed to the callback) and the actual height of the node.
var scaledHeight = targetSize.Height / _node.ActualHeight;
var absoluteHeight = NodeModel.HeaderHeight + PortModel.Index * PortModel.Height;
var y = absoluteHeight * scaledHeight;

var placement = new CustomPopupPlacement(new Point(x, y), PopupPrimaryAxis.None);

return new[] { placement };
}

private void Workspace_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
Expand Down Expand Up @@ -406,7 +408,7 @@ private bool CanConnect(object parameter)
private void AutoComplete(object parameter)
{
var wsViewModel = _node.WorkspaceViewModel;
var svm = wsViewModel.NodeAutoCompleteSearchViewModel as NodeAutoCompleteSearchViewModel;
var svm = wsViewModel.NodeAutoCompleteSearchViewModel;
svm.PortViewModel = this;

wsViewModel.OnRequestNodeAutoCompleteSearch(ShowHideFlags.Show);
Expand Down
2 changes: 1 addition & 1 deletion src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public DynamoPreferencesData DynamoPreferences
/// ViewModel that is used in NodeAutoComplete feature in context menu and called by Shift+DoubleClick.
/// </summary>
[JsonIgnore]
public SearchViewModel NodeAutoCompleteSearchViewModel { get; private set; }
public NodeAutoCompleteSearchViewModel NodeAutoCompleteSearchViewModel { get; private set; }

/// <summary>
/// Cursor Property Binding for WorkspaceView
Expand Down
3 changes: 3 additions & 0 deletions src/DynamoCoreWpf/Views/Core/NodeView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ private void CachedValueChanged()
private void ViewModel_RequestAutoCompletePopupPlacementTarget(Popup popup)
{
popup.PlacementTarget = this;

ViewModel.ActualHeight = ActualHeight;
ViewModel.ActualWidth = ActualWidth;
}

void ViewModel_RequestsSelection(object sender, EventArgs e)
Expand Down
1 change: 1 addition & 0 deletions src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@
StaysOpen="True"
AllowsTransparency="True"
IsOpen="False"
Placement="Custom"
DataContext="{Binding NodeAutoCompleteSearchViewModel}">
<ui:NodeAutoCompleteSearchControl RequestShowNodeAutoCompleteSearch="ShowHideNodeAutoCompleteControl" />
</Popup>
Expand Down
13 changes: 12 additions & 1 deletion src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
using Dynamo.Graph.Nodes;
using Dynamo.Graph.Notes;
using Dynamo.Graph.Workspaces;
using Dynamo.Logging;
using Dynamo.Models;
using Dynamo.Search.SearchElements;
using Dynamo.Selection;
using Dynamo.UI;
using Dynamo.UI.Controls;
using Dynamo.Utilities;
using Dynamo.ViewModels;
using Dynamo.Wpf.UI;
Expand Down Expand Up @@ -186,7 +188,16 @@ private void ShowHidePopup(ShowHideFlags flag, Popup popup)
break;
case ShowHideFlags.Show:
// Show InCanvas search just in case, when mouse is over workspace.
popup.IsOpen = DynamoModel.IsTestMode || IsMouseOver;
var displayPopup = DynamoModel.IsTestMode || IsMouseOver;
if (displayPopup && popup == NodeAutoCompleteSearchBar)
{
if (ViewModel.NodeAutoCompleteSearchViewModel.PortViewModel == null) return;

ViewModel.NodeAutoCompleteSearchViewModel.PortViewModel.SetupNodeAutocompleteWindowPlacement(popup);
}
popup.IsOpen = displayPopup;
popup.CustomPopupPlacementCallback = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a must have or just sanity cleanup?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, otherwise the CustomPopupPlacementCallback continues pointing to PlaceAutocompletePopup, which can be invoked unexpectedly, say when in-canvas search popup is opened.


ViewModel.InCanvasSearchViewModel.SearchText = string.Empty;
ViewModel.InCanvasSearchViewModel.InCanvasSearchPosition = inCanvasSearchPosition;
break;
Expand Down
20 changes: 0 additions & 20 deletions test/DynamoCoreWpfTests/CoreUITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -886,26 +886,6 @@ public void WorkspaceContextMenu_IfSubmenuOpenOnMouseHover()
Assert.IsTrue(currentWs.WorkspaceLacingMenu.IsSubmenuOpen);
}

[Test]
[Category("UnitTests")]
public void ShowHideNodeAutoCompleteSearchControl()
{
var currentWs = View.ChildOfType<WorkspaceView>();

// Show Node AutoCompleteSearchBar
ViewModel.CurrentSpaceViewModel.OnRequestNodeAutoCompleteSearch(ShowHideFlags.Show);
Assert.IsTrue(currentWs.NodeAutoCompleteSearchBar.IsOpen);

RightClick(currentWs.zoomBorder);
// Notice AutoCompleteSearchBar can co-exist with right click search for now
Assert.IsTrue(currentWs.ContextMenuPopup.IsOpen);
Assert.IsTrue(currentWs.NodeAutoCompleteSearchBar.IsOpen);

// Hide Node AutoCompleteSearchBar
ViewModel.CurrentSpaceViewModel.OnRequestNodeAutoCompleteSearch(ShowHideFlags.Hide);
Assert.IsFalse(currentWs.NodeAutoCompleteSearchBar.IsOpen);
}

private void RightClick(IInputElement element)
{
element.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Right)
Expand Down
23 changes: 17 additions & 6 deletions test/DynamoCoreWpfTests/NodeAutoCompleteSearchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
using Dynamo.Graph.Nodes;
using Dynamo.Models;
using Dynamo.Search.SearchElements;
using Dynamo.Utilities;
using Dynamo.ViewModels;
using Dynamo.Views;
using DynamoCoreWpfTests.Utility;
using NUnit.Framework;

Expand Down Expand Up @@ -78,7 +80,7 @@ public void NodeSuggestions_InputPortZeroTouchNode_AreCorrect()
type = port.GetInputPortType();
Assert.AreEqual("FFITarget.DummyVector", type);

var searchViewModel = (ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel as NodeAutoCompleteSearchViewModel);
var searchViewModel = ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel;
searchViewModel.PortViewModel = inPorts[1];
var suggestions = searchViewModel.GetMatchingSearchElements();
Assert.AreEqual(5, suggestions.Count());
Expand All @@ -91,6 +93,15 @@ public void NodeSuggestions_InputPortZeroTouchNode_AreCorrect()
{
Assert.AreEqual(expectedNodes.ElementAt(i), suggestedNodes.ElementAt(i));
}

// Show Node AutoCompleteSearchBar
ViewModel.CurrentSpaceViewModel.OnRequestNodeAutoCompleteSearch(ShowHideFlags.Show);
var currentWs = View.ChildOfType<WorkspaceView>();
Assert.IsTrue(currentWs.NodeAutoCompleteSearchBar.IsOpen);

// Hide Node AutoCompleteSearchBar
ViewModel.CurrentSpaceViewModel.OnRequestNodeAutoCompleteSearch(ShowHideFlags.Hide);
Assert.IsFalse(currentWs.NodeAutoCompleteSearchBar.IsOpen);
}

[Test]
Expand All @@ -108,7 +119,7 @@ public void NodeSuggestions_InputPortZeroTouchNodeForProperty_AreCorrect()
var type = port.GetInputPortType();
Assert.AreEqual("FFITarget.ClassFunctionality", type);

var searchViewModel = (ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel as NodeAutoCompleteSearchViewModel);
var searchViewModel = ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel;
searchViewModel.PortViewModel = inPorts[0];
var suggestions = searchViewModel.GetMatchingSearchElements();
Assert.AreEqual(4, suggestions.Count());
Expand All @@ -134,7 +145,7 @@ public void NodeSuggestions_GeometryNodes_SortedBy_NodeGroup_CreateActionQuery()
node, 0, 0, true, false));
DispatcherUtil.DoEvents();
var nodeView = NodeViewWithGuid(node.GUID.ToString());
var searchViewModel = (ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel as NodeAutoCompleteSearchViewModel);
var searchViewModel = ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel;
searchViewModel.PortViewModel = nodeView.ViewModel.InPorts.FirstOrDefault();

var suggestions = searchViewModel.GetMatchingSearchElements();
Expand Down Expand Up @@ -163,7 +174,7 @@ public void NodeSuggestions_InputPortBuiltInNode_AreCorrect()
type = port.GetInputPortType();
Assert.AreEqual("string", type);

var searchViewModel = (ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel as NodeAutoCompleteSearchViewModel);
var searchViewModel = ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel;
searchViewModel.PortViewModel = inPorts[0];
var suggestions = searchViewModel.GetMatchingSearchElements();
Assert.AreEqual(0, suggestions.Count());
Expand Down Expand Up @@ -240,7 +251,7 @@ public void NodeSuggestions_DefaultSuggestions()
var type = port.GetInputPortType();
Assert.AreEqual("DSCore.Color[]", type);

var searchViewModel = (ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel as NodeAutoCompleteSearchViewModel);
var searchViewModel = ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel;
searchViewModel.PortViewModel = inPorts[0];

// Running the default algorithm should return no suggestions
Expand All @@ -267,7 +278,7 @@ public void NodeSuggestions_SkippedSuggestions()
var type = port.GetInputPortType();
Assert.AreEqual("double", type);

var searchViewModel = (ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel as NodeAutoCompleteSearchViewModel);
var searchViewModel = ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel;
searchViewModel.PortViewModel = inPorts[0];

// Running the algorithm against skipped nodes should return no suggestions
Expand Down