diff --git a/src/Design.cs b/src/Design.cs index fb9e63fa..e271ede3 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -625,6 +625,8 @@ private IEnumerable LoadDesignableProperties() yield return this.CreateProperty(nameof(this.View.X)); yield return this.CreateProperty(nameof(this.View.Y)); + yield return this.CreateSuppressedProperty(nameof(this.View.Visible), true); + yield return new ColorSchemeProperty(this); // its important that this comes before Text because @@ -809,6 +811,14 @@ private Property CreateProperty(string name) this.View.GetType().GetProperty(name) ?? throw new Exception($"Could not find expected Property '{name}' on View of Type '{this.View.GetType()}'")); } + private Property CreateSuppressedProperty(string name, object? designTimeValue) + { + return new SuppressedProperty( + this, + this.View.GetType().GetProperty(name) ?? throw new Exception($"Could not find expected Property '{name}' on View of Type '{this.View.GetType()}'"), + designTimeValue); + } + private bool DependsOnUs(Design other, Design[] everyone) { // obviously we cannot depend on ourselves diff --git a/src/ToCode/Property.cs b/src/ToCode/Property.cs index 0c1e3c3e..abe46471 100644 --- a/src/ToCode/Property.cs +++ b/src/ToCode/Property.cs @@ -95,55 +95,7 @@ public Property(Design design, PropertyInfo property, string subProperty, object /// Thrown if invalid values are passed. public virtual void SetValue(object? value) { - // handle type conversions - if (this.PropertyInfo.PropertyType == typeof(Rune)) - { - if (value is char ch) - { - value = new Rune(ch); - } - } - - if (this.PropertyInfo.PropertyType == typeof(Dim)) - { - if (value is int i) - { - value = Dim.Sized(i); - } - } - - if (this.PropertyInfo.PropertyType == typeof(ustring)) - { - if (value is string s) - { - value = ustring.Make(s); - - // TODO: This seems like something AutoSize should do automatically - // if renaming a button update its size to match - if (this.Design.View is Button b && this.PropertyInfo.Name.Equals("Text") && b.Width.IsAbsolute()) - { - b.Width = s.Length + (b.IsDefault ? 6 : 4); - } - } - - // some views don't like null and only work with "" e.g. TextView - // see https://github.com/gui-cs/TerminalGuiDesigner/issues/91 - if (value == null) - { - value = ustring.Make(string.Empty); - } - } - - if (this.PropertyInfo.PropertyType == typeof(IListDataSource)) - { - if (value != null && value is Array a) - { - // accept arrays as valid input values - // for setting an IListDataSource. Just - // convert them to ListWrappers - value = new ListWrapper(a.ToList()); - } - } + value = AdjustValueBeingSet(value); // TODO: This hack gets around an ArgumentException that gets thrown when // switching from Computed to Absolute values of Dim/Pos @@ -376,6 +328,67 @@ protected virtual string GetHumanReadableValue() return val.ToString() ?? string.Empty; } + /// + /// Adjust to match the expectations of + /// e.g. convert char to . + /// + /// + /// + protected object? AdjustValueBeingSet(object? value) + { + // handle type conversions + if (this.PropertyInfo.PropertyType == typeof(Rune)) + { + if (value is char ch) + { + value = new Rune(ch); + } + } + + if (this.PropertyInfo.PropertyType == typeof(Dim)) + { + if (value is int i) + { + value = Dim.Sized(i); + } + } + + if (this.PropertyInfo.PropertyType == typeof(ustring)) + { + if (value is string s) + { + value = ustring.Make(s); + + // TODO: This seems like something AutoSize should do automatically + // if renaming a button update its size to match + if (this.Design.View is Button b && this.PropertyInfo.Name.Equals("Text") && b.Width.IsAbsolute()) + { + b.Width = s.Length + (b.IsDefault ? 6 : 4); + } + } + + // some views don't like null and only work with "" e.g. TextView + // see https://github.com/gui-cs/TerminalGuiDesigner/issues/91 + if (value == null) + { + value = ustring.Make(string.Empty); + } + } + + if (this.PropertyInfo.PropertyType == typeof(IListDataSource)) + { + if (value != null && value is Array a) + { + // accept arrays as valid input values + // for setting an IListDataSource. Just + // convert them to ListWrappers + value = new ListWrapper(a.ToList()); + } + } + + return value; + } + /// /// Calls any methods that update the state of the View /// and refresh it against its style e.g. . diff --git a/src/ToCode/SuppressedProperty.cs b/src/ToCode/SuppressedProperty.cs new file mode 100644 index 00000000..8867bfc1 --- /dev/null +++ b/src/ToCode/SuppressedProperty.cs @@ -0,0 +1,55 @@ +using System.CodeDom; +using System.Reflection; +using NStack; +using Terminal.Gui; +using Terminal.Gui.Graphs; +using Terminal.Gui.TextValidateProviders; +using TerminalGuiDesigner; +using static Terminal.Gui.TableView; +using Attribute = Terminal.Gui.Attribute; + +namespace TerminalGuiDesigner.ToCode; + +/// +/// A whose value is stored but does not manifest within +/// the editor because it would make design time operation difficult e.g. . +/// +public class SuppressedProperty : Property +{ + private object? value; + + /// + public SuppressedProperty(Design design, PropertyInfo property, object? designTimeValue) + : base(design, property) + { + this.StoreInitialValueButUse(designTimeValue); + } + + /// + public SuppressedProperty(Design design, PropertyInfo property, string subProperty, object declaringObject, object? designTimeValue) + : base(design, property, subProperty, declaringObject) + { + this.StoreInitialValueButUse(designTimeValue); + } + + /// + public override object? GetValue() + { + return this.value; + } + + /// + public override void SetValue(object? value) + { + this.value = this.AdjustValueBeingSet(value); + } + + private void StoreInitialValueButUse(object? designTimeValue) + { + // Pull the compiled/created value from the view (e.g. Visible=false) + this.value = base.GetValue(); + + // But immediately override with the design time view (e.g. Visible=true) + base.SetValue(designTimeValue); + } +} diff --git a/tests/VisiblePropertyTests.cs b/tests/VisiblePropertyTests.cs new file mode 100644 index 00000000..371d71b7 --- /dev/null +++ b/tests/VisiblePropertyTests.cs @@ -0,0 +1,48 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Terminal.Gui; +using TerminalGuiDesigner.ToCode; +using TerminalGuiDesigner; + +namespace UnitTests +{ + class VisiblePropertyTests : Tests + { + [Test] + public void TestSettingVisible_False() + { + var result = RoundTrip((d, v) => + { + // In Designer + + var prop = d.GetDesignableProperties().Single( + p => p.PropertyInfo.Name.Equals(nameof(View.Visible)) + ); + + // Should start off visible + Assert.AreEqual(true, prop.GetValue()); + Assert.True(v.Visible); + + prop.SetValue(false); + + // Prop should know it is not visible + Assert.AreEqual(false, prop.GetValue()); + // But View in editor should remain visible so that it can be clicked on etc + Assert.True(v.Visible); + + },out _); + + // After reloading Designer + var d = (Design)result.Data; + var prop = d.GetDesignableProperties().Single(p => p.PropertyInfo.Name.Equals(nameof(View.Visible))); + + Assert.AreEqual(false, prop.GetValue()); + Assert.True(result.Visible); + } + } +}