From 29aae8cf52ed52f2a00384633c10e7cd2d4c442b Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 29 Dec 2023 08:44:08 +0000 Subject: [PATCH 01/28] Ability to add a Slider --- src/Operations/AddViewOperation.cs | 27 ++++++++++++++++++++++++++- src/ViewFactory.cs | 2 -- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Operations/AddViewOperation.cs b/src/Operations/AddViewOperation.cs index 3c30210b..05801d24 100644 --- a/src/Operations/AddViewOperation.cs +++ b/src/Operations/AddViewOperation.cs @@ -78,8 +78,23 @@ protected override bool DoImpl() { var selectable = ViewFactory.SupportedViewTypes.ToArray(); - if (Modals.Get("Type of Control", "Add", true, selectable, static t => t?.Name ?? "Null", false, null, out var selected) && selected != null) + if (Modals.Get("Type of Control", "Add", true, selectable, this.TypeNameDelegate, false, null, out var selected) && selected != null) { + if (selected.IsGenericType) + { + // TODO: Move to some kind of helper class and allow more options later + var allowedTTypes = new[] { typeof(int), typeof(string), typeof(float) }; + + if(Modals.Get("Enter a Type for ", "Choose", true, allowedTTypes, this.TypeNameDelegate, false, null, out var selectedTType) && selectedTType != null) + { + selected = selected.MakeGenericType(new[] { selectedTType }); + } + else + { + return false; + } + } + this.add = ViewFactory.Create(selected); this.fieldName = this.to.GetUniqueFieldName(selected); } @@ -108,6 +123,16 @@ protected override bool DoImpl() return true; } + private string TypeNameDelegate(Type? t) + { + if (t == null) + { + return "Null"; + } + + return t.Name.Replace("`1", ""); + } + private View GetViewToAddTo() { if (this.to.View is TabView tabView) diff --git a/src/ViewFactory.cs b/src/ViewFactory.cs index 05edbf3e..239f5204 100644 --- a/src/ViewFactory.cs +++ b/src/ViewFactory.cs @@ -23,8 +23,6 @@ public static class ViewFactory typeof( ScrollBarView ), typeof( TreeView<> ), - typeof( Slider<> ), - // Theses are special types of view and shouldn't be added manually by user typeof( Frame ), From 6a1d070112407f2eca3eaf8577d327506bf29ac8 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 29 Dec 2023 08:55:33 +0000 Subject: [PATCH 02/28] Make Slider.Options a designable property --- src/Design.cs | 10 +++++++++- src/Operations/AddViewOperation.cs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Design.cs b/src/Design.cs index 10aada35..34b1fa9b 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -633,6 +633,9 @@ private void RegisterCheckboxDesignTimeChanges(CheckBox cb) private IEnumerable LoadDesignableProperties() { + var viewType = this.View.GetType(); + var isGenericType = viewType.IsGenericType; + yield return this.CreateProperty(nameof(this.View.Width)); yield return this.CreateProperty(nameof(this.View.Height)); @@ -655,13 +658,18 @@ private IEnumerable LoadDesignableProperties() yield return this.CreateProperty(nameof(TextField.Secret)); } + if (isGenericType && viewType.GetGenericTypeDefinition() == typeof(Slider<>)) + { + yield return this.CreateProperty(nameof(Slider.Options)); + } + if (this.View is SpinnerView) { yield return this.CreateProperty(nameof(SpinnerView.AutoSpin)); yield return new InstanceOfProperty( this, - this.View.GetType().GetProperty(nameof(SpinnerView.Style)) ?? throw new Exception($"Could not find expected Property SpinnerView.Style on View of Type '{this.View.GetType()}'")); + viewType.GetProperty(nameof(SpinnerView.Style)) ?? throw new Exception($"Could not find expected Property SpinnerView.Style on View of Type '{this.View.GetType()}'")); } if (this.View is ScrollView) diff --git a/src/Operations/AddViewOperation.cs b/src/Operations/AddViewOperation.cs index 05801d24..1143720c 100644 --- a/src/Operations/AddViewOperation.cs +++ b/src/Operations/AddViewOperation.cs @@ -83,7 +83,7 @@ protected override bool DoImpl() if (selected.IsGenericType) { // TODO: Move to some kind of helper class and allow more options later - var allowedTTypes = new[] { typeof(int), typeof(string), typeof(float) }; + var allowedTTypes = new[] { typeof(int), typeof(string), typeof(float), typeof(double) }; if(Modals.Get("Enter a Type for ", "Choose", true, allowedTTypes, this.TypeNameDelegate, false, null, out var selectedTType) && selectedTType != null) { From ef1ae6cfd1eb6e0a03b95095fa4275b207039e0a Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 29 Dec 2023 09:43:35 +0000 Subject: [PATCH 03/28] Add UI for designing Option (array and element) --- src/UI/Windows/ArrayEditor.Designer.cs | 110 +++++++++++++ src/UI/Windows/ArrayEditor.cs | 20 +++ src/UI/Windows/EditDialog.cs | 27 +++- src/UI/Windows/SliderOptionEditor.Designer.cs | 151 ++++++++++++++++++ src/UI/Windows/SliderOptionEditor.cs | 20 +++ 5 files changed, 320 insertions(+), 8 deletions(-) create mode 100644 src/UI/Windows/ArrayEditor.Designer.cs create mode 100644 src/UI/Windows/ArrayEditor.cs create mode 100644 src/UI/Windows/SliderOptionEditor.Designer.cs create mode 100644 src/UI/Windows/SliderOptionEditor.cs diff --git a/src/UI/Windows/ArrayEditor.Designer.cs b/src/UI/Windows/ArrayEditor.Designer.cs new file mode 100644 index 00000000..ee72c60e --- /dev/null +++ b/src/UI/Windows/ArrayEditor.Designer.cs @@ -0,0 +1,110 @@ + +//------------------------------------------------------------------------------ + +// +// This code was generated by: +// TerminalGuiDesigner v1.1.0.0 +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ----------------------------------------------------------------------------- +namespace TerminalGuiDesigner.UI.Windows { + using System; + using Terminal.Gui; + + + public partial class ArrayEditor : Terminal.Gui.Dialog { + + private Terminal.Gui.FrameView frameView; + + private Terminal.Gui.ListView lvElements; + + private Terminal.Gui.Button btnAddElement; + + private Terminal.Gui.LineView lineView; + + private Terminal.Gui.Button btnOk; + + private Terminal.Gui.Button btnCancel; + + private void InitializeComponent() { + this.btnCancel = new Terminal.Gui.Button(); + this.btnOk = new Terminal.Gui.Button(); + this.lineView = new Terminal.Gui.LineView(); + this.btnAddElement = new Terminal.Gui.Button(); + this.lvElements = new Terminal.Gui.ListView(); + this.frameView = new Terminal.Gui.FrameView(); + this.Width = Dim.Percent(85f); + this.Height = Dim.Percent(85f); + this.X = Pos.Center(); + this.Y = Pos.Center(); + this.Visible = true; + this.Modal = true; + this.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.Title = "Array Editor"; + this.frameView.Width = Dim.Fill(0); + this.frameView.Height = Dim.Fill(3); + this.frameView.X = 0; + this.frameView.Y = 0; + this.frameView.Visible = true; + this.frameView.Data = "frameView"; + this.frameView.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.frameView.Title = "Elements"; + this.Add(this.frameView); + this.lvElements.Width = Dim.Fill(0); + this.lvElements.Height = Dim.Fill(0); + this.lvElements.X = 1; + this.lvElements.Y = 0; + this.lvElements.Visible = true; + this.lvElements.Data = "lvElements"; + this.lvElements.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.lvElements.Source = new Terminal.Gui.ListWrapper(new string[] { + "Item1", + "Item2", + "Item3"}); + this.lvElements.AllowsMarking = false; + this.lvElements.AllowsMultipleSelection = true; + this.frameView.Add(this.lvElements); + this.btnAddElement.Width = 8; + this.btnAddElement.Height = 1; + this.btnAddElement.X = 1; + this.btnAddElement.Y = Pos.AnchorEnd(3); + this.btnAddElement.Visible = true; + this.btnAddElement.Data = "btnAddElement"; + this.btnAddElement.Text = "Add"; + this.btnAddElement.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.btnAddElement.IsDefault = false; + this.Add(this.btnAddElement); + this.lineView.Width = Dim.Fill(1); + this.lineView.Height = 1; + this.lineView.X = -1; + this.lineView.Y = Pos.AnchorEnd(2); + this.lineView.Visible = true; + this.lineView.Data = "lineView"; + this.lineView.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.lineView.LineRune = new System.Text.Rune('─'); + this.lineView.Orientation = Terminal.Gui.Orientation.Horizontal; + this.Add(this.lineView); + this.btnOk.Width = 8; + this.btnOk.Height = 1; + this.btnOk.X = 0; + this.btnOk.Y = Pos.AnchorEnd(1); + this.btnOk.Visible = true; + this.btnOk.Data = "btnOk"; + this.btnOk.Text = "Ok"; + this.btnOk.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.btnOk.IsDefault = false; + this.Add(this.btnOk); + this.btnCancel.Width = 8; + this.btnCancel.Height = 1; + this.btnCancel.X = 9; + this.btnCancel.Y = Pos.AnchorEnd(1); + this.btnCancel.Visible = true; + this.btnCancel.Data = "btnCancel"; + this.btnCancel.Text = "Cancel"; + this.btnCancel.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.btnCancel.IsDefault = false; + this.Add(this.btnCancel); + } + } +} diff --git a/src/UI/Windows/ArrayEditor.cs b/src/UI/Windows/ArrayEditor.cs new file mode 100644 index 00000000..b42dd36d --- /dev/null +++ b/src/UI/Windows/ArrayEditor.cs @@ -0,0 +1,20 @@ + +//------------------------------------------------------------------------------ + +// +// This code was generated by: +// TerminalGuiDesigner v1.1.0.0 +// You can make changes to this file and they will not be overwritten when saving. +// +// ----------------------------------------------------------------------------- +namespace TerminalGuiDesigner.UI.Windows { + using Terminal.Gui; + + + public partial class ArrayEditor { + + public ArrayEditor() { + InitializeComponent(); + } + } +} diff --git a/src/UI/Windows/EditDialog.cs b/src/UI/Windows/EditDialog.cs index 5fd24188..9a154192 100644 --- a/src/UI/Windows/EditDialog.cs +++ b/src/UI/Windows/EditDialog.cs @@ -247,16 +247,27 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa else if (property.PropertyInfo.PropertyType.IsArray) { - if (Modals.GetArray( - property.PropertyInfo.Name, - "New Array Value", - property.PropertyInfo.PropertyType.GetElementType() ?? throw new Exception("Property was an Array but GetElementType returned null"), - (Array?)oldValue, - out Array? resultArray)) + var elementType = property.PropertyInfo.PropertyType.GetElementType() + ?? throw new Exception($"Property {property.GetHumanReadableName()} was array but had no element type"); + + if (elementType.IsValueType) { - newValue = resultArray; - return true; + if (Modals.GetArray( + property.PropertyInfo.Name, + "New Array Value", + property.PropertyInfo.PropertyType.GetElementType() ?? throw new Exception("Property was an Array but GetElementType returned null"), + (Array?)oldValue, + out Array? resultArray)) + { + newValue = resultArray; + return true; + } + } + else + { + } + } else if (property.PropertyInfo.PropertyType == typeof(IListDataSource)) diff --git a/src/UI/Windows/SliderOptionEditor.Designer.cs b/src/UI/Windows/SliderOptionEditor.Designer.cs new file mode 100644 index 00000000..d7e60450 --- /dev/null +++ b/src/UI/Windows/SliderOptionEditor.Designer.cs @@ -0,0 +1,151 @@ + +//------------------------------------------------------------------------------ + +// +// This code was generated by: +// TerminalGuiDesigner v1.1.0.0 +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ----------------------------------------------------------------------------- +namespace TerminalGuiDesigner.UI.Windows { + using System; + using Terminal.Gui; + + + public partial class SliderOptionEditor : Terminal.Gui.Dialog { + + private Terminal.Gui.ColorScheme redOnBlack; + + private Terminal.Gui.Label label; + + private Terminal.Gui.TextField tfLegend; + + private Terminal.Gui.Label label2; + + private Terminal.Gui.TextField tfLegendAbbr; + + private Terminal.Gui.Label label3; + + private Terminal.Gui.TextField tfData; + + private Terminal.Gui.Label lblError; + + private Terminal.Gui.Button btnOk; + + private Terminal.Gui.Button btnCancel; + + private void InitializeComponent() { + this.btnCancel = new Terminal.Gui.Button(); + this.btnOk = new Terminal.Gui.Button(); + this.lblError = new Terminal.Gui.Label(); + this.tfData = new Terminal.Gui.TextField(); + this.label3 = new Terminal.Gui.Label(); + this.tfLegendAbbr = new Terminal.Gui.TextField(); + this.label2 = new Terminal.Gui.Label(); + this.tfLegend = new Terminal.Gui.TextField(); + this.label = new Terminal.Gui.Label(); + this.redOnBlack = new Terminal.Gui.ColorScheme(); + this.redOnBlack.Normal = new Terminal.Gui.Attribute(Terminal.Gui.Color.Red, Terminal.Gui.Color.Black); + this.redOnBlack.HotNormal = new Terminal.Gui.Attribute(Terminal.Gui.Color.BrightRed, Terminal.Gui.Color.Black); + this.redOnBlack.Focus = new Terminal.Gui.Attribute(Terminal.Gui.Color.Red, Terminal.Gui.Color.Yellow); + this.redOnBlack.HotFocus = new Terminal.Gui.Attribute(Terminal.Gui.Color.BrightRed, Terminal.Gui.Color.Yellow); + this.redOnBlack.Disabled = new Terminal.Gui.Attribute(Terminal.Gui.Color.Gray, Terminal.Gui.Color.Black); + this.Width = 50; + this.Height = 7; + this.X = Pos.Center(); + this.Y = Pos.Center(); + this.Visible = true; + this.Modal = true; + this.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.Title = "OptionEditor"; + this.label.Width = 4; + this.label.Height = 1; + this.label.X = 5; + this.label.Y = 0; + this.label.Visible = true; + this.label.Data = "label"; + this.label.Text = "Legend:"; + this.label.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.Add(this.label); + this.tfLegend.Width = Dim.Fill(0); + this.tfLegend.Height = 1; + this.tfLegend.X = 12; + this.tfLegend.Y = 0; + this.tfLegend.Visible = true; + this.tfLegend.Secret = false; + this.tfLegend.Data = "tfLegend"; + this.tfLegend.Text = ""; + this.tfLegend.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.Add(this.tfLegend); + this.label2.Width = 4; + this.label2.Height = 1; + this.label2.X = 0; + this.label2.Y = 1; + this.label2.Visible = true; + this.label2.Data = "label2"; + this.label2.Text = "Abbreviated:"; + this.label2.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.Add(this.label2); + this.tfLegendAbbr.Width = Dim.Fill(0); + this.tfLegendAbbr.Height = 1; + this.tfLegendAbbr.X = 12; + this.tfLegendAbbr.Y = 1; + this.tfLegendAbbr.Visible = true; + this.tfLegendAbbr.Secret = false; + this.tfLegendAbbr.Data = "tfLegendAbbr"; + this.tfLegendAbbr.Text = ""; + this.tfLegendAbbr.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.Add(this.tfLegendAbbr); + this.label3.Width = 4; + this.label3.Height = 1; + this.label3.X = 7; + this.label3.Y = 2; + this.label3.Visible = true; + this.label3.Data = "label3"; + this.label3.Text = "Data:"; + this.label3.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.Add(this.label3); + this.tfData.Width = Dim.Fill(0); + this.tfData.Height = 1; + this.tfData.X = 12; + this.tfData.Y = 2; + this.tfData.Visible = true; + this.tfData.Secret = false; + this.tfData.Data = "tfData"; + this.tfData.Text = ""; + this.tfData.TextAlignment = Terminal.Gui.TextAlignment.Left; + this.Add(this.tfData); + this.lblError.Width = Dim.Fill(0); + this.lblError.Height = 1; + this.lblError.X = 0; + this.lblError.Y = 3; + this.lblError.Visible = false; + this.lblError.ColorScheme = this.redOnBlack; + this.lblError.Data = "lblError"; + this.lblError.Text = "Heya"; + this.lblError.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.Add(this.lblError); + this.btnOk.Width = 8; + this.btnOk.Height = 1; + this.btnOk.X = 11; + this.btnOk.Y = 4; + this.btnOk.Visible = true; + this.btnOk.Data = "btnOk"; + this.btnOk.Text = "Ok"; + this.btnOk.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.btnOk.IsDefault = true; + this.Add(this.btnOk); + this.btnCancel.Width = 8; + this.btnCancel.Height = 1; + this.btnCancel.X = 23; + this.btnCancel.Y = 4; + this.btnCancel.Visible = true; + this.btnCancel.Data = "btnCancel"; + this.btnCancel.Text = "Cancel"; + this.btnCancel.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.btnCancel.IsDefault = false; + this.Add(this.btnCancel); + } + } +} diff --git a/src/UI/Windows/SliderOptionEditor.cs b/src/UI/Windows/SliderOptionEditor.cs new file mode 100644 index 00000000..28055650 --- /dev/null +++ b/src/UI/Windows/SliderOptionEditor.cs @@ -0,0 +1,20 @@ + +//------------------------------------------------------------------------------ + +// +// This code was generated by: +// TerminalGuiDesigner v1.1.0.0 +// You can make changes to this file and they will not be overwritten when saving. +// +// ----------------------------------------------------------------------------- +namespace TerminalGuiDesigner.UI.Windows { + using Terminal.Gui; + + + public partial class SliderOptionEditor { + + public SliderOptionEditor() { + InitializeComponent(); + } + } +} From e1ff2a5b544f9d38e86c0d929b7db75fcf585755 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 29 Dec 2023 11:52:06 +0000 Subject: [PATCH 04/28] WIP: Add Property.GetElementType and prototype ArrayEditor events --- src/ToCode/Property.cs | 23 +++++++++++++ src/UI/Windows/ArrayEditor.Designer.cs | 2 +- src/UI/Windows/ArrayEditor.cs | 46 +++++++++++++++++++++++--- src/UI/Windows/EditDialog.cs | 24 ++++++++++++-- 4 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/ToCode/Property.cs b/src/ToCode/Property.cs index 9bd11b0a..c2100f1a 100644 --- a/src/ToCode/Property.cs +++ b/src/ToCode/Property.cs @@ -1,4 +1,5 @@ using System.CodeDom; +using System.Collections; using System.Reflection; using System.Text; using Terminal.Gui; @@ -410,4 +411,26 @@ private void CallRefreshMethodsIfAny() this.Design.View.SetNeedsDisplay(); } + + /// + /// Returns the element type for collections (IList or Array) or if it is not. + /// + /// Element type of collection or . + public Type GetElementType() + { + var propertyType = this.PropertyInfo.PropertyType; + var elementType = propertyType.GetElementType(); + + if(elementType != null) + { + return elementType; + } + + if (propertyType.IsAssignableTo(typeof(IList)) && propertyType.IsGenericType) + { + return propertyType.GetGenericTypeDefinition().GetGenericArguments().Single(); + } + + return null; + } } diff --git a/src/UI/Windows/ArrayEditor.Designer.cs b/src/UI/Windows/ArrayEditor.Designer.cs index ee72c60e..0d8854df 100644 --- a/src/UI/Windows/ArrayEditor.Designer.cs +++ b/src/UI/Windows/ArrayEditor.Designer.cs @@ -26,7 +26,7 @@ public partial class ArrayEditor : Terminal.Gui.Dialog { private Terminal.Gui.Button btnOk; private Terminal.Gui.Button btnCancel; - + private void InitializeComponent() { this.btnCancel = new Terminal.Gui.Button(); this.btnOk = new Terminal.Gui.Button(); diff --git a/src/UI/Windows/ArrayEditor.cs b/src/UI/Windows/ArrayEditor.cs index b42dd36d..10260a54 100644 --- a/src/UI/Windows/ArrayEditor.cs +++ b/src/UI/Windows/ArrayEditor.cs @@ -8,13 +8,51 @@ // // ----------------------------------------------------------------------------- namespace TerminalGuiDesigner.UI.Windows { + using System.Collections; using Terminal.Gui; - - + + public partial class ArrayEditor { - - public ArrayEditor() { + + /// + /// True if the editing was aborted. + /// + public bool Cancelled { get; private set; } = true; + + /// + /// The new array + /// + public IList Result { get; private set; } + + public ArrayEditor(ToCode.Property property) { InitializeComponent(); + + // TODO: implement + Result = new int[] { 1, 2, 3 }; + + lvElements.SetSource(Result); + btnOk.Clicked += BtnOk_Clicked; + btnCancel.Clicked += BtnCancel_Clicked; + btnAddElement.Clicked += BtnAddElement_Clicked; + } + + private void BtnAddElement_Clicked(object sender, EventArgs e) + { + // TODO: implement + Result.Add(32); + lvElements.SetNeedsDisplay(); + } + + private void BtnCancel_Clicked(object sender, EventArgs e) + { + Cancelled = true; + Application.RequestStop(); + } + + private void BtnOk_Clicked(object sender, EventArgs e) + { + Cancelled = false; + Application.RequestStop(); } } } diff --git a/src/UI/Windows/EditDialog.cs b/src/UI/Windows/EditDialog.cs index 9a154192..02838427 100644 --- a/src/UI/Windows/EditDialog.cs +++ b/src/UI/Windows/EditDialog.cs @@ -1,3 +1,5 @@ +using System.Collections; +using System.ComponentModel.Design; using System.Text; using Terminal.Gui; using Terminal.Gui.TextValidateProviders; @@ -245,10 +247,13 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa return answer != -1; } else - if (property.PropertyInfo.PropertyType.IsArray) + if ( + // TODO: I just changed this from IsArray to IList assignable, need to worry about conversions a bit more + property.PropertyInfo.PropertyType.IsAssignableTo(typeof(IList)) + ) { - var elementType = property.PropertyInfo.PropertyType.GetElementType() - ?? throw new Exception($"Property {property.GetHumanReadableName()} was array but had no element type"); + var elementType = property.GetElementType() + ?? throw new Exception($"Property {property.GetHumanReadableName()} was array but had no element type"); ; if (elementType.IsValueType) { @@ -265,7 +270,20 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa } else { + var designer = new ArrayEditor(property); + Application.Run(designer); + if (!designer.Cancelled) + { + newValue = designer.Result; + return true; + } + else + { + // user cancelled designing the Dim + newValue = null; + return false; + } } } From 2a8c2a2e6df007752f2ba0247099f0e1e01ea91f Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 29 Dec 2023 14:56:56 +0000 Subject: [PATCH 05/28] Inching towards assigning property of result of ArrayEditor --- src/ToCode/Property.cs | 4 ++-- src/UI/Windows/ArrayEditor.cs | 20 +++++++++++++++++--- src/UI/Windows/EditDialog.cs | 3 ++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/ToCode/Property.cs b/src/ToCode/Property.cs index c2100f1a..3f58c6b1 100644 --- a/src/ToCode/Property.cs +++ b/src/ToCode/Property.cs @@ -416,7 +416,7 @@ private void CallRefreshMethodsIfAny() /// Returns the element type for collections (IList or Array) or if it is not. /// /// Element type of collection or . - public Type GetElementType() + public Type? GetElementType() { var propertyType = this.PropertyInfo.PropertyType; var elementType = propertyType.GetElementType(); @@ -428,7 +428,7 @@ public Type GetElementType() if (propertyType.IsAssignableTo(typeof(IList)) && propertyType.IsGenericType) { - return propertyType.GetGenericTypeDefinition().GetGenericArguments().Single(); + return propertyType.GetGenericArguments().Single(); } return null; diff --git a/src/UI/Windows/ArrayEditor.cs b/src/UI/Windows/ArrayEditor.cs index 10260a54..cce2c51c 100644 --- a/src/UI/Windows/ArrayEditor.cs +++ b/src/UI/Windows/ArrayEditor.cs @@ -19,16 +19,25 @@ public partial class ArrayEditor { /// public bool Cancelled { get; private set; } = true; + private Type elementType; + /// /// The new array /// - public IList Result { get; private set; } + public object[] Result { get; private set; } public ArrayEditor(ToCode.Property property) { InitializeComponent(); + this.elementType = property.GetElementType(); + + + var list = ((IList)property.GetValue()); + var array = new object[list.Count]; + list.CopyTo(array, 0); + // TODO: implement - Result = new int[] { 1, 2, 3 }; + Result = array; lvElements.SetSource(Result); btnOk.Clicked += BtnOk_Clicked; @@ -39,7 +48,12 @@ public ArrayEditor(ToCode.Property property) { private void BtnAddElement_Clicked(object sender, EventArgs e) { // TODO: implement - Result.Add(32); + object toAdd = new SliderOption(); + var list = Result.ToList(); + list.Add(toAdd); + + Result = list.ToArray(); + lvElements.SetSource(Result); lvElements.SetNeedsDisplay(); } diff --git a/src/UI/Windows/EditDialog.cs b/src/UI/Windows/EditDialog.cs index 02838427..c93c462c 100644 --- a/src/UI/Windows/EditDialog.cs +++ b/src/UI/Windows/EditDialog.cs @@ -275,7 +275,8 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa if (!designer.Cancelled) { - newValue = designer.Result; + // TODO: BUG: this creates a System.Collections.Generic.List not the element type + newValue = designer.Result.ToList().Select(e => e.CastToReflected(property.GetElementType())).ToList(); return true; } else From 9f216970dfc225369848d997726c5d7aa5737a86 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 29 Dec 2023 15:04:38 +0000 Subject: [PATCH 06/28] WIP: Hard coded SliderOption support --- src/UI/Windows/ArrayEditor.cs | 20 +++++++++++--------- src/UI/Windows/EditDialog.cs | 3 +-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/UI/Windows/ArrayEditor.cs b/src/UI/Windows/ArrayEditor.cs index cce2c51c..a678035e 100644 --- a/src/UI/Windows/ArrayEditor.cs +++ b/src/UI/Windows/ArrayEditor.cs @@ -24,7 +24,7 @@ public partial class ArrayEditor { /// /// The new array /// - public object[] Result { get; private set; } + public IList Result { get; private set; } public ArrayEditor(ToCode.Property property) { InitializeComponent(); @@ -32,12 +32,15 @@ public ArrayEditor(ToCode.Property property) { this.elementType = property.GetElementType(); - var list = ((IList)property.GetValue()); - var array = new object[list.Count]; - list.CopyTo(array, 0); - // TODO: implement - Result = array; + Type listType = typeof(List<>).MakeGenericType(property.GetElementType()); + Result = (IList)Activator.CreateInstance(listType); + + + foreach(var e in (IList)property.GetValue()) + { + Result.Add(e); + } lvElements.SetSource(Result); btnOk.Clicked += BtnOk_Clicked; @@ -49,10 +52,9 @@ private void BtnAddElement_Clicked(object sender, EventArgs e) { // TODO: implement object toAdd = new SliderOption(); - var list = Result.ToList(); - list.Add(toAdd); - Result = list.ToArray(); + Result.Add(toAdd); + lvElements.SetSource(Result); lvElements.SetNeedsDisplay(); } diff --git a/src/UI/Windows/EditDialog.cs b/src/UI/Windows/EditDialog.cs index c93c462c..02838427 100644 --- a/src/UI/Windows/EditDialog.cs +++ b/src/UI/Windows/EditDialog.cs @@ -275,8 +275,7 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa if (!designer.Cancelled) { - // TODO: BUG: this creates a System.Collections.Generic.List not the element type - newValue = designer.Result.ToList().Select(e => e.CastToReflected(property.GetElementType())).ToList(); + newValue = designer.Result; return true; } else From 374dcd2d9dc461a5a9835d9f4ec7755f08dd4a3b Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 29 Dec 2023 16:08:31 +0000 Subject: [PATCH 07/28] Add placeholder values --- src/UI/Windows/ArrayEditor.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/UI/Windows/ArrayEditor.cs b/src/UI/Windows/ArrayEditor.cs index a678035e..96fd6564 100644 --- a/src/UI/Windows/ArrayEditor.cs +++ b/src/UI/Windows/ArrayEditor.cs @@ -51,7 +51,12 @@ public ArrayEditor(ToCode.Property property) { private void BtnAddElement_Clicked(object sender, EventArgs e) { // TODO: implement - object toAdd = new SliderOption(); + object toAdd = new SliderOption() + { + Legend = "test", + LegendAbbr = new System.Text.Rune('t'), + Data = "test" + }; Result.Add(toAdd); From a36f631a3a840edacf2939752bce943bb7fee7a2 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 29 Dec 2023 17:28:52 +0000 Subject: [PATCH 08/28] Add test case (failing) for Slider --- tests/SliderTests.cs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/SliderTests.cs diff --git a/tests/SliderTests.cs b/tests/SliderTests.cs new file mode 100644 index 00000000..d0567041 --- /dev/null +++ b/tests/SliderTests.cs @@ -0,0 +1,37 @@ +using System.IO; +using System.Linq; +using System.Text; +using TerminalGuiDesigner.FromCode; +using TerminalGuiDesigner.Operations; +using TerminalGuiDesigner.ToCode; + + +namespace UnitTests +{ + internal class SliderTests : Tests + { + + [Test] + public void TestRoundTrip_Slider() + { + RoundTrip>((d, v) => + { + + v.Options.Add(new SliderOption { Legend = "l1", LegendAbbr = new Rune('1'), Data = "Fun1" }); + v.Options.Add(new SliderOption { Legend = "l2", LegendAbbr = new Rune('2'), Data = "Fun2" }); + + ClassicAssert.AreEqual(2, v.Options.Count); + }, out var sliderIn); + + ClassicAssert.AreEqual(2, sliderIn.Options.Count); + + ClassicAssert.AreEqual("l1", sliderIn.Options[0].Legend); + ClassicAssert.AreEqual(new Rune('1'), sliderIn.Options[0].LegendAbbr); + ClassicAssert.AreEqual("Fun1", sliderIn.Options[0].Data); + + ClassicAssert.AreEqual("l2", sliderIn.Options[1].Legend); + ClassicAssert.AreEqual(new Rune('2'), sliderIn.Options[1].LegendAbbr); + ClassicAssert.AreEqual("Fun2", sliderIn.Options[1].Data); + } + } +} From 68ecbd98f6e667fa9ac52d8057e9fc4538cd3e9d Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 29 Dec 2023 20:12:48 +0000 Subject: [PATCH 09/28] Trying (and failing) to compile with System.Collections.Generic reference --- src/FromCode/CodeToView.cs | 6 ++++-- src/ToCode/Property.cs | 31 ++++++++++++++++++++++++++++++- src/ToCode/ViewToCode.cs | 2 ++ tests/SliderTests.cs | 1 - 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/FromCode/CodeToView.cs b/src/FromCode/CodeToView.cs index e4a1813e..6e7bd4e9 100644 --- a/src/FromCode/CodeToView.cs +++ b/src/FromCode/CodeToView.cs @@ -1,5 +1,7 @@ -using System.ComponentModel; +using System.Collections; +using System.ComponentModel; using System.Reflection; +using System.Text.RegularExpressions; using Basic.Reference.Assemblies; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -157,7 +159,7 @@ public Assembly CompileAssembly() }; var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); - + var compilation = CSharpCompilation.Create( Guid.NewGuid().ToString() + ".dll", new CSharpSyntaxTree[] { csTree, designerTree }, diff --git a/src/ToCode/Property.cs b/src/ToCode/Property.cs index 3f58c6b1..1dc8e015 100644 --- a/src/ToCode/Property.cs +++ b/src/ToCode/Property.cs @@ -5,6 +5,7 @@ using Terminal.Gui; using Terminal.Gui.TextValidateProviders; using TerminalGuiDesigner; +using YamlDotNet.Core.Tokens; using Attribute = Terminal.Gui.Attribute; namespace TerminalGuiDesigner.ToCode; @@ -178,7 +179,7 @@ public virtual CodeExpression GetRhs() else { throw new Exception($"Unexpected unicode character size. Rune was {rune}"); - } + } } if (val is Attribute attribute) @@ -264,9 +265,37 @@ public virtual CodeExpression GetRhs() values.Select(v => v.ToCodePrimitiveExpression()).ToArray()); } + if (val is IList valList) + { + var elementType = this.GetElementType() + ?? throw new Exception($"Type {type} was an IList but {nameof(Type.GetElementType)} returned null"); + + return new CodeObjectCreateExpression( + new CodeTypeReference(val.GetType()), + new CodeArrayCreateExpression( + elementType, + valList.Cast().Select(this.ValueFactory).ToArray()) + ); + } + return val.ToCodePrimitiveExpression(); } + private CodeExpression ValueFactory(object val) + { + // TODO: Could move lots of logic in GetRHS into here + if (val.GetType().GetGenericTypeDefinition() == typeof(SliderOption<>)) + { + // TODO: Cannot call initializers :( + return new CodeObjectCreateExpression( + new CodeTypeReference(val.GetType())); + } + else + { + throw new NotSupportedException($"Cannot generate code for value '{val}'"); + } + } + /// /// Gets a CodeDOM code block for the left hand side of an assignment operation e.g.: /// this.label1.Text diff --git a/src/ToCode/ViewToCode.cs b/src/ToCode/ViewToCode.cs index e4977d55..bfe72907 100644 --- a/src/ToCode/ViewToCode.cs +++ b/src/ToCode/ViewToCode.cs @@ -125,6 +125,8 @@ public void GenerateDesignerCs(Design rootDesign, Type viewType) var ns = new CodeNamespace(rosylyn.Namespace); ns.Imports.Add(new CodeNamespaceImport("System")); ns.Imports.Add(new CodeNamespaceImport("Terminal.Gui")); + ns.Imports.Add(new CodeNamespaceImport("System.Collections")); + ns.Imports.Add(new CodeNamespaceImport("System.Collections.Generic")); this.AddCustomHeaderForDesignerCsFile(ns); diff --git a/tests/SliderTests.cs b/tests/SliderTests.cs index d0567041..96820f90 100644 --- a/tests/SliderTests.cs +++ b/tests/SliderTests.cs @@ -16,7 +16,6 @@ public void TestRoundTrip_Slider() { RoundTrip>((d, v) => { - v.Options.Add(new SliderOption { Legend = "l1", LegendAbbr = new Rune('1'), Data = "Fun1" }); v.Options.Add(new SliderOption { Legend = "l2", LegendAbbr = new Rune('2'), Data = "Fun2" }); From a80b6a50c2cdb6ebfd19cf45383ab9d79ee5bb7b Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 30 Dec 2023 02:01:26 +0000 Subject: [PATCH 10/28] Add reference to System.Collections --- src/FromCode/CodeToView.cs | 7 +++++-- tests/SliderTests.cs | 13 ++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/FromCode/CodeToView.cs b/src/FromCode/CodeToView.cs index 6e7bd4e9..b8e63aa6 100644 --- a/src/FromCode/CodeToView.cs +++ b/src/FromCode/CodeToView.cs @@ -155,10 +155,13 @@ public Assembly CompileAssembly() MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(MarshalByValueComponent).Assembly.Location), MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "mscorlib.dll"), - MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "System.Runtime.dll") + MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "System.Runtime.dll"), + MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "System.Collections.dll"), }; - var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + var options = new CSharpCompilationOptions( + OutputKind.DynamicallyLinkedLibrary + ); var compilation = CSharpCompilation.Create( Guid.NewGuid().ToString() + ".dll", diff --git a/tests/SliderTests.cs b/tests/SliderTests.cs index 96820f90..12ad8386 100644 --- a/tests/SliderTests.cs +++ b/tests/SliderTests.cs @@ -1,9 +1,4 @@ -using System.IO; -using System.Linq; -using System.Text; -using TerminalGuiDesigner.FromCode; -using TerminalGuiDesigner.Operations; -using TerminalGuiDesigner.ToCode; +using System.Text; namespace UnitTests @@ -12,15 +7,15 @@ internal class SliderTests : Tests { [Test] - public void TestRoundTrip_Slider() + public void TestRoundTrip_Slider_PreserveStringOptions() { - RoundTrip>((d, v) => + var sliderIn = RoundTrip>((d, v) => { v.Options.Add(new SliderOption { Legend = "l1", LegendAbbr = new Rune('1'), Data = "Fun1" }); v.Options.Add(new SliderOption { Legend = "l2", LegendAbbr = new Rune('2'), Data = "Fun2" }); ClassicAssert.AreEqual(2, v.Options.Count); - }, out var sliderIn); + }, out _); ClassicAssert.AreEqual(2, sliderIn.Options.Count); From def3d65f6fc475d53dac0bde48957354559da7ef Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 30 Dec 2023 03:00:15 +0000 Subject: [PATCH 11/28] Make Orientation designable and do not offer `Slider` (which is secretly `Slider`) --- src/Design.cs | 1 + src/ViewFactory.cs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Design.cs b/src/Design.cs index bf9a1ad1..d8263b8b 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -668,6 +668,7 @@ private IEnumerable LoadDesignableProperties() if (isGenericType && viewType.GetGenericTypeDefinition() == typeof(Slider<>)) { yield return this.CreateProperty(nameof(Slider.Options)); + yield return this.CreateProperty(nameof(Slider.Orientation)); } if (this.View is SpinnerView) diff --git a/src/ViewFactory.cs b/src/ViewFactory.cs index 2c6e34a4..66c1a7a5 100644 --- a/src/ViewFactory.cs +++ b/src/ViewFactory.cs @@ -81,7 +81,9 @@ internal static MenuBarItem[] DefaultMenuBarItems } ) .Where( filteredType => filteredType.IsSubclassOf( typeof(View) ) ) .Where( viewDescendantType => !KnownUnsupportedTypes.Any( viewDescendantType.IsAssignableTo ) - || viewDescendantType == typeof( Window ) ); + || viewDescendantType == typeof( Window )) + // Slider is an alias of Slider so don't offer that + .Where(vt=>vt != typeof(Slider)); private static bool IsSupportedType( this Type t ) { From 4030cf0e1c3f4431c05062e2ffbb4203a94060e1 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 30 Dec 2023 03:10:56 +0000 Subject: [PATCH 12/28] Tests to ensure orientation persists and is designable --- tests/SliderTests.cs | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/tests/SliderTests.cs b/tests/SliderTests.cs index 12ad8386..9c67b3e8 100644 --- a/tests/SliderTests.cs +++ b/tests/SliderTests.cs @@ -5,7 +5,17 @@ namespace UnitTests { internal class SliderTests : Tests { - + private static IEnumerable Orientation_Cases + { + get + { + return new TestCaseData[] + { + new TestCaseData( Orientation.Horizontal ), + new TestCaseData( Orientation.Vertical ), + }; + } + } [Test] public void TestRoundTrip_Slider_PreserveStringOptions() { @@ -14,18 +24,32 @@ public void TestRoundTrip_Slider_PreserveStringOptions() v.Options.Add(new SliderOption { Legend = "l1", LegendAbbr = new Rune('1'), Data = "Fun1" }); v.Options.Add(new SliderOption { Legend = "l2", LegendAbbr = new Rune('2'), Data = "Fun2" }); - ClassicAssert.AreEqual(2, v.Options.Count); + Assert.That(v.Options.Count, Is.EqualTo(2)); }, out _); - ClassicAssert.AreEqual(2, sliderIn.Options.Count); - - ClassicAssert.AreEqual("l1", sliderIn.Options[0].Legend); - ClassicAssert.AreEqual(new Rune('1'), sliderIn.Options[0].LegendAbbr); - ClassicAssert.AreEqual("Fun1", sliderIn.Options[0].Data); + Assert.That(sliderIn.Options.Count, Is.EqualTo(2)); + + // TODO: Will pass tests when https://github.com/gui-cs/Terminal.Gui/issues/3100 is merged and nuget package drawn down. And relevant constructor called in our CodeDOM + Assert.That(sliderIn.Options[0].Legend, Is.EqualTo("l1")); + Assert.That(sliderIn.Options[0].LegendAbbr, Is.EqualTo(new Rune('1'))); + Assert.That(sliderIn.Options[0].Data, Is.EqualTo("Fun1")); + + Assert.That(sliderIn.Options[1].Legend, Is.EqualTo("l2")); + Assert.That(sliderIn.Options[1].LegendAbbr, Is.EqualTo(new Rune('2'))); + Assert.That(sliderIn.Options[1].Data, Is.EqualTo("Fun2")); + } + + [Test] + [TestCaseSource(nameof(Orientation_Cases))] + public void TestRoundTrip_Slider_PreserveOrientation(Orientation o) + { + var sliderIn = RoundTrip>((d, v) => + { + d.GetDesignableProperty("Orientation")?.SetValue(o); + Assert.That(v.Orientation, Is.EqualTo(o)); + }, out _); - ClassicAssert.AreEqual("l2", sliderIn.Options[1].Legend); - ClassicAssert.AreEqual(new Rune('2'), sliderIn.Options[1].LegendAbbr); - ClassicAssert.AreEqual("Fun2", sliderIn.Options[1].Data); + Assert.That(sliderIn.Orientation, Is.EqualTo(o)); } } } From 342e035a18a903c9d6822233bb5292ea19281998 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 30 Dec 2023 09:53:07 +0000 Subject: [PATCH 13/28] Launch SliderOptionEditor on adding element in ArrayEditor --- src/UI/Windows/ArrayEditor.cs | 27 +++++++++++++++----- src/UI/Windows/SliderOptionEditor.cs | 38 ++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/UI/Windows/ArrayEditor.cs b/src/UI/Windows/ArrayEditor.cs index 96fd6564..129bdc54 100644 --- a/src/UI/Windows/ArrayEditor.cs +++ b/src/UI/Windows/ArrayEditor.cs @@ -50,13 +50,28 @@ public ArrayEditor(ToCode.Property property) { private void BtnAddElement_Clicked(object sender, EventArgs e) { - // TODO: implement - object toAdd = new SliderOption() + object toAdd; + if(this.elementType.IsGenericType && this.elementType.GetGenericTypeDefinition() == typeof(SliderOption<>)) { - Legend = "test", - LegendAbbr = new System.Text.Rune('t'), - Data = "test" - }; + var designer = new SliderOptionEditor(this.elementType.GetGenericArguments()[0]); + Application.Run(designer); + + if (!designer.Cancelled) + { + toAdd = designer.Result; + } + else + { + // user canceled designing the Option + return; + } + } + else + { + // TODO: Handle other cases here + return; + //toAdd = default; + } Result.Add(toAdd); diff --git a/src/UI/Windows/SliderOptionEditor.cs b/src/UI/Windows/SliderOptionEditor.cs index 28055650..cef5334e 100644 --- a/src/UI/Windows/SliderOptionEditor.cs +++ b/src/UI/Windows/SliderOptionEditor.cs @@ -12,9 +12,43 @@ namespace TerminalGuiDesigner.UI.Windows { public partial class SliderOptionEditor { - - public SliderOptionEditor() { + private readonly Type genericTypeArgument; + + public bool Cancelled { get; internal set; } = true; + public object Result { get; internal set; } + + /// + /// Creates a new instance of the Designer to create an instance of + /// where T is of . + /// + /// The T Type of the you want to design + public SliderOptionEditor(Type genericTypeArgument) { InitializeComponent(); + + // TODO: implement + Result = new SliderOption() + { + Legend = "test", + LegendAbbr = new System.Text.Rune('t'), + Data = "test" + }; + + this.genericTypeArgument = genericTypeArgument; + + btnOk.Clicked += BtnOk_Clicked; + btnCancel.Clicked += BtnCancel_Clicked; + } + + private void BtnCancel_Clicked(object sender, EventArgs e) + { + this.Cancelled = true; + Application.RequestStop(); + } + + private void BtnOk_Clicked(object sender, EventArgs e) + { + this.Cancelled = false; + Application.RequestStop(); } } } From afb5aac9ebf9b240ebe2d85555faa8e9c8bde255 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 30 Dec 2023 10:02:27 +0000 Subject: [PATCH 14/28] Implementation of BuildResult for SliderOptionEditor --- src/UI/Windows/SliderOptionEditor.cs | 45 +++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/UI/Windows/SliderOptionEditor.cs b/src/UI/Windows/SliderOptionEditor.cs index cef5334e..ec5429d0 100644 --- a/src/UI/Windows/SliderOptionEditor.cs +++ b/src/UI/Windows/SliderOptionEditor.cs @@ -8,6 +8,8 @@ // // ----------------------------------------------------------------------------- namespace TerminalGuiDesigner.UI.Windows { + using System.Reflection; + using System.Text; using Terminal.Gui; @@ -25,14 +27,6 @@ public partial class SliderOptionEditor { public SliderOptionEditor(Type genericTypeArgument) { InitializeComponent(); - // TODO: implement - Result = new SliderOption() - { - Legend = "test", - LegendAbbr = new System.Text.Rune('t'), - Data = "test" - }; - this.genericTypeArgument = genericTypeArgument; btnOk.Clicked += BtnOk_Clicked; @@ -47,8 +41,43 @@ private void BtnCancel_Clicked(object sender, EventArgs e) private void BtnOk_Clicked(object sender, EventArgs e) { + try + { + this.BuildResult(); + } + catch(Exception ex) + { + ExceptionViewer.ShowException("Could not build result", ex); + return; + } + this.Cancelled = false; Application.RequestStop(); } + + private void BuildResult() + { + + Type sliderOptionType = typeof(SliderOption<>).MakeGenericType(this.genericTypeArgument); + Result = Activator.CreateInstance(sliderOptionType); + + var p = sliderOptionType.GetProperty("Legend"); + p.SetValue(Result, tfLegend.Text); + + p = sliderOptionType.GetProperty("LegendAbbr"); + p.SetValue(Result, new Rune(tfLegendAbbr.Text[0])); + + p = sliderOptionType.GetProperty("Data"); + + if(this.genericTypeArgument == typeof(string)) + { + p.SetValue(Result, tfData.Text); + } + else + { + p.SetValue(Result, Convert.ChangeType(tfData.Text, this.genericTypeArgument)); + } + + } } } From 3fd21b1bfd54f0f1855926fc1271f05b6ccaea26 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 30 Dec 2023 10:13:41 +0000 Subject: [PATCH 15/28] Usability improvements for SliderOptionEditor --- src/UI/Windows/SliderOptionEditor.Designer.cs | 67 ++++++++++++------- src/UI/Windows/SliderOptionEditor.cs | 2 + 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/UI/Windows/SliderOptionEditor.Designer.cs b/src/UI/Windows/SliderOptionEditor.Designer.cs index d7e60450..844a86e0 100644 --- a/src/UI/Windows/SliderOptionEditor.Designer.cs +++ b/src/UI/Windows/SliderOptionEditor.Designer.cs @@ -11,12 +11,16 @@ namespace TerminalGuiDesigner.UI.Windows { using System; using Terminal.Gui; + using System.Collections; + using System.Collections.Generic; public partial class SliderOptionEditor : Terminal.Gui.Dialog { private Terminal.Gui.ColorScheme redOnBlack; + private Terminal.Gui.ColorScheme tgDefault; + private Terminal.Gui.Label label; private Terminal.Gui.TextField tfLegend; @@ -25,11 +29,13 @@ public partial class SliderOptionEditor : Terminal.Gui.Dialog { private Terminal.Gui.TextField tfLegendAbbr; + private Terminal.Gui.Label lblOneChar; + private Terminal.Gui.Label label3; private Terminal.Gui.TextField tfData; - private Terminal.Gui.Label lblError; + private Terminal.Gui.Label lblType; private Terminal.Gui.Button btnOk; @@ -38,9 +44,10 @@ public partial class SliderOptionEditor : Terminal.Gui.Dialog { private void InitializeComponent() { this.btnCancel = new Terminal.Gui.Button(); this.btnOk = new Terminal.Gui.Button(); - this.lblError = new Terminal.Gui.Label(); + this.lblType = new Terminal.Gui.Label(); this.tfData = new Terminal.Gui.TextField(); this.label3 = new Terminal.Gui.Label(); + this.lblOneChar = new Terminal.Gui.Label(); this.tfLegendAbbr = new Terminal.Gui.TextField(); this.label2 = new Terminal.Gui.Label(); this.tfLegend = new Terminal.Gui.TextField(); @@ -51,8 +58,14 @@ private void InitializeComponent() { this.redOnBlack.Focus = new Terminal.Gui.Attribute(Terminal.Gui.Color.Red, Terminal.Gui.Color.Yellow); this.redOnBlack.HotFocus = new Terminal.Gui.Attribute(Terminal.Gui.Color.BrightRed, Terminal.Gui.Color.Yellow); this.redOnBlack.Disabled = new Terminal.Gui.Attribute(Terminal.Gui.Color.Gray, Terminal.Gui.Color.Black); + this.tgDefault = new Terminal.Gui.ColorScheme(); + this.tgDefault.Normal = new Terminal.Gui.Attribute(Terminal.Gui.Color.White, Terminal.Gui.Color.Blue); + this.tgDefault.HotNormal = new Terminal.Gui.Attribute(Terminal.Gui.Color.BrightCyan, Terminal.Gui.Color.Blue); + this.tgDefault.Focus = new Terminal.Gui.Attribute(Terminal.Gui.Color.Black, Terminal.Gui.Color.Gray); + this.tgDefault.HotFocus = new Terminal.Gui.Attribute(Terminal.Gui.Color.BrightBlue, Terminal.Gui.Color.Gray); + this.tgDefault.Disabled = new Terminal.Gui.Attribute(Terminal.Gui.Color.Yellow, Terminal.Gui.Color.Blue); this.Width = 50; - this.Height = 7; + this.Height = 8; this.X = Pos.Center(); this.Y = Pos.Center(); this.Visible = true; @@ -61,7 +74,7 @@ private void InitializeComponent() { this.Title = "OptionEditor"; this.label.Width = 4; this.label.Height = 1; - this.label.X = 5; + this.label.X = 6; this.label.Y = 0; this.label.Visible = true; this.label.Data = "label"; @@ -70,7 +83,7 @@ private void InitializeComponent() { this.Add(this.label); this.tfLegend.Width = Dim.Fill(0); this.tfLegend.Height = 1; - this.tfLegend.X = 12; + this.tfLegend.X = 14; this.tfLegend.Y = 0; this.tfLegend.Visible = true; this.tfLegend.Secret = false; @@ -84,12 +97,12 @@ private void InitializeComponent() { this.label2.Y = 1; this.label2.Visible = true; this.label2.Data = "label2"; - this.label2.Text = "Abbreviated:"; + this.label2.Text = "Abbreviation:"; this.label2.TextAlignment = Terminal.Gui.TextAlignment.Left; this.Add(this.label2); - this.tfLegendAbbr.Width = Dim.Fill(0); + this.tfLegendAbbr.Width = 1; this.tfLegendAbbr.Height = 1; - this.tfLegendAbbr.X = 12; + this.tfLegendAbbr.X = 14; this.tfLegendAbbr.Y = 1; this.tfLegendAbbr.Visible = true; this.tfLegendAbbr.Secret = false; @@ -97,10 +110,19 @@ private void InitializeComponent() { this.tfLegendAbbr.Text = ""; this.tfLegendAbbr.TextAlignment = Terminal.Gui.TextAlignment.Left; this.Add(this.tfLegendAbbr); + this.lblOneChar.Width = 10; + this.lblOneChar.Height = 1; + this.lblOneChar.X = 0; + this.lblOneChar.Y = 2; + this.lblOneChar.Visible = true; + this.lblOneChar.Data = "lblOneChar"; + this.lblOneChar.Text = "(Single Char) "; + this.lblOneChar.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.Add(this.lblOneChar); this.label3.Width = 4; this.label3.Height = 1; this.label3.X = 7; - this.label3.Y = 2; + this.label3.Y = 3; this.label3.Visible = true; this.label3.Data = "label3"; this.label3.Text = "Data:"; @@ -108,28 +130,27 @@ private void InitializeComponent() { this.Add(this.label3); this.tfData.Width = Dim.Fill(0); this.tfData.Height = 1; - this.tfData.X = 12; - this.tfData.Y = 2; + this.tfData.X = 14; + this.tfData.Y = 3; this.tfData.Visible = true; this.tfData.Secret = false; this.tfData.Data = "tfData"; this.tfData.Text = ""; this.tfData.TextAlignment = Terminal.Gui.TextAlignment.Left; this.Add(this.tfData); - this.lblError.Width = Dim.Fill(0); - this.lblError.Height = 1; - this.lblError.X = 0; - this.lblError.Y = 3; - this.lblError.Visible = false; - this.lblError.ColorScheme = this.redOnBlack; - this.lblError.Data = "lblError"; - this.lblError.Text = "Heya"; - this.lblError.TextAlignment = Terminal.Gui.TextAlignment.Centered; - this.Add(this.lblError); + this.lblType.Width = Dim.Fill(0); + this.lblType.Height = 1; + this.lblType.X = 0; + this.lblType.Y = 4; + this.lblType.Visible = true; + this.lblType.Data = "lblType"; + this.lblType.Text = "( Type ) "; + this.lblType.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.Add(this.lblType); this.btnOk.Width = 8; this.btnOk.Height = 1; this.btnOk.X = 11; - this.btnOk.Y = 4; + this.btnOk.Y = 5; this.btnOk.Visible = true; this.btnOk.Data = "btnOk"; this.btnOk.Text = "Ok"; @@ -139,7 +160,7 @@ private void InitializeComponent() { this.btnCancel.Width = 8; this.btnCancel.Height = 1; this.btnCancel.X = 23; - this.btnCancel.Y = 4; + this.btnCancel.Y = 5; this.btnCancel.Visible = true; this.btnCancel.Data = "btnCancel"; this.btnCancel.Text = "Cancel"; diff --git a/src/UI/Windows/SliderOptionEditor.cs b/src/UI/Windows/SliderOptionEditor.cs index ec5429d0..b338f1b8 100644 --- a/src/UI/Windows/SliderOptionEditor.cs +++ b/src/UI/Windows/SliderOptionEditor.cs @@ -31,6 +31,8 @@ public SliderOptionEditor(Type genericTypeArgument) { btnOk.Clicked += BtnOk_Clicked; btnCancel.Clicked += BtnCancel_Clicked; + + lblType.Text = $"({genericTypeArgument.Name})"; } private void BtnCancel_Clicked(object sender, EventArgs e) From 34fd7609b65ab0a1f4723830342924a2120da183 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 30 Dec 2023 17:12:29 +0000 Subject: [PATCH 16/28] Refactoring --- src/Operations/AddViewOperation.cs | 3 +-- src/ViewFactory.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Operations/AddViewOperation.cs b/src/Operations/AddViewOperation.cs index 1143720c..235b1147 100644 --- a/src/Operations/AddViewOperation.cs +++ b/src/Operations/AddViewOperation.cs @@ -82,8 +82,7 @@ protected override bool DoImpl() { if (selected.IsGenericType) { - // TODO: Move to some kind of helper class and allow more options later - var allowedTTypes = new[] { typeof(int), typeof(string), typeof(float), typeof(double) }; + var allowedTTypes = ViewFactory.GetSupportedTTypesForGenericViewOfType(selected).ToArray(); if(Modals.Get("Enter a Type for ", "Choose", true, allowedTTypes, this.TypeNameDelegate, false, null, out var selectedTType) && selectedTType != null) { diff --git a/src/ViewFactory.cs b/src/ViewFactory.cs index 66c1a7a5..94713d32 100644 --- a/src/ViewFactory.cs +++ b/src/ViewFactory.cs @@ -296,4 +296,19 @@ public static T CreateAnother( T oneOfThese ) { return Create( ); } + + /// + /// Returns all Types which can be used with generic view of the given . + /// + /// A generic view type e.g. (Slider<>) + /// + public static IEnumerable GetSupportedTTypesForGenericViewOfType(Type viewType) + { + if(viewType == typeof(Slider<>)) + { + return new[] { typeof(int), typeof(string), typeof(float), typeof(double), typeof(bool) }; + } + + throw new NotSupportedException($"Generic View {viewType} is not yet supported"); + } } From b7585278a33e630e07103898332f0489b9724da1 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 30 Dec 2023 17:31:33 +0000 Subject: [PATCH 17/28] Fix EditDialog with string array back to original approach. Add extra properties that are designable on slider --- src/Design.cs | 8 ++++++++ src/UI/Windows/EditDialog.cs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Design.cs b/src/Design.cs index d8263b8b..28811913 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -669,6 +669,14 @@ private IEnumerable LoadDesignableProperties() { yield return this.CreateProperty(nameof(Slider.Options)); yield return this.CreateProperty(nameof(Slider.Orientation)); + yield return this.CreateProperty(nameof(Slider.RangeAllowSingle)); + yield return this.CreateProperty(nameof(Slider.AllowEmpty)); + yield return this.CreateProperty(nameof(Slider.AutoSize)); + yield return this.CreateProperty(nameof(Slider.InnerSpacing)); + yield return this.CreateProperty(nameof(Slider.LegendsOrientation)); + yield return this.CreateProperty(nameof(Slider.ShowLegends)); + yield return this.CreateProperty(nameof(Slider.ShowSpacing)); + yield return this.CreateProperty(nameof(Slider.Type)); } if (this.View is SpinnerView) diff --git a/src/UI/Windows/EditDialog.cs b/src/UI/Windows/EditDialog.cs index 51afa5d1..f34be96a 100644 --- a/src/UI/Windows/EditDialog.cs +++ b/src/UI/Windows/EditDialog.cs @@ -255,7 +255,7 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa var elementType = property.GetElementType() ?? throw new Exception($"Property {property.GetHumanReadableName()} was array but had no element type"); ; - if (elementType.IsValueType) + if (elementType.IsValueType || elementType == typeof(string)) { if (Modals.GetArray( property.PropertyInfo.Name, From 135a55b6936a1343bb38ef767d64e9653fccd61a Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 31 Dec 2023 11:38:57 +0000 Subject: [PATCH 18/28] Extract 'get value of type x' code from ArrayEditor and EditDialog into ValueFactory --- src/UI/Editor.cs | 2 +- src/UI/ValueFactory.cs | 389 ++++++++++++++++++++++++++++++++++ src/UI/Windows/ArrayEditor.cs | 31 +-- src/UI/Windows/EditDialog.cs | 351 +----------------------------- 4 files changed, 398 insertions(+), 375 deletions(-) create mode 100644 src/UI/ValueFactory.cs diff --git a/src/UI/Editor.cs b/src/UI/Editor.cs index d6e63112..c5455fa0 100644 --- a/src/UI/Editor.cs +++ b/src/UI/Editor.cs @@ -614,7 +614,7 @@ private void CreateAndShowContextMenu(MouseEvent? m, Design? rightClicked) // BUG: This is an improper exception here and could have unexpected behavior if this method is ever called asynchronously. var factory = new OperationFactory( - (p, v) => EditDialog.GetNewValue(p.Design, p, v, out var newValue) ? newValue : throw new OperationCanceledException() ); + (p, v) => ValueFactory.GetNewValue(p.Design, p, v, out var newValue) ? newValue : throw new OperationCanceledException() ); var operations = factory .CreateOperations(selected, m, rightClicked, out string name) diff --git a/src/UI/ValueFactory.cs b/src/UI/ValueFactory.cs new file mode 100644 index 00000000..e144851c --- /dev/null +++ b/src/UI/ValueFactory.cs @@ -0,0 +1,389 @@ +using System.Collections; +using System.Text; +using Terminal.Gui.TextValidateProviders; +using Terminal.Gui; +using TerminalGuiDesigner.ToCode; +using TerminalGuiDesigner.UI.Windows; +using ColorPicker = TerminalGuiDesigner.UI.Windows.ColorPicker; +using Attribute = Terminal.Gui.Attribute; + +namespace TerminalGuiDesigner.UI +{ + static class ValueFactory + { + internal static bool GetNewValue(Type type, out object? newValue) + { + newValue = null; + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(SliderOption<>)) + { + var designer = new SliderOptionEditor(type.GetGenericArguments()[0]); + Application.Run(designer); + + if (!designer.Cancelled) + { + newValue = designer.Result; + return true; + } + else + { + // user canceled designing the Option + return false; + } + } + else + { + // TODO: Handle other cases here + return false; + //toAdd = default; + } + } + internal static bool GetNewValue(Design design, Property property, object? oldValue, out object? newValue) + { + if (property.PropertyInfo.PropertyType == typeof(ColorScheme)) + { + return GetNewColorSchemeValue(design, property, out newValue); + } + else + if (property.PropertyInfo.PropertyType == typeof(Attribute) || + property.PropertyInfo.PropertyType == typeof(Attribute?)) + { + // if its an Attribute or nullableAttribute + var picker = new ColorPicker((Attribute?)property.GetValue()); + Application.Run(picker); + + if (!picker.Cancelled) + { + newValue = picker.Result; + return true; + } + else + { + // user cancelled designing the Color + newValue = null; + return false; + } + } + else + if (property.PropertyInfo.PropertyType == typeof(ITextValidateProvider)) + { + string? oldPattern = oldValue is TextRegexProvider r ? (string?)r.Pattern.ToPrimitive() : null; + if (Modals.GetString("New Validation Pattern", "Regex Pattern", oldPattern, out var newPattern)) + { + newValue = string.IsNullOrWhiteSpace(newPattern) ? null : new TextRegexProvider(newPattern); + return true; + } + + // user cancelled entering a pattern + newValue = null; + return false; + } + else + if (property.PropertyInfo.PropertyType == typeof(Pos)) + { + // user is editing a Pos + var designer = new PosEditor(design, property); + + Application.Run(designer); + + if (!designer.Cancelled) + { + newValue = designer.Result; + return true; + } + else + { + // user cancelled designing the Pos + newValue = null; + return false; + } + } + else + if (property.PropertyInfo.PropertyType == typeof(Size)) + { + // user is editing a Size + var oldSize = (Size)(oldValue ?? throw new Exception($"Property {property.PropertyInfo.Name} is of Type Size but it's current value is null")); + var designer = new SizeEditor(oldSize); + + Application.Run(designer); + + if (!designer.Cancelled) + { + newValue = designer.Result; + return true; + } + else + { + // user cancelled designing the Pos + newValue = null; + return false; + } + } + else + if (property.PropertyInfo.PropertyType == typeof(PointF)) + { + // user is editing a PointF + var oldPointF = (PointF)(oldValue ?? throw new Exception($"Property {property.PropertyInfo.Name} is of Type PointF but it's current value is null")); + var designer = new PointEditor(oldPointF.X, oldPointF.Y); + + Application.Run(designer); + + if (!designer.Cancelled) + { + newValue = new PointF(designer.ResultX, designer.ResultY); + return true; + } + else + { + // user cancelled designing the Pos + newValue = null; + return false; + } + } + else + if (property.PropertyInfo.PropertyType == typeof(Dim)) + { + // user is editing a Dim + var designer = new DimEditor(design, property); + Application.Run(designer); + + if (!designer.Cancelled) + { + newValue = designer.Result; + return true; + } + else + { + // user cancelled designing the Dim + newValue = null; + return false; + } + } + else + if (property is InstanceOfProperty inst) + { + if (Modals.Get( + property.PropertyInfo.Name, + "New Value", + typeof(Label).Assembly.GetTypes().Where(inst.MustBeDerrivedFrom.IsAssignableFrom).ToArray(), + inst.GetValue()?.GetType(), + out Type? typeChosen)) + { + if (typeChosen == null) + { + newValue = null; + return false; + } + + newValue = Activator.CreateInstance(typeChosen); + return true; + } + } + else + if (property.PropertyInfo.PropertyType == typeof(bool)) + { + int answer = ChoicesDialog.Query(property.PropertyInfo.Name, $"New value for {property.PropertyInfo.PropertyType}", "Yes", "No"); + + newValue = answer == 0 ? true : false; + return answer != -1; + } + else + if ( + // TODO: I just changed this from IsArray to IList assignable, need to worry about conversions a bit more + property.PropertyInfo.PropertyType.IsAssignableTo(typeof(IList)) + ) + { + var elementType = property.GetElementType() + ?? throw new Exception($"Property {property.GetHumanReadableName()} was array but had no element type"); ; + + if (elementType.IsValueType || elementType == typeof(string)) + { + if (Modals.GetArray( + property.PropertyInfo.Name, + "New Array Value", + property.PropertyInfo.PropertyType.GetElementType() ?? throw new Exception("Property was an Array but GetElementType returned null"), + (Array?)oldValue, + out Array? resultArray)) + { + newValue = resultArray; + return true; + } + } + else + { + var designer = new ArrayEditor(property); + Application.Run(designer); + + if (!designer.Cancelled) + { + newValue = designer.Result; + return true; + } + else + { + // user cancelled designing the Dim + newValue = null; + return false; + } + } + + } + else + if (property.PropertyInfo.PropertyType == typeof(IListDataSource)) + { + // TODO : Make this work with non strings e.g. + // if user types a bunch of numbers in or dates + var oldValueAsArrayOfStrings = oldValue == null ? + Array.Empty() : + ((IListDataSource)oldValue).ToList() + .Cast() + .Select(o => o?.ToString()) + .ToArray(); + + if (Modals.TryGetArray( + property.PropertyInfo.Name, + "New List Value", + oldValueAsArrayOfStrings, + out Array? resultArray)) + { + newValue = resultArray; + return true; + } + } + else + if (property.PropertyInfo.PropertyType.IsEnum) + { + if (Modals.GetEnum(property.PropertyInfo.Name, "New Enum Value", property.PropertyInfo.PropertyType, (Enum?)property.GetValue(), out var resultEnum)) + { + newValue = resultEnum; + return true; + } + } + else + if (property.PropertyInfo.PropertyType == typeof(int) + || property.PropertyInfo.PropertyType == typeof(int?) + || property.PropertyInfo.PropertyType == typeof(uint) + || property.PropertyInfo.PropertyType == typeof(uint?)) + { + // deals with null, int and uint + var v = oldValue == null ? null : (int?)Convert.ToInt32(oldValue); + + if (Modals.GetInt(property.PropertyInfo.Name, "New Int Value", v, out var resultInt)) + { + // change back to uint/int/null + newValue = resultInt == null ? null : Convert.ChangeType(resultInt, property.PropertyInfo.PropertyType); + return true; + } + } + else + if (property.PropertyInfo.PropertyType == typeof(float) + || property.PropertyInfo.PropertyType == typeof(float?)) + { + if (Modals.GetFloat(property.PropertyInfo.Name, "New Float Value", (float?)oldValue, out var resultInt)) + { + newValue = resultInt; + return true; + } + } + else + if (property.PropertyInfo.PropertyType == typeof(char?) + || property.PropertyInfo.PropertyType == typeof(char)) + { + if (Modals.GetChar(property.PropertyInfo.Name, "New Single Character", oldValue is null ? null : (char?)oldValue.ToPrimitive() ?? null, out var resultChar)) + { + newValue = resultChar; + return true; + } + } + else + if (property.PropertyInfo.PropertyType == typeof(Rune) + || property.PropertyInfo.PropertyType == typeof(Rune?)) + { + if (Modals.GetChar(property.PropertyInfo.Name, "New Single Character", oldValue is null ? null : (char?)oldValue.ToPrimitive() ?? null, out var resultChar)) + { + newValue = resultChar == null ? null : new Rune(resultChar.Value); + return true; + } + } + else + if (Modals.GetString(property.PropertyInfo.Name, "New String Value", oldValue?.ToString(), out var result, AllowMultiLine(property))) + { + newValue = result; + return true; + } + + newValue = null; + return false; + } + private static bool AllowMultiLine(Property property) + { + // for the text editor control let them put multiple lines in + if (property.PropertyInfo.Name.Equals("Text") && property.Design.View is TextView tv && tv.Multiline) + { + return true; + } + + return false; + } + + private static bool GetNewColorSchemeValue(Design design, Property property, out object? newValue) + { + const string custom = "Edit Color Schemes..."; + List offer = new(); + + var defaults = new DefaultColorSchemes(); + var schemes = ColorSchemeManager.Instance.Schemes.ToList(); + + offer.AddRange(schemes); + + foreach (var d in defaults.GetDefaultSchemes()) + { + // user is already explicitly using this default and may even have modified it + if (offer.OfType().Any(s => s.Name.Equals(d.Name))) + { + continue; + } + + offer.Add(d); + } + + // add the option to jump to custom colors + offer.Add(custom); + + if (Modals.Get("Color Scheme", "Ok", offer.ToArray(), design.View.ColorScheme, out var selected)) + { + // if user clicked "Custom..." + if (selected is string s && string.Equals(s, custom)) + { + // show the custom colors dialog + var colorSchemesUI = new ColorSchemesUI(design); + Application.Run(colorSchemesUI); + newValue = null; + return false; + } + + if (selected is NamedColorScheme ns) + { + newValue = ns.Scheme; + + // if it was a default one, tell ColorSchemeManager we are now using it + if (!schemes.Contains(ns)) + { + ColorSchemeManager.Instance.AddOrUpdateScheme(ns.Name, ns.Scheme, design.GetRootDesign()); + } + + return true; + } + + newValue = null; + return false; + } + else + { + // user cancelled selecting scheme + newValue = null; + return false; + } + } + } +} diff --git a/src/UI/Windows/ArrayEditor.cs b/src/UI/Windows/ArrayEditor.cs index 129bdc54..b8fce5a8 100644 --- a/src/UI/Windows/ArrayEditor.cs +++ b/src/UI/Windows/ArrayEditor.cs @@ -10,7 +10,7 @@ namespace TerminalGuiDesigner.UI.Windows { using System.Collections; using Terminal.Gui; - + using TerminalGuiDesigner.ToCode; public partial class ArrayEditor { @@ -20,13 +20,14 @@ public partial class ArrayEditor { public bool Cancelled { get; private set; } = true; private Type elementType; + private readonly Property property; /// /// The new array /// public IList Result { get; private set; } - public ArrayEditor(ToCode.Property property) { + public ArrayEditor(Property property) { InitializeComponent(); this.elementType = property.GetElementType(); @@ -46,35 +47,17 @@ public ArrayEditor(ToCode.Property property) { btnOk.Clicked += BtnOk_Clicked; btnCancel.Clicked += BtnCancel_Clicked; btnAddElement.Clicked += BtnAddElement_Clicked; + this.property = property; } private void BtnAddElement_Clicked(object sender, EventArgs e) { - object toAdd; - if(this.elementType.IsGenericType && this.elementType.GetGenericTypeDefinition() == typeof(SliderOption<>)) - { - var designer = new SliderOptionEditor(this.elementType.GetGenericArguments()[0]); - Application.Run(designer); - - if (!designer.Cancelled) - { - toAdd = designer.Result; - } - else - { - // user canceled designing the Option - return; - } - } - else + + if(ValueFactory.GetNewValue(this.elementType,out var newValue)) { - // TODO: Handle other cases here - return; - //toAdd = default; + Result.Add(newValue); } - Result.Add(toAdd); - lvElements.SetSource(Result); lvElements.SetNeedsDisplay(); } diff --git a/src/UI/Windows/EditDialog.cs b/src/UI/Windows/EditDialog.cs index f34be96a..fb8cea55 100644 --- a/src/UI/Windows/EditDialog.cs +++ b/src/UI/Windows/EditDialog.cs @@ -86,7 +86,7 @@ public override bool OnKeyDown(Key Key) internal static bool SetPropertyToNewValue(Design design, Property p, object? oldValue) { // user wants to give us a new value for this property - if (GetNewValue(design, p, p.GetValue(), out object? newValue)) + if (ValueFactory.GetNewValue(design, p, p.GetValue(), out object? newValue)) { OperationManager.Instance.Do( new SetPropertyOperation(design, p, oldValue, newValue)); @@ -97,355 +97,6 @@ internal static bool SetPropertyToNewValue(Design design, Property p, object? ol return false; } - internal static bool GetNewValue(Design design, Property property, object? oldValue, out object? newValue) - { - if (property.PropertyInfo.PropertyType == typeof(ColorScheme)) - { - return GetNewColorSchemeValue(design, property, out newValue); - } - else - if (property.PropertyInfo.PropertyType == typeof(Attribute) || - property.PropertyInfo.PropertyType == typeof(Attribute?)) - { - // if its an Attribute or nullableAttribute - var picker = new ColorPicker((Attribute?)property.GetValue()); - Application.Run(picker); - - if (!picker.Cancelled) - { - newValue = picker.Result; - return true; - } - else - { - // user cancelled designing the Color - newValue = null; - return false; - } - } - else - if (property.PropertyInfo.PropertyType == typeof(ITextValidateProvider)) - { - string? oldPattern = oldValue is TextRegexProvider r ? (string?)r.Pattern.ToPrimitive() : null; - if (Modals.GetString("New Validation Pattern", "Regex Pattern", oldPattern, out var newPattern)) - { - newValue = string.IsNullOrWhiteSpace(newPattern) ? null : new TextRegexProvider(newPattern); - return true; - } - - // user cancelled entering a pattern - newValue = null; - return false; - } - else - if (property.PropertyInfo.PropertyType == typeof(Pos)) - { - // user is editing a Pos - var designer = new PosEditor(design, property); - - Application.Run(designer); - - if (!designer.Cancelled) - { - newValue = designer.Result; - return true; - } - else - { - // user cancelled designing the Pos - newValue = null; - return false; - } - } - else - if (property.PropertyInfo.PropertyType == typeof(Size)) - { - // user is editing a Size - var oldSize = (Size)(oldValue ?? throw new Exception($"Property {property.PropertyInfo.Name} is of Type Size but it's current value is null")); - var designer = new SizeEditor(oldSize); - - Application.Run(designer); - - if (!designer.Cancelled) - { - newValue = designer.Result; - return true; - } - else - { - // user cancelled designing the Pos - newValue = null; - return false; - } - } - else - if (property.PropertyInfo.PropertyType == typeof(PointF)) - { - // user is editing a PointF - var oldPointF = (PointF)(oldValue ?? throw new Exception($"Property {property.PropertyInfo.Name} is of Type PointF but it's current value is null")); - var designer = new PointEditor(oldPointF.X, oldPointF.Y); - - Application.Run(designer); - - if (!designer.Cancelled) - { - newValue = new PointF(designer.ResultX, designer.ResultY); - return true; - } - else - { - // user cancelled designing the Pos - newValue = null; - return false; - } - } - else - if (property.PropertyInfo.PropertyType == typeof(Dim)) - { - // user is editing a Dim - var designer = new DimEditor(design, property); - Application.Run(designer); - - if (!designer.Cancelled) - { - newValue = designer.Result; - return true; - } - else - { - // user cancelled designing the Dim - newValue = null; - return false; - } - } - else - if (property is InstanceOfProperty inst) - { - if (Modals.Get( - property.PropertyInfo.Name, - "New Value", - typeof(Label).Assembly.GetTypes().Where(inst.MustBeDerrivedFrom.IsAssignableFrom).ToArray(), - inst.GetValue()?.GetType(), - out Type? typeChosen)) - { - if (typeChosen == null) - { - newValue = null; - return false; - } - - newValue = Activator.CreateInstance(typeChosen); - return true; - } - } - else - if (property.PropertyInfo.PropertyType == typeof(bool)) - { - int answer = ChoicesDialog.Query(property.PropertyInfo.Name, $"New value for {property.PropertyInfo.PropertyType}", "Yes", "No"); - - newValue = answer == 0 ? true : false; - return answer != -1; - } - else - if ( - // TODO: I just changed this from IsArray to IList assignable, need to worry about conversions a bit more - property.PropertyInfo.PropertyType.IsAssignableTo(typeof(IList)) - ) - { - var elementType = property.GetElementType() - ?? throw new Exception($"Property {property.GetHumanReadableName()} was array but had no element type"); ; - - if (elementType.IsValueType || elementType == typeof(string)) - { - if (Modals.GetArray( - property.PropertyInfo.Name, - "New Array Value", - property.PropertyInfo.PropertyType.GetElementType() ?? throw new Exception("Property was an Array but GetElementType returned null"), - (Array?)oldValue, - out Array? resultArray)) - { - newValue = resultArray; - return true; - } - } - else - { - var designer = new ArrayEditor(property); - Application.Run(designer); - - if (!designer.Cancelled) - { - newValue = designer.Result; - return true; - } - else - { - // user cancelled designing the Dim - newValue = null; - return false; - } - } - - } - else - if (property.PropertyInfo.PropertyType == typeof(IListDataSource)) - { - // TODO : Make this work with non strings e.g. - // if user types a bunch of numbers in or dates - var oldValueAsArrayOfStrings = oldValue == null ? - Array.Empty() : - ((IListDataSource)oldValue).ToList() - .Cast() - .Select(o => o?.ToString()) - .ToArray(); - - if (Modals.TryGetArray( - property.PropertyInfo.Name, - "New List Value", - oldValueAsArrayOfStrings, - out Array? resultArray)) - { - newValue = resultArray; - return true; - } - } - else - if (property.PropertyInfo.PropertyType.IsEnum) - { - if (Modals.GetEnum(property.PropertyInfo.Name, "New Enum Value", property.PropertyInfo.PropertyType, (Enum?)property.GetValue(), out var resultEnum)) - { - newValue = resultEnum; - return true; - } - } - else - if (property.PropertyInfo.PropertyType == typeof(int) - || property.PropertyInfo.PropertyType == typeof(int?) - || property.PropertyInfo.PropertyType == typeof(uint) - || property.PropertyInfo.PropertyType == typeof(uint?)) - { - // deals with null, int and uint - var v = oldValue == null ? null : (int?)Convert.ToInt32(oldValue); - - if (Modals.GetInt(property.PropertyInfo.Name, "New Int Value", v, out var resultInt)) - { - // change back to uint/int/null - newValue = resultInt == null ? null : Convert.ChangeType(resultInt, property.PropertyInfo.PropertyType); - return true; - } - } - else - if (property.PropertyInfo.PropertyType == typeof(float) - || property.PropertyInfo.PropertyType == typeof(float?)) - { - if (Modals.GetFloat(property.PropertyInfo.Name, "New Float Value", (float?)oldValue, out var resultInt)) - { - newValue = resultInt; - return true; - } - } - else - if (property.PropertyInfo.PropertyType == typeof(char?) - || property.PropertyInfo.PropertyType == typeof(char)) - { - if (Modals.GetChar(property.PropertyInfo.Name, "New Single Character", oldValue is null ? null : (char?)oldValue.ToPrimitive() ?? null, out var resultChar)) - { - newValue = resultChar; - return true; - } - } - else - if (property.PropertyInfo.PropertyType == typeof(Rune) - || property.PropertyInfo.PropertyType == typeof(Rune?)) - { - if (Modals.GetChar(property.PropertyInfo.Name, "New Single Character", oldValue is null ? null : (char?)oldValue.ToPrimitive() ?? null, out var resultChar)) - { - newValue = resultChar == null ? null : new Rune(resultChar.Value); - return true; - } - } - else - if (Modals.GetString(property.PropertyInfo.Name, "New String Value", oldValue?.ToString(), out var result, AllowMultiLine(property))) - { - newValue = result; - return true; - } - - newValue = null; - return false; - } - - private static bool GetNewColorSchemeValue(Design design, Property property, out object? newValue) - { - const string custom = "Edit Color Schemes..."; - List offer = new(); - - var defaults = new DefaultColorSchemes(); - var schemes = ColorSchemeManager.Instance.Schemes.ToList(); - - offer.AddRange(schemes); - - foreach (var d in defaults.GetDefaultSchemes()) - { - // user is already explicitly using this default and may even have modified it - if (offer.OfType().Any(s => s.Name.Equals(d.Name))) - { - continue; - } - - offer.Add(d); - } - - // add the option to jump to custom colors - offer.Add(custom); - - if (Modals.Get("Color Scheme", "Ok", offer.ToArray(), design.View.ColorScheme, out var selected)) - { - // if user clicked "Custom..." - if (selected is string s && string.Equals(s, custom)) - { - // show the custom colors dialog - var colorSchemesUI = new ColorSchemesUI(design); - Application.Run(colorSchemesUI); - newValue = null; - return false; - } - - if (selected is NamedColorScheme ns) - { - newValue = ns.Scheme; - - // if it was a default one, tell ColorSchemeManager we are now using it - if (!schemes.Contains(ns)) - { - ColorSchemeManager.Instance.AddOrUpdateScheme(ns.Name, ns.Scheme, design.GetRootDesign()); - } - - return true; - } - - newValue = null; - return false; - } - else - { - // user cancelled selecting scheme - newValue = null; - return false; - } - } - - private static bool AllowMultiLine(Property property) - { - // for the text editor control let them put multiple lines in - if (property.PropertyInfo.Name.Equals("Text") && property.Design.View is TextView tv && tv.Multiline) - { - return true; - } - - return false; - } - private void SetProperty(bool setNull) { if (this.list.SelectedItem != -1) From a30022797a232e3d069fc679cb0719c1e41620bd Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 31 Dec 2023 11:54:41 +0000 Subject: [PATCH 19/28] wip: Refactor ValueFactory to single pathway (fails build) --- src/UI/ValueFactory.cs | 143 +++++++++++++++++----------------- src/UI/Windows/ArrayEditor.cs | 3 +- src/UI/Windows/PosEditor.cs | 8 +- 3 files changed, 77 insertions(+), 77 deletions(-) diff --git a/src/UI/ValueFactory.cs b/src/UI/ValueFactory.cs index e144851c..22682729 100644 --- a/src/UI/ValueFactory.cs +++ b/src/UI/ValueFactory.cs @@ -11,7 +11,7 @@ namespace TerminalGuiDesigner.UI { static class ValueFactory { - internal static bool GetNewValue(Type type, out object? newValue) + internal static bool GetNewValue(string propertyName, Design design, Type type, object? oldValue, out object? newValue, bool allowMultiLine) { newValue = null; @@ -32,24 +32,16 @@ internal static bool GetNewValue(Type type, out object? newValue) } } else + if (type== typeof(ColorScheme)) { - // TODO: Handle other cases here - return false; - //toAdd = default; - } - } - internal static bool GetNewValue(Design design, Property property, object? oldValue, out object? newValue) - { - if (property.PropertyInfo.PropertyType == typeof(ColorScheme)) - { - return GetNewColorSchemeValue(design, property, out newValue); + return GetNewColorSchemeValue(design, out newValue); } else - if (property.PropertyInfo.PropertyType == typeof(Attribute) || - property.PropertyInfo.PropertyType == typeof(Attribute?)) + if (type == typeof(Attribute) || + type == typeof(Attribute?)) { // if its an Attribute or nullableAttribute - var picker = new ColorPicker((Attribute?)property.GetValue()); + var picker = new ColorPicker((Attribute?)oldValue); Application.Run(picker); if (!picker.Cancelled) @@ -65,7 +57,7 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa } } else - if (property.PropertyInfo.PropertyType == typeof(ITextValidateProvider)) + if (type== typeof(ITextValidateProvider)) { string? oldPattern = oldValue is TextRegexProvider r ? (string?)r.Pattern.ToPrimitive() : null; if (Modals.GetString("New Validation Pattern", "Regex Pattern", oldPattern, out var newPattern)) @@ -79,10 +71,10 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa return false; } else - if (property.PropertyInfo.PropertyType == typeof(Pos)) + if (type== typeof(Pos)) { // user is editing a Pos - var designer = new PosEditor(design, property); + var designer = new PosEditor(design, (Pos)oldValue?? throw new Exception("Pos property was unexpectedly null")); Application.Run(designer); @@ -99,10 +91,10 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa } } else - if (property.PropertyInfo.PropertyType == typeof(Size)) + if (type== typeof(Size)) { // user is editing a Size - var oldSize = (Size)(oldValue ?? throw new Exception($"Property {property.PropertyInfo.Name} is of Type Size but it's current value is null")); + var oldSize = (Size)(oldValue ?? throw new Exception($"Property {propertyName} is of Type Size but it's current value is null")); var designer = new SizeEditor(oldSize); Application.Run(designer); @@ -120,10 +112,10 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa } } else - if (property.PropertyInfo.PropertyType == typeof(PointF)) + if (type== typeof(PointF)) { // user is editing a PointF - var oldPointF = (PointF)(oldValue ?? throw new Exception($"Property {property.PropertyInfo.Name} is of Type PointF but it's current value is null")); + var oldPointF = (PointF)(oldValue ?? throw new Exception($"Property {propertyName} is of Type PointF but it's current value is null")); var designer = new PointEditor(oldPointF.X, oldPointF.Y); Application.Run(designer); @@ -141,7 +133,7 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa } } else - if (property.PropertyInfo.PropertyType == typeof(Dim)) + if (type== typeof(Dim)) { // user is editing a Dim var designer = new DimEditor(design, property); @@ -160,29 +152,9 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa } } else - if (property is InstanceOfProperty inst) - { - if (Modals.Get( - property.PropertyInfo.Name, - "New Value", - typeof(Label).Assembly.GetTypes().Where(inst.MustBeDerrivedFrom.IsAssignableFrom).ToArray(), - inst.GetValue()?.GetType(), - out Type? typeChosen)) - { - if (typeChosen == null) - { - newValue = null; - return false; - } - - newValue = Activator.CreateInstance(typeChosen); - return true; - } - } - else - if (property.PropertyInfo.PropertyType == typeof(bool)) + if (type== typeof(bool)) { - int answer = ChoicesDialog.Query(property.PropertyInfo.Name, $"New value for {property.PropertyInfo.PropertyType}", "Yes", "No"); + int answer = ChoicesDialog.Query(propertyName, $"New value for {type}", "Yes", "No"); newValue = answer == 0 ? true : false; return answer != -1; @@ -190,18 +162,18 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa else if ( // TODO: I just changed this from IsArray to IList assignable, need to worry about conversions a bit more - property.PropertyInfo.PropertyType.IsAssignableTo(typeof(IList)) + type.IsAssignableTo(typeof(IList)) ) { var elementType = property.GetElementType() - ?? throw new Exception($"Property {property.GetHumanReadableName()} was array but had no element type"); ; + ?? throw new Exception($"Property {propertyName} was array but had no element type"); ; if (elementType.IsValueType || elementType == typeof(string)) { if (Modals.GetArray( - property.PropertyInfo.Name, + propertyName, "New Array Value", - property.PropertyInfo.PropertyType.GetElementType() ?? throw new Exception("Property was an Array but GetElementType returned null"), + type.GetElementType() ?? throw new Exception("Property was an Array but GetElementType returned null"), (Array?)oldValue, out Array? resultArray)) { @@ -229,7 +201,7 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa } else - if (property.PropertyInfo.PropertyType == typeof(IListDataSource)) + if (type== typeof(IListDataSource)) { // TODO : Make this work with non strings e.g. // if user types a bunch of numbers in or dates @@ -241,7 +213,7 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa .ToArray(); if (Modals.TryGetArray( - property.PropertyInfo.Name, + propertyName, "New List Value", oldValueAsArrayOfStrings, out Array? resultArray)) @@ -251,62 +223,62 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa } } else - if (property.PropertyInfo.PropertyType.IsEnum) + if (type.IsEnum) { - if (Modals.GetEnum(property.PropertyInfo.Name, "New Enum Value", property.PropertyInfo.PropertyType, (Enum?)property.GetValue(), out var resultEnum)) + if (Modals.GetEnum(propertyName, "New Enum Value", type, (Enum?)oldValue, out var resultEnum)) { newValue = resultEnum; return true; } } else - if (property.PropertyInfo.PropertyType == typeof(int) - || property.PropertyInfo.PropertyType == typeof(int?) - || property.PropertyInfo.PropertyType == typeof(uint) - || property.PropertyInfo.PropertyType == typeof(uint?)) + if (type== typeof(int) + || type== typeof(int?) + || type== typeof(uint) + || type== typeof(uint?)) { // deals with null, int and uint var v = oldValue == null ? null : (int?)Convert.ToInt32(oldValue); - if (Modals.GetInt(property.PropertyInfo.Name, "New Int Value", v, out var resultInt)) + if (Modals.GetInt(propertyName, "New Int Value", v, out var resultInt)) { // change back to uint/int/null - newValue = resultInt == null ? null : Convert.ChangeType(resultInt, property.PropertyInfo.PropertyType); + newValue = resultInt == null ? null : Convert.ChangeType(resultInt, type); return true; } } else - if (property.PropertyInfo.PropertyType == typeof(float) - || property.PropertyInfo.PropertyType == typeof(float?)) + if (type== typeof(float) + || type== typeof(float?)) { - if (Modals.GetFloat(property.PropertyInfo.Name, "New Float Value", (float?)oldValue, out var resultInt)) + if (Modals.GetFloat(propertyName, "New Float Value", (float?)oldValue, out var resultInt)) { newValue = resultInt; return true; } } else - if (property.PropertyInfo.PropertyType == typeof(char?) - || property.PropertyInfo.PropertyType == typeof(char)) + if (type== typeof(char?) + || type== typeof(char)) { - if (Modals.GetChar(property.PropertyInfo.Name, "New Single Character", oldValue is null ? null : (char?)oldValue.ToPrimitive() ?? null, out var resultChar)) + if (Modals.GetChar(propertyName, "New Single Character", oldValue is null ? null : (char?)oldValue.ToPrimitive() ?? null, out var resultChar)) { newValue = resultChar; return true; } } else - if (property.PropertyInfo.PropertyType == typeof(Rune) - || property.PropertyInfo.PropertyType == typeof(Rune?)) + if (type== typeof(Rune) + || type== typeof(Rune?)) { - if (Modals.GetChar(property.PropertyInfo.Name, "New Single Character", oldValue is null ? null : (char?)oldValue.ToPrimitive() ?? null, out var resultChar)) + if (Modals.GetChar(propertyName, "New Single Character", oldValue is null ? null : (char?)oldValue.ToPrimitive() ?? null, out var resultChar)) { newValue = resultChar == null ? null : new Rune(resultChar.Value); return true; } } else - if (Modals.GetString(property.PropertyInfo.Name, "New String Value", oldValue?.ToString(), out var result, AllowMultiLine(property))) + if (Modals.GetString(propertyName, "New String Value", oldValue?.ToString(), out var result, allowMultiLine)) { newValue = result; return true; @@ -315,7 +287,38 @@ internal static bool GetNewValue(Design design, Property property, object? oldVa newValue = null; return false; } - private static bool AllowMultiLine(Property property) + internal static bool GetNewValue(Design design, Property property, object? oldValue, out object? newValue) + { + if (property is InstanceOfProperty inst) + { + if (Modals.Get( + property.PropertyInfo.Name, + "New Value", + typeof(Label).Assembly.GetTypes().Where(inst.MustBeDerrivedFrom.IsAssignableFrom).ToArray(), + inst.GetValue()?.GetType(), + out Type? typeChosen)) + { + if (typeChosen == null) + { + newValue = null; + return false; + } + + newValue = Activator.CreateInstance(typeChosen); + return true; + } + + // User cancelled dialog + newValue = null; + return false; + } + else + { + return GetNewValue(property.PropertyInfo.Name, design, property.PropertyInfo.PropertyType,oldValue, out newValue, ValueFactory.AllowMultiLine(property)); + } + + } + public static bool AllowMultiLine(Property property) { // for the text editor control let them put multiple lines in if (property.PropertyInfo.Name.Equals("Text") && property.Design.View is TextView tv && tv.Multiline) @@ -326,7 +329,7 @@ private static bool AllowMultiLine(Property property) return false; } - private static bool GetNewColorSchemeValue(Design design, Property property, out object? newValue) + private static bool GetNewColorSchemeValue(Design design, out object? newValue) { const string custom = "Edit Color Schemes..."; List offer = new(); diff --git a/src/UI/Windows/ArrayEditor.cs b/src/UI/Windows/ArrayEditor.cs index b8fce5a8..cafb388c 100644 --- a/src/UI/Windows/ArrayEditor.cs +++ b/src/UI/Windows/ArrayEditor.cs @@ -52,8 +52,7 @@ public ArrayEditor(Property property) { private void BtnAddElement_Clicked(object sender, EventArgs e) { - - if(ValueFactory.GetNewValue(this.elementType,out var newValue)) + if(ValueFactory.GetNewValue(property.PropertyInfo.Name, this.property.Design, this.elementType,null, out var newValue, ValueFactory.AllowMultiLine(property))) { Result.Add(newValue); } diff --git a/src/UI/Windows/PosEditor.cs b/src/UI/Windows/PosEditor.cs index d1e4ebb3..e4323d89 100644 --- a/src/UI/Windows/PosEditor.cs +++ b/src/UI/Windows/PosEditor.cs @@ -36,16 +36,14 @@ public partial class PosEditor : Dialog { /// /// Prompt user to create a new value to populate - /// on with. + /// on with. /// /// What to set the value on. - /// The property to set (must be of type ). - public PosEditor(Design design, Property property) { + /// The current value for the property. + public PosEditor(Design design, Pos oldValue) { InitializeComponent(); this.design = design; - this.property = property; - Title = "Pos Designer"; Border.BorderStyle = LineStyle.Double; From 1220a952915da7b6827098c070be6c23f752a031 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 31 Dec 2023 23:06:24 +0000 Subject: [PATCH 20/28] Fix previous commit build errors --- src/ToCode/Property.cs | 24 +----------------------- src/TypeExtensions.cs | 34 ++++++++++++++++++++++++++++++++++ src/UI/ValueFactory.cs | 6 +++--- src/UI/Windows/ArrayEditor.cs | 18 +++++++----------- src/UI/Windows/DimEditor.cs | 7 ++----- 5 files changed, 47 insertions(+), 42 deletions(-) create mode 100644 src/TypeExtensions.cs diff --git a/src/ToCode/Property.cs b/src/ToCode/Property.cs index 1dc8e015..4aa08dbf 100644 --- a/src/ToCode/Property.cs +++ b/src/ToCode/Property.cs @@ -267,7 +267,7 @@ public virtual CodeExpression GetRhs() if (val is IList valList) { - var elementType = this.GetElementType() + var elementType = type.GetElementTypeEx() ?? throw new Exception($"Type {type} was an IList but {nameof(Type.GetElementType)} returned null"); return new CodeObjectCreateExpression( @@ -440,26 +440,4 @@ private void CallRefreshMethodsIfAny() this.Design.View.SetNeedsDisplay(); } - - /// - /// Returns the element type for collections (IList or Array) or if it is not. - /// - /// Element type of collection or . - public Type? GetElementType() - { - var propertyType = this.PropertyInfo.PropertyType; - var elementType = propertyType.GetElementType(); - - if(elementType != null) - { - return elementType; - } - - if (propertyType.IsAssignableTo(typeof(IList)) && propertyType.IsGenericType) - { - return propertyType.GetGenericArguments().Single(); - } - - return null; - } } diff --git a/src/TypeExtensions.cs b/src/TypeExtensions.cs new file mode 100644 index 00000000..71962137 --- /dev/null +++ b/src/TypeExtensions.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TerminalGuiDesigner +{ + public static class TypeExtensions + { + /// + /// Implementation of that also works for + /// + /// + /// Element type of collection or . + public static Type? GetElementTypeEx(this Type type) + { + var elementType = type.GetElementType(); + + if (elementType != null) + { + return elementType; + } + + if (type.IsAssignableTo(typeof(IList)) && type.IsGenericType) + { + return type.GetGenericArguments().Single(); + } + + return null; + } + } +} diff --git a/src/UI/ValueFactory.cs b/src/UI/ValueFactory.cs index 22682729..b4547e4a 100644 --- a/src/UI/ValueFactory.cs +++ b/src/UI/ValueFactory.cs @@ -136,7 +136,7 @@ internal static bool GetNewValue(string propertyName, Design design, Type type, if (type== typeof(Dim)) { // user is editing a Dim - var designer = new DimEditor(design, property); + var designer = new DimEditor(design, (Dim)oldValue); Application.Run(designer); if (!designer.Cancelled) @@ -165,7 +165,7 @@ internal static bool GetNewValue(string propertyName, Design design, Type type, type.IsAssignableTo(typeof(IList)) ) { - var elementType = property.GetElementType() + var elementType = type.GetElementTypeEx() ?? throw new Exception($"Property {propertyName} was array but had no element type"); ; if (elementType.IsValueType || elementType == typeof(string)) @@ -183,7 +183,7 @@ internal static bool GetNewValue(string propertyName, Design design, Type type, } else { - var designer = new ArrayEditor(property); + var designer = new ArrayEditor(design,type.GetElementTypeEx(), (IList)oldValue); Application.Run(designer); if (!designer.Cancelled) diff --git a/src/UI/Windows/ArrayEditor.cs b/src/UI/Windows/ArrayEditor.cs index cafb388c..7fce42b2 100644 --- a/src/UI/Windows/ArrayEditor.cs +++ b/src/UI/Windows/ArrayEditor.cs @@ -19,26 +19,23 @@ public partial class ArrayEditor { /// public bool Cancelled { get; private set; } = true; + private readonly Design design; private Type elementType; - private readonly Property property; /// /// The new array /// public IList Result { get; private set; } - public ArrayEditor(Property property) { + public ArrayEditor(Design design, Type elementType, IList oldValue) { InitializeComponent(); + this.design = design; + this.elementType = elementType; - this.elementType = property.GetElementType(); - - - - Type listType = typeof(List<>).MakeGenericType(property.GetElementType()); + Type listType = typeof(List<>).MakeGenericType(elementType); Result = (IList)Activator.CreateInstance(listType); - - foreach(var e in (IList)property.GetValue()) + foreach(var e in oldValue) { Result.Add(e); } @@ -47,12 +44,11 @@ public ArrayEditor(Property property) { btnOk.Clicked += BtnOk_Clicked; btnCancel.Clicked += BtnCancel_Clicked; btnAddElement.Clicked += BtnAddElement_Clicked; - this.property = property; } private void BtnAddElement_Clicked(object sender, EventArgs e) { - if(ValueFactory.GetNewValue(property.PropertyInfo.Name, this.property.Design, this.elementType,null, out var newValue, ValueFactory.AllowMultiLine(property))) + if(ValueFactory.GetNewValue("Element Value", design, this.elementType,null, out var newValue,true)) { Result.Add(newValue); } diff --git a/src/UI/Windows/DimEditor.cs b/src/UI/Windows/DimEditor.cs index 51001781..95aa89c2 100644 --- a/src/UI/Windows/DimEditor.cs +++ b/src/UI/Windows/DimEditor.cs @@ -36,8 +36,7 @@ public partial class DimEditor : Dialog /// Creates a new instance of the class. /// /// - /// - public DimEditor(Design design, Property property) { + public DimEditor(Design design, Dim oldValue) { InitializeComponent(); this.design = design; @@ -53,9 +52,7 @@ public DimEditor(Design design, Property property) { Modal = true; rgDimType.KeyDown += RgDimType_KeyPress; - - var val = (Dim)property.GetValue(); - if(val.GetDimType(out var type,out var value, out var offset)) + if(oldValue.GetDimType(out var type,out var value, out var offset)) { switch(type) { From 209052db3bb51db81760d5312fa64c5443aba3dc Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 31 Dec 2023 23:15:04 +0000 Subject: [PATCH 21/28] Bump nuget package and fix tests --- src/DimExtensions.cs | 10 +++++----- src/PosExtensions.cs | 6 +++--- src/TerminalGuiDesigner.csproj | 2 +- src/ToCode/Property.cs | 15 ++++++++++++--- tests/SliderTests.cs | 3 +-- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/DimExtensions.cs b/src/DimExtensions.cs index 07a9c951..67ac4dac 100644 --- a/src/DimExtensions.cs +++ b/src/DimExtensions.cs @@ -40,8 +40,8 @@ public static bool IsPercent(this Dim d, out float percent) { if (d != null && d.IsPercent()) { - var nField = d.GetType().GetField("factor", BindingFlags.NonPublic | BindingFlags.Instance) - ?? throw new Exception("Expected private field 'factor' of DimPercent was missing"); + var nField = d.GetType().GetField("_factor", BindingFlags.NonPublic | BindingFlags.Instance) + ?? throw new Exception("Expected private field '_factor' of DimPercent was missing"); percent = ((float?)nField.GetValue(d) ?? throw new Exception("Expected private field 'factor' to be a float")) * 100f; return true; } @@ -72,8 +72,8 @@ public static bool IsFill(this Dim d, out int margin) { if (d != null && d.IsFill()) { - var nField = d.GetType().GetField("margin", BindingFlags.NonPublic | BindingFlags.Instance) - ?? throw new Exception("Expected private field 'margin' of DimFill was missing"); + var nField = d.GetType().GetField("_margin", BindingFlags.NonPublic | BindingFlags.Instance) + ?? throw new Exception("Expected private field '_margin' of DimFill was missing"); margin = (int?)nField.GetValue(d) ?? throw new Exception("Expected private field 'margin' of DimFill had unexpected Type"); return true; } @@ -110,7 +110,7 @@ public static bool IsAbsolute(this Dim d, out int n) return TreatNullDimAs0; } - var nField = d.GetType().GetField("n", BindingFlags.NonPublic | BindingFlags.Instance) + var nField = d.GetType().GetField("_n", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field was missing from DimAbsolute"); n = (int?)nField.GetValue(d) ?? throw new Exception("Expected private field 'n' to be in int for DimAbsolute"); diff --git a/src/PosExtensions.cs b/src/PosExtensions.cs index 6d38f987..657e085b 100644 --- a/src/PosExtensions.cs +++ b/src/PosExtensions.cs @@ -43,10 +43,10 @@ public static bool IsAbsolute(this Pos? p, out int n) return TreatNullPosAs0; } - var nField = p.GetType().GetField("n", BindingFlags.NonPublic | BindingFlags.Instance) - ?? throw new Exception("Expected private field 'n' of PosAbsolute was missing"); + var nField = p.GetType().GetField("_n", BindingFlags.NonPublic | BindingFlags.Instance) + ?? throw new Exception("Expected private field '_n' of PosAbsolute was missing"); n = (int?)nField.GetValue(p) - ?? throw new Exception("Expected private field 'n' of PosAbsolute to be int"); + ?? throw new Exception("Expected private field '_n' of PosAbsolute to be int"); return true; } diff --git a/src/TerminalGuiDesigner.csproj b/src/TerminalGuiDesigner.csproj index c2cd0d4b..dc993dbb 100644 --- a/src/TerminalGuiDesigner.csproj +++ b/src/TerminalGuiDesigner.csproj @@ -146,7 +146,7 @@ - + diff --git a/src/ToCode/Property.cs b/src/ToCode/Property.cs index 4aa08dbf..facf538e 100644 --- a/src/ToCode/Property.cs +++ b/src/ToCode/Property.cs @@ -283,12 +283,21 @@ public virtual CodeExpression GetRhs() private CodeExpression ValueFactory(object val) { + + var type = val.GetType(); + // TODO: Could move lots of logic in GetRHS into here - if (val.GetType().GetGenericTypeDefinition() == typeof(SliderOption<>)) + if (type.GetGenericTypeDefinition() == typeof(SliderOption<>)) { - // TODO: Cannot call initializers :( + var a1 = type.GetProperty(nameof(SliderOption.Legend)).GetValue(val); + var a2 = type.GetProperty(nameof(SliderOption.LegendAbbr)).GetValue(val); + var a3 = type.GetProperty(nameof(SliderOption.Data)).GetValue(val); + return new CodeObjectCreateExpression( - new CodeTypeReference(val.GetType())); + new CodeTypeReference(val.GetType()), + new CodePrimitiveExpression(a1), + new CodePrimitiveExpression(a2), + new CodePrimitiveExpression(a3)); } else { diff --git a/tests/SliderTests.cs b/tests/SliderTests.cs index 9c67b3e8..aec6c32c 100644 --- a/tests/SliderTests.cs +++ b/tests/SliderTests.cs @@ -21,7 +21,7 @@ public void TestRoundTrip_Slider_PreserveStringOptions() { var sliderIn = RoundTrip>((d, v) => { - v.Options.Add(new SliderOption { Legend = "l1", LegendAbbr = new Rune('1'), Data = "Fun1" }); + v.Options.Add(new SliderOption("l1", new Rune('1'), "Fun1")); v.Options.Add(new SliderOption { Legend = "l2", LegendAbbr = new Rune('2'), Data = "Fun2" }); Assert.That(v.Options.Count, Is.EqualTo(2)); @@ -29,7 +29,6 @@ public void TestRoundTrip_Slider_PreserveStringOptions() Assert.That(sliderIn.Options.Count, Is.EqualTo(2)); - // TODO: Will pass tests when https://github.com/gui-cs/Terminal.Gui/issues/3100 is merged and nuget package drawn down. And relevant constructor called in our CodeDOM Assert.That(sliderIn.Options[0].Legend, Is.EqualTo("l1")); Assert.That(sliderIn.Options[0].LegendAbbr, Is.EqualTo(new Rune('1'))); Assert.That(sliderIn.Options[0].Data, Is.EqualTo("Fun1")); From e4559ab3f976dd56f492f060d2ad52deff716226 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 31 Dec 2023 23:20:33 +0000 Subject: [PATCH 22/28] Fixes for private member renames --- src/DimExtensions.cs | 6 +++--- src/PosExtensions.cs | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/DimExtensions.cs b/src/DimExtensions.cs index 67ac4dac..97d9b5de 100644 --- a/src/DimExtensions.cs +++ b/src/DimExtensions.cs @@ -151,13 +151,13 @@ public static bool IsCombine(this Dim d, out Dim left, out Dim right, out bool a { if (d.IsCombine()) { - var fLeft = d.GetType().GetField("left", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field was missing from Dim.Combine"); + var fLeft = d.GetType().GetField("_left", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field was missing from Dim.Combine"); left = fLeft.GetValue(d) as Dim ?? throw new Exception("Expected private field in DimCombine to be of Type Dim"); - var fRight = d.GetType().GetField("right", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field was missing from Dim.Combine"); + var fRight = d.GetType().GetField("_right", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field was missing from Dim.Combine"); right = fRight.GetValue(d) as Dim ?? throw new Exception("Expected private field in DimCombine to be of Type Dim"); - var fAdd = d.GetType().GetField("add", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field was missing from Dim.Combine"); + var fAdd = d.GetType().GetField("_add", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field was missing from Dim.Combine"); add = fAdd.GetValue(d) as bool? ?? throw new Exception("Expected private field in DimCombine to be of Type bool"); return true; diff --git a/src/PosExtensions.cs b/src/PosExtensions.cs index 657e085b..cfa31c0a 100644 --- a/src/PosExtensions.cs +++ b/src/PosExtensions.cs @@ -79,10 +79,10 @@ public static bool IsPercent(this Pos? p, out float percent) { if (p != null && p.IsPercent()) { - var nField = p.GetType().GetField("factor", BindingFlags.NonPublic | BindingFlags.Instance) - ?? throw new Exception("Expected private field 'factor' was missing from PosFactor"); + var nField = p.GetType().GetField("_factor", BindingFlags.NonPublic | BindingFlags.Instance) + ?? throw new Exception("Expected private field '_factor' was missing from PosFactor"); percent = ((float?)nField.GetValue(p) - ?? throw new Exception("Expected private field 'factor' of PosFactor to be float")) + ?? throw new Exception("Expected private field '_factor' of PosFactor to be float")) * 100f; return true; } @@ -137,10 +137,10 @@ public static bool IsAnchorEnd(this Pos? p, out int margin) return TreatNullPosAs0; } - var nField = p.GetType().GetField("n", BindingFlags.NonPublic | BindingFlags.Instance) - ?? throw new Exception("Expected private field 'n' of PosAbsolute was missing"); + var nField = p.GetType().GetField("_offset", BindingFlags.NonPublic | BindingFlags.Instance) + ?? throw new Exception("Expected private field '_offset' of PosAbsolute was missing. Fields were:" + string.Join(",", p.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Select(f=>f.Name).ToArray())); margin = (int?)nField.GetValue(p) - ?? throw new Exception("Expected private field 'n' of PosAbsolute to be int"); + ?? throw new Exception("Expected private field '_offset' of PosAbsolute to be int"); return true; } @@ -233,14 +233,14 @@ public static bool IsCombine(this Pos? p, out Pos left, out Pos right, out bool { if (IsCombine(p)) { - var fLeft = p.GetType().GetField("left", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field missing from PosCombine"); - left = fLeft.GetValue(p) as Pos ?? throw new Exception("Expected field 'left' of PosCombine to be a Pos"); + var fLeft = p.GetType().GetField("_left", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field missing from PosCombine"); + left = fLeft.GetValue(p) as Pos ?? throw new Exception("Expected field '_left' of PosCombine to be a Pos"); - var fRight = p.GetType().GetField("right", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field missing from PosCombine"); - right = fRight.GetValue(p) as Pos ?? throw new Exception("Expected field 'right' of PosCombine to be a Pos"); + var fRight = p.GetType().GetField("_right", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field missing from PosCombine"); + right = fRight.GetValue(p) as Pos ?? throw new Exception("Expected field '_right' of PosCombine to be a Pos"); - var fAdd = p.GetType().GetField("add", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field missing from PosCombine"); - add = fAdd.GetValue(p) as bool? ?? throw new Exception("Expected field 'add' of PosCombine to be a bool"); + var fAdd = p.GetType().GetField("_add", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception("Expected private field missing from PosCombine"); + add = fAdd.GetValue(p) as bool? ?? throw new Exception("Expected field '_add' of PosCombine to be a bool"); return true; } From 708dc71b0dafec9641a3829b9ffc11c0dfd4a7bf Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 31 Dec 2023 23:22:41 +0000 Subject: [PATCH 23/28] Slider round trip passing --- src/ToCode/Property.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ToCode/Property.cs b/src/ToCode/Property.cs index facf538e..cd2efb70 100644 --- a/src/ToCode/Property.cs +++ b/src/ToCode/Property.cs @@ -289,14 +289,16 @@ private CodeExpression ValueFactory(object val) // TODO: Could move lots of logic in GetRHS into here if (type.GetGenericTypeDefinition() == typeof(SliderOption<>)) { + // TODO: this feels very brittle! var a1 = type.GetProperty(nameof(SliderOption.Legend)).GetValue(val); - var a2 = type.GetProperty(nameof(SliderOption.LegendAbbr)).GetValue(val); + var a2 = (Rune)type.GetProperty(nameof(SliderOption.LegendAbbr)).GetValue(val); var a3 = type.GetProperty(nameof(SliderOption.Data)).GetValue(val); + return new CodeObjectCreateExpression( new CodeTypeReference(val.GetType()), new CodePrimitiveExpression(a1), - new CodePrimitiveExpression(a2), + new CodeObjectCreateExpression(typeof(Rune),new CodePrimitiveExpression(a2.ToString()[0])), new CodePrimitiveExpression(a3)); } else From 850bf29f445dfdf3b7eea6a74ace8340861b42b5 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 1 Jan 2024 10:16:11 +0000 Subject: [PATCH 24/28] For AddViewOperationTests use first compatible T type for generics --- tests/Operations/AddViewOperationTests.cs | 12 ++++++++++++ tests/ViewFactoryTests.cs | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/Operations/AddViewOperationTests.cs b/tests/Operations/AddViewOperationTests.cs index 3a094b7d..af7e1a24 100644 --- a/tests/Operations/AddViewOperationTests.cs +++ b/tests/Operations/AddViewOperationTests.cs @@ -13,8 +13,20 @@ internal class AddViewOperationTests : Tests { private static Type[] SupportedViewTypes { get; } = ViewFactory.SupportedViewTypes // Add MenuBar last so order is preserved in Assert check. .OrderBy( t => t == typeof( MenuBar ) ? int.MaxValue : 0 ) + .Select(PickFirstTTypeForGenerics) .ToArray( ); + private static Type PickFirstTTypeForGenerics(Type type) + { + if (type.IsGenericTypeDefinition) + { + var tType = ViewFactory.GetSupportedTTypesForGenericViewOfType(type).First(); + return type.MakeGenericType(tType); + } + + return type; + } + [Test( Description = "Tests AddViewOperation against all SupportedViewTypes" )] public void Do_AddsExpectedSubview( [ValueSource( nameof( SupportedViewTypes ) )] Type candidateType ) { diff --git a/tests/ViewFactoryTests.cs b/tests/ViewFactoryTests.cs index 39d43cb1..d17ff8c1 100644 --- a/tests/ViewFactoryTests.cs +++ b/tests/ViewFactoryTests.cs @@ -271,7 +271,6 @@ private static Type[] KnownUnsupportedTypes_ExpectedTypes( ) typeof( OpenDialog ), typeof( ScrollBarView ), typeof( TreeView<> ), - typeof( Slider<> ), typeof( Frame ), typeof( Wizard ), typeof( WizardStep ) From 74eea2aba7344d3677f3f71f7f5c867a4de63c56 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 1 Jan 2024 10:47:46 +0000 Subject: [PATCH 25/28] Move up/down/edit/delete support for ArrayEditor --- src/UI/ValueFactory.cs | 2 +- src/UI/Windows/ArrayEditor.Designer.cs | 56 ++++++++++++++++- src/UI/Windows/ArrayEditor.cs | 86 ++++++++++++++++++++++++++ src/UI/Windows/SliderOptionEditor.cs | 26 +++++++- 4 files changed, 165 insertions(+), 5 deletions(-) diff --git a/src/UI/ValueFactory.cs b/src/UI/ValueFactory.cs index b4547e4a..1e4935e9 100644 --- a/src/UI/ValueFactory.cs +++ b/src/UI/ValueFactory.cs @@ -17,7 +17,7 @@ internal static bool GetNewValue(string propertyName, Design design, Type type, if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(SliderOption<>)) { - var designer = new SliderOptionEditor(type.GetGenericArguments()[0]); + var designer = new SliderOptionEditor(type.GetGenericArguments()[0], oldValue); Application.Run(designer); if (!designer.Cancelled) diff --git a/src/UI/Windows/ArrayEditor.Designer.cs b/src/UI/Windows/ArrayEditor.Designer.cs index 0d8854df..b4820434 100644 --- a/src/UI/Windows/ArrayEditor.Designer.cs +++ b/src/UI/Windows/ArrayEditor.Designer.cs @@ -11,6 +11,8 @@ namespace TerminalGuiDesigner.UI.Windows { using System; using Terminal.Gui; + using System.Collections; + using System.Collections.Generic; public partial class ArrayEditor : Terminal.Gui.Dialog { @@ -21,16 +23,28 @@ public partial class ArrayEditor : Terminal.Gui.Dialog { private Terminal.Gui.Button btnAddElement; + private Terminal.Gui.Button btnDelete; + + private Terminal.Gui.Button btnMoveUp; + + private Terminal.Gui.Button btnMoveDown; + + private Terminal.Gui.Button btnEdit; + private Terminal.Gui.LineView lineView; private Terminal.Gui.Button btnOk; private Terminal.Gui.Button btnCancel; - + private void InitializeComponent() { this.btnCancel = new Terminal.Gui.Button(); this.btnOk = new Terminal.Gui.Button(); this.lineView = new Terminal.Gui.LineView(); + this.btnEdit = new Terminal.Gui.Button(); + this.btnMoveDown = new Terminal.Gui.Button(); + this.btnMoveUp = new Terminal.Gui.Button(); + this.btnDelete = new Terminal.Gui.Button(); this.btnAddElement = new Terminal.Gui.Button(); this.lvElements = new Terminal.Gui.ListView(); this.frameView = new Terminal.Gui.FrameView(); @@ -75,6 +89,46 @@ private void InitializeComponent() { this.btnAddElement.TextAlignment = Terminal.Gui.TextAlignment.Centered; this.btnAddElement.IsDefault = false; this.Add(this.btnAddElement); + this.btnDelete.Width = 8; + this.btnDelete.Height = 1; + this.btnDelete.X = 9; + this.btnDelete.Y = Pos.AnchorEnd(3); + this.btnDelete.Visible = true; + this.btnDelete.Data = "btnDelete"; + this.btnDelete.Text = "Delete"; + this.btnDelete.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.btnDelete.IsDefault = false; + this.Add(this.btnDelete); + this.btnMoveUp.Width = 8; + this.btnMoveUp.Height = 1; + this.btnMoveUp.X = 20; + this.btnMoveUp.Y = Pos.AnchorEnd(3); + this.btnMoveUp.Visible = true; + this.btnMoveUp.Data = "btnMoveUp"; + this.btnMoveUp.Text = "Move Up"; + this.btnMoveUp.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.btnMoveUp.IsDefault = false; + this.Add(this.btnMoveUp); + this.btnMoveDown.Width = 8; + this.btnMoveDown.Height = 1; + this.btnMoveDown.X = 32; + this.btnMoveDown.Y = Pos.AnchorEnd(3); + this.btnMoveDown.Visible = true; + this.btnMoveDown.Data = "btnMoveDown"; + this.btnMoveDown.Text = "Move Down"; + this.btnMoveDown.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.btnMoveDown.IsDefault = false; + this.Add(this.btnMoveDown); + this.btnEdit.Width = 8; + this.btnEdit.Height = 1; + this.btnEdit.X = 46; + this.btnEdit.Y = Pos.AnchorEnd(3); + this.btnEdit.Visible = true; + this.btnEdit.Data = "btnEdit"; + this.btnEdit.Text = "Edit"; + this.btnEdit.TextAlignment = Terminal.Gui.TextAlignment.Centered; + this.btnEdit.IsDefault = false; + this.Add(this.btnEdit); this.lineView.Width = Dim.Fill(1); this.lineView.Height = 1; this.lineView.X = -1; diff --git a/src/UI/Windows/ArrayEditor.cs b/src/UI/Windows/ArrayEditor.cs index 7fce42b2..2381cff9 100644 --- a/src/UI/Windows/ArrayEditor.cs +++ b/src/UI/Windows/ArrayEditor.cs @@ -41,9 +41,74 @@ public ArrayEditor(Design design, Type elementType, IList oldValue) { } lvElements.SetSource(Result); + lvElements.KeyUp += LvElements_KeyUp; btnOk.Clicked += BtnOk_Clicked; btnCancel.Clicked += BtnCancel_Clicked; btnAddElement.Clicked += BtnAddElement_Clicked; + btnDelete.Clicked += (s, e) => DeleteSelectedItem(); + btnMoveDown.Clicked += BtnMoveDown_Clicked; + btnMoveUp.Clicked += BtnMoveUp_Clicked; + btnEdit.Clicked += BtnEdit_Clicked; + } + + + private void BtnMoveUp_Clicked(object sender, EventArgs e) + { + // Moving up means reducing the index by 1 + var idx = lvElements.SelectedItem; + + if (idx >= 1 && idx < Result.Count) + { + var toMove = Result[idx]; + var newIndex = idx - 1; + Result.RemoveAt(idx); + Result.Insert(newIndex, toMove); + + lvElements.SetSource(Result); + lvElements.SelectedItem = newIndex; + lvElements.SetNeedsDisplay(); + } + } + + private void BtnMoveDown_Clicked(object sender, EventArgs e) + { + // Moving up means increasing the index by 1 + var idx = lvElements.SelectedItem; + + if (idx >= 0 && idx < Result.Count-1) + { + var toMove = Result[idx]; + var newIndex = idx + 1; + Result.RemoveAt(idx); + Result.Insert(newIndex, toMove); + + lvElements.SetSource(Result); + lvElements.SelectedItem = newIndex; + lvElements.SetNeedsDisplay(); + } + } + + private void LvElements_KeyUp(object sender, Key e) + { + if(e == Key.DeleteChar) + { + DeleteSelectedItem(); + e.Handled = true; + } + } + + private void DeleteSelectedItem() + { + var idx = lvElements.SelectedItem; + + if (idx >= 0 && idx < Result.Count) + { + Result.RemoveAt(idx); + + lvElements.SetSource(Result); + lvElements.SetNeedsDisplay(); + lvElements.SelectedItem = 0; + } } private void BtnAddElement_Clicked(object sender, EventArgs e) @@ -54,8 +119,29 @@ private void BtnAddElement_Clicked(object sender, EventArgs e) } lvElements.SetSource(Result); + lvElements.SelectedItem = Result.Count - 1; lvElements.SetNeedsDisplay(); } + private void BtnEdit_Clicked(object sender, EventArgs e) + { + var idx = lvElements.SelectedItem; + + if (idx >= 0 && idx < Result.Count) + { + var toEdit = Result[idx]; + + if (ValueFactory.GetNewValue("Element Value", design, this.elementType, toEdit, out var newValue, true)) + { + // Replace old with new + Result.RemoveAt(idx); + Result.Insert(idx, newValue); + } + + lvElements.SetSource(Result); + lvElements.SelectedItem = idx; + lvElements.SetNeedsDisplay(); + } + } private void BtnCancel_Clicked(object sender, EventArgs e) { diff --git a/src/UI/Windows/SliderOptionEditor.cs b/src/UI/Windows/SliderOptionEditor.cs index b338f1b8..393826d5 100644 --- a/src/UI/Windows/SliderOptionEditor.cs +++ b/src/UI/Windows/SliderOptionEditor.cs @@ -15,6 +15,7 @@ namespace TerminalGuiDesigner.UI.Windows { public partial class SliderOptionEditor { private readonly Type genericTypeArgument; + private readonly Type sliderOptionType; public bool Cancelled { get; internal set; } = true; public object Result { get; internal set; } @@ -24,15 +25,36 @@ public partial class SliderOptionEditor { /// where T is of . /// /// The T Type of the you want to design - public SliderOptionEditor(Type genericTypeArgument) { + public SliderOptionEditor(Type genericTypeArgument, object? oldValue) { InitializeComponent(); this.genericTypeArgument = genericTypeArgument; + this.sliderOptionType = typeof(SliderOption<>).MakeGenericType(this.genericTypeArgument); btnOk.Clicked += BtnOk_Clicked; btnCancel.Clicked += BtnCancel_Clicked; lblType.Text = $"({genericTypeArgument.Name})"; + + if(oldValue != null) + { + var p = sliderOptionType.GetProperty("Legend"); + tfLegend.Text = (string)p.GetValue(oldValue); + + p = sliderOptionType.GetProperty("LegendAbbr"); + tfLegendAbbr.Text = ((Rune)p.GetValue(oldValue)).ToString(); + + p = sliderOptionType.GetProperty("Data"); + + if (this.genericTypeArgument == typeof(string)) + { + tfData.Text = (string)p.GetValue(oldValue); + } + else + { + tfData.Text = p.GetValue(oldValue)?.ToString() ?? ""; + } + } } private void BtnCancel_Clicked(object sender, EventArgs e) @@ -59,8 +81,6 @@ private void BtnOk_Clicked(object sender, EventArgs e) private void BuildResult() { - - Type sliderOptionType = typeof(SliderOption<>).MakeGenericType(this.genericTypeArgument); Result = Activator.CreateInstance(sliderOptionType); var p = sliderOptionType.GetProperty("Legend"); From 74bf7ec0645679d350ac60f9a18bc83b50f50985 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 1 Jan 2024 10:53:51 +0000 Subject: [PATCH 26/28] Test fixes --- tests/Operations/AddViewOperationTests.cs | 11 +---------- tests/Tests.cs | 11 +++++++++++ tests/ViewFactoryTests.cs | 7 ++++++- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/Operations/AddViewOperationTests.cs b/tests/Operations/AddViewOperationTests.cs index af7e1a24..e42f992c 100644 --- a/tests/Operations/AddViewOperationTests.cs +++ b/tests/Operations/AddViewOperationTests.cs @@ -16,16 +16,7 @@ internal class AddViewOperationTests : Tests .Select(PickFirstTTypeForGenerics) .ToArray( ); - private static Type PickFirstTTypeForGenerics(Type type) - { - if (type.IsGenericTypeDefinition) - { - var tType = ViewFactory.GetSupportedTTypesForGenericViewOfType(type).First(); - return type.MakeGenericType(tType); - } - - return type; - } + [Test( Description = "Tests AddViewOperation against all SupportedViewTypes" )] public void Do_AddsExpectedSubview( [ValueSource( nameof( SupportedViewTypes ) )] Type candidateType ) diff --git a/tests/Tests.cs b/tests/Tests.cs index 9bbf0f4a..39f1b116 100644 --- a/tests/Tests.cs +++ b/tests/Tests.cs @@ -156,4 +156,15 @@ protected static void MouseDrag(Design root, int x1, int y1, int x2, int y2) Flags = MouseFlags.Button1Released, }, root); } + + public static Type PickFirstTTypeForGenerics(Type type) + { + if (type.IsGenericTypeDefinition) + { + var tType = ViewFactory.GetSupportedTTypesForGenericViewOfType(type).First(); + return type.MakeGenericType(tType); + } + + return type; + } } \ No newline at end of file diff --git a/tests/ViewFactoryTests.cs b/tests/ViewFactoryTests.cs index d17ff8c1..d0f9c4dd 100644 --- a/tests/ViewFactoryTests.cs +++ b/tests/ViewFactoryTests.cs @@ -40,7 +40,12 @@ public virtual void TearDown() /// private static IEnumerable Create_And_CreateT_Type_Provider { - get { return ViewFactory_SupportedViewTypes.Select( static t => new TestCaseData( RuntimeHelpers.GetUninitializedObject( t ) ) ); } + get { return ViewFactory_SupportedViewTypes + .Select(Tests.PickFirstTTypeForGenerics) + .Select( + static t => new TestCaseData( + RuntimeHelpers.GetUninitializedObject( t ) + ) ); } } private static MenuBarItem[] ViewFactory_DefaultMenuBarItems => ViewFactory.DefaultMenuBarItems; From cf171925dde8d9fa2c618c533e6f33135c6324b6 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 1 Jan 2024 11:10:41 +0000 Subject: [PATCH 27/28] Make Create Slider call generic method Create --- src/ViewFactory.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ViewFactory.cs b/src/ViewFactory.cs index 94713d32..ac906cd7 100644 --- a/src/ViewFactory.cs +++ b/src/ViewFactory.cs @@ -259,6 +259,14 @@ static void SetDefaultDimensions( T v, int width = 5, int height = 1 ) [Obsolete( "Migrate to using generic Create method" )] public static View Create( Type requestedType ) { + if (requestedType.IsGenericType) + { + var method = typeof(ViewFactory).GetMethods().Single(m=>m.Name=="Create" && m.IsGenericMethodDefinition); + method = method.MakeGenericMethod(requestedType) ?? throw new Exception("Could not find Create method on ViewFactory"); + + return (View)(method.Invoke(null, new object?[] { null, null, null }) ?? throw new Exception("ViewFactory.Create resulted in null")); + } + return requestedType switch { null => throw new ArgumentNullException( nameof( requestedType ) ), From fe346fe2e1da6dc8eb24caed4c0190fc42608c97 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 1 Jan 2024 11:31:00 +0000 Subject: [PATCH 28/28] Mark slider supported --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7be8ef0..c3e426cd 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ italics are experimental and require passing the `-e` flag when starting applica - [x] TimeField - [x] TreeView - [x] View - - [ ] Slider + - [x] Slider ### Class Diagram -------------------------------