public override VisualElement CreatePropertyGUI(SerializedProperty property)
        {
            var container = new VisualElement();

            var keyProperty    = property.FindPropertyRelative("key");
            var targetProperty = keyProperty.FindPropertyRelative("target");

            var itemContainer = new PropertyField(property.FindPropertyRelative("item"));

            var component          = (Component)property.serializedObject.targetObject;
            var scene              = component.gameObject.scene;
            var isInScene          = scene.name != null && !EditorSceneManager.IsPreviewScene(scene);
            var messageType        = isInScene ? MessageType.Error : MessageType.Warning;
            var localPlayerHelpBox = new IMGUIContainer(() => EditorGUILayout.HelpBox(LocalPlayerGimmickValidation.ErrorMessage, messageType));

            void SwitchDisplayItem(GimmickTarget target)
            {
                itemContainer.SetVisibility(target == GimmickTarget.Item);
                localPlayerHelpBox.SetVisibility(!LocalPlayerGimmickValidation.IsValid(target, component));
            }

            SwitchDisplayItem((GimmickTarget)targetProperty.enumValueIndex);

            var targetField = EnumField.Create(
                targetProperty.displayName,
                targetProperty,
                LocalPlayerGimmickValidation.IsLocalizable(component) ? LocalizableSelectables : ConsistentlySyncSelectables,
                (GimmickTarget)targetProperty.enumValueIndex,
                FormatTarget, SwitchDisplayItem);

            var keyField = new PropertyField(keyProperty.FindPropertyRelative("key"));

            container.Add(localPlayerHelpBox);
            container.Add(targetField);
            container.Add(keyField);
            container.Add(itemContainer);

            return(container);
        }
        static VisualElement CreateConstantValuePropertyGUI(SerializedProperty property)
        {
            Assert.AreEqual(property.type, nameof(ConstantValue));
            var container = new VisualElement
            {
                style =
                {
                    flexDirection = new StyleEnum <FlexDirection>(FlexDirection.Row),
                    flexGrow      = new StyleFloat(9)
                }
            };

            var boolValueProperty = property.FindPropertyRelative("boolValue");

            Assert.AreEqual(boolValueProperty.propertyType, SerializedPropertyType.Boolean);
            var boolValueField = new Toggle
            {
                bindingPath = boolValueProperty.propertyPath,
                style       = { flexGrow = new StyleFloat(9) }
            };

            boolValueField.Bind(boolValueProperty.serializedObject);
            container.Add(boolValueField);

            var floatValueProperty = property.FindPropertyRelative("floatValue");

            Assert.AreEqual(floatValueProperty.propertyType, SerializedPropertyType.Float);
            var floatValueField = new FloatField
            {
                bindingPath = floatValueProperty.propertyPath,
                style       = { flexGrow = new StyleFloat(9) }
            };

            floatValueField.Bind(floatValueProperty.serializedObject);
            container.Add(floatValueField);

            var integerValueProperty = property.FindPropertyRelative("integerValue");

            Assert.AreEqual(integerValueProperty.propertyType, SerializedPropertyType.Integer);
            var integerValueField = new IntegerField
            {
                bindingPath = integerValueProperty.propertyPath,
                style       = { flexGrow = new StyleFloat(9) }
            };

            integerValueField.Bind(integerValueProperty.serializedObject);
            container.Add(integerValueField);

            var typeProperty = property.FindPropertyRelative("type");
            var typeChoices  = new List <ParameterType> {
                ParameterType.Bool, ParameterType.Float, ParameterType.Integer
            };
            var currentType     = (ParameterType)typeProperty.intValue;
            var selectingTarget = typeChoices.Contains(currentType) ? currentType : typeChoices[0];
            var typeField       = EnumField.Create(typeProperty, typeChoices, selectingTarget, SwitchField);

            container.Insert(0, typeField);

            void SwitchField(ParameterType type)
            {
                boolValueField.SetVisibility(type == ParameterType.Bool);
                floatValueField.SetVisibility(type == ParameterType.Float);
                integerValueField.SetVisibility(type == ParameterType.Integer);
            }

            SwitchField(selectingTarget);

            return(container);
        }
        static VisualElement CreateExpressionPropertyGUI(SerializedProperty property,
                                                         List <GimmickTarget> targetChoices,
                                                         Func <GimmickTarget, string> formatTarget,
                                                         int depth = 0)
        {
            Assert.AreEqual(property.type, nameof(Expression));
            var container = new VisualElement
            {
                style = { marginLeft = new StyleLength(5) }
            };

            var valueProperty = property.FindPropertyRelative("value");
            var valueField    = CreateValuePropertyGUI(valueProperty, targetChoices, formatTarget);

            container.Add(valueField);

            if (depth > 0)
            {
                return(container);
            }

            valueField.style.marginLeft = new StyleLength(5);

            var typeProperty = property.FindPropertyRelative("type");
            var currentType  = (ExpressionType)typeProperty.intValue;
            var operatorExpressionProperty = property.FindPropertyRelative("operatorExpression");
            var operatorProperty           = operatorExpressionProperty.FindPropertyRelative("operator");
            var currentOperator            = (Operator)operatorProperty.intValue;
            var operandsProperty           = operatorExpressionProperty.FindPropertyRelative("operands");

            var operandsContainer = new VisualElement();

            container.Add(operandsContainer);

            const int ExpressionTypeValue      = -1;
            var       typeOperatorChoices      = Enum.GetValues(typeof(Operator)).Cast <int>().Prepend(ExpressionTypeValue).ToList();
            var       typeOperatorDefaultIndex = typeOperatorChoices.IndexOf(currentType == ExpressionType.Value ? ExpressionTypeValue : (int)currentOperator);
            var       typeOperatorField        = new PopupField <int>(typeOperatorChoices, typeOperatorDefaultIndex, TypeOperatorFormat, TypeOperatorFormat);

            string TypeOperatorFormat(int value)
            {
                return(value == ExpressionTypeValue ? "=" : $"= {(Operator) value}");
            }

            typeOperatorField.RegisterValueChangedCallback(e =>
            {
                var type = e.newValue == ExpressionTypeValue ? ExpressionType.Value : ExpressionType.OperatorExpression;

                if (typeProperty.intValue != (int)type)
                {
                    typeProperty.intValue = (int)type;
                    typeProperty.serializedObject.ApplyModifiedProperties();

                    var operandsFirstProperty = operandsProperty.GetArrayElementAtIndex(0);
                    switch (type)
                    {
                    case ExpressionType.Value:
                        if (operandsFirstProperty != null)
                        {
                            CopyValueProperty(operandsFirstProperty.FindPropertyRelative("value"), valueProperty);
                        }
                        break;

                    case ExpressionType.OperatorExpression:
                        if (operandsFirstProperty == null)
                        {
                            operandsProperty.InsertArrayElementAtIndex(0);
                        }
                        CopyValueProperty(valueProperty, operandsFirstProperty.FindPropertyRelative("value"));
                        break;
                    }
#if !UNITY_2019_3_OR_NEWER
                    OnTypeChanged(type);
#endif
                }

                if (type == ExpressionType.OperatorExpression && operatorProperty.intValue != e.newValue)
                {
                    operatorProperty.intValue = e.newValue;
                    operatorProperty.serializedObject.ApplyModifiedProperties();
#if !UNITY_2019_3_OR_NEWER
                    OnOperatorChanged((Operator)e.newValue);
#endif
                }
            });
            container.Insert(0, typeOperatorField);

            var typeField = EnumField.CreateAsStringPopupField <ExpressionType>(typeProperty, newValue =>
            {
                typeOperatorField.SetValueWithoutNotify(newValue == ExpressionType.Value ? ExpressionTypeValue : operatorProperty.intValue);
                OnTypeChanged(newValue);
            });
            typeField.style.display = new StyleEnum <DisplayStyle>(DisplayStyle.None);
            container.Add(typeField);

            var operatorField = EnumField.CreateAsStringPopupField <Operator>(operatorProperty, newValue =>
            {
                typeOperatorField.SetValueWithoutNotify(typeProperty.intValue == (int)ExpressionType.Value ? ExpressionTypeValue : (int)newValue);
                OnOperatorChanged(newValue);
            });
            operatorField.style.display = new StyleEnum <DisplayStyle>(DisplayStyle.None);
            container.Add(operatorField);

            void OnOperatorChanged(Operator @operator)
            {
                operandsContainer.Unbind();
                operandsContainer.Clear();

                var requiredLength = @operator.GetRequiredLength();

                if (requiredLength != operandsProperty.arraySize)
                {
                    operandsProperty.arraySize = requiredLength;
                    operandsProperty.serializedObject.ApplyModifiedProperties();
                }

                var operandsField = CreateOperandsPropertyGUI(operandsProperty, targetChoices, formatTarget, depth);

                operandsContainer.Add(operandsField);
            }

            OnOperatorChanged(currentOperator);

            void OnTypeChanged(ExpressionType type)
            {
                valueField.SetVisibility(type == ExpressionType.Value);
                operandsContainer.SetVisibility(type == ExpressionType.OperatorExpression);
            }

            OnTypeChanged(currentType);

            return(container);
        }