Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SuppressedProperty Visible #228

Merged
merged 1 commit into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Design.cs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,8 @@ private IEnumerable<Property> 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);
tznind marked this conversation as resolved.
Show resolved Hide resolved

yield return new ColorSchemeProperty(this);

// its important that this comes before Text because
Expand Down Expand Up @@ -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
Expand Down
111 changes: 62 additions & 49 deletions src/ToCode/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,55 +95,7 @@ public Property(Design design, PropertyInfo property, string subProperty, object
/// <exception cref="ArgumentException">Thrown if invalid values are passed.</exception>
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
Expand Down Expand Up @@ -376,6 +328,67 @@ protected virtual string GetHumanReadableValue()
return val.ToString() ?? string.Empty;
}

/// <summary>
/// Adjust <paramref name="value"/> to match the expectations of <see cref="PropertyInfo"/>
/// e.g. convert char to <see cref="Rune"/>.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
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;
}

/// <summary>
/// Calls any methods that update the state of the View
/// and refresh it against its style e.g. <see cref="TableView.Update"/>.
Expand Down
55 changes: 55 additions & 0 deletions src/ToCode/SuppressedProperty.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// A <see cref="Property"/> whose value is stored but does not manifest within
/// the editor because it would make design time operation difficult e.g. <see cref="View.Visible"/>.
/// </summary>
public class SuppressedProperty : Property
{
private object? value;

/// <inheritdoc cref="Property(Design, PropertyInfo)"/>
public SuppressedProperty(Design design, PropertyInfo property, object? designTimeValue)
: base(design, property)
{
this.StoreInitialValueButUse(designTimeValue);
}

/// <inheritdoc cref="Property(Design, PropertyInfo, string, object)"/>
public SuppressedProperty(Design design, PropertyInfo property, string subProperty, object declaringObject, object? designTimeValue)
: base(design, property, subProperty, declaringObject)
{
this.StoreInitialValueButUse(designTimeValue);
}

/// <inheritdoc/>
public override object? GetValue()
{
return this.value;
}

/// <inheritdoc/>
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);
}
}
48 changes: 48 additions & 0 deletions tests/VisiblePropertyTests.cs
Original file line number Diff line number Diff line change
@@ -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<Window, Label>((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);
}
}
}
Loading