From 348fe44cd379a56cbdaf6da8454300271e88826b Mon Sep 17 00:00:00 2001 From: Alan Rynne Date: Fri, 3 Nov 2023 12:57:44 +0100 Subject: [PATCH] fix(gh): Fixes sorting on dropdown for input enum values (#3020) * fix(gh): Fixes sorting on dropdown for input enum values Sorting was performed on the original values, which changed the indexes of the enum, hence modifying it's return value to an unrelated one * fix: Robust enum extraction + unified implementation --- .../SchemaBuilder/CreateSchemaObject.cs | 28 ++----- .../SchemaBuilder/CreateSchemaObjectBase.cs | 34 +------- .../UserInterfaceUtils.cs | 84 +++++++++++++++++++ 3 files changed, 92 insertions(+), 54 deletions(-) create mode 100644 ConnectorGrasshopper/ConnectorGrasshopperUtils/UserInterfaceUtils.cs diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObject.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObject.cs index b137c5dcfb..d5416f1b54 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObject.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObject.cs @@ -25,7 +25,6 @@ namespace ConnectorGrasshopper; public class CreateSchemaObject : SelectKitComponentBase, IGH_VariableParameterComponent { - private GH_Document _document; private DebounceDispatcher nicknameChangeDebounce = new(); private bool readFailed; @@ -179,8 +178,6 @@ public override void AddedToDocument(GH_Document document) return; } - _document = document; - var dialog = new CreateSchemaObjectDialog(); dialog.Owner = Instances.EtoDocumentEditor; var mouse = Control.MousePosition; @@ -231,6 +228,8 @@ public void SwitchConstructor(ConstructorInfo constructor) foreach (var p in props) RegisterPropertyAsInputParameter(p, k++); + UserInterfaceUtils.CreateCanvasDropdownForAllEnumInputs(this, props); + Name = constructor.GetCustomAttribute().Name; Description = constructor.GetCustomAttribute().Description; @@ -288,23 +287,9 @@ private void RegisterPropertyAsInputParameter(ParameterInfo param, int index) newInputParam.Access = GH_ParamAccess.item; Params.RegisterInputParam(newInputParam, index); - - //add dropdown - if (propType.IsEnum) - { - //expire solution so that node gets proper size - ExpireSolution(true); - - var instance = Activator.CreateInstance(propType); - - var vals = Enum.GetValues(propType).Cast().Select(x => x.ToString()).ToList(); - var options = CreateDropDown(propName, vals, Attributes.Bounds.X, Params.Input[index].Attributes.Bounds.Y); - _document.AddObject(options, false); - Params.Input[index].AddSource(options); - } } - public static GH_ValueList CreateDropDown(string name, List values, float x, float y) + public static GH_ValueList CreateDropDown(string name, List<(string name, int value)> values, float x, float y) { var valueList = new GH_ValueList(); valueList.CreateAttributes(); @@ -314,13 +299,12 @@ public static GH_ValueList CreateDropDown(string name, List values, floa valueList.ListMode = GH_ValueListMode.DropDown; valueList.ListItems.Clear(); - values = values.OrderBy(x=>x).ToList(); - for (int i = 0; i < values.Count; i++) - valueList.ListItems.Add(new GH_ValueListItem(values[i], i.ToString())); - + valueList.ListItems.Add(new GH_ValueListItem(values[i].name, values[i].value.ToString())); + valueList.Attributes.Pivot = new PointF(x - 200, y - 10); + valueList.ListItems.Sort((item, listItem) => string.Compare(item.Name, listItem.Name)); return valueList; } diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObjectBase.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObjectBase.cs index e063c7e366..4d5827df9c 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObjectBase.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/SchemaBuilder/CreateSchemaObjectBase.cs @@ -275,6 +275,8 @@ public void SetupComponent(ConstructorInfo constructor) foreach (var p in props) RegisterPropertyAsInputParameter(p, k++); + UserInterfaceUtils.CreateCanvasDropdownForAllEnumInputs(this, props); + Name = constructor.GetCustomAttribute().Name; Description = constructor.GetCustomAttribute().Description; @@ -330,38 +332,6 @@ private void RegisterPropertyAsInputParameter(ParameterInfo param, int index) newInputParam.Access = GH_ParamAccess.item; Params.RegisterInputParam(newInputParam, index); - - //add dropdown - if (propType.IsEnum) - { - //expire solution so that node gets proper size - ExpireSolution(true); - - var instance = Activator.CreateInstance(propType); - - var vals = Enum.GetValues(propType).Cast().Select(x => x.ToString()).ToList(); - var options = CreateDropDown(propName, vals, Attributes.Bounds.X, Params.Input[index].Attributes.Bounds.Y); - OnPingDocument().AddObject(options, false); - Params.Input[index].AddSource(options); - } - } - - public static GH_ValueList CreateDropDown(string name, List values, float x, float y) - { - var valueList = new GH_ValueList(); - valueList.CreateAttributes(); - valueList.Name = name; - valueList.NickName = name + ":"; - valueList.Description = "Select an option..."; - valueList.ListMode = GH_ValueListMode.DropDown; - valueList.ListItems.Clear(); - - for (int i = 0; i < values.Count; i++) - valueList.ListItems.Add(new GH_ValueListItem(values[i], i.ToString())); - - valueList.Attributes.Pivot = new PointF(x - 200, y - 10); - - return valueList; } public override void SolveInstanceWithLogContext(IGH_DataAccess DA) diff --git a/ConnectorGrasshopper/ConnectorGrasshopperUtils/UserInterfaceUtils.cs b/ConnectorGrasshopper/ConnectorGrasshopperUtils/UserInterfaceUtils.cs new file mode 100644 index 0000000000..0b36ef3b55 --- /dev/null +++ b/ConnectorGrasshopper/ConnectorGrasshopperUtils/UserInterfaceUtils.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Reflection; +using Grasshopper.Kernel; +using Grasshopper.Kernel.Special; + +namespace ConnectorGrasshopperUtils; + +public static class UserInterfaceUtils +{ + /// + /// Used to Create + /// + /// + /// + /// + /// + /// + public static GH_ValueList CreateEnumDropDown(Type enumType, string name, float xPos, float yPos, bool sorted = true) + { + if (!enumType.IsEnum) + { + throw new ArgumentException($"Input type must be {typeof(Enum)} but got {enumType.Name} ", nameof(enumType)); + } + + // Convert all values of an enum into a GH_ValueListItem and add them to the value list. + // This preserves both the name of the Enum value, as well as it's declared integer value (saved as a string). + var items = Enum.GetValues(enumType) + .Cast() + .Select(x => new GH_ValueListItem(name: x.ToString(), expression: Convert.ToInt32(x).ToString())) + .ToList(); + + var valueList = new GH_ValueList(); + valueList.CreateAttributes(); + valueList.Name = name; + valueList.NickName = name + ":"; + valueList.Description = "Select an option..."; + valueList.ListMode = GH_ValueListMode.DropDown; + valueList.Attributes.Pivot = new PointF(xPos, yPos); + + valueList.ListItems.Clear(); + valueList.ListItems.AddRange(items); + + if (sorted) + { + valueList.ListItems.Sort((itemA, itemB) => string.Compare(itemA.Name, itemB.Name)); + } + + return valueList; + } + + /// + /// Creates a dropdown component on the canvas for every type input of the component. + /// This assumes the inputs have already been created, and that there is a 1 to 1 relationship between + /// the number of inputs of the component and the number of properties. + /// + /// The component to connect the dropdowns to + /// The list of the class being created as a component. + public static void CreateCanvasDropdownForAllEnumInputs(IGH_Component component, ParameterInfo[] props) + { + //Expiring solution ensures the size and position of the parent are updated before placing any input connections + component.ExpireSolution(true); + + for (int index = 0; index < props.Length; index++) + { + ParameterInfo p = props[index]; + if (!p.ParameterType.IsEnum) + { + continue; + } + + var options = CreateEnumDropDown( + p.ParameterType, + p.Name, + component.Attributes.Bounds.X - 200, + component.Params.Input[index].Attributes.Bounds.Y - 1 + ); + component.OnPingDocument().AddObject(options, false); + component.Params.Input[index].AddSource(options); + } + } +}