public static GuiWidget CreatePropertyEditor(EditableProperty property, UndoBuffer undoBuffer, PPEContext context, ThemeConfig theme)
        {
            var object3D             = property.Item;
            var propertyGridModifier = property.Item as IPropertyGridModifier;

            GuiWidget rowContainer = null;

            // Get reflected property value once, then test for each case below
            var propertyValue = property.Value;

            // create a double editor
            if (propertyValue is double doubleValue)
            {
                var field = new DoubleField();
                field.Initialize(0);
                field.DoubleValue   = doubleValue;
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(field.DoubleValue);
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                void RefreshField(object s, InvalidateArgs e)
                {
                    if (e.InvalidateType == InvalidateType.Properties)
                    {
                        double newValue = (double)property.Value;
                        if (newValue != field.DoubleValue)
                        {
                            field.DoubleValue = newValue;
                        }
                    }
                }

                object3D.Invalidated += RefreshField;
                field.Content.Closed += (s, e) => object3D.Invalidated -= RefreshField;

                rowContainer = CreateSettingsRow(property, field);
            }
            else if (propertyValue is Vector2 vector2)
            {
                var field = new Vector2Field();
                field.Initialize(0);
                field.Vector2       = vector2;
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(field.Vector2);
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer = CreateSettingsColumn(property, field);
            }
            else if (propertyValue is Vector3 vector3)
            {
                var field = new Vector3Field();
                field.Initialize(0);
                field.Vector3       = vector3;
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(field.Vector3);
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer = CreateSettingsColumn(property, field);
            }
            else if (propertyValue is DirectionVector directionVector)
            {
                var field = new DirectionVectorField(theme);
                field.Initialize(0);
                field.SetValue(directionVector);
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(field.DirectionVector);
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer = CreateSettingsRow(property, field);
            }
            else if (propertyValue is DirectionAxis directionAxis)
            {
                rowContainer = CreateSettingsColumn(property);
                var newDirectionVector = new DirectionVector()
                {
                    Normal = directionAxis.Normal
                };
                var row1   = CreateSettingsRow("Axis".Localize());
                var field1 = new DirectionVectorField(theme);
                field1.Initialize(0);
                field1.SetValue(newDirectionVector);
                row1.AddChild(field1.Content);

                rowContainer.AddChild(row1);

                // the direction axis
                // the distance from the center of the part
                // create a double editor
                var field2 = new Vector3Field();
                field2.Initialize(0);
                field2.Vector3 = directionAxis.Origin - property.Item.Children.First().GetAxisAlignedBoundingBox().Center;
                var row2 = CreateSettingsColumn("Offset", field2);

                // update this when changed
                EventHandler <InvalidateArgs> updateData = (s, e) =>
                {
                    field2.Vector3 = ((DirectionAxis)property.Value).Origin - property.Item.Children.First().GetAxisAlignedBoundingBox().Center;
                };
                property.Item.Invalidated += updateData;
                field2.Content.Closed     += (s, e) =>
                {
                    property.Item.Invalidated -= updateData;
                };

                // update functions
                field1.ValueChanged += (s, e) =>
                {
                    property.SetValue(new DirectionAxis()
                    {
                        Normal = field1.DirectionVector.Normal,
                        Origin = property.Item.Children.First().GetAxisAlignedBoundingBox().Center + field2.Vector3
                    });
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };
                field2.ValueChanged += (s, e) =>
                {
                    property.SetValue(new DirectionAxis()
                    {
                        Normal = field1.DirectionVector.Normal,
                        Origin = property.Item.Children.First().GetAxisAlignedBoundingBox().Center + field2.Vector3
                    });
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer.AddChild(row2);
            }
            else if (propertyValue is ChildrenSelector childSelector)
            {
                var showAsList = property.PropertyInfo.GetCustomAttributes(true).OfType <ShowAsListAttribute>().FirstOrDefault() != null;
                if (showAsList)
                {
                    UIField field = new ChildrenSelectorListField(property, theme);

                    field.Initialize(0);
                    field.ValueChanged += (s, e) =>
                    {
                        property.SetValue(new ChildrenSelector()
                        {
                            field.Value
                        });
                        object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                        propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                    };

                    rowContainer = CreateSettingsRow(property, field);
                }
                else                 // show the subtarct editor for boolean subtract and subtract and replace
                {
                    rowContainer = CreateSettingsColumn(property);
                    rowContainer.AddChild(CreateSelector(childSelector, property.Item, theme));
                }
            }
            else if (propertyValue is ImageBuffer imageBuffer)
            {
                rowContainer = CreateSettingsColumn(property);
                rowContainer.AddChild(CreateImageDisplay(imageBuffer, property.Item, theme));
            }
#if !__ANDROID__
            else if (propertyValue is List <string> stringList)
            {
                var selectedItem = ApplicationController.Instance.DragDropData.SceneContext.Scene.SelectedItem;

                var field = new SurfacedEditorsField(theme, selectedItem);
                field.Initialize(0);
                field.ListValue     = stringList;
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(field.ListValue);
                };

                rowContainer = CreateSettingsColumn(property, field);

                rowContainer.Descendants <HorizontalSpacer>().FirstOrDefault()?.Close();
            }
#endif
            // create a int editor
            else if (propertyValue is int intValue)
            {
                var field = new IntField();
                field.Initialize(0);
                field.IntValue      = intValue;
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(field.IntValue);
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                void RefreshField(object s, InvalidateArgs e)
                {
                    if (e.InvalidateType == InvalidateType.Properties)
                    {
                        int newValue = (int)property.Value;
                        if (newValue != field.IntValue)
                        {
                            field.IntValue = newValue;
                        }
                    }
                }

                object3D.Invalidated += RefreshField;
                field.Content.Closed += (s, e) => object3D.Invalidated -= RefreshField;

                rowContainer = CreateSettingsRow(property, field);
            }
            // create a bool editor
            else if (propertyValue is bool boolValue)
            {
                var field = new ToggleboxField(theme);
                field.Initialize(0);
                field.Checked       = boolValue;
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(field.Checked);
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer = CreateSettingsRow(property, field);
            }
            // create a string editor
            else if (propertyValue is string stringValue)
            {
                var field = new TextField();
                field.Initialize(0);
                field.SetValue(stringValue, false);
                field.Content.HAnchor = HAnchor.Stretch;
                field.ValueChanged   += (s, e) =>
                {
                    property.SetValue(field.Value);
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer = CreateSettingsRow(property, field);

                var label = rowContainer.Children.First();

                if (field is TextField)
                {
                    var spacer = rowContainer.Children.OfType <HorizontalSpacer>().FirstOrDefault();
                    spacer.HAnchor = HAnchor.Absolute;
                    spacer.Width   = Math.Max(0, 100 - label.Width);
                }
            }
            // create a char editor
            else if (propertyValue is char charValue)
            {
                var field = new CharField();
                field.Initialize(0);
                field.SetValue(charValue.ToString(), false);
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(Convert.ToChar(field.Value));
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer = CreateSettingsRow(property, field);
            }
            // create an enum editor
            else if (property.PropertyType.IsEnum)
            {
                UIField field;
                var     iconsAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType <IconsAttribute>().FirstOrDefault();
                if (iconsAttribute != null)
                {
                    field = new IconEnumField(property, iconsAttribute, theme)
                    {
                        InitialValue = propertyValue.ToString()
                    };
                }
                else
                {
                    field = new EnumField(property, theme);
                }

                field.Initialize(0);
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(Enum.Parse(property.PropertyType, field.Value));
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer = CreateSettingsRow(property, field);
            }
            // Use known IObject3D editors
            else if (propertyValue is IObject3D item &&
                     ApplicationController.Instance.GetEditorsForType(property.PropertyType)?.FirstOrDefault() is IObject3DEditor iObject3DEditor)
            {
                rowContainer = iObject3DEditor.Create(item, theme);
            }

            // remember the row name and widget
            context.editRows.Add(property.PropertyInfo.Name, rowContainer);

            return(rowContainer);
        }
Exemple #2
0
        public static GuiWidget CreatePropertyEditor(EditableProperty property, UndoBuffer undoBuffer, PPEContext context, ThemeConfig theme)
        {
            var object3D             = property.Item;
            var propertyGridModifier = property.Item as IPropertyGridModifier;

            GuiWidget rowContainer = null;

            // Get reflected property value once, then test for each case below
            var propertyValue = property.Value;

            void RegisterValueChanged(UIField field, Func <string, object> valueFromString, Func <object, string> valueToString = null)
            {
                field.ValueChanged += (s, e) =>
                {
                    var newValue = field.Value;
                    var oldValue = property.Value.ToString();
                    if (valueToString != null)
                    {
                        oldValue = valueToString(property.Value);
                    }

                    // field.Content
                    if (undoBuffer != null)
                    {
                        undoBuffer.AddAndDo(new UndoRedoActions(() =>
                        {
                            property.SetValue(valueFromString(oldValue));
                            object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties));
                            propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                        },
                                                                () =>
                        {
                            property.SetValue(valueFromString(newValue));
                            object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties));
                            propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                        }));
                    }
                    else
                    {
                        property.SetValue(valueFromString(newValue));
                        object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties));
                        propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                    }
                };
            }

            var readOnly = property.PropertyInfo.GetCustomAttributes(true).OfType <ReadOnlyAttribute>().FirstOrDefault() != null;

            // create a double editor
            if (propertyValue is double doubleValue)
            {
                if (readOnly)
                {
                    var valueField = new TextWidget(string.Format("{0:n}", doubleValue), textColor: theme.TextColor, pointSize: 10)
                    {
                        AutoExpandBoundsToText = true
                    };

                    rowContainer = new SettingsRow(property.DisplayName.Localize(),
                                                   property.Description,
                                                   valueField,
                                                   theme);

                    void RefreshField(object s, InvalidateArgs e)
                    {
                        if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues))
                        {
                            double newValue = (double)property.Value;
                            valueField.Text = string.Format("{0:n}", newValue);
                        }
                    }

                    object3D.Invalidated += RefreshField;
                    valueField.Closed    += (s, e) => object3D.Invalidated -= RefreshField;
                }
                else                 // normal edit row
                {
                    var field = new DoubleField(theme);
                    field.Initialize(0);
                    field.DoubleValue = doubleValue;
                    field.ClearUndoHistory();
                    RegisterValueChanged(field, (valueString) => { return(double.Parse(valueString)); });

                    void RefreshField(object s, InvalidateArgs e)
                    {
                        if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues))
                        {
                            double newValue = (double)property.Value;
                            if (newValue != field.DoubleValue)
                            {
                                field.DoubleValue = newValue;
                            }
                        }
                    }

                    object3D.Invalidated += RefreshField;
                    field.Content.Descendants <InternalTextEditWidget>().First().Name = property.DisplayName + " Edit";
                    field.Content.Closed += (s, e) => object3D.Invalidated -= RefreshField;

                    rowContainer = CreateSettingsRow(property, field, theme);
                }
            }
            else if (propertyValue is Color color)
            {
                var field = new ColorField(theme, object3D.Color);
                field.Initialize(0);
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(field.Color);
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer = CreateSettingsRow(property, field, theme);
            }
            else if (propertyValue is Vector2 vector2)
            {
                var field = new Vector2Field(theme);
                field.Initialize(0);
                field.Vector2 = vector2;
                field.ClearUndoHistory();

                RegisterValueChanged(field,
                                     (valueString) => Vector2.Parse(valueString),
                                     (value) =>
                {
                    var s = ((Vector2)value).ToString();
                    return(s.Substring(1, s.Length - 2));
                });

                rowContainer = CreateSettingsColumn(property, field);
            }
            else if (propertyValue is Vector3 vector3)
            {
                var field = new Vector3Field(theme);
                field.Initialize(0);
                field.Vector3 = vector3;
                field.ClearUndoHistory();

                RegisterValueChanged(
                    field,
                    (valueString) => Vector3.Parse(valueString),
                    (value) =>
                {
                    var s = ((Vector3)value).ToString();
                    return(s.Substring(1, s.Length - 2));
                });

                rowContainer = CreateSettingsColumn(property, field);
            }
            else if (propertyValue is DirectionVector directionVector)
            {
                var field = new DirectionVectorField(theme);
                field.Initialize(0);
                field.SetValue(directionVector);
                field.ClearUndoHistory();

                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(field.DirectionVector);
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer = CreateSettingsRow(property, field, theme);
            }
            else if (propertyValue is DirectionAxis directionAxis)
            {
                rowContainer = CreateSettingsColumn(property);

                var field1 = new DirectionVectorField(theme);
                field1.Initialize(0);
                field1.ClearUndoHistory();

                field1.SetValue(new DirectionVector()
                {
                    Normal = directionAxis.Normal
                });

                rowContainer.AddChild(new SettingsRow("Axis".Localize(), null, field1.Content, theme));

                // the direction axis
                // the distance from the center of the part
                // create a double editor
                var field2 = new Vector3Field(theme);
                field2.Initialize(0);
                field2.Vector3 = directionAxis.Origin - property.Item.Children.First().GetAxisAlignedBoundingBox().Center;
                field2.ClearUndoHistory();

                var row2 = CreateSettingsColumn("Offset".Localize(), field2);

                // update this when changed
                void UpdateData(object s, InvalidateArgs e)
                {
                    field2.Vector3 = ((DirectionAxis)property.Value).Origin - property.Item.Children.First().GetAxisAlignedBoundingBox().Center;
                }

                property.Item.Invalidated += UpdateData;
                field2.Content.Closed     += (s, e) =>
                {
                    property.Item.Invalidated -= UpdateData;
                };

                // update functions
                field1.ValueChanged += (s, e) =>
                {
                    property.SetValue(new DirectionAxis()
                    {
                        Normal = field1.DirectionVector.Normal,
                        Origin = property.Item.Children.First().GetAxisAlignedBoundingBox().Center + field2.Vector3
                    });
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };
                field2.ValueChanged += (s, e) =>
                {
                    property.SetValue(new DirectionAxis()
                    {
                        Normal = field1.DirectionVector.Normal,
                        Origin = property.Item.Children.First().GetAxisAlignedBoundingBox().Center + field2.Vector3
                    });
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer.AddChild(row2);
            }
            else if (propertyValue is SelectedChildren childSelector)
            {
                var showAsList = property.PropertyInfo.GetCustomAttributes(true).OfType <ShowAsListAttribute>().FirstOrDefault() != null;
                if (showAsList)
                {
                    UIField field = new ChildrenSelectorListField(property, theme);

                    field.Initialize(0);
                    RegisterValueChanged(field,
                                         (valueString) =>
                    {
                        var childrenSelector = new SelectedChildren();
                        foreach (var child in valueString.Split(','))
                        {
                            childrenSelector.Add(child);
                        }

                        return(childrenSelector);
                    });

                    rowContainer = CreateSettingsRow(property, field, theme);
                }
                else                 // show the subtract editor for boolean subtract and subtract and replace
                {
                    rowContainer = CreateSettingsColumn(property);
                    if (property.Item is OperationSourceContainerObject3D sourceContainer)
                    {
                        Action selected = null;
                        if (!(context.item.GetType().GetCustomAttributes(typeof(ShowUpdateButtonAttribute), true).FirstOrDefault() is ShowUpdateButtonAttribute showUpdate))
                        {
                            selected = () =>
                            {
                                object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties));
                            };
                        }

                        rowContainer.AddChild(CreateSourceChildSelector(childSelector, sourceContainer, theme, selected));
                    }
                    else
                    {
                        rowContainer.AddChild(CreateSelector(childSelector, property.Item, theme));
                    }
                }
            }
            else if (propertyValue is ImageBuffer imageBuffer)
            {
                rowContainer = CreateSettingsColumn(property);
                rowContainer.AddChild(new ImageWidget(imageBuffer)
                {
                    HAnchor = HAnchor.Left,
                    Margin  = new BorderDouble(0, 3)
                });
            }
#if !__ANDROID__
            else if (propertyValue is List <string> stringList)
            {
                var field = new SurfacedEditorsField(theme, property.Item);
                field.Initialize(0);
                field.ListValue     = stringList;
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(field.ListValue);
                };

                rowContainer = CreateSettingsColumn(property, field);

                rowContainer.Descendants <HorizontalSpacer>().FirstOrDefault()?.Close();
            }
#endif
            // create a int editor
            else if (propertyValue is int intValue)
            {
                if (readOnly)
                {
                    var valueField = new TextWidget(string.Format("{0:n0}", intValue),
                                                    textColor: theme.TextColor,
                                                    pointSize: 10)
                    {
                        AutoExpandBoundsToText = true
                    };

                    rowContainer = new SettingsRow(property.DisplayName.Localize(),
                                                   property.Description,
                                                   valueField,
                                                   theme);

                    void RefreshField(object s, InvalidateArgs e)
                    {
                        if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues))
                        {
                            int newValue = (int)property.Value;
                            valueField.Text = string.Format("{0:n0}", newValue);
                        }
                    }

                    object3D.Invalidated += RefreshField;
                    valueField.Closed    += (s, e) => object3D.Invalidated -= RefreshField;
                }
                else                 // normal edit row
                {
                    var field = new IntField(theme);
                    field.Initialize(0);
                    field.IntValue = intValue;
                    field.ClearUndoHistory();

                    RegisterValueChanged(field, (valueString) => { return(int.Parse(valueString)); });

                    void RefreshField(object s, InvalidateArgs e)
                    {
                        if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues))
                        {
                            int newValue = (int)property.Value;
                            if (newValue != field.IntValue)
                            {
                                field.IntValue = newValue;
                            }
                        }
                    }

                    object3D.Invalidated += RefreshField;
                    field.Content.Closed += (s, e) => object3D.Invalidated -= RefreshField;

                    rowContainer = CreateSettingsRow(property, field, theme);
                }
            }
            else if (propertyValue is bool boolValue)
            {
                // create a bool editor
                var field = new ToggleboxField(theme);
                field.Initialize(0);
                field.Checked = boolValue;

                RegisterValueChanged(field,
                                     (valueString) => { return(valueString == "1"); },
                                     (value) => { return(((bool)value) ? "1" : "0"); });
                rowContainer = CreateSettingsRow(property, field, theme);
            }
            else if (propertyValue is string stringValue)
            {
                if (readOnly)
                {
                    rowContainer = new WrappedTextWidget(stringValue,
                                                         textColor: theme.TextColor,
                                                         pointSize: 10)
                    {
                        Margin = 5
                    };
                }
                else                 // normal edit row
                {
                    var multiLineEditAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType <MultiLineEditAttribute>().FirstOrDefault();

                    if (multiLineEditAttribute != null)
                    {
                        // create a a multi-line string editor
                        var field = new MultilineStringField(theme);
                        field.Initialize(0);
                        field.SetValue(stringValue, false);
                        field.ClearUndoHistory();
                        field.Content.HAnchor = HAnchor.Stretch;
                        // field.Content.MinimumSize = new Vector2(0, 200 * GuiWidget.DeviceScale);
                        RegisterValueChanged(field, (valueString) => valueString);
                        rowContainer = CreateSettingsColumn(property, field, fullWidth: true);
                    }
                    else
                    {
                        // create a string editor
                        var field = new TextField(theme);
                        field.Initialize(0);
                        field.SetValue(stringValue, false);
                        field.ClearUndoHistory();
                        field.Content.HAnchor = HAnchor.Stretch;
                        RegisterValueChanged(field, (valueString) => valueString);
                        rowContainer = CreateSettingsRow(property, field, theme);

                        var label = rowContainer.Children.First();

                        var spacer = rowContainer.Children.OfType <HorizontalSpacer>().FirstOrDefault();
                        spacer.HAnchor = HAnchor.Absolute;
                        spacer.Width   = Math.Max(0, 100 - label.Width);
                    }
                }
            }
            else if (propertyValue is char charValue)
            {
                // create a char editor
                var field = new CharField(theme);
                field.Initialize(0);
                field.SetValue(charValue.ToString(), false);
                field.ClearUndoHistory();
                field.ValueChanged += (s, e) =>
                {
                    property.SetValue(Convert.ToChar(field.Value));
                    object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties));
                    propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                };

                rowContainer = CreateSettingsRow(property, field, theme);
            }
            else if (property.PropertyType.IsEnum)
            {
                // create an enum editor
                UIField field;
                var     enumDisplayAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType <EnumDisplayAttribute>().FirstOrDefault();
                var     addToSettingsRow     = true;
                if (enumDisplayAttribute != null)
                {
                    field = new EnumDisplayField(property, enumDisplayAttribute, theme)
                    {
                        InitialValue = propertyValue.ToString(),
                    };

                    if (enumDisplayAttribute.Mode == EnumDisplayAttribute.PresentationMode.Tabs)
                    {
                        addToSettingsRow = false;
                    }
                }
                else
                {
                    if (property.PropertyType == typeof(NamedTypeFace))
                    {
                        field = new FontSelectorField(property, theme);
                    }
                    else
                    {
                        field = new EnumField(property, theme);
                    }
                }

                field.Initialize(0);
                RegisterValueChanged(field,
                                     (valueString) =>
                {
                    return(Enum.Parse(property.PropertyType, valueString));
                });

                field.ValueChanged += (s, e) =>
                {
                    if (property.Value.ToString() != field.Value)
                    {
                        property.SetValue(Enum.Parse(property.PropertyType, field.Value));
                        object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties));
                        propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name));
                    }
                };

                if (addToSettingsRow)
                {
                    rowContainer = CreateSettingsRow(property, field, theme);
                }
                else
                {
                    // field.Content.Margin = new BorderDouble(3, 0);
                    field.Content.HAnchor = HAnchor.Stretch;
                    rowContainer          = field.Content;
                }
            }
            else if (propertyValue is IObject3D item &&
                     ApplicationController.Instance.Extensions.GetEditorsForType(property.PropertyType)?.FirstOrDefault() is IObject3DEditor iObject3DEditor)
            {
                // Use known IObject3D editors
                rowContainer = iObject3DEditor.Create(item, undoBuffer, theme);
            }

            // remember the row name and widget
            context.editRows.Add(property.PropertyInfo.Name, rowContainer);

            return(rowContainer);
        }