diff --git a/.gitignore b/.gitignore index 129cbdf7b1b..8525f1d6c38 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,10 @@ launchSettings.json ############### *_ReSharper* +# IDE # +############### +.idea + #documentation files doc/html/ Dynamo.sln.DotSettings diff --git a/doc/distrib/NodeHelpFiles/CoreNodeModels.Input.CustomSelection.md b/doc/distrib/NodeHelpFiles/CoreNodeModels.Input.CustomSelection.md new file mode 100644 index 00000000000..ba539d425d1 --- /dev/null +++ b/doc/distrib/NodeHelpFiles/CoreNodeModels.Input.CustomSelection.md @@ -0,0 +1,6 @@ +## In Depth +The Custom Dropdown node allows a user to create a dropdown selection input with custom labels and values. If all values are numbers, the output will be a double, and if all values are integers the output will be an integer. In the example below, "Two" is selected in the Custom Dropdown Menu node, making the output of that node the integer `2`. +___ +## Example File + +![Number](./CoreNodeModels.Input.CustomSelection_img.jpg) diff --git a/doc/distrib/NodeHelpFiles/CoreNodeModels.Input.CustomSelection_img.jpg b/doc/distrib/NodeHelpFiles/CoreNodeModels.Input.CustomSelection_img.jpg new file mode 100644 index 00000000000..f5792094ea9 Binary files /dev/null and b/doc/distrib/NodeHelpFiles/CoreNodeModels.Input.CustomSelection_img.jpg differ diff --git a/doc/distrib/NodeHelpFiles/CoreNodeModels.Input.DoubleInput.md b/doc/distrib/NodeHelpFiles/CoreNodeModels.Input.DoubleInput.md index 89356f5ed35..62b375d3d82 100644 --- a/doc/distrib/NodeHelpFiles/CoreNodeModels.Input.DoubleInput.md +++ b/doc/distrib/NodeHelpFiles/CoreNodeModels.Input.DoubleInput.md @@ -1,5 +1,5 @@ ## In Depth -Room numbers are returned as strings. +The Number node allows a user to enter a static number. This node is useful for things like constants that don't change values. Numbers can also be created by using a Code Block node. In the example below, the number is paired with a code block to create a list sequence with a variable step size. ___ ## Example File diff --git a/src/DynamoCoreWpf/Controls/DynamoNodeButton.cs b/src/DynamoCoreWpf/Controls/DynamoNodeButton.cs index 7cd72b777eb..a0e0f49c675 100644 --- a/src/DynamoCoreWpf/Controls/DynamoNodeButton.cs +++ b/src/DynamoCoreWpf/Controls/DynamoNodeButton.cs @@ -30,7 +30,6 @@ private DynamoViewModel DynamoViewModel public DynamoNodeButton() { - Style = (Style)SharedDictionaryManager.DynamoModernDictionary["SNodeTextButton"]; } public DynamoNodeButton(ModelBase model, string eventName) diff --git a/src/DynamoCoreWpf/UI/Themes/Modern/DynamoModern.xaml b/src/DynamoCoreWpf/UI/Themes/Modern/DynamoModern.xaml index 7d01c63f4b8..c652049586d 100644 --- a/src/DynamoCoreWpf/UI/Themes/Modern/DynamoModern.xaml +++ b/src/DynamoCoreWpf/UI/Themes/Modern/DynamoModern.xaml @@ -1,11 +1,11 @@ - @@ -1414,20 +1414,28 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Engine/ProtoCore/Utils/CoreUtils.cs b/src/Engine/ProtoCore/Utils/CoreUtils.cs index d64fd6a4e63..695203d4c10 100644 --- a/src/Engine/ProtoCore/Utils/CoreUtils.cs +++ b/src/Engine/ProtoCore/Utils/CoreUtils.cs @@ -966,7 +966,8 @@ public static bool IsPrimitiveASTNode(AssociativeNode node) if (node is IntNode || node is DoubleNode || node is BooleanNode - || node is StringNode) + || node is StringNode + || node is NullNode) { return true; } @@ -1001,4 +1002,4 @@ public static StackValue BuildStackValueForPrimitive(AssociativeNode node, Runti return StackValue.BuildNull(); } } -} \ No newline at end of file +} diff --git a/src/Libraries/CoreNodeModels/CoreNodeModelsImages.resx b/src/Libraries/CoreNodeModels/CoreNodeModelsImages.resx index 9c40a7b7aaf..efb40687d82 100644 --- a/src/Libraries/CoreNodeModels/CoreNodeModelsImages.resx +++ b/src/Libraries/CoreNodeModels/CoreNodeModelsImages.resx @@ -1343,6 +1343,39 @@ PIw7vteQPuhLVMkA479rRIQ9hegBi3gVlTPYvr1bMcEHxT5Ginf+R2dm53gHaDPpF5En0WZeR3VH6qT1 trlz75fwK2w6VIVFBQ7AJvsESZk6BO/iW/E+9u9A57xW7BNsVoaQqfvVwBky/FPkch8Yc7bTvUnhewAA AABJRU5ErkJggg== + + + + + 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 \ No newline at end of file diff --git a/src/Libraries/CoreNodeModels/Input/CustomSelection.cs b/src/Libraries/CoreNodeModels/Input/CustomSelection.cs new file mode 100644 index 00000000000..923bf829974 --- /dev/null +++ b/src/Libraries/CoreNodeModels/Input/CustomSelection.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Xml; + +using Autodesk.DesignScript.Runtime; + +using Dynamo.Graph; +using Dynamo.Graph.Nodes; + +using Newtonsoft.Json; + +using ProtoCore.AST.AssociativeAST; + +namespace CoreNodeModels.Input +{ + /// + /// This node allows the user to create a dropdown selection list with an arbitrary number of custom items. + /// + [NodeName("Custom Selection")] + [NodeCategory(BuiltinNodeCategories.CORE_INPUT)] + [NodeDescription("CustomSelectionNodeDescription", typeof(Properties.Resources))] + [NodeSearchTags("CustomSelectionSearchTags", typeof(Properties.Resources))] + [OutPortNames("value")] + [OutPortTypes("var")] + [OutPortDescriptions(typeof(Properties.Resources), "CustomSelectionOutputDescription")] + [IsDesignScriptCompatible] + public class CustomSelection : DSDropDownBase + { + private List serializedItems; + + /// + /// Copy of to be serialized./> + /// + [JsonProperty] + protected List SerializedItems + { + get => serializedItems; + set + { + serializedItems = value; + + Items.Clear(); + + foreach (DynamoDropDownItem item in serializedItems) + { + Items.Add(item); + } + } + } + + /// + /// Construct a new Custom Dropdown Menu node + /// + public CustomSelection() : base("Value") + { + ArgumentLacing = LacingStrategy.Disabled; + + Items.Add(new DynamoDropDownItem("One", "1")); + Items.Add(new DynamoDropDownItem("Two", "2")); + Items.Add(new DynamoDropDownItem("Three", "3")); + + SelectedIndex = 0; + } + + [JsonConstructor] + protected CustomSelection(IEnumerable inPorts, IEnumerable outPorts) : base("Value", inPorts, outPorts) + { + } + + /// + /// Build the AST for this node + /// + /// + /// + [IsVisibleInDynamoLibrary(false)] + public override IEnumerable BuildOutputAst(List inputAstNodes) + { + AssociativeNode associativeNode = AstFactory.BuildPrimitiveNodeFromObject(GetSelectedValue()); + + return new[] { AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), associativeNode) }; + } + + /// + /// Return the selected item as an int, or a double, or a string + /// + /// + private object GetSelectedValue() + { + if (SelectedIndex == -1) + { + return null; + } + + DynamoDropDownItem selectedItem = Items[SelectedIndex]; + + if (selectedItem?.Item is string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return value; + } + + if (Items.All(item => item is null || ( item.Item is string v && ( string.IsNullOrEmpty(v) || int.TryParse(v, out _) ) ))) + { + int.TryParse(value, out int intValue); + return intValue; + } + + if (Items.All(item => item is null || ( item.Item is string v && ( string.IsNullOrEmpty(v) || double.TryParse(v, out _) ) ))) + { + double.TryParse(value, out double doubleValue); + return doubleValue; + } + + return value; + } + + return selectedItem?.Item; + } + + protected override SelectionState PopulateItemsCore(string currentSelection) + { + return SelectionState.Restore; + } + + [OnSerializing] + private void OnSerializing(StreamingContext context) + { + serializedItems = Items.ToList(); + } + + [Obsolete] + protected override void SerializeCore(XmlElement nodeElement, SaveContext context) + { + nodeElement.SetAttribute("serializedItems", JsonConvert.SerializeObject(Items)); + + base.SerializeCore(nodeElement, context); + } + + [Obsolete] + protected override void DeserializeCore(XmlElement nodeElement, SaveContext context) + { + XmlAttribute itemsAttribute = nodeElement.Attributes["serializedItems"]; + + if (itemsAttribute == null) + { + return; + } + + List items = JsonConvert.DeserializeObject>(itemsAttribute.Value); + + Items.Clear(); + + foreach (DynamoDropDownItem item in items) + { + Items.Add(item); + } + + base.DeserializeCore(nodeElement, context); + } + + } +} \ No newline at end of file diff --git a/src/Libraries/CoreNodeModels/Properties/Resources.Designer.cs b/src/Libraries/CoreNodeModels/Properties/Resources.Designer.cs index f2570297243..16cacceb42f 100644 --- a/src/Libraries/CoreNodeModels/Properties/Resources.Designer.cs +++ b/src/Libraries/CoreNodeModels/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace CoreNodeModels.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -258,6 +258,33 @@ public static string CreateListPortDataResultToolTip { } } + /// + /// Looks up a localized string similar to A dropdown menu with customizable values.. + /// + public static string CustomSelectionNodeDescription { + get { + return ResourceManager.GetString("CustomSelectionNodeDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Selected value. + /// + public static string CustomSelectionOutputDescription { + get { + return ResourceManager.GetString("CustomSelectionOutputDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to dropdown;custom;custom dropdown;enum;custom enum. + /// + public static string CustomSelectionSearchTags { + get { + return ResourceManager.GetString("CustomSelectionSearchTags", resourceCulture); + } + } + /// /// Looks up a localized string similar to Create a DateTime object from a formatted date and time string. Date and time must be of the format "April 12, 1977 12:00 PM". /// diff --git a/src/Libraries/CoreNodeModels/Properties/Resources.en-US.resx b/src/Libraries/CoreNodeModels/Properties/Resources.en-US.resx index be8609e1dfa..8a76aac8352 100644 --- a/src/Libraries/CoreNodeModels/Properties/Resources.en-US.resx +++ b/src/Libraries/CoreNodeModels/Properties/Resources.en-US.resx @@ -617,10 +617,19 @@ Default value: {0} This node has been updated and will be removed in a future version of Dynamo. Existing behavior is retained, but a new version now supports Empty Lists, Null values and inputs of varying length. Please replace this node if you wish to use this improved behavior. + + dropdown;custom;custom dropdown;enum;custom enum + + + A dropdown menu with customizable values. + is obsolete, please use the new Convert Units node. The value entered is not in the int64 range. + + Selected value + \ No newline at end of file diff --git a/src/Libraries/CoreNodeModels/Properties/Resources.resx b/src/Libraries/CoreNodeModels/Properties/Resources.resx index 2867df7c14f..a80fdc7dbde 100644 --- a/src/Libraries/CoreNodeModels/Properties/Resources.resx +++ b/src/Libraries/CoreNodeModels/Properties/Resources.resx @@ -620,7 +620,16 @@ Default value: {0} is obsolete, please use the new Convert Units node. + + A dropdown menu with customizable values. + + + dropdown;custom;custom dropdown;enum;custom enum + The value entered is not in the int64 range. + + Selected value + \ No newline at end of file diff --git a/src/Libraries/CoreNodeModelsWpf/Controls/CustomSelectionControl.xaml b/src/Libraries/CoreNodeModelsWpf/Controls/CustomSelectionControl.xaml new file mode 100644 index 00000000000..e32427a7cd7 --- /dev/null +++ b/src/Libraries/CoreNodeModelsWpf/Controls/CustomSelectionControl.xaml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Libraries/CoreNodeModelsWpf/Controls/CustomSelectionControl.xaml.cs b/src/Libraries/CoreNodeModelsWpf/Controls/CustomSelectionControl.xaml.cs new file mode 100644 index 00000000000..cf4a8154eea --- /dev/null +++ b/src/Libraries/CoreNodeModelsWpf/Controls/CustomSelectionControl.xaml.cs @@ -0,0 +1,83 @@ +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +using CoreNodeModels.Input; + +using Dynamo.Utilities; + +namespace CoreNodeModelsWpf.Controls +{ + /// + /// Interaction logic for CustomSelectionControl.xaml + /// + public partial class CustomSelectionControl : UserControl + { + private readonly CustomSelection model; + + /// + /// Create the control for the custom dropdown menu and editor + /// + public CustomSelectionControl(CustomSelectionViewModel viewModel) + { + DataContext = viewModel; + model = viewModel.Model; + + InitializeComponent(); + } + + /// + /// Dropdown menu to select an item. Created by . + /// + internal ComboBox BaseComboBox { get; set; } + + private void EnumItemsListbox_PreviewKeyDown(object sender, KeyEventArgs e) + { + if (!( sender is ListBox listBox )) + { + return; + } + + if (e.Key == Key.Tab) + { + int offset = ( e.KeyboardDevice.Modifiers == System.Windows.Input.ModifierKeys.Shift ) ? + 0 : 1; + var textBoxes = listBox.ChildrenOfType().ToList(); + + for (int i = 0; i < textBoxes.Count; i++) + { + if (textBoxes[i].IsKeyboardFocused) + { + TextBox nextBox = textBoxes[( i + offset ) % textBoxes.Count]; + nextBox.Focus(); + nextBox.CaretIndex = nextBox.Text.Length; + + return; + } + } + } + } + + private void ItemNameChanged(object sender, RoutedEventArgs e) + { + int selectedIndex = BaseComboBox.SelectedIndex; + + if (selectedIndex != -1) + { + // This hack forces the combo box to update the display of the selected item. + BaseComboBox.SelectedIndex = -1; + BaseComboBox.SelectedIndex = selectedIndex; + } + + BaseComboBox?.Items.Refresh(); + } + + private void ItemValueChanged(object sender, RoutedEventArgs e) + { + BaseComboBox?.Items.Refresh(); + + model.OnNodeModified(); + } + } +} \ No newline at end of file diff --git a/src/Libraries/CoreNodeModelsWpf/Controls/DynamoSlider.xaml b/src/Libraries/CoreNodeModelsWpf/Controls/DynamoSlider.xaml index 6ef8dd671c3..d4b104af0e7 100644 --- a/src/Libraries/CoreNodeModelsWpf/Controls/DynamoSlider.xaml +++ b/src/Libraries/CoreNodeModelsWpf/Controls/DynamoSlider.xaml @@ -1,4 +1,4 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/Libraries/CoreNodeModelsWpf/CustomSelectionViewModel.cs b/src/Libraries/CoreNodeModelsWpf/CustomSelectionViewModel.cs new file mode 100644 index 00000000000..bd3c575d3c8 --- /dev/null +++ b/src/Libraries/CoreNodeModelsWpf/CustomSelectionViewModel.cs @@ -0,0 +1,59 @@ +using CoreNodeModels; +using CoreNodeModels.Input; + +using Dynamo.Core; +using Dynamo.UI.Commands; + +namespace CoreNodeModelsWpf +{ + /// + /// View model for , for the Custom Selection node. + /// + public class CustomSelectionViewModel : NotificationObject + { + public CustomSelection Model { get; } + + /// + /// Add an item to the list. This command is bound to the + button in the GUI. + /// + public DelegateCommand AddCommand { get; } + + /// + /// Remove an item from the list. This command is bound to the - button in the GUI. + /// + public DelegateCommand RemoveCommand { get; } + + private void AddItem(object obj) + { + Model.Items.Add(new DynamoDropDownItem(string.Empty, string.Empty)); + } + + private void RemoveItem(object parameter) + { + if (parameter is DynamoDropDownItem item) + { + Model.Items.Remove(item); + Model.OnNodeModified(); + } + } + + /// + /// Create a new . Used by the view in design-time. + /// + public CustomSelectionViewModel() + { + } + + /// + /// Create a new with an existing model. + /// + /// The model data. + public CustomSelectionViewModel(CustomSelection model) + { + Model = model; + + AddCommand = new DelegateCommand(AddItem); + RemoveCommand = new DelegateCommand(RemoveItem); + } + } +} \ No newline at end of file diff --git a/src/Libraries/CoreNodeModelsWpf/NodeViewCustomizations/CustomSelection.cs b/src/Libraries/CoreNodeModelsWpf/NodeViewCustomizations/CustomSelection.cs new file mode 100644 index 00000000000..fe100434466 --- /dev/null +++ b/src/Libraries/CoreNodeModelsWpf/NodeViewCustomizations/CustomSelection.cs @@ -0,0 +1,38 @@ +using Dynamo.Controls; +using CoreNodeModels.Input; +using CoreNodeModelsWpf.Controls; +using System.Windows.Controls; +using System.Windows; +using Dynamo.Wpf; + +namespace CoreNodeModelsWpf.Nodes +{ + /// + /// View customizer for Custom Selection node model. + /// + public class CustomSelectionNodeViewCustomization : DropDownNodeViewCustomization, INodeViewCustomization + { + /// + /// Customize the visual appearance of the custom dropdown node. + /// + /// + /// + public void CustomizeView(CustomSelection model, NodeView nodeView) + { + var formControl = new CustomSelectionControl(new CustomSelectionViewModel(model)); + + nodeView.inputGrid.Children.Add(formControl); + + // Add the dropdown. + base.CustomizeView(model, nodeView); + + var dropdown = (ComboBox)nodeView.inputGrid.Children[1]; + + formControl.BaseComboBox = dropdown; + + // Add margin to the dropdown to show the expander. + dropdown.Margin = new Thickness(40, 0, 0, 0); + dropdown.VerticalAlignment = VerticalAlignment.Top; + } + } +} \ No newline at end of file diff --git a/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.Designer.cs b/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.Designer.cs index 442bc0348ad..4b8f1f5158d 100644 --- a/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.Designer.cs +++ b/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -104,5 +104,23 @@ public static string DateTimeInputToolTip { return ResourceManager.GetString("DateTimeInputToolTip", resourceCulture); } } + + /// + /// Looks up a localized string similar to Display. + /// + public static string LblDisplay { + get { + return ResourceManager.GetString("LblDisplay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value. + /// + public static string LblValue { + get { + return ResourceManager.GetString("LblValue", resourceCulture); + } + } } } diff --git a/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.en-US.resx b/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.en-US.resx index e5718d85d89..a0ae26f9384 100644 --- a/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.en-US.resx +++ b/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.en-US.resx @@ -132,4 +132,10 @@ Enter time and date in the format 'MMMM dd, yyyy h:mm tt' + + Display + + + Value + \ No newline at end of file diff --git a/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.resx b/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.resx index e5718d85d89..a0ae26f9384 100644 --- a/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.resx +++ b/src/Libraries/CoreNodeModelsWpf/Properties/CoreNodeModelWpfResources.resx @@ -132,4 +132,10 @@ Enter time and date in the format 'MMMM dd, yyyy h:mm tt' + + Display + + + Value + \ No newline at end of file diff --git a/src/LibraryViewExtension/web/library/layoutSpecs.json b/src/LibraryViewExtension/web/library/layoutSpecs.json index b7dc0654493..f9408896f4a 100644 --- a/src/LibraryViewExtension/web/library/layoutSpecs.json +++ b/src/LibraryViewExtension/web/library/layoutSpecs.json @@ -443,6 +443,9 @@ }, { "path": "Core.Input.Output" + }, + { + "path": "Core.Input.Custom Selection" } ], "childElements": [] diff --git a/src/LibraryViewExtensionMSWebBrowser/web/library/layoutSpecs.json b/src/LibraryViewExtensionMSWebBrowser/web/library/layoutSpecs.json index 91134b7bc49..4a9ae8f3d1f 100644 --- a/src/LibraryViewExtensionMSWebBrowser/web/library/layoutSpecs.json +++ b/src/LibraryViewExtensionMSWebBrowser/web/library/layoutSpecs.json @@ -443,6 +443,9 @@ }, { "path": "Core.Input.Output" + }, + { + "path": "Core.Input.Custom Selection" } ], "childElements": [] diff --git a/test/DynamoCoreTests/Nodes/CustomDropdownTests.cs b/test/DynamoCoreTests/Nodes/CustomDropdownTests.cs new file mode 100644 index 00000000000..5bf69089fca --- /dev/null +++ b/test/DynamoCoreTests/Nodes/CustomDropdownTests.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; + +using CoreNodeModels; +using CoreNodeModels.Input; + +using Dynamo.PackageManager; + +using NUnit.Framework; + +using SystemTestServices; + +namespace Dynamo.Tests.Nodes +{ + /// + /// Test the creation of a custom selection node. + /// + [TestFixture] + public class CustomDropDownTests : DynamoModelTestBase + { + protected override void GetLibrariesToPreload(List libraries) + { + libraries.Add("DesignScriptBuiltin.dll"); + libraries.Add("DSCoreNodes.dll"); + base.GetLibrariesToPreload(libraries); + } + + [Test] + public void OpenJsonDYNWithCorrectMenuItems() + { + // Define package loading reference path + string dir = SystemTestBase.GetTestDirectory(ExecutingDirectory); + string pkgDir = Path.Combine(dir, "pkgs\\Dynamo Samples"); + PackageManagerExtension pkgMan = CurrentDynamoModel.GetPackageManagerExtension(); + PackageLoader loader = pkgMan.PackageLoader; + Package pkg = loader.ScanPackageDirectory(pkgDir); + + // Load the sample package + loader.LoadPackages(new List { pkg }); + // Assert expected package was loaded + Assert.AreEqual(pkg.Name, "Dynamo Samples"); + + // Run the graph with correct info serialized, node should deserialize to correct selection + string path = Path.Combine(TestDirectory, "pkgs", "Dynamo Samples", "extra", "CustomDropdownMenuNodeSample.dyn"); + RunModel(path); + + Graph.Nodes.NodeModel node = CurrentDynamoModel.CurrentWorkspace.Nodes.FirstOrDefault(); + + object itemsAsObject = node.GetType().GetProperty(nameof(DSDropDownBase.Items), typeof(ObservableCollection)).GetValue(node); + Assert.NotNull(itemsAsObject); + var items = (ObservableCollection)itemsAsObject; + Assert.AreEqual(3, items.Count); + Assert.AreEqual("One", items[0].Name); + Assert.AreEqual("1", items[0].Item); + } + + [Test] + public void OpenJsonDYNWithCorrectSelectedItem() + { + // Define package loading reference path + string dir = SystemTestBase.GetTestDirectory(ExecutingDirectory); + string pkgDir = Path.Combine(dir, "pkgs\\Dynamo Samples"); + PackageManagerExtension pkgMan = CurrentDynamoModel.GetPackageManagerExtension(); + PackageLoader loader = pkgMan.PackageLoader; + Package pkg = loader.ScanPackageDirectory(pkgDir); + + // Load the sample package + loader.LoadPackages(new List { pkg }); + // Assert expected package was loaded + Assert.AreEqual(pkg.Name, "Dynamo Samples"); + + // Run the graph with correct info serialized, node should deserialize to correct selection + string path = Path.Combine(TestDirectory, "pkgs", "Dynamo Samples", "extra", "CustomDropdownMenuNodeSample.dyn"); + RunModel(path); + + Graph.Nodes.NodeModel node = CurrentDynamoModel.CurrentWorkspace.Nodes.FirstOrDefault(); + + object selectedItemAsObject = node.GetType().GetProperty(nameof(CustomSelection.SelectedString)).GetValue(node); + Assert.NotNull(selectedItemAsObject); + string selectedItem = (string)selectedItemAsObject; + Assert.AreEqual("Two", selectedItem); + } + } +} \ No newline at end of file diff --git a/test/DynamoCoreWpfTests/NodeExecutionUITest.cs b/test/DynamoCoreWpfTests/NodeExecutionUITest.cs index 3d3ebcf9b49..ab17fafdb9d 100644 --- a/test/DynamoCoreWpfTests/NodeExecutionUITest.cs +++ b/test/DynamoCoreWpfTests/NodeExecutionUITest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using CoreNodeModels.Input; using DesignScript.Builtin; @@ -6,6 +6,8 @@ using Dynamo.Graph.Nodes.ZeroTouch; using Dynamo.Models; using Dynamo.Tests; +using CoreNodeModels; +using CoreNodeModelsWpf; using NUnit.Framework; using ProtoCore.Namespace; @@ -254,5 +256,29 @@ public void NewWorkspaceFunctionDefinitionTest() // by asserting null for code block node invoking it. AssertPreviewValue(cbn.GUID.ToString(), null); } + + [Test] + public void TestCustomSelectionNodeUpdate() + { + var model = GetModel(); + var cdn = new CustomSelection(); + + var command = new DynamoModel.CreateNodeCommand(cdn, 0, 0, true, false); + model.ExecuteCommand(command); + + AssertPreviewValue(cdn.GUID.ToString(), 1); + + cdn.SelectedIndex = -1; + var vm = new CustomSelectionViewModel(cdn); + var item = cdn.Items[0]; + vm.RemoveCommand.Execute(item); + + AssertPreviewValue(cdn.GUID.ToString(), null); + + cdn.SelectedIndex = 0; + cdn.OnNodeModified(); + + AssertPreviewValue(cdn.GUID.ToString(), 2); + } } } diff --git a/test/Tools/NodeDocumentationMarkdownGeneratorTests/MarkdownGeneratorCommandTests.cs b/test/Tools/NodeDocumentationMarkdownGeneratorTests/MarkdownGeneratorCommandTests.cs index a4f463672c1..7c528a4137f 100644 --- a/test/Tools/NodeDocumentationMarkdownGeneratorTests/MarkdownGeneratorCommandTests.cs +++ b/test/Tools/NodeDocumentationMarkdownGeneratorTests/MarkdownGeneratorCommandTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -162,8 +162,8 @@ public void ProducesCorrectOutputFromCoreDirectory_preloadedbinaries() var generatedFileNames = tempDirectory.GetFiles().Select(x => x.Name); //assert count is correct. - //TODO this should be 680 - but 2 tsplines nodes have such long signatures the paths are too long for windows. - Assert.AreEqual(682, generatedFileNames.Count()); + //TODO this should be 685 - but 2 tsplines nodes have such long signatures the paths are too long for windows. + Assert.AreEqual(683, generatedFileNames.Count()); } [Test] public void ProducesCorrectOutputFromCoreDirectory_dsFiles() diff --git a/test/Tools/docGeneratorTestFiles/TestMdOutput_CoreNodeModels/CoreNodeModels.Input.CustomSelection.md b/test/Tools/docGeneratorTestFiles/TestMdOutput_CoreNodeModels/CoreNodeModels.Input.CustomSelection.md new file mode 100644 index 00000000000..84922e161d8 --- /dev/null +++ b/test/Tools/docGeneratorTestFiles/TestMdOutput_CoreNodeModels/CoreNodeModels.Input.CustomSelection.md @@ -0,0 +1,5 @@ +## Custom Dropdown - Documentation +This documentation file is auto generated by NodeDocumentationMarkdownGenerator, Version=2.13.0.2564, Culture=neutral, PublicKeyToken=null. + +For more information about adding documentation to nodes see https://github.com/DynamoDS/Dynamo/wiki/Create-and-Add-Custom-Documentation-to-Nodes + diff --git a/test/Tools/docGeneratorTestFiles/sampledictionarycontent/Dynamo_Nodes_Documentation.json b/test/Tools/docGeneratorTestFiles/sampledictionarycontent/Dynamo_Nodes_Documentation.json index 09b0362d62d..0e7d0c9c70c 100644 --- a/test/Tools/docGeneratorTestFiles/sampledictionarycontent/Dynamo_Nodes_Documentation.json +++ b/test/Tools/docGeneratorTestFiles/sampledictionarycontent/Dynamo_Nodes_Documentation.json @@ -2992,6 +2992,12 @@ "dynFile": ["Output"], "folderPath": "Input/Basic/Action", "inDepth": "Output will create an output port for a custom node. The syntax for an output node is simply its name, along with an optional custom comment. In the example below, an output named Percentage is created with a custom comment. It is important to note that this node is only available while creating a custom node." +}, { + "Name": "Custom Selection", + "imageFile": ["Custom Selection"], + "dynFile": ["Custom Selection"], + "folderPath": "Input/Basic/Action", + "inDepth": "This node allows you to pre-define a dropdown menu with with an arbitrary number of custom items. Each item consists of a label and a value. The label is always a string, while the value can be an integer, a number or a string" }, { "Name": "String", "imageFile": ["String"], diff --git a/test/pkgs/Dynamo Samples/extra/CustomDropdownMenuNodeSample.dyn b/test/pkgs/Dynamo Samples/extra/CustomDropdownMenuNodeSample.dyn new file mode 100644 index 00000000000..1b9d4f8205d --- /dev/null +++ b/test/pkgs/Dynamo Samples/extra/CustomDropdownMenuNodeSample.dyn @@ -0,0 +1,108 @@ +{ + "Uuid": "8772a794-9ae5-41d9-82c0-7aff13ccac84", + "IsCustomNode": false, + "Description": "", + "Name": "CustomDropdownMenuNodeSample", + "ElementResolver": { + "ResolutionMap": {} + }, + "Inputs": [], + "Outputs": [], + "Nodes": [ + { + "ConcreteType": "CoreNodeModels.Input.CustomSelection, CoreNodeModels", + "SerializedItems": [ + { + "Name": "One", + "Item": "1" + }, + { + "Name": "Two", + "Item": "2" + }, + { + "Name": "Three", + "Item": "3" + } + ], + "SelectedIndex": 1, + "SelectedString": "Two", + "NodeType": "ExtensionNode", + "Id": "2c9b14f9f3b04c909cbbf4fc1bf58315", + "Inputs": [], + "Outputs": [ + { + "Id": "c4f968efd3b34cd4ae68f7e1bb073213", + "Name": "Value", + "Description": "The selected Value", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Replication": "Disabled", + "Description": "A dropdown menu with customizable values." + } + ], + "Connectors": [], + "Dependencies": [], + "NodeLibraryDependencies": [], + "Thumbnail": "", + "GraphDocumentationURL": null, + "ExtensionWorkspaceData": [ + { + "ExtensionGuid": "28992e1d-abb9-417f-8b1b-05e053bee670", + "Name": "Properties", + "Version": "2.14", + "Data": {} + } + ], + "Author": "", + "Linting": { + "activeLinter": "None", + "activeLinterId": "7b75fb44-43fd-4631-a878-29f4d5d8399a", + "warningCount": 0, + "errorCount": 0 + }, + "Bindings": [], + "View": { + "Dynamo": { + "ScaleFactor": 1.0, + "HasRunWithoutCrash": true, + "IsVisibleInDynamoLibrary": true, + "Version": "2.16.0.2215", + "RunType": "Automatic", + "RunPeriod": "1000" + }, + "Camera": { + "Name": "Background Preview", + "EyeX": -17.0, + "EyeY": 24.0, + "EyeZ": 50.0, + "LookX": 12.0, + "LookY": -13.0, + "LookZ": -58.0, + "UpX": 0.0, + "UpY": 1.0, + "UpZ": 0.0 + }, + "ConnectorPins": [], + "NodeViews": [ + { + "Name": "Custom Dropdown Menu", + "ShowGeometry": true, + "Id": "2c9b14f9f3b04c909cbbf4fc1bf58315", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "X": 125.0, + "Y": 159.0 + } + ], + "Annotations": [], + "X": 0.0, + "Y": 0.0, + "Zoom": 1.0 + } +} \ No newline at end of file