From 905d976fdcec7e8aa8c84b9fa3f23b8af6ad0e8e Mon Sep 17 00:00:00 2001 From: aparajit-pratap Date: Mon, 2 Nov 2020 21:53:46 -0500 Subject: [PATCH] Fix autocomplete popup - #3 (#11224) * fix autocomplete popup - part 3 * revert unchanged file * fix failing test, fix analytics for node autocomplete * add null check * review comments --- .../NodeAutoCompleteSearchControl.xaml.cs | 6 +-- .../ViewModels/Core/NodeViewModel.cs | 3 ++ .../ViewModels/Core/PortViewModel.cs | 42 ++++++++++--------- .../ViewModels/Core/WorkspaceViewModel.cs | 2 +- src/DynamoCoreWpf/Views/Core/NodeView.xaml.cs | 3 ++ .../Views/Core/WorkspaceView.xaml | 1 + .../Views/Core/WorkspaceView.xaml.cs | 13 +++++- test/DynamoCoreWpfTests/CoreUITests.cs | 20 --------- .../NodeAutoCompleteSearchTests.cs | 23 +++++++--- 9 files changed, 60 insertions(+), 53 deletions(-) diff --git a/src/DynamoCoreWpf/Controls/NodeAutoCompleteSearchControl.xaml.cs b/src/DynamoCoreWpf/Controls/NodeAutoCompleteSearchControl.xaml.cs index 03a7098fad0..d0c9c127d77 100644 --- a/src/DynamoCoreWpf/Controls/NodeAutoCompleteSearchControl.xaml.cs +++ b/src/DynamoCoreWpf/Controls/NodeAutoCompleteSearchControl.xaml.cs @@ -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) diff --git a/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs index 2418b5926a0..9a602ab30a4 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs @@ -563,6 +563,9 @@ public double Y } } + internal double ActualHeight { get; set; } + internal double ActualWidth { get; set; } + #endregion #region events diff --git a/src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs index 9b0ddd4b6b8..223b23698cc 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs @@ -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; /// /// Port model. @@ -230,38 +230,40 @@ public override void Dispose() } /// - /// 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. /// - /// - /// - internal void PlaceNodeAutocompleteWindow(object sender, EventArgs e) + /// Node autocomplete popup. + 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) @@ -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); diff --git a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs index ebdaf5fd06b..0f9583d619f 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs @@ -238,7 +238,7 @@ public DynamoPreferencesData DynamoPreferences /// ViewModel that is used in NodeAutoComplete feature in context menu and called by Shift+DoubleClick. /// [JsonIgnore] - public SearchViewModel NodeAutoCompleteSearchViewModel { get; private set; } + public NodeAutoCompleteSearchViewModel NodeAutoCompleteSearchViewModel { get; private set; } /// /// Cursor Property Binding for WorkspaceView diff --git a/src/DynamoCoreWpf/Views/Core/NodeView.xaml.cs b/src/DynamoCoreWpf/Views/Core/NodeView.xaml.cs index cf37c23c32c..5edfbb93a1f 100644 --- a/src/DynamoCoreWpf/Views/Core/NodeView.xaml.cs +++ b/src/DynamoCoreWpf/Views/Core/NodeView.xaml.cs @@ -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) diff --git a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml index 9e2da2510f2..d29341f147f 100644 --- a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml +++ b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml @@ -349,6 +349,7 @@ StaysOpen="True" AllowsTransparency="True" IsOpen="False" + Placement="Custom" DataContext="{Binding NodeAutoCompleteSearchViewModel}"> diff --git a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs index 117e109bc31..254b97ba5d8 100644 --- a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs +++ b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs @@ -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; @@ -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; + ViewModel.InCanvasSearchViewModel.SearchText = string.Empty; ViewModel.InCanvasSearchViewModel.InCanvasSearchPosition = inCanvasSearchPosition; break; diff --git a/test/DynamoCoreWpfTests/CoreUITests.cs b/test/DynamoCoreWpfTests/CoreUITests.cs index 1865951c907..2dc20cb61a5 100644 --- a/test/DynamoCoreWpfTests/CoreUITests.cs +++ b/test/DynamoCoreWpfTests/CoreUITests.cs @@ -886,26 +886,6 @@ public void WorkspaceContextMenu_IfSubmenuOpenOnMouseHover() Assert.IsTrue(currentWs.WorkspaceLacingMenu.IsSubmenuOpen); } - [Test] - [Category("UnitTests")] - public void ShowHideNodeAutoCompleteSearchControl() - { - var currentWs = View.ChildOfType(); - - // 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) diff --git a/test/DynamoCoreWpfTests/NodeAutoCompleteSearchTests.cs b/test/DynamoCoreWpfTests/NodeAutoCompleteSearchTests.cs index 9c07e909ff3..8a56b42c329 100644 --- a/test/DynamoCoreWpfTests/NodeAutoCompleteSearchTests.cs +++ b/test/DynamoCoreWpfTests/NodeAutoCompleteSearchTests.cs @@ -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; @@ -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()); @@ -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(); + Assert.IsTrue(currentWs.NodeAutoCompleteSearchBar.IsOpen); + + // Hide Node AutoCompleteSearchBar + ViewModel.CurrentSpaceViewModel.OnRequestNodeAutoCompleteSearch(ShowHideFlags.Hide); + Assert.IsFalse(currentWs.NodeAutoCompleteSearchBar.IsOpen); } [Test] @@ -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()); @@ -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(); @@ -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()); @@ -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 @@ -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