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); }
private static FlowLayoutWidget CreateSettingsColumn(EditableProperty property) { return(CreateSettingsColumn(property.DisplayName.Localize(), property.Description.Localize())); }
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); }
private static FlowLayoutWidget CreateSettingsColumn(EditableProperty property, UIField field, bool fullWidth = false) { return(CreateSettingsColumn(property.DisplayName.Localize(), field, property.Description, fullWidth: fullWidth)); }
private static GuiWidget CreateSettingsRow(EditableProperty property, UIField field, ThemeConfig theme) { return(new SettingsRow(property.DisplayName.Localize(), property.Description, field.Content, theme)); }
public static GuiWidget GetFieldContentWithSlider(EditableProperty property, PPEContext context, UIField field, UndoBuffer undoBuffer, Func <string, object> valueFromString, ThemeConfig theme) { var sliderAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType <SliderAttribute>().FirstOrDefault(); if (sliderAttribute != null) { var min = sliderAttribute.Min; var max = sliderAttribute.Max; var delta = max - min; // the slider values go from 0 to 1 and are then mapped during translation to the field var slider = new Slider(new Vector2(0, 0), 80 * GuiWidget.DeviceScale, 0, 1) { VAnchor = VAnchor.Center, }; slider.View.TrackColor = theme.TextColor; slider.View.ThumbColor = theme.PrimaryAccentColor; slider.View.TrackHeight = 1 * GuiWidget.DeviceScale; Func <double> getFieldValue = null; double GetSlider0To1FromField() { return(GetSlider0To1FromValue(getFieldValue())); } double GetSlider0To1FromValue(double value) { var mapped0To1 = Math.Max(0, Math.Min(1, (value - min) / delta)); return(Easing.CalculateInverse(sliderAttribute.EasingType, sliderAttribute.EaseOption, mapped0To1)); } double GetFieldFromSlider0To1() { var fieldValue = Easing.Calculate(sliderAttribute.EasingType, sliderAttribute.EaseOption, slider.Value) * delta + min; var snapGridDistance = sliderAttribute.SnapDistance; if (sliderAttribute.UseSnappingGrid) { snapGridDistance = SnapGridDistance(); } if (snapGridDistance > 0) { // snap this position to the grid // snap this position to the grid fieldValue = ((int)((fieldValue / snapGridDistance) + .5)) * snapGridDistance; } return(fieldValue); } double SnapGridDistance() { var view3DWidget = slider.Parents <View3DWidget>().FirstOrDefault(); if (view3DWidget != null) { var object3DControlLayer = view3DWidget.Descendants <Object3DControlsLayer>().FirstOrDefault(); if (object3DControlLayer != null) { return(object3DControlLayer.SnapGridDistance); } } return(0); } var changeDueToSlider = false; var changeDueToField = false; slider.ValueChanged += (s, e) => { if (!changeDueToField) { changeDueToSlider = true; SetValue(property, context, valueFromString, GetFieldFromSlider0To1()); changeDueToSlider = false; } }; double sliderDownValue = 0; slider.SliderGrabed += (s, e) => { sliderDownValue = getFieldValue(); }; slider.SliderReleased += (s, e) => { var currentValue = GetFieldFromSlider0To1(); changeDueToSlider = true; SetValue(property, context, valueFromString, currentValue); changeDueToSlider = false; // save the undo information undoBuffer.Add(new UndoRedoActions(() => { SetValue(property, context, valueFromString, sliderDownValue); }, () => { SetValue(property, context, valueFromString, currentValue); })); }; GuiWidget content = null; var sliderRightMargin = 11; if (field is DoubleField doubleField) { getFieldValue = () => doubleField.DoubleValue; doubleField.ValueChanged += (s, e) => { changeDueToField = true; slider.Value = GetSlider0To1FromField(); changeDueToField = false; }; content = new FlowLayoutWidget(); content.AddChild(slider); content.AddChild(new GuiWidget() { Width = sliderRightMargin * GuiWidget.DeviceScale, Height = 3 }); content.AddChild(field.Content); } else if (field is ExpressionField expressionField) { getFieldValue = () => { if (double.TryParse(expressionField.Value, out double value)) { return(value); } return(0); }; expressionField.ValueChanged += (s, e) => { if (!changeDueToSlider) { changeDueToField = true; slider.Value = GetSlider0To1FromField(); changeDueToField = false; } }; expressionField.Content.Descendants <TextWidget>().First().TextChanged += (s, e) => { if (!changeDueToSlider) { changeDueToField = true; var textWidget = expressionField.Content.Descendants <TextWidget>().First(); double.TryParse(textWidget.Text, out double textWidgetValue); slider.Value = GetSlider0To1FromValue(textWidgetValue); changeDueToField = false; } }; var leftHold = new FlowLayoutWidget() { HAnchor = HAnchor.Stretch, Margin = new BorderDouble(0, 0, 66 / GuiWidget.DeviceScale + sliderRightMargin, 0) }; leftHold.AddChild(new HorizontalSpacer()); leftHold.AddChild(slider); field.Content.AddChild(leftHold, 0); content = field.Content; } // set the initial value of the slider changeDueToField = true; slider.Value = GetSlider0To1FromField(); changeDueToField = false; return(content); } return(field.Content); }