public static VisualElement CreateEditorForNodeModel(this VisualElement element, IConstantNodeModel model, Action <IChangeEvent> onValueChanged)
        {
            VisualElement editorElement;

            var ext = ModelUtility.ExtensionMethodCache <IConstantEditorBuilder> .GetExtensionMethod(model.GetType(), ConstantEditorBuilder.FilterMethods, ConstantEditorBuilder.KeySelector);

            var graphAsset = model.AssetModel;

            if (ext != null)
            {
                Action <IChangeEvent> myValueChanged = evt =>
                {
                    if (evt != null) // Enum editor sends null
                    {
                        var p        = evt.GetType().GetProperty("newValue");
                        var newValue = p.GetValue(evt);
                        ((ConstantNodeModel)model).ObjectValue = newValue;
                        onValueChanged(evt);
                    }

                    // TODO ugly but required to actually get the graph to be saved. Note that for some reason, the
                    // first edit works because the graph is already dirty
                    EditorUtility.SetDirty(graphAsset as Object);
                };
                var constantBuilder = new ConstantEditorBuilder(myValueChanged);
                editorElement = (VisualElement)ext.Invoke(null, new object[] { constantBuilder, model });
            }
            else
            {
                Debug.Log($"Could not draw Editor GUI for node of type {model.GetType()}");
                editorElement = new Label("<Unknown>");
            }

            return(editorElement);
        }
        public static VisualElement CreateEditorForNodeModel(this VisualElement element, IConstantNodeModel model, Action <IChangeEvent> onValueChanged)
        {
            VisualElement editorElement;

            var ext = ModelUtility.ExtensionMethodCache <IConstantEditorBuilder> .GetExtensionMethod(model.GetType(), ConstantEditorBuilder.FilterMethods, ConstantEditorBuilder.KeySelector);

            if (ext != null)
            {
                var constantBuilder = new ConstantEditorBuilder(onValueChanged);
                editorElement = (VisualElement)ext.Invoke(null, new object[] { constantBuilder, model });
            }
            else if (model is ConstantNodeModel constantNodeModel && constantNodeModel.NodeAssetReference != null)
            {
                var serializedObject = new SerializedObject(constantNodeModel.NodeAssetReference);

                SerializedProperty serializedProperty = serializedObject.FindProperty("m_NodeModel.value");
                var propertyField = new PropertyField(serializedProperty);

                editorElement = propertyField;
                editorElement.SetEnabled(!constantNodeModel.IsLocked);

                // delayed because the initial binding would cause an event otherwise, and then a compilation
                propertyField.schedule.Execute(() =>
                {
                    var onValueChangedEventCallback = new EventCallback <IChangeEvent>(onValueChanged);

                    // HERE BE DRAGONS
                    // there's no way atm to be notified that a PropertyField's value changed so we build a ChangeEvent<T>
                    // callback registration using reflection, but actually provide an Action<IChangeEvent>
                    Type type      = constantNodeModel.Type;
                    Type eventType = typeof(ChangeEvent <>).MakeGenericType(type);
                    MethodInfo genericRegisterCallbackMethod = typeof(VisualElement).GetMethods().Single(m =>
                    {
                        var parameterInfos = m.GetParameters();
                        return(m.Name == nameof(VisualElement.RegisterCallback) && parameterInfos.Length == 2 && parameterInfos[1].ParameterType == typeof(TrickleDown));
                    });
                    MethodInfo registerCallbackMethod = genericRegisterCallbackMethod.MakeGenericMethod(eventType);
                    registerCallbackMethod.Invoke(propertyField, new object[] { onValueChangedEventCallback, TrickleDown.NoTrickleDown });
                    foreach (var floatField in propertyField.Query <FloatField>().ToList())
                    {
                        floatField.isDelayed = true;
                        floatField.RegisterValueChangedCallback(_ =>
                        {
                            onValueChangedEventCallback.Invoke(null);
                            floatField.UnregisterValueChangedCallback(onValueChangedEventCallback);
                        });
                    }
                }).ExecuteLater(1);
            }