From 7b65b02d928911c0818f1ae3d7d3e1ae8a74d8ce Mon Sep 17 00:00:00 2001 From: Lars Brubaker Date: Fri, 27 Oct 2023 16:54:35 -0700 Subject: [PATCH] Making the editor have local function to reduce complexity --- .../ApplicationView/ApplicationController.cs | 8 +- ...onsConfig.cs => EditorExtensionsConfig.cs} | 31 +- .../EditorTools/EditorToolsPlugin.cs | 2 +- .../DesignTools/PropertyEditor.cs | 2222 ++++++++--------- ...r.cs => SelectedChildrenPropertyEditor.cs} | 11 +- .../DesignTools/StringPropertyEditor.cs | 264 ++ .../PartPreviewWindow/GenerateSupportPanel.cs | 5 +- .../PartPreviewWindow/SelectedObjectPanel.cs | 13 +- 8 files changed, 1325 insertions(+), 1231 deletions(-) rename MatterControlLib/ApplicationView/Config/{ExtensionsConfig.cs => EditorExtensionsConfig.cs} (69%) rename MatterControlLib/DesignTools/{SelectedChildrenEditor.cs => SelectedChildrenPropertyEditor.cs} (96%) create mode 100644 MatterControlLib/DesignTools/StringPropertyEditor.cs diff --git a/MatterControlLib/ApplicationView/ApplicationController.cs b/MatterControlLib/ApplicationView/ApplicationController.cs index 1f6f10879..a5e2567b2 100644 --- a/MatterControlLib/ApplicationView/ApplicationController.cs +++ b/MatterControlLib/ApplicationView/ApplicationController.cs @@ -146,7 +146,7 @@ namespace MatterHackers.MatterControl public IEnumerable ActivePrinters => this.Workspaces.Where(w => w.Printer != null).Select(w => w.Printer); - public ExtensionsConfig Extensions { get; } + public EditorExtensionsConfig EditorExtensions { get; } public PopupMenu GetActionMenuForSceneItem(bool addInSubmenu, View3DWidget view3DWidget) { @@ -1166,9 +1166,9 @@ namespace MatterHackers.MatterControl // _activePrinters = new List(); }; - this.Extensions = new ExtensionsConfig(this.Library); - this.Extensions.Register(new SheetEditor()); - this.Extensions.Register(new PropertyEditor()); + this.EditorExtensions = new EditorExtensionsConfig(this.Library); + this.EditorExtensions.RegisterFactory((theme, undoBuffer) => new SheetEditor()); + this.EditorExtensions.RegisterFactory((theme, undoBuffer) => new PropertyEditor(theme, undoBuffer)); HelpArticle helpArticle = null; diff --git a/MatterControlLib/ApplicationView/Config/ExtensionsConfig.cs b/MatterControlLib/ApplicationView/Config/EditorExtensionsConfig.cs similarity index 69% rename from MatterControlLib/ApplicationView/Config/ExtensionsConfig.cs rename to MatterControlLib/ApplicationView/Config/EditorExtensionsConfig.cs index e32f24468..94bb1d7bd 100644 --- a/MatterControlLib/ApplicationView/Config/ExtensionsConfig.cs +++ b/MatterControlLib/ApplicationView/Config/EditorExtensionsConfig.cs @@ -30,6 +30,7 @@ either expressed or implied, of the FreeBSD Project. using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; using MatterHackers.MatterControl.Library; using MatterHackers.MatterControl.PartPreviewWindow; @@ -41,7 +42,7 @@ using MatterHackers.MeshVisualizer; namespace MatterHackers.MatterControl { - public class ExtensionsConfig + public class EditorExtensionsConfig { private LibraryConfig libraryConfig; @@ -52,37 +53,39 @@ namespace MatterHackers.MatterControl // new SubtractAndReplace() // }; - public ExtensionsConfig(LibraryConfig libraryConfig) + public EditorExtensionsConfig(LibraryConfig libraryConfig) { this.libraryConfig = libraryConfig; - objectEditorsByType = new Dictionary>(); + objectEditorsByType = new Dictionary>>(); } - private void MapTypesToEditor(IObjectEditor editor) + private void MapTypesToEditorFactory(Func object3DEditorFactory) { - foreach (Type type in editor.SupportedTypes()) + var editor = object3DEditorFactory.Invoke(null, null); + + foreach (Type type in editor.SupportedTypes()) { - if (!objectEditorsByType.TryGetValue(type, out HashSet mappedEditors)) + if (!objectEditorsByType.TryGetValue(type, out HashSet> mappedEditorsFactories)) { - mappedEditors = new HashSet(); - objectEditorsByType.Add(type, mappedEditors); + mappedEditorsFactories = new HashSet>(); + objectEditorsByType.Add(type, mappedEditorsFactories); } - mappedEditors.Add(editor); + mappedEditorsFactories.Add(object3DEditorFactory); } } - public void Register(IObjectEditor object3DEditor) + public void RegisterFactory(Func object3DEditorFactory) { - this.MapTypesToEditor(object3DEditor); + this.MapTypesToEditorFactory(object3DEditorFactory); } - private Dictionary> objectEditorsByType; + private Dictionary>> objectEditorsByType; - public HashSet GetEditorsForType(Type selectedItemType) + public HashSet> GetEditorsForType(Type selectedItemType) { - HashSet mappedEditors; + HashSet> mappedEditors; objectEditorsByType.TryGetValue(selectedItemType, out mappedEditors); if (mappedEditors == null) diff --git a/MatterControlLib/DesignTools/EditorTools/EditorToolsPlugin.cs b/MatterControlLib/DesignTools/EditorTools/EditorToolsPlugin.cs index 20b437672..c1d08ea03 100644 --- a/MatterControlLib/DesignTools/EditorTools/EditorToolsPlugin.cs +++ b/MatterControlLib/DesignTools/EditorTools/EditorToolsPlugin.cs @@ -48,7 +48,7 @@ namespace MatterHackers.Plugins.EditorTools applicationController.Library.RegisterCreator(item); } - applicationController.Extensions.Register(new OpenSCADBuilder()); + applicationController.EditorExtensions.RegisterFactory((theme, undoBuffer) => new OpenSCADBuilder()); // applicationController.Extensions.Register(new PrimitivesEditor()); } diff --git a/MatterControlLib/DesignTools/PropertyEditor.cs b/MatterControlLib/DesignTools/PropertyEditor.cs index e4d9ee7cd..40d08a5d3 100644 --- a/MatterControlLib/DesignTools/PropertyEditor.cs +++ b/MatterControlLib/DesignTools/PropertyEditor.cs @@ -27,13 +27,6 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Web; using Markdig.Agg; using MatterHackers.Agg; using MatterHackers.Agg.Image; @@ -48,280 +41,186 @@ using MatterHackers.MatterControl.Library.Widgets; using MatterHackers.MatterControl.PartPreviewWindow; using MatterHackers.MatterControl.SlicerConfiguration; using MatterHackers.VectorMath; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; namespace MatterHackers.MatterControl.DesignTools { + public interface IPropertyEditorFactory + { + GuiWidget CreateEditor(PropertyEditor propertyEditor, EditableProperty property, EditorContext context); + } + public class PropertyEditor : IObjectEditor - { - static PropertyEditor() - { - SelectedChildrenEditor.Register(); + { + public const BindingFlags OwnedPropertiesOnly = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; + + private static readonly Dictionary AllowedTypes = new Dictionary() + { + { typeof(double), null }, + { typeof(int), null }, + { typeof(char), null }, + { typeof(bool), null }, + { typeof(StringOrExpression), null }, + { typeof(DoubleOrExpression), null }, + { typeof(IntOrExpression), null }, + { typeof(Color), null }, + { typeof(Vector2), null }, + { typeof(Vector3), null }, + { typeof(Vector4), null }, + { typeof(DirectionVector), null }, + { typeof(DirectionAxis), null }, + { typeof(ImageBuffer), null }, + { typeof(Histogram), null }, + { typeof(DateTime), null }, + { typeof(List), null }, + { typeof(PrinterSettingsLayer), null } + }; + + private SafeList rows = new SafeList(); + + public ThemeConfig Theme { get; } + + public UndoBuffer UndoBuffer { get; } + + static PropertyEditor() + { + SelectedChildrenPropertyEditor.Register(); + StringPropertyEditor.Register(); + } + + public PropertyEditor(ThemeConfig theme, UndoBuffer undoBuffer) + { + this.Theme = theme; + this.UndoBuffer = undoBuffer; } public string Name => "Property Editor"; - public IEnumerable SupportedTypes() => new Type[] { typeof(IObject3D) }; + public static void AddMarkDownDescription(object item, GuiWidget editControlsContainer, ThemeConfig theme) + { + if (item.GetType().GetCustomAttributes(typeof(MarkDownDescriptionAttribute), true).FirstOrDefault() is MarkDownDescriptionAttribute markdownDescription) + { + var markdownWidget = new MarkdownWidget(theme) + { + Padding = new BorderDouble(left: theme.DefaultContainerPadding / 2), + Markdown = markdownDescription.Markdown, + VAnchor = VAnchor.Fit + }; - private static readonly Dictionary AllowedTypes = new Dictionary() - { - { typeof(double), null }, - { typeof(int), null }, - { typeof(char), null }, - { typeof(string), null }, - { typeof(bool), null }, - { typeof(StringOrExpression), null }, - { typeof(DoubleOrExpression), null }, - { typeof(IntOrExpression), null }, - { typeof(Color), null }, - { typeof(Vector2), null }, - { typeof(Vector3), null }, - { typeof(Vector4), null }, - { typeof(DirectionVector), null }, - { typeof(DirectionAxis), null }, - { typeof(ImageBuffer), null }, - { typeof(Histogram), null }, - { typeof(DateTime), null }, - { typeof(List), null }, - { typeof(PrinterSettingsLayer), null } - }; - - public delegate GuiWidget CreateEditorDelegate(EditableProperty property, EditorContext context, UndoBuffer undoBuffer, ThemeConfig theme); - - public static void RegisterEditor(Type type, CreateEditorDelegate createEditorDelegate) - { - if (!AllowedTypes.ContainsKey(type)) - { - AllowedTypes.Add(type, createEditorDelegate); + editControlsContainer.AddChild(markdownWidget); } - } + } - public const BindingFlags OwnedPropertiesOnly = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; + public static void AddUnlockLinkIfRequired(IObject3D item, GuiWidget editControlsContainer, ThemeConfig theme) + { + (string url, GuiWidget markdownWidget)? unlockdata = null; - private SafeList rows = new SafeList(); + if (item.GetType().GetCustomAttributes(typeof(RequiresPermissionsAttribute), true).FirstOrDefault() is RequiresPermissionsAttribute unlockLink + && !ApplicationController.Instance.UserHasPermission(item)) + { + unlockdata = ApplicationController.Instance.GetUnlockData?.Invoke(item, theme); + } + else if (!item.Persistable) + { + // find the first self or child that is not authorized + var permission = item.DescendantsAndSelf() + .Where(i => !i.Persistable && !ApplicationController.Instance.UserHasPermission(i)); - public GuiWidget Create(object item, UndoBuffer undoBuffer, ThemeConfig theme) - { - var mainContainer = new FlowLayoutWidget(FlowDirection.TopToBottom) - { - HAnchor = HAnchor.Stretch - }; + if (permission.Any()) + { + var unlockItem = permission.First(); + unlockdata = ApplicationController.Instance.GetUnlockData?.Invoke(unlockItem, theme); + } + } - if (item != null) - { - var context = new EditorContext() - { - Item = item - }; + if (unlockdata != null && !string.IsNullOrEmpty(unlockdata.Value.url)) + { + if (unlockdata.Value.markdownWidget != null) + { + unlockdata.Value.markdownWidget.VAnchor = VAnchor.Fit; + editControlsContainer.AddChild(unlockdata.Value.markdownWidget); + } - // CreateEditor - if (context.Item is IObject3D itemAsIObject3D) - { - AddUnlockLinkIfRequired(itemAsIObject3D, mainContainer, theme); - } - - AddMarkDownDescription(context.Item, mainContainer, theme); + editControlsContainer.AddChild(GetUnlockRow(theme, unlockdata.Value.url)); + } + } - GuiWidget scope = mainContainer; + public static void AddWebPageLinkIfRequired(object item, FlowLayoutWidget editControlsContainer, ThemeConfig theme) + { + if (item.GetType().GetCustomAttributes(typeof(WebPageLinkAttribute), true).FirstOrDefault() is WebPageLinkAttribute unlockLink) + { + var detailsLink = new ThemedTextIconButton(unlockLink.ButtonName.Localize(), StaticData.Instance.LoadIcon("internet.png", 16, 16).GrayToColor(theme.TextColor), theme) + { + BackgroundColor = theme.MinimalShade, + ToolTipText = unlockLink.Url, + }; + detailsLink.Click += (s, e) => + { + ApplicationController.LaunchBrowser(unlockLink.Url); + }; - rows.Clear(); + // website row + editControlsContainer.AddChild(new SettingsRow(unlockLink.RowName, null, detailsLink, theme)); + } + } - // Create a field editor for each editable property detected via reflection - foreach (var property in GetEditablePropreties(context.Item)) - { - if (property.PropertyInfo.GetCustomAttributes(true).OfType().Any()) - { - continue; - } + public static FlowLayoutWidget CreateSettingsColumn(EditableProperty property, UIField field, bool fullWidth = false) + { + return CreateSettingsColumn(property.DisplayName.Localize(), field, property.Description, fullWidth: fullWidth); + } - // Create SectionWidget for SectionStartAttributes - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is SectionStartAttribute sectionStart) - { - var column = new FlowLayoutWidget() - { - FlowDirection = FlowDirection.TopToBottom, - Padding = new BorderDouble(theme.DefaultContainerPadding).Clone(top: 0) - }; + public static FlowLayoutWidget CreateSettingsColumn(EditableProperty property) + { + return CreateSettingsColumn(property.DisplayName.Localize(), property.Description); + } - bool expanded = true; + public static IEnumerable GetEditablePropreties(object item) + { + return item.GetType().GetProperties(OwnedPropertiesOnly) + .Where(pi => (AllowedTypes.ContainsKey(pi.PropertyType) || pi.PropertyType.IsEnum) + && pi.GetGetMethod() != null + && pi.GetSetMethod() != null) + .Select(p => new EditableProperty(p, item)); + } - var sectionState = item as ISectionState; - if (sectionState != null) - { - expanded = sectionState.GetSectionExpansion(sectionStart.Title); - } + public static IEnumerable GetExecutableFunctions(IObject3D item) + { + BindingFlags buttonFunctionsOnly = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; - var section = new SectionWidget(sectionStart.Title, column, theme, expanded: expanded); - theme.ApplyBoxStyle(section); + return item.GetType().GetProperties(buttonFunctionsOnly) + .Where(pi => (AllowedTypes.ContainsKey(pi.PropertyType) || pi.PropertyType.IsEnum) + && pi.GetGetMethod() != null + && pi.GetSetMethod() != null) + .Select(p => new EditableProperty(p, item)); + } - if (sectionState != null) - { - section.ExpandedChanged += (s, e) => sectionState.SectionExpansionChanged(sectionStart.Title, e); - } + public static GuiWidget GetUnlockRow(ThemeConfig theme, string url) + { + var detailsLink = new ThemedTextIconButton("Unlock".Localize(), StaticData.Instance.LoadIcon("locked.png", 16, 16).GrayToColor(theme.TextColor), theme) + { + Margin = 5, + ToolTipText = "Visit MatterHackers.com to Purchase".Localize() + }; + detailsLink.Click += (s, e) => + { + ApplicationController.LaunchBrowser(url); + }; + theme.ApplyPrimaryActionStyle(detailsLink); - mainContainer.AddChild(section); + return new SettingsRow("Demo Mode".Localize(), null, detailsLink, theme); + } - scope = column; - } - - // Create SectionWidget for SectionStartAttributes - if (property.PropertyInfo.GetCustomAttributes(true).OfType().Any()) - { - // Push scope back to mainContainer on - scope = mainContainer; - } - - var editor = CreatePropertyEditor(rows, property, undoBuffer, context, theme); - if (editor != null) - { - scope.AddChild(editor); - } - } - - AddWebPageLinkIfRequired(context.Item, mainContainer, theme); - - // add in an Update button if applicable - var showUpdate = context.Item.GetType().GetCustomAttributes(typeof(ShowUpdateButtonAttribute), true).FirstOrDefault() as ShowUpdateButtonAttribute; - if (showUpdate?.Show == true) - { - var updateButton = new ThemedTextButton("Update".Localize(), theme) - { - Margin = 5, - BackgroundColor = theme.MinimalShade, - HAnchor = HAnchor.Right, - VAnchor = VAnchor.Absolute - }; - updateButton.Click += (s, e) => - { - if (context.Item is IObject3D itemAsIObject3D) - { - itemAsIObject3D.Invalidate(new InvalidateArgs(itemAsIObject3D, InvalidateType.Properties)); - } - }; - mainContainer.AddChild(updateButton); - } - - // add any function buttons last - AddFunctionButtons(item, mainContainer, theme); - - // Init with custom 'UpdateControls' hooks - (context.Item as IPropertyGridModifier)?.UpdateControls(new PublicPropertyChange(context, "Update_Button")); - } - - return mainContainer; - } - - private void AddFunctionButtons(object item, FlowLayoutWidget mainContainer, ThemeConfig theme) - { - if (item is IEditorButtonProvider editorButtonProvider) - { - foreach (var editorButtonData in editorButtonProvider.GetEditorButtonsData()) - { - var editorButton = new ThemedTextButton(editorButtonData.Name, theme) - { - Margin = 5, - ToolTipText = editorButtonData.HelpText, - BackgroundColor = theme.MinimalShade, - }; - if (editorButtonData.PrimaryAction) - { - theme.ApplyPrimaryActionStyle(editorButton); - } - - var row = new SettingsRow("".Localize(), null, editorButton, theme); - editorButtonData.SetStates?.Invoke(editorButton, row); - editorButton.Click += (s, e) => - { - editorButtonData.Action?.Invoke(); - }; - - mainContainer.AddChild(row); - } - } - } - - public static SettingsRow CreateSettingsRow(EditableProperty property, GuiWidget content, ThemeConfig theme, SafeList rows = null) - { - var row = new SettingsRow(property.DisplayName.Localize(), property.Description, content, theme); - if (rows != null) - { - rows.Add(row); - row.SetTextRightMargin(rows); - } - return row; - } - - public static FlowLayoutWidget CreateSettingsColumn(EditableProperty property, UIField field, bool fullWidth = false) - { - return CreateSettingsColumn(property.DisplayName.Localize(), field, property.Description, fullWidth: fullWidth); - } - - public static FlowLayoutWidget CreateSettingsColumn(EditableProperty property) - { - return CreateSettingsColumn(property.DisplayName.Localize(), property.Description); - } - - private static FlowLayoutWidget CreateSettingsColumn(string labelText, UIField field, string toolTipText = null, bool fullWidth = false) - { - var row = new FlowLayoutWidget() - { - HAnchor = HAnchor.Stretch - }; - - if (!fullWidth) - { - row.AddChild(new HorizontalSpacer()); - } - - row.AddChild(field.Content); - - var column = CreateSettingsColumn(labelText, toolTipText); - column.AddChild(row); - - return column; - } - - private static FlowLayoutWidget CreateSettingsColumn(string labelText, string toolTipText = null) - { - var theme = AppContext.Theme; - - var column = new FlowLayoutWidget(FlowDirection.TopToBottom) - { - HAnchor = HAnchor.Stretch, - Padding = new BorderDouble(9, 5, 5, 5), // Use hard-coded 9 pixel left margin to match SettingsRow - ToolTipText = toolTipText - }; - - if (!string.IsNullOrEmpty(labelText)) - { - var label = SettingsRow.CreateSettingsLabel(labelText, toolTipText, theme.TextColor); - label.VAnchor = VAnchor.Absolute; - label.HAnchor = HAnchor.Left; - - column.AddChild(label); - } - - return column; - } - - public static IEnumerable GetEditablePropreties(object item) - { - return item.GetType().GetProperties(OwnedPropertiesOnly) - .Where(pi => (AllowedTypes.ContainsKey(pi.PropertyType) || pi.PropertyType.IsEnum) - && pi.GetGetMethod() != null - && pi.GetSetMethod() != null) - .Select(p => new EditableProperty(p, item)); - } - - public static IEnumerable GetExecutableFunctions(IObject3D item) - { - BindingFlags buttonFunctionsOnly = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; - - return item.GetType().GetProperties(buttonFunctionsOnly) - .Where(pi => (AllowedTypes.ContainsKey(pi.PropertyType) || pi.PropertyType.IsEnum) - && pi.GetGetMethod() != null - && pi.GetSetMethod() != null) - .Select(p => new EditableProperty(p, item)); - } + public static void RegisterEditor(Type type, IPropertyEditorFactory propertyEditorFactory) + { + if (!AllowedTypes.ContainsKey(type)) + { + AllowedTypes.Add(type, propertyEditorFactory); + } + } public static void RegisterValueChanged(EditableProperty property, UndoBuffer undoBuffer, EditorContext context, UIField field, Func valueFromString, Func valueToString = null) { @@ -376,1054 +275,977 @@ namespace MatterHackers.MatterControl.DesignTools } }; } - - public static GuiWidget CreatePropertyEditor(SafeList rows, EditableProperty property, UndoBuffer undoBuffer, EditorContext context, ThemeConfig theme) - { - if (property == null - || context == null) + + public GuiWidget Create(object item, UndoBuffer undoBuffer, ThemeConfig theme) + { + var mainContainer = new FlowLayoutWidget(FlowDirection.TopToBottom) { - return null; + HAnchor = HAnchor.Stretch + }; + + if (item != null) + { + var context = new EditorContext() + { + Item = item + }; + + // CreateEditor + if (context.Item is IObject3D itemAsIObject3D) + { + AddUnlockLinkIfRequired(itemAsIObject3D, mainContainer, theme); + } + + AddMarkDownDescription(context.Item, mainContainer, theme); + + GuiWidget scope = mainContainer; + + rows.Clear(); + + // Create a field editor for each editable property detected via reflection + foreach (var property in GetEditablePropreties(context.Item)) + { + if (property.PropertyInfo.GetCustomAttributes(true).OfType().Any()) + { + continue; + } + + // Create SectionWidget for SectionStartAttributes + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is SectionStartAttribute sectionStart) + { + var column = new FlowLayoutWidget() + { + FlowDirection = FlowDirection.TopToBottom, + Padding = new BorderDouble(theme.DefaultContainerPadding).Clone(top: 0) + }; + + bool expanded = true; + + var sectionState = item as ISectionState; + if (sectionState != null) + { + expanded = sectionState.GetSectionExpansion(sectionStart.Title); + } + + var section = new SectionWidget(sectionStart.Title, column, theme, expanded: expanded); + theme.ApplyBoxStyle(section); + + if (sectionState != null) + { + section.ExpandedChanged += (s, e) => sectionState.SectionExpansionChanged(sectionStart.Title, e); + } + + mainContainer.AddChild(section); + + scope = column; + } + + // Create SectionWidget for SectionStartAttributes + if (property.PropertyInfo.GetCustomAttributes(true).OfType().Any()) + { + // Push scope back to mainContainer on + scope = mainContainer; + } + + var editor = CreatePropertyEditor(property, undoBuffer, context, theme); + if (editor != null) + { + scope.AddChild(editor); + } + } + + AddWebPageLinkIfRequired(context.Item, mainContainer, theme); + + // add in an Update button if applicable + var showUpdate = context.Item.GetType().GetCustomAttributes(typeof(ShowUpdateButtonAttribute), true).FirstOrDefault() as ShowUpdateButtonAttribute; + if (showUpdate?.Show == true) + { + var updateButton = new ThemedTextButton("Update".Localize(), theme) + { + Margin = 5, + BackgroundColor = theme.MinimalShade, + HAnchor = HAnchor.Right, + VAnchor = VAnchor.Absolute + }; + updateButton.Click += (s, e) => + { + if (context.Item is IObject3D itemAsIObject3D) + { + itemAsIObject3D.Invalidate(new InvalidateArgs(itemAsIObject3D, InvalidateType.Properties)); + } + }; + mainContainer.AddChild(updateButton); + } + + // add any function buttons last + AddFunctionButtons(item, mainContainer, theme); + + // Init with custom 'UpdateControls' hooks + (context.Item as IPropertyGridModifier)?.UpdateControls(new PublicPropertyChange(context, "Update_Button")); } - var contextItem = context.Item; + return mainContainer; + } + + public GuiWidget CreatePropertyEditor(EditableProperty property, UndoBuffer undoBuffer, EditorContext context, ThemeConfig theme) + { + if (property == null + || context == null) + { + return null; + } + + var contextItem = context.Item; var contextObject3D = contextItem as IObject3D; var propertyIObject3D = property.Source as IObject3D; var propertyGridModifier = property.Source as IPropertyGridModifier; - GuiWidget rowContainer = null; + GuiWidget rowContainer = null; - // Get reflected property value once, then test for each case below - var propertyValue = property.Value; + // Get reflected property value once, then test for each case below + var propertyValue = property.Value; - var readOnly = property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null; + var readOnly = property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null; - // create a double editor - if (AllowedTypes.ContainsKey(propertyValue.GetType()) - && AllowedTypes[propertyValue.GetType()] != null) - { - rowContainer = AllowedTypes[propertyValue.GetType()](property, context, undoBuffer, theme); + // create a double editor + if (AllowedTypes.ContainsKey(propertyValue.GetType()) + && AllowedTypes[propertyValue.GetType()] != null) + { + rowContainer = AllowedTypes[propertyValue.GetType()].CreateEditor(this, property, context); } - else if (propertyValue is double doubleValue) - { - if (readOnly) - { - var valueField = new TextWidget(string.Format("{0:n}", doubleValue), textColor: theme.TextColor, pointSize: 10) - { - AutoExpandBoundsToText = true - }; + else 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); + 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); - } - } + void RefreshField(object s, InvalidateArgs e) + { + if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) + { + double newValue = (double)property.Value; + valueField.Text = string.Format("{0:n}", newValue); + } + } - propertyIObject3D.Invalidated += RefreshField; - valueField.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; - } - else // normal edit row - { - var field = new DoubleField(theme); - field.Initialize(0); - field.DoubleValue = doubleValue; - field.ClearUndoHistory(); - RegisterValueChanged(property, undoBuffer, context, field, (valueString) => { return double.Parse(valueString); }); + propertyIObject3D.Invalidated += RefreshField; + valueField.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; + } + else // normal edit row + { + var field = new DoubleField(theme); + field.Initialize(0); + field.DoubleValue = doubleValue; + field.ClearUndoHistory(); + RegisterValueChanged(property, undoBuffer, context, 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; - } - } - } + void RefreshField(object s, InvalidateArgs e) + { + if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) + { + double newValue = (double)property.Value; + if (newValue != field.DoubleValue) + { + field.DoubleValue = newValue; + } + } + } - field.Content.Descendants().First().Name = property.DisplayName + " Edit"; - if (propertyIObject3D != null) - { - propertyIObject3D.Invalidated += RefreshField; - field.Content.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; - } + field.Content.Descendants().First().Name = property.DisplayName + " Edit"; + if (propertyIObject3D != null) + { + propertyIObject3D.Invalidated += RefreshField; + field.Content.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; + } - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is MaxDecimalPlacesAttribute decimalPlaces) - { - field.Content.Descendants().First().MaxDecimalsPlaces = decimalPlaces.Number; - } + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is MaxDecimalPlacesAttribute decimalPlaces) + { + field.Content.Descendants().First().MaxDecimalsPlaces = decimalPlaces.Number; + } - rowContainer = CreateSettingsRow(property, - PublicPropertySliderFunctions.GetFieldContentWithSlider(property, context, field, undoBuffer, (valueString) => { return double.Parse(valueString); }, theme), - theme); - } - } - else if (propertyValue is Color color) - { - var field = new ColorField(theme, color, null, false); - field.Initialize(0); - field.ValueChanged += (s, e) => - { - property.SetValue(field.Color); - propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); - propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); - }; + rowContainer = CreateSettingsRow(property, + PublicPropertySliderFunctions.GetFieldContentWithSlider(property, context, field, undoBuffer, (valueString) => { return double.Parse(valueString); }, theme), + theme); + } + } + else if (propertyValue is Color color) + { + var field = new ColorField(theme, color, null, false); + field.Initialize(0); + field.ValueChanged += (s, e) => + { + property.SetValue(field.Color); + propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); + propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); + }; - rowContainer = CreateSettingsRow(property, field.Content, theme); - } - else if (propertyValue is Vector2 vector2) - { - var field = new Vector2Field(theme); - field.Initialize(0); - field.Vector2 = vector2; - field.ClearUndoHistory(); + rowContainer = CreateSettingsRow(property, field.Content, theme); + } + else if (propertyValue is Vector2 vector2) + { + var field = new Vector2Field(theme); + field.Initialize(0); + field.Vector2 = vector2; + field.ClearUndoHistory(); - RegisterValueChanged(property, - undoBuffer, - context, - field, - (valueString) => Vector2.Parse(valueString), - (value) => - { - var s = ((Vector2)value).ToString(); - return s.Substring(1, s.Length - 2); - }); + RegisterValueChanged(property, + undoBuffer, + context, + 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(); + rowContainer = CreateSettingsColumn(property, field); + } + else if (propertyValue is Vector3 vector3) + { + var field = new Vector3Field(theme); + field.Initialize(0); + field.Vector3 = vector3; + field.ClearUndoHistory(); - RegisterValueChanged( + RegisterValueChanged( property, undoBuffer, context, field, - (valueString) => Vector3.Parse(valueString), - (value) => - { - var s = ((Vector3)value).ToString(); - return s.Substring(1, s.Length - 2); - }); + (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 Vector4 vector4) - { - var field = new Vector4Field(theme); - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is VectorFieldLabelsAttribute vectorFieldLabels) - { - field.Labels = vectorFieldLabels.Labels; - } + rowContainer = CreateSettingsColumn(property, field); + } + else if (propertyValue is Vector4 vector4) + { + var field = new Vector4Field(theme); + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is VectorFieldLabelsAttribute vectorFieldLabels) + { + field.Labels = vectorFieldLabels.Labels; + } - field.Initialize(0); - field.Vector4 = vector4; - field.ClearUndoHistory(); + field.Initialize(0); + field.Vector4 = vector4; + field.ClearUndoHistory(); - RegisterValueChanged( + RegisterValueChanged( property, undoBuffer, context, field, - (valueString) => Vector4.Parse(valueString), - (value) => - { - var s = ((Vector4)value).ToString(); - return s.Substring(1, s.Length - 2); - }); + (valueString) => Vector4.Parse(valueString), + (value) => + { + var s = ((Vector4)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(); + 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); - propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); - propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); - }; + field.ValueChanged += (s, e) => + { + property.SetValue(field.DirectionVector); + propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); + propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); + }; - rowContainer = CreateSettingsRow(property, field.Content, theme); - } - else if (propertyValue is DirectionAxis directionAxis) - { - rowContainer = CreateSettingsColumn(property); + rowContainer = CreateSettingsRow(property, field.Content, theme); + } + else if (propertyValue is DirectionAxis directionAxis) + { + rowContainer = CreateSettingsColumn(property); - var field1 = new DirectionVectorField(theme); - field1.Initialize(0); - field1.ClearUndoHistory(); + var field1 = new DirectionVectorField(theme); + field1.Initialize(0); + field1.ClearUndoHistory(); - field1.SetValue(new DirectionVector() - { - Normal = directionAxis.Normal - }); + field1.SetValue(new DirectionVector() + { + Normal = directionAxis.Normal + }); - rowContainer.AddChild(new SettingsRow("Axis".Localize(), null, field1.Content, theme)); + 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 - propertyIObject3D.Children.First().GetAxisAlignedBoundingBox().Center; - field2.ClearUndoHistory(); + // 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 - propertyIObject3D.Children.First().GetAxisAlignedBoundingBox().Center; + field2.ClearUndoHistory(); - var row2 = CreateSettingsColumn("Offset".Localize(), field2); + var row2 = CreateSettingsColumn("Offset".Localize(), field2); - // update this when changed - void UpdateData(object s, InvalidateArgs e) - { - field2.Vector3 = ((DirectionAxis)property.Value).Origin - propertyIObject3D.Children.First().GetAxisAlignedBoundingBox().Center; - } + // update this when changed + void UpdateData(object s, InvalidateArgs e) + { + field2.Vector3 = ((DirectionAxis)property.Value).Origin - propertyIObject3D.Children.First().GetAxisAlignedBoundingBox().Center; + } propertyIObject3D.Invalidated += UpdateData; - field2.Content.Closed += (s, e) => - { + field2.Content.Closed += (s, e) => + { propertyIObject3D.Invalidated -= UpdateData; - }; + }; - // update functions - field1.ValueChanged += (s, e) => - { - property.SetValue(new DirectionAxis() - { - Normal = field1.DirectionVector.Normal, - Origin = propertyIObject3D.Children.First().GetAxisAlignedBoundingBox().Center + field2.Vector3 - }); - propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); - propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); - }; - field2.ValueChanged += (s, e) => - { - property.SetValue(new DirectionAxis() - { - Normal = field1.DirectionVector.Normal, - Origin = propertyIObject3D.Children.First().GetAxisAlignedBoundingBox().Center + field2.Vector3 - }); - propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); - propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); - }; + // update functions + field1.ValueChanged += (s, e) => + { + property.SetValue(new DirectionAxis() + { + Normal = field1.DirectionVector.Normal, + Origin = propertyIObject3D.Children.First().GetAxisAlignedBoundingBox().Center + field2.Vector3 + }); + propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); + propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); + }; + field2.ValueChanged += (s, e) => + { + property.SetValue(new DirectionAxis() + { + Normal = field1.DirectionVector.Normal, + Origin = propertyIObject3D.Children.First().GetAxisAlignedBoundingBox().Center + field2.Vector3 + }); + propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); + propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); + }; - rowContainer.AddChild(row2); - } - else if (propertyValue is PrinterSettingsLayer printerSettingsLayer) - { - var printerProfile = new PrinterSettings(); - rowContainer = AddMaterialWidget.CreateSetingsList(printerProfile, printerSettingsLayer, theme); + rowContainer.AddChild(row2); + } + else if (propertyValue is PrinterSettingsLayer printerSettingsLayer) + { + var printerProfile = new PrinterSettings(); + rowContainer = AddMaterialWidget.CreateSetingsList(printerProfile, printerSettingsLayer, theme); - rowContainer.Children.First().AddChild(new HorizontalLine(Color.Green) - { - Height = 4 * GuiWidget.DeviceScale - }, 0); + rowContainer.Children.First().AddChild(new HorizontalLine(Color.Green) + { + Height = 4 * GuiWidget.DeviceScale + }, 0); - rowContainer.Children.First().AddChild(new HorizontalLine(Color.Green) - { - Height = 4 * GuiWidget.DeviceScale - }); - } - else if (propertyValue is ImageBuffer imageBuffer) - { - var imageDisplayAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault(); + rowContainer.Children.First().AddChild(new HorizontalLine(Color.Green) + { + Height = 4 * GuiWidget.DeviceScale + }); + } + else if (propertyValue is ImageBuffer imageBuffer) + { + var imageDisplayAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault(); - rowContainer = CreateSettingsColumn(property); - GuiWidget imageWidget; - if (imageDisplayAttribute?.Stretch == true) - { - var responsiveImageWidget = new ResponsiveImageWidget(imageBuffer); - responsiveImageWidget.RenderCheckerboard = true; - imageWidget = responsiveImageWidget; - } - else - { - imageWidget = new ImageWidget(imageBuffer); - } + rowContainer = CreateSettingsColumn(property); + GuiWidget imageWidget; + if (imageDisplayAttribute?.Stretch == true) + { + var responsiveImageWidget = new ResponsiveImageWidget(imageBuffer); + responsiveImageWidget.RenderCheckerboard = true; + imageWidget = responsiveImageWidget; + } + else + { + imageWidget = new ImageWidget(imageBuffer); + } - if (imageDisplayAttribute != null) - { - imageWidget.MaximumSize = new Vector2(imageDisplayAttribute.MaxXSize * GuiWidget.DeviceScale, int.MaxValue); - imageWidget.Margin = imageDisplayAttribute.GetMargin(); - } - else - { - imageWidget.Margin = new BorderDouble(0, 3); - } + if (imageDisplayAttribute != null) + { + imageWidget.MaximumSize = new Vector2(imageDisplayAttribute.MaxXSize * GuiWidget.DeviceScale, int.MaxValue); + imageWidget.Margin = imageDisplayAttribute.GetMargin(); + } + else + { + imageWidget.Margin = new BorderDouble(0, 3); + } - ImageBuffer GetImageCheckingForErrors() - { - var image = imageBuffer; - if (propertyIObject3D is ImageObject3D imageObject2) - { - image = imageObject2.Image; - } + ImageBuffer GetImageCheckingForErrors() + { + var image = imageBuffer; + if (propertyIObject3D is ImageObject3D imageObject2) + { + image = imageObject2.Image; + } - // Show image load error if needed - if (image == null) - { - image = new ImageBuffer(185, 185).SetPreMultiply(); - var graphics2D = image.NewGraphics2D(); + // Show image load error if needed + if (image == null) + { + image = new ImageBuffer(185, 185).SetPreMultiply(); + var graphics2D = image.NewGraphics2D(); - graphics2D.FillRectangle(0, 0, 185, 185, theme.MinimalShade); - graphics2D.Rectangle(0, 0, 185, 185, theme.SlightShade); - graphics2D.DrawString("Error Loading Image".Localize() + "...", 10, 185 / 2, baseline: Agg.Font.Baseline.BoundsCenter, color: Color.Red, pointSize: theme.DefaultFontSize, drawFromHintedCach: true); - } + graphics2D.FillRectangle(0, 0, 185, 185, theme.MinimalShade); + graphics2D.Rectangle(0, 0, 185, 185, theme.SlightShade); + graphics2D.DrawString("Error Loading Image".Localize() + "...", 10, 185 / 2, baseline: Agg.Font.Baseline.BoundsCenter, color: Color.Red, pointSize: theme.DefaultFontSize, drawFromHintedCach: true); + } - return image; - } + return image; + } - void UpdateEditorImage() - { - if (imageWidget is ResponsiveImageWidget responsive) - { - responsive.Image = GetImageCheckingForErrors(); - } - else - { - ((ImageWidget)imageWidget).Image = GetImageCheckingForErrors(); - } - } + void UpdateEditorImage() + { + if (imageWidget is ResponsiveImageWidget responsive) + { + responsive.Image = GetImageCheckingForErrors(); + } + else + { + ((ImageWidget)imageWidget).Image = GetImageCheckingForErrors(); + } + } - void RefreshField(object s, InvalidateArgs e) - { - if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) - { - UpdateEditorImage(); - } - } + void RefreshField(object s, InvalidateArgs e) + { + if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) + { + UpdateEditorImage(); + } + } - propertyIObject3D.Invalidated += RefreshField; - imageWidget.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; + propertyIObject3D.Invalidated += RefreshField; + imageWidget.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; - if (propertyIObject3D is IEditorWidgetModifier editorWidgetModifier) - { - editorWidgetModifier.ModifyEditorWidget(imageWidget, theme, UpdateEditorImage); - } + if (propertyIObject3D is IEditorWidgetModifier editorWidgetModifier) + { + editorWidgetModifier.ModifyEditorWidget(imageWidget, theme, UpdateEditorImage); + } - rowContainer.AddChild(imageWidget); - } - else if (propertyValue is Histogram histogram) - { - rowContainer = CreateSettingsColumn(property); - var histogramWidget = histogram.NewEditWidget(theme); - rowContainer.AddChild(histogramWidget); - void RefreshField(object s, InvalidateArgs e) - { - if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) - { - if (propertyIObject3D is IImageProvider imageProvider) - { - var _ = imageProvider.Image; - } - } - } + rowContainer.AddChild(imageWidget); + } + else if (propertyValue is Histogram histogram) + { + rowContainer = CreateSettingsColumn(property); + var histogramWidget = histogram.NewEditWidget(theme); + rowContainer.AddChild(histogramWidget); + void RefreshField(object s, InvalidateArgs e) + { + if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) + { + if (propertyIObject3D is IImageProvider imageProvider) + { + var _ = imageProvider.Image; + } + } + } - propertyIObject3D.Invalidated += RefreshField; - rowContainer.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; - } - else if (propertyValue is List stringList) - { - var field = new SurfacedEditorsField(theme, propertyIObject3D); - field.Initialize(0); - field.ListValue = stringList; - field.ValueChanged += (s, e) => - { - property.SetValue(field.ListValue); - }; + propertyIObject3D.Invalidated += RefreshField; + rowContainer.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; + } + else if (propertyValue is List stringList) + { + var field = new SurfacedEditorsField(theme, propertyIObject3D); + field.Initialize(0); + field.ListValue = stringList; + field.ValueChanged += (s, e) => + { + property.SetValue(field.ListValue); + }; - rowContainer = CreateSettingsColumn(property, field); + rowContainer = CreateSettingsColumn(property, field); - rowContainer.Descendants().FirstOrDefault()?.Close(); - } - // create a int editor - else if (propertyValue is int intValue) - { - if (readOnly) - { - string FormateInt(int value) - { - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null) - { - var minutes = intValue / 60; - var hours = minutes / 60; - return $"{hours:00}:{minutes % 60:00}:{intValue % 60:00}"; - } - else - { - return string.Format("{0:n0}", intValue); - } - } + rowContainer.Descendants().FirstOrDefault()?.Close(); + } + // create a int editor + else if (propertyValue is int intValue) + { + if (readOnly) + { + string FormateInt(int value) + { + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null) + { + var minutes = intValue / 60; + var hours = minutes / 60; + return $"{hours:00}:{minutes % 60:00}:{intValue % 60:00}"; + } + else + { + return string.Format("{0:n0}", intValue); + } + } - var valueField = new TextWidget(FormateInt(intValue), - textColor: theme.TextColor, - pointSize: 10) - { - AutoExpandBoundsToText = true, - Margin = new BorderDouble(0, 0, 7, 0), - }; + var valueField = new TextWidget(FormateInt(intValue), + textColor: theme.TextColor, + pointSize: 10) + { + AutoExpandBoundsToText = true, + Margin = new BorderDouble(0, 0, 7, 0), + }; - rowContainer = new SettingsRow(property.DisplayName.Localize(), - property.Description, - valueField, - theme); + 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(FormateInt(intValue), newValue); - } - } + void RefreshField(object s, InvalidateArgs e) + { + if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) + { + int newValue = (int)property.Value; + valueField.Text = string.Format(FormateInt(intValue), newValue); + } + } - propertyIObject3D.Invalidated += RefreshField; - valueField.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; - } - else // normal edit row - { - var field = new IntField(theme); - field.Initialize(0); - field.IntValue = intValue; - field.ClearUndoHistory(); + propertyIObject3D.Invalidated += RefreshField; + valueField.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; + } + else // normal edit row + { + var field = new IntField(theme); + field.Initialize(0); + field.IntValue = intValue; + field.ClearUndoHistory(); - RegisterValueChanged(property, undoBuffer, context, field, (valueString) => { return int.Parse(valueString); }); + RegisterValueChanged(property, undoBuffer, context, 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; - } - } - } + void RefreshField(object s, InvalidateArgs e) + { + if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) + { + int newValue = (int)property.Value; + if (newValue != field.IntValue) + { + field.IntValue = newValue; + } + } + } - if (propertyIObject3D != null) - { - propertyIObject3D.Invalidated += RefreshField; - field.Content.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; - } + if (propertyIObject3D != null) + { + propertyIObject3D.Invalidated += RefreshField; + field.Content.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; + } - rowContainer = CreateSettingsRow(property, field.Content, theme); - } - } - else if (propertyValue is bool boolValue) - { - // create a bool editor - var field = new ToggleboxField(theme); - field.Initialize(0); - field.Checked = boolValue; + rowContainer = CreateSettingsRow(property, field.Content, theme); + } + } + else if (propertyValue is bool boolValue) + { + // create a bool editor + var field = new ToggleboxField(theme); + field.Initialize(0); + field.Checked = boolValue; - RegisterValueChanged(property, undoBuffer, context, - field, - (valueString) => { return valueString == "1"; }, - (value) => { return ((bool)value) ? "1" : "0"; }); - rowContainer = CreateSettingsRow(property, field.Content, theme); - } - else if (propertyValue is DoubleOrExpression doubleExpresion) - { - // create a string editor - var field = new ExpressionField(theme) - { - Name = property.DisplayName + " Field" - }; - field.Initialize(0); - if (doubleExpresion.Expression.Contains("=")) - { - field.SetValue(doubleExpresion.Expression, false); - } - else // make sure it is formatted - { - var format = "0." + new string('#', 5); - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is MaxDecimalPlacesAttribute decimalPlaces) - { - format = "0." + new string('#', Math.Min(10, decimalPlaces.Number)); - } + RegisterValueChanged(property, undoBuffer, context, + field, + (valueString) => { return valueString == "1"; }, + (value) => { return ((bool)value) ? "1" : "0"; }); + rowContainer = CreateSettingsRow(property, field.Content, theme); + } + else if (propertyValue is DoubleOrExpression doubleExpresion) + { + // create a string editor + var field = new ExpressionField(theme) + { + Name = property.DisplayName + " Field" + }; + field.Initialize(0); + if (doubleExpresion.Expression.Contains("=")) + { + field.SetValue(doubleExpresion.Expression, false); + } + else // make sure it is formatted + { + var format = "0." + new string('#', 5); + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is MaxDecimalPlacesAttribute decimalPlaces) + { + format = "0." + new string('#', Math.Min(10, decimalPlaces.Number)); + } - field.SetValue(doubleExpresion.Value(propertyIObject3D).ToString(format), false); - } + field.SetValue(doubleExpresion.Value(propertyIObject3D).ToString(format), false); + } - field.ClearUndoHistory(); - RegisterValueChanged(property, undoBuffer, context, - field, - (valueString) => - { - doubleExpresion.Expression = valueString; - return doubleExpresion; - }, - (value) => - { - return ((DoubleOrExpression)value).Expression; - }); + field.ClearUndoHistory(); + RegisterValueChanged(property, undoBuffer, context, + field, + (valueString) => + { + doubleExpresion.Expression = valueString; + return doubleExpresion; + }, + (value) => + { + return ((DoubleOrExpression)value).Expression; + }); - rowContainer = CreateSettingsRow(property, - PublicPropertySliderFunctions.GetFieldContentWithSlider(property, context, field, undoBuffer, (valueString) => - { - doubleExpresion.Expression = valueString; - return doubleExpresion; - }, theme), - theme, - rows); + rowContainer = CreateSettingsRow(property, + PublicPropertySliderFunctions.GetFieldContentWithSlider(property, context, field, undoBuffer, (valueString) => + { + doubleExpresion.Expression = valueString; + return doubleExpresion; + }, theme), + theme, + true); - void RefreshField(object s, InvalidateArgs e) - { - // This code only executes when the in scene controls are updating the objects data and the display needs to tack them. - if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) - { - var newValue = (DoubleOrExpression)property.Value; - // if (newValue.Expression != field.Value) - { - // we should never be in the situation where there is an '=' as the in scene controls should be disabled - if (newValue.Expression.StartsWith("=")) - { - field.TextValue = newValue.Expression; - } - else - { - var format = "0." + new string('#', 5); - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is MaxDecimalPlacesAttribute decimalPlaces) - { - format = "0." + new string('#', Math.Min(10, decimalPlaces.Number)); - } + void RefreshField(object s, InvalidateArgs e) + { + // This code only executes when the in scene controls are updating the objects data and the display needs to tack them. + if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) + { + var newValue = (DoubleOrExpression)property.Value; + // if (newValue.Expression != field.Value) + { + // we should never be in the situation where there is an '=' as the in scene controls should be disabled + if (newValue.Expression.StartsWith("=")) + { + field.TextValue = newValue.Expression; + } + else + { + var format = "0." + new string('#', 5); + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is MaxDecimalPlacesAttribute decimalPlaces) + { + format = "0." + new string('#', Math.Min(10, decimalPlaces.Number)); + } - var rawValue = newValue.Value(propertyIObject3D); - field.TextValue = rawValue.ToString(format); - } - } - } - } + var rawValue = newValue.Value(propertyIObject3D); + field.TextValue = rawValue.ToString(format); + } + } + } + } - propertyIObject3D.Invalidated += RefreshField; - field.Content.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; - } - else if (propertyValue is IntOrExpression intExpresion) - { - // create a string editor - var field = new ExpressionField(theme) - { - Name = property.DisplayName + " Field" - }; - field.Initialize(0); - if (intExpresion.Expression.Contains("=")) - { - field.SetValue(intExpresion.Expression, false); - } - else // make sure it is formatted - { - var format = "0." + new string('#', 5); - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is MaxDecimalPlacesAttribute decimalPlaces) - { - format = "0." + new string('#', Math.Min(10, decimalPlaces.Number)); - } + propertyIObject3D.Invalidated += RefreshField; + field.Content.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; + } + else if (propertyValue is IntOrExpression intExpresion) + { + // create a string editor + var field = new ExpressionField(theme) + { + Name = property.DisplayName + " Field" + }; + field.Initialize(0); + if (intExpresion.Expression.Contains("=")) + { + field.SetValue(intExpresion.Expression, false); + } + else // make sure it is formatted + { + var format = "0." + new string('#', 5); + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is MaxDecimalPlacesAttribute decimalPlaces) + { + format = "0." + new string('#', Math.Min(10, decimalPlaces.Number)); + } - field.SetValue(intExpresion.Value(propertyIObject3D).ToString(format), false); - } + field.SetValue(intExpresion.Value(propertyIObject3D).ToString(format), false); + } - field.ClearUndoHistory(); - RegisterValueChanged(property, undoBuffer, context, - field, - (valueString) => - { - intExpresion.Expression = valueString; - return intExpresion; - }, - (value) => - { - return ((IntOrExpression)value).Expression; - }); + field.ClearUndoHistory(); + RegisterValueChanged(property, undoBuffer, context, + field, + (valueString) => + { + intExpresion.Expression = valueString; + return intExpresion; + }, + (value) => + { + return ((IntOrExpression)value).Expression; + }); - rowContainer = CreateSettingsRow(property, - PublicPropertySliderFunctions.GetFieldContentWithSlider(property, context, field, undoBuffer, (valueString) => - { - intExpresion.Expression = valueString; - return intExpresion; - }, theme), - theme, - rows); + rowContainer = CreateSettingsRow(property, + PublicPropertySliderFunctions.GetFieldContentWithSlider(property, context, field, undoBuffer, (valueString) => + { + intExpresion.Expression = valueString; + return intExpresion; + }, theme), + theme, + true); - void RefreshField(object s, InvalidateArgs e) - { - // This code only executes when the in scene controls are updating the objects data and the display needs to tack them. - if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) - { - var newValue = (IntOrExpression)property.Value; - // if (newValue.Expression != field.Value) - { - // we should never be in the situation where there is an '=' as the in scene controls should be disabled - if (newValue.Expression.StartsWith("=")) - { - field.TextValue = newValue.Expression; - } - else - { - var format = "0." + new string('#', 5); - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is MaxDecimalPlacesAttribute decimalPlaces) - { - format = "0." + new string('#', Math.Min(10, decimalPlaces.Number)); - } + void RefreshField(object s, InvalidateArgs e) + { + // This code only executes when the in scene controls are updating the objects data and the display needs to tack them. + if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) + { + var newValue = (IntOrExpression)property.Value; + // if (newValue.Expression != field.Value) + { + // we should never be in the situation where there is an '=' as the in scene controls should be disabled + if (newValue.Expression.StartsWith("=")) + { + field.TextValue = newValue.Expression; + } + else + { + var format = "0." + new string('#', 5); + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() is MaxDecimalPlacesAttribute decimalPlaces) + { + format = "0." + new string('#', Math.Min(10, decimalPlaces.Number)); + } - var rawValue = newValue.Value(propertyIObject3D); - field.TextValue = rawValue.ToString(format); - } - } - } - } + var rawValue = newValue.Value(propertyIObject3D); + field.TextValue = rawValue.ToString(format); + } + } + } + } - propertyIObject3D.Invalidated += RefreshField; - field.Content.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; - } - else if (propertyValue is string stringValue) - { - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null) - { - rowContainer = NewImageSearchWidget(theme); - } - else if (propertyIObject3D is AssetObject3D assetObject - && property.PropertyInfo.Name == "AssetPath") - { - // This is the AssetPath property of an asset object, add a button to set the AssetPath from a file - // Change button - var changeButton = new ThemedTextButton(property.Description, theme) - { - BackgroundColor = theme.MinimalShade, - Margin = 3 - }; + propertyIObject3D.Invalidated += RefreshField; + field.Content.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; + } + else if (propertyValue is StringOrExpression stringOrExpression) + { + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null) + { + // create a a multi-line string editor + var field = new MultilineStringField(theme); + field.Initialize(0); + field.SetValue(stringOrExpression.Expression, false); + field.ClearUndoHistory(); + field.Content.HAnchor = HAnchor.Stretch; + field.Content.Descendants().FirstOrDefault().MaximumSize = new Vector2(double.MaxValue, 200); + field.Content.Descendants().FirstOrDefault().Parent.VAnchor = VAnchor.Top; + field.Content.MinimumSize = new Vector2(0, 100 * GuiWidget.DeviceScale); + field.Content.Margin = new BorderDouble(0, 0, 0, 5); + RegisterValueChanged(property, undoBuffer, context, + field, + (valueString) => new StringOrExpression(valueString), + (value) => + { + return ((StringOrExpression)value).Expression; + }); + rowContainer = CreateSettingsColumn(property, field, fullWidth: true); + } + else + { + // create a string editor + var field = new TextField(theme); + field.Initialize(0); + field.SetValue(stringOrExpression.Expression, false); + field.ClearUndoHistory(); + field.Content.HAnchor = HAnchor.Stretch; + RegisterValueChanged(property, undoBuffer, context, + field, + (valueString) => new StringOrExpression(valueString), + (value) => + { + return ((StringOrExpression)value).Expression; + }); + rowContainer = CreateSettingsColumn(property, field, fullWidth: true); + } + } + else if (propertyValue is DateTime dateTime) + { + // create a string editor + var field = new TextField(theme); + field.Initialize(0); + field.SetValue(dateTime.ToString("MM/dd/yyyy HH:mm"), false); + field.ClearUndoHistory(); + field.Content.HAnchor = HAnchor.Stretch; + RegisterValueChanged(property, undoBuffer, context, + field, + (valueString) => DateTime.Parse(valueString), + (value) => + { + return ((DateTime)value).ToString("MM/dd/yyyy HH:mm"); + }); + rowContainer = CreateSettingsColumn(property, field, fullWidth: true); + } + 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)); + propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); + propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); + }; - rowContainer = new SettingsRow(property.DisplayName, - null, - changeButton, - theme); + rowContainer = CreateSettingsRow(property, field.Content, theme); + } + else if (property.PropertyType.IsEnum) + { + // create an enum editor + UIField field; + var enumDisplayAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType().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); + } + } - changeButton.Click += (sender, e) => - { - UiThread.RunOnIdle(() => - { - ImageObject3D.ShowOpenDialog(assetObject); - }); - }; - } - else - { - if (readOnly) - { - WrappedTextWidget wrappedTextWidget = null; - if (!string.IsNullOrEmpty(property.DisplayName)) - { - rowContainer = new GuiWidget() - { - HAnchor = HAnchor.Stretch, - VAnchor = VAnchor.Fit, - Margin = 9 - }; + field.Initialize(0); + RegisterValueChanged(property, undoBuffer, context, + field, + (valueString) => + { + return Enum.Parse(property.PropertyType, valueString); + }); - var displayName = rowContainer.AddChild(new TextWidget(property.DisplayName, - textColor: theme.TextColor, - pointSize: 10) - { - VAnchor = VAnchor.Center, - }); + field.ValueChanged += (s, e) => + { + if (property.Value.ToString() != field.Value) + { + property.SetValue(Enum.Parse(property.PropertyType, field.Value)); + propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); + propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); + } + }; - var wrapContainer = new GuiWidget() - { - Margin = new BorderDouble(displayName.Width + displayName.Margin.Width + 15, 3, 3, 3), - HAnchor = HAnchor.Stretch, - VAnchor = VAnchor.Fit - }; - wrappedTextWidget = new WrappedTextWidget(stringValue, textColor: theme.TextColor, pointSize: 10) - { - HAnchor = HAnchor.Stretch - }; - wrappedTextWidget.TextWidget.HAnchor = HAnchor.Right; - wrapContainer.AddChild(wrappedTextWidget); - rowContainer.AddChild(wrapContainer); - } - else - { - rowContainer = wrappedTextWidget = new WrappedTextWidget(stringValue, - textColor: theme.TextColor, - pointSize: 10) - { - Margin = 9 - }; - } + if (addToSettingsRow) + { + rowContainer = CreateSettingsRow(property, field.Content, theme); + } + else + { + // field.Content.Margin = new BorderDouble(3, 0); + field.Content.HAnchor = HAnchor.Stretch; + rowContainer = field.Content; + } - void RefreshField(object s, InvalidateArgs e) - { - if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) - { - wrappedTextWidget.Text = property.Value.ToString(); - } - } + void RefreshField(object s, InvalidateArgs e) + { + if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) + { + var newValue = property.Value.ToString(); + if (field.Content is MHDropDownList dropDown) + { + if (field.Value != newValue) + { + field.SetValue(newValue, false); + dropDown.SelectedValue = newValue; + } + } + } + } - if (propertyIObject3D != null) - { - propertyIObject3D.Invalidated += RefreshField; - wrappedTextWidget.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; - } - } - else // normal edit row - { - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null) - { - // create a a multi-line string editor - var field = new MultilineStringField(theme, property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null); - field.Initialize(0); - field.SetValue(stringValue, false); - field.ClearUndoHistory(); - field.Content.HAnchor = HAnchor.Stretch; - field.Content.Descendants().FirstOrDefault().MaximumSize = new Vector2(double.MaxValue, 200); - field.Content.Descendants().FirstOrDefault().Parent.VAnchor = VAnchor.Top; - field.Content.MinimumSize = new Vector2(0, 100 * GuiWidget.DeviceScale); - field.Content.Margin = new BorderDouble(0, 0, 0, 5); - RegisterValueChanged(property, undoBuffer, context, 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(property, undoBuffer, context, field, (valueString) => valueString); - rowContainer = CreateSettingsRow(property, field.Content, theme, rows); - - // check for DirectoryPathAttribute - var directoryPathAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault(); - if (directoryPathAttribute != null) - { - // add a browse button - var browseButton = new ThemedIconButton(StaticData.Instance.LoadIcon(Path.Combine("Library", "folder.png"), 16, 16).GrayToColor(theme.TextColor), theme) - { - ToolTipText = "Select Folder".Localize(), - }; - browseButton.Click += (s, e) => - { - UiThread.RunOnIdle(() => - { - AggContext.FileDialogs.SelectFolderDialog( - new SelectFolderDialogParams(directoryPathAttribute.Message) - { - ActionButtonLabel = directoryPathAttribute.ActionLabel, - Title = ApplicationController.Instance.ProductName + " - " + "Select A Folder".Localize(), - RootFolder = SelectFolderDialogParams.RootFolderTypes.Specify, - FolderPath = stringValue - }, - (openParams) => - { - if (!string.IsNullOrEmpty(openParams.FolderPath)) - { - field.SetValue(openParams.FolderPath, true); - } - }); - }); - }; - rowContainer.AddChild(browseButton); - } - } - } - } - } - else if (propertyValue is StringOrExpression stringOrExpression) - { - if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null) - { - // create a a multi-line string editor - var field = new MultilineStringField(theme); - field.Initialize(0); - field.SetValue(stringOrExpression.Expression, false); - field.ClearUndoHistory(); - field.Content.HAnchor = HAnchor.Stretch; - field.Content.Descendants().FirstOrDefault().MaximumSize = new Vector2(double.MaxValue, 200); - field.Content.Descendants().FirstOrDefault().Parent.VAnchor = VAnchor.Top; - field.Content.MinimumSize = new Vector2(0, 100 * GuiWidget.DeviceScale); - field.Content.Margin = new BorderDouble(0, 0, 0, 5); - RegisterValueChanged(property, undoBuffer, context, - field, - (valueString) => new StringOrExpression(valueString), - (value) => - { - return ((StringOrExpression)value).Expression; - }); - rowContainer = CreateSettingsColumn(property, field, fullWidth: true); - } - else - { - // create a string editor - var field = new TextField(theme); - field.Initialize(0); - field.SetValue(stringOrExpression.Expression, false); - field.ClearUndoHistory(); - field.Content.HAnchor = HAnchor.Stretch; - RegisterValueChanged(property, undoBuffer, context, - field, - (valueString) => new StringOrExpression(valueString), - (value) => - { - return ((StringOrExpression)value).Expression; - }); - rowContainer = CreateSettingsColumn(property, field, fullWidth: true); - } - } - else if (propertyValue is DateTime dateTime) - { - // create a string editor - var field = new TextField(theme); - field.Initialize(0); - field.SetValue(dateTime.ToString("MM/dd/yyyy HH:mm"), false); - field.ClearUndoHistory(); - field.Content.HAnchor = HAnchor.Stretch; - RegisterValueChanged(property, undoBuffer, context, - field, - (valueString) => DateTime.Parse(valueString), - (value) => - { - return ((DateTime)value).ToString("MM/dd/yyyy HH:mm"); - }); - rowContainer = CreateSettingsColumn(property, field, fullWidth: true); - } - 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)); - propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); - propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); - }; - - rowContainer = CreateSettingsRow(property, field.Content, theme); - } - else if (property.PropertyType.IsEnum) - { - // create an enum editor - UIField field; - var enumDisplayAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType().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(property, undoBuffer, context, - 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)); - propertyIObject3D?.Invalidate(new InvalidateArgs(contextObject3D, InvalidateType.Properties)); - propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); - } - }; - - if (addToSettingsRow) - { - rowContainer = CreateSettingsRow(property, field.Content, theme); - } - else - { - // field.Content.Margin = new BorderDouble(3, 0); - field.Content.HAnchor = HAnchor.Stretch; - rowContainer = field.Content; - } - - void RefreshField(object s, InvalidateArgs e) - { - if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) - { - var newValue = property.Value.ToString(); - if (field.Content is MHDropDownList dropDown) - { - if (field.Value != newValue) - { - field.SetValue(newValue, false); - dropDown.SelectedValue = newValue; - } - } - } - } - - if (propertyIObject3D != null) - { - propertyIObject3D.Invalidated += RefreshField; - field.Content.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; - } - - } - else if (propertyValue is IObject3D item - && ApplicationController.Instance.Extensions.GetEditorsForType(property.PropertyType)?.FirstOrDefault() is IObjectEditor iObject3DEditor) - { - // Use known IObject3D editors - rowContainer = iObject3DEditor.Create(item, undoBuffer, theme); - } + if (propertyIObject3D != null) + { + propertyIObject3D.Invalidated += RefreshField; + field.Content.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; + } + } + else if (propertyValue is IObject3D item + && ApplicationController.Instance.EditorExtensions.GetEditorsForType(property.PropertyType)?.FirstOrDefault() is Func iObject3DEditorFactory) + { + // Use known IObject3D editors + rowContainer = iObject3DEditorFactory.Invoke(theme, undoBuffer).Create(item, undoBuffer, theme); + } // remember the row name and widget context.EditRows.Add(property.PropertyInfo.Name, rowContainer); - return rowContainer; + return rowContainer; } - public static GuiWidget NewImageSearchWidget(ThemeConfig theme, string postPend = "silhouette") - { - var searchRow = new FlowLayoutWidget() - { - HAnchor = HAnchor.Stretch, - Margin = new BorderDouble(5, 0) - }; + public SettingsRow CreateSettingsRow(EditableProperty property, GuiWidget content, ThemeConfig theme, bool alignRightMargin = false) + { + var row = new SettingsRow(property.DisplayName.Localize(), property.Description, content, theme); + if (rows != null) + { + rows.Add(row); + row.SetTextRightMargin(rows); + } + return row; + } - var searchField = new ThemedTextEditWidget("", theme, messageWhenEmptyAndNotSelected: "Search Google for images") - { - HAnchor = HAnchor.Stretch, - VAnchor = VAnchor.Center - }; - searchRow.AddChild(searchField); - var searchButton = new ThemedIconButton(StaticData.Instance.LoadIcon("icon_search_24x24.png", 16, 16).GrayToColor(theme.TextColor), theme) - { - ToolTipText = "Search".Localize(), - }; - searchRow.AddChild(searchButton); + public IEnumerable SupportedTypes() => new Type[] { typeof(IObject3D) }; - void DoSearch(object s, EventArgs e) - { - var search = HttpUtility.UrlEncode(searchField.Text); - if (!string.IsNullOrEmpty(search)) - { - ApplicationController.LaunchBrowser($"http://www.google.com/search?q={search} {postPend}&tbm=isch"); - } - }; + private static FlowLayoutWidget CreateSettingsColumn(string labelText, UIField field, string toolTipText = null, bool fullWidth = false) + { + var row = new FlowLayoutWidget() + { + HAnchor = HAnchor.Stretch + }; - searchField.ActualTextEditWidget.EditComplete += DoSearch; - searchButton.Click += DoSearch; - return searchRow; - } + if (!fullWidth) + { + row.AddChild(new HorizontalSpacer()); + } - public static void AddUnlockLinkIfRequired(IObject3D item, GuiWidget editControlsContainer, ThemeConfig theme) - { - (string url, GuiWidget markdownWidget)? unlockdata = null; + row.AddChild(field.Content); - if (item.GetType().GetCustomAttributes(typeof(RequiresPermissionsAttribute), true).FirstOrDefault() is RequiresPermissionsAttribute unlockLink - && !ApplicationController.Instance.UserHasPermission(item)) - { - unlockdata = ApplicationController.Instance.GetUnlockData?.Invoke(item, theme); - } - else if (!item.Persistable) - { - // find the first self or child that is not authorized - var permission = item.DescendantsAndSelf() - .Where(i => !i.Persistable && !ApplicationController.Instance.UserHasPermission(i)); + var column = CreateSettingsColumn(labelText, toolTipText); + column.AddChild(row); - if (permission.Any()) - { - var unlockItem = permission.First(); - unlockdata = ApplicationController.Instance.GetUnlockData?.Invoke(unlockItem, theme); - } - } + return column; + } - if (unlockdata != null && !string.IsNullOrEmpty(unlockdata.Value.url)) - { - if (unlockdata.Value.markdownWidget != null) - { - unlockdata.Value.markdownWidget.VAnchor = VAnchor.Fit; - editControlsContainer.AddChild(unlockdata.Value.markdownWidget); - } + private static FlowLayoutWidget CreateSettingsColumn(string labelText, string toolTipText = null) + { + var theme = AppContext.Theme; - editControlsContainer.AddChild(GetUnlockRow(theme, unlockdata.Value.url)); - } - } + var column = new FlowLayoutWidget(FlowDirection.TopToBottom) + { + HAnchor = HAnchor.Stretch, + Padding = new BorderDouble(9, 5, 5, 5), // Use hard-coded 9 pixel left margin to match SettingsRow + ToolTipText = toolTipText + }; - public static void AddMarkDownDescription(object item, GuiWidget editControlsContainer, ThemeConfig theme) - { - if (item.GetType().GetCustomAttributes(typeof(MarkDownDescriptionAttribute), true).FirstOrDefault() is MarkDownDescriptionAttribute markdownDescription) - { - var markdownWidget = new MarkdownWidget(theme) - { - Padding = new BorderDouble(left: theme.DefaultContainerPadding / 2), - Markdown = markdownDescription.Markdown, - VAnchor = VAnchor.Fit - }; - - editControlsContainer.AddChild(markdownWidget); - } - } + if (!string.IsNullOrEmpty(labelText)) + { + var label = SettingsRow.CreateSettingsLabel(labelText, toolTipText, theme.TextColor); + label.VAnchor = VAnchor.Absolute; + label.HAnchor = HAnchor.Left; - public static GuiWidget GetUnlockRow(ThemeConfig theme, string url) - { - var detailsLink = new ThemedTextIconButton("Unlock".Localize(), StaticData.Instance.LoadIcon("locked.png", 16, 16).GrayToColor(theme.TextColor), theme) - { - Margin = 5, - ToolTipText = "Visit MatterHackers.com to Purchase".Localize() - }; - detailsLink.Click += (s, e) => - { - ApplicationController.LaunchBrowser(url); - }; - theme.ApplyPrimaryActionStyle(detailsLink); + column.AddChild(label); + } - return new SettingsRow("Demo Mode".Localize(), null, detailsLink, theme); - } + return column; + } - public static void AddWebPageLinkIfRequired(object item, FlowLayoutWidget editControlsContainer, ThemeConfig theme) - { - if (item.GetType().GetCustomAttributes(typeof(WebPageLinkAttribute), true).FirstOrDefault() is WebPageLinkAttribute unlockLink) - { - var detailsLink = new ThemedTextIconButton(unlockLink.ButtonName.Localize(), StaticData.Instance.LoadIcon("internet.png", 16, 16).GrayToColor(theme.TextColor), theme) - { - BackgroundColor = theme.MinimalShade, - ToolTipText = unlockLink.Url, - }; - detailsLink.Click += (s, e) => - { - ApplicationController.LaunchBrowser(unlockLink.Url); - }; + private void AddFunctionButtons(object item, FlowLayoutWidget mainContainer, ThemeConfig theme) + { + if (item is IEditorButtonProvider editorButtonProvider) + { + foreach (var editorButtonData in editorButtonProvider.GetEditorButtonsData()) + { + var editorButton = new ThemedTextButton(editorButtonData.Name, theme) + { + Margin = 5, + ToolTipText = editorButtonData.HelpText, + BackgroundColor = theme.MinimalShade, + }; + if (editorButtonData.PrimaryAction) + { + theme.ApplyPrimaryActionStyle(editorButton); + } - // website row - editControlsContainer.AddChild(new SettingsRow(unlockLink.RowName, null, detailsLink, theme)); - } - } - } + var row = new SettingsRow("".Localize(), null, editorButton, theme); + editorButtonData.SetStates?.Invoke(editorButton, row); + editorButton.Click += (s, e) => + { + editorButtonData.Action?.Invoke(); + }; + + mainContainer.AddChild(row); + } + } + } + } } \ No newline at end of file diff --git a/MatterControlLib/DesignTools/SelectedChildrenEditor.cs b/MatterControlLib/DesignTools/SelectedChildrenPropertyEditor.cs similarity index 96% rename from MatterControlLib/DesignTools/SelectedChildrenEditor.cs rename to MatterControlLib/DesignTools/SelectedChildrenPropertyEditor.cs index 0b5169413..dc7f8fb8f 100644 --- a/MatterControlLib/DesignTools/SelectedChildrenEditor.cs +++ b/MatterControlLib/DesignTools/SelectedChildrenPropertyEditor.cs @@ -37,12 +37,13 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using static MatterHackers.Agg.UI.OnScreenKeyboard; namespace MatterHackers.MatterControl.DesignTools { - public static class SelectedChildrenEditor + public class SelectedChildrenPropertyEditor : IPropertyEditorFactory { - public static GuiWidget CreateEditor(EditableProperty property, EditorContext context, UndoBuffer undoBuffer, ThemeConfig theme) + public GuiWidget CreateEditor(PropertyEditor propertyEditor, EditableProperty property, EditorContext context) { var childSelector = property.Source as SelectedChildren; if (childSelector != null) @@ -51,6 +52,8 @@ namespace MatterHackers.MatterControl.DesignTools throw new Exception("Passed the wrong type to the SelectedChildrenEditor"); } + var theme = propertyEditor.Theme; + var undoBuffer = propertyEditor.UndoBuffer; var contextItem = context.Item; var contextObject3D = contextItem as IObject3D; var propertyIObject3D = property.Source as IObject3D; @@ -76,7 +79,7 @@ namespace MatterHackers.MatterControl.DesignTools return childrenSelector; }); - rowContainer = PropertyEditor.CreateSettingsRow(property, field.Content, theme); + rowContainer = propertyEditor.CreateSettingsRow(property, field.Content, theme); } else // show the subtract editor for boolean subtract and subtract and replace { @@ -187,7 +190,7 @@ namespace MatterHackers.MatterControl.DesignTools public static void Register() { - PropertyEditor.RegisterEditor(typeof(SelectedChildren), CreateEditor); + PropertyEditor.RegisterEditor(typeof(SelectedChildren), new SelectedChildrenPropertyEditor()); } private static GuiWidget CreateSelector(SelectedChildren childSelector, IObject3D parent, ThemeConfig theme) diff --git a/MatterControlLib/DesignTools/StringPropertyEditor.cs b/MatterControlLib/DesignTools/StringPropertyEditor.cs new file mode 100644 index 000000000..06a9d89f0 --- /dev/null +++ b/MatterControlLib/DesignTools/StringPropertyEditor.cs @@ -0,0 +1,264 @@ +/* +Copyright (c) 2018, Lars Brubaker, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using MatterHackers.Agg; +using MatterHackers.Agg.Platform; +using MatterHackers.ImageProcessing; +using MatterHackers.Agg.UI; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.SlicerConfiguration; +using System; +using System.Linq; +using System.Web; +using System.ComponentModel; +using MatterHackers.VectorMath; +using System.IO; + +namespace MatterHackers.MatterControl.DesignTools +{ + public class StringPropertyEditor : IPropertyEditorFactory + { + public GuiWidget CreateEditor(PropertyEditor propertyEditor, EditableProperty property, EditorContext context) + { + var stringValue = property.Source as string; + if (stringValue != null) + { + // we passed the wrong type + throw new Exception("Passed the wrong type to the SelectedChildrenEditor"); + } + + var theme = propertyEditor.Theme; + var undoBuffer = propertyEditor.UndoBuffer; + + var contextItem = context.Item; + var contextObject3D = contextItem as IObject3D; + var propertyIObject3D = property.Source as IObject3D; + var propertyGridModifier = property.Source as IPropertyGridModifier; + + GuiWidget rowContainer = null; + + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null) + { + rowContainer = NewImageSearchWidget(theme); + } + else if (propertyIObject3D is AssetObject3D assetObject + && property.PropertyInfo.Name == "AssetPath") + { + // This is the AssetPath property of an asset object, add a button to set the AssetPath from a file + // Change button + var changeButton = new ThemedTextButton(property.Description, theme) + { + BackgroundColor = theme.MinimalShade, + Margin = 3 + }; + + rowContainer = new SettingsRow(property.DisplayName, + null, + changeButton, + theme); + + + changeButton.Click += (sender, e) => + { + UiThread.RunOnIdle(() => + { + ImageObject3D.ShowOpenDialog(assetObject); + }); + }; + } + else + { + var readOnly = property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null; + + if (readOnly) + { + WrappedTextWidget wrappedTextWidget = null; + if (!string.IsNullOrEmpty(property.DisplayName)) + { + rowContainer = new GuiWidget() + { + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Fit, + Margin = 9 + }; + + var displayName = rowContainer.AddChild(new TextWidget(property.DisplayName, + textColor: theme.TextColor, + pointSize: 10) + { + VAnchor = VAnchor.Center, + }); + + var wrapContainer = new GuiWidget() + { + Margin = new BorderDouble(displayName.Width + displayName.Margin.Width + 15, 3, 3, 3), + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Fit + }; + wrappedTextWidget = new WrappedTextWidget(stringValue, textColor: theme.TextColor, pointSize: 10) + { + HAnchor = HAnchor.Stretch + }; + wrappedTextWidget.TextWidget.HAnchor = HAnchor.Right; + wrapContainer.AddChild(wrappedTextWidget); + rowContainer.AddChild(wrapContainer); + } + else + { + rowContainer = wrappedTextWidget = new WrappedTextWidget(stringValue, + textColor: theme.TextColor, + pointSize: 10) + { + Margin = 9 + }; + } + + void RefreshField(object s, InvalidateArgs e) + { + if (e.InvalidateType.HasFlag(InvalidateType.DisplayValues)) + { + wrappedTextWidget.Text = property.Value.ToString(); + } + } + + if (propertyIObject3D != null) + { + propertyIObject3D.Invalidated += RefreshField; + wrappedTextWidget.Closed += (s, e) => propertyIObject3D.Invalidated -= RefreshField; + } + } + else // normal edit row + { + if (property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null) + { + // create a a multi-line string editor + var field = new MultilineStringField(theme, property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault() != null); + field.Initialize(0); + field.SetValue(stringValue, false); + field.ClearUndoHistory(); + field.Content.HAnchor = HAnchor.Stretch; + field.Content.Descendants().FirstOrDefault().MaximumSize = new Vector2(double.MaxValue, 200); + field.Content.Descendants().FirstOrDefault().Parent.VAnchor = VAnchor.Top; + field.Content.MinimumSize = new Vector2(0, 100 * GuiWidget.DeviceScale); + field.Content.Margin = new BorderDouble(0, 0, 0, 5); + PropertyEditor.RegisterValueChanged(property, undoBuffer, context, field, (valueString) => valueString); + rowContainer = PropertyEditor.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; + PropertyEditor.RegisterValueChanged(property, undoBuffer, context, field, (valueString) => valueString); + rowContainer = propertyEditor.CreateSettingsRow(property, field.Content, theme, true); + + // check for DirectoryPathAttribute + var directoryPathAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault(); + if (directoryPathAttribute != null) + { + // add a browse button + var browseButton = new ThemedIconButton(StaticData.Instance.LoadIcon(Path.Combine("Library", "folder.png"), 16, 16).GrayToColor(theme.TextColor), theme) + { + ToolTipText = "Select Folder".Localize(), + }; + browseButton.Click += (s, e) => + { + UiThread.RunOnIdle(() => + { + AggContext.FileDialogs.SelectFolderDialog( + new SelectFolderDialogParams(directoryPathAttribute.Message) + { + ActionButtonLabel = directoryPathAttribute.ActionLabel, + Title = ApplicationController.Instance.ProductName + " - " + "Select A Folder".Localize(), + RootFolder = SelectFolderDialogParams.RootFolderTypes.Specify, + FolderPath = stringValue + }, + (openParams) => + { + if (!string.IsNullOrEmpty(openParams.FolderPath)) + { + field.SetValue(openParams.FolderPath, true); + } + }); + }); + }; + rowContainer.AddChild(browseButton); + } + } + } + } + + return rowContainer; + } + + public static GuiWidget NewImageSearchWidget(ThemeConfig theme, string postPend = "silhouette") + { + var searchRow = new FlowLayoutWidget() + { + HAnchor = HAnchor.Stretch, + Margin = new BorderDouble(5, 0) + }; + + var searchField = new ThemedTextEditWidget("", theme, messageWhenEmptyAndNotSelected: "Search Google for images") + { + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Center + }; + searchRow.AddChild(searchField); + var searchButton = new ThemedIconButton(StaticData.Instance.LoadIcon("icon_search_24x24.png", 16, 16).GrayToColor(theme.TextColor), theme) + { + ToolTipText = "Search".Localize(), + }; + searchRow.AddChild(searchButton); + + void DoSearch(object s, EventArgs e) + { + var search = HttpUtility.UrlEncode(searchField.Text); + if (!string.IsNullOrEmpty(search)) + { + ApplicationController.LaunchBrowser($"http://www.google.com/search?q={search} {postPend}&tbm=isch"); + } + }; + + searchField.ActualTextEditWidget.EditComplete += DoSearch; + searchButton.Click += DoSearch; + return searchRow; + } + + public static void Register() + { + PropertyEditor.RegisterEditor(typeof(String), new StringPropertyEditor()); + } + } +} \ No newline at end of file diff --git a/MatterControlLib/PartPreviewWindow/GenerateSupportPanel.cs b/MatterControlLib/PartPreviewWindow/GenerateSupportPanel.cs index 41d020d70..7e59e5110 100644 --- a/MatterControlLib/PartPreviewWindow/GenerateSupportPanel.cs +++ b/MatterControlLib/PartPreviewWindow/GenerateSupportPanel.cs @@ -60,8 +60,9 @@ namespace MatterHackers.MatterControl.PartPreviewWindow var rows = new SafeList(); - var editor = PropertyEditor.CreatePropertyEditor( - rows, + var propertyEditor = new PropertyEditor(theme, new UndoBuffer()); + + var editor = propertyEditor.CreatePropertyEditor( new EditableProperty(propertyInfo, supportGenerator), null, new EditorContext(), diff --git a/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs b/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs index 5821f442d..e09914f08 100644 --- a/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs +++ b/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs @@ -250,7 +250,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow editorSectionWidget.Text = selectedItem.Name ?? selectedItemType.Name; - HashSet mappedEditors = ApplicationController.Instance.Extensions.GetEditorsForType(selectedItemType); + HashSet> mappedEditors = ApplicationController.Instance.EditorExtensions.GetEditorsForType(selectedItemType); var undoBuffer = sceneContext.Scene.UndoBuffer; @@ -310,9 +310,9 @@ namespace MatterHackers.MatterControl.PartPreviewWindow else { if (item != null - && ApplicationController.Instance.Extensions.GetEditorsForType(item.GetType())?.FirstOrDefault() is IObjectEditor editor) + && ApplicationController.Instance.EditorExtensions.GetEditorsForType(item.GetType())?.FirstOrDefault() is Func editorFactory) { - ShowObjectEditor((editor, item, item.Name), selectedItem); + ShowObjectEditor((editorFactory.Invoke(theme, undoBuffer), item, item.Name), selectedItem); } } @@ -559,9 +559,9 @@ namespace MatterHackers.MatterControl.PartPreviewWindow { if (instance is IObject3D object3D) { - if (ApplicationController.Instance.Extensions.GetEditorsForType(object3D.GetType())?.FirstOrDefault() is IObjectEditor editor) + if (ApplicationController.Instance.EditorExtensions.GetEditorsForType(object3D.GetType())?.FirstOrDefault() is Func editorFactory) { - ShowObjectEditor((editor, object3D, object3D.Name), selectedItem); + ShowObjectEditor((editorFactory.Invoke(theme, undoBuffer), object3D, object3D.Name), selectedItem); } } else if (JsonPathContext.ReflectionValueSystem.LastMemberValue is ReflectionTarget reflectionTarget) @@ -577,7 +577,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow var editableProperty = new EditableProperty(reflectionTarget.PropertyInfo, reflectionTarget.Source); - var editor = PropertyEditor.CreatePropertyEditor(rows, editableProperty, undoBuffer, context, theme); + var propertyEditor = new PropertyEditor(theme, undoBuffer); + var editor = propertyEditor.CreatePropertyEditor(editableProperty, undoBuffer, context, theme); if (editor != null) { editorPanel.AddChild(editor);