Skip to content

Commit

Permalink
Merge pull request #271 from gui-cs/add-generic-views-support
Browse files Browse the repository at this point in the history
Add support for `Slider<T>`
  • Loading branch information
tznind committed Jan 1, 2024
2 parents 08e7981 + fe346fe commit d187bdc
Show file tree
Hide file tree
Showing 24 changed files with 1,255 additions and 370 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------------------------
Expand Down
19 changes: 18 additions & 1 deletion src/Design.cs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,9 @@ private void RegisterCheckboxDesignTimeChanges(CheckBox cb)

private IEnumerable<Property> 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));

Expand All @@ -662,13 +665,27 @@ private IEnumerable<Property> LoadDesignableProperties()
yield return this.CreateProperty(nameof(TextField.Secret));
}

if (isGenericType && viewType.GetGenericTypeDefinition() == typeof(Slider<>))
{
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)
{
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)
Expand Down
16 changes: 8 additions & 8 deletions src/DimExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 9 additions & 4 deletions src/FromCode/CodeToView.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -153,11 +155,14 @@ 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",
new CSharpSyntaxTree[] { csTree, designerTree },
Expand Down
26 changes: 25 additions & 1 deletion src/Operations/AddViewOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,22 @@ 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)
{
var allowedTTypes = ViewFactory.GetSupportedTTypesForGenericViewOfType(selected).ToArray();

if(Modals.Get("Enter a Type for <T>", "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);

Check warning on line 97 in src/Operations/AddViewOperation.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

'ViewFactory.Create(Type)' is obsolete: 'Migrate to using generic Create<T> method'
this.fieldName = this.to.GetUniqueFieldName(selected);
}
Expand Down Expand Up @@ -108,6 +122,16 @@ protected override bool DoImpl()
return true;
}

private string TypeNameDelegate(Type? t)
{
if (t == null)
{
return "Null";
}

return t.Name.Replace("`1", "<T>");
}

private View GetViewToAddTo()
{
if (this.to.View is TabView tabView)
Expand Down
30 changes: 15 additions & 15 deletions src/PosExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/TerminalGuiDesigner.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
<PackageReference Include="nlog" Version="5.2.7" />
<PackageReference Include="Terminal.Gui" Version="2.0.0-pre.243" />
<PackageReference Include="Terminal.Gui" Version="2.0.0-pre.250" />
<PackageReference Include="Basic.Reference.Assemblies.Net80" Version="1.4.5" />
<PackageReference Include="System.CodeDom" Version="8.0.0" />
</ItemGroup>
Expand Down
43 changes: 42 additions & 1 deletion src/ToCode/Property.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.CodeDom;
using System.Collections;
using System.Reflection;
using System.Text;
using Terminal.Gui;
using Terminal.Gui.TextValidateProviders;
using TerminalGuiDesigner;
using YamlDotNet.Core.Tokens;
using Attribute = Terminal.Gui.Attribute;

namespace TerminalGuiDesigner.ToCode;
Expand Down Expand Up @@ -177,7 +179,7 @@ public virtual CodeExpression GetRhs()
else
{
throw new Exception($"Unexpected unicode character size. Rune was {rune}");
}
}
}

if (val is Attribute attribute)
Expand Down Expand Up @@ -263,9 +265,48 @@ public virtual CodeExpression GetRhs()
values.Select(v => v.ToCodePrimitiveExpression()).ToArray());
}

if (val is IList valList)
{
var elementType = type.GetElementTypeEx()
?? 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<object>().Select(this.ValueFactory).ToArray())
);
}

return val.ToCodePrimitiveExpression();
}

private CodeExpression ValueFactory(object val)
{

var type = val.GetType();

// 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<object>.Legend)).GetValue(val);

Check warning on line 293 in src/ToCode/Property.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.
var a2 = (Rune)type.GetProperty(nameof(SliderOption<object>.LegendAbbr)).GetValue(val);

Check warning on line 294 in src/ToCode/Property.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.

Check warning on line 294 in src/ToCode/Property.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Unboxing a possibly null value.
var a3 = type.GetProperty(nameof(SliderOption<object>.Data)).GetValue(val);

Check warning on line 295 in src/ToCode/Property.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.


return new CodeObjectCreateExpression(
new CodeTypeReference(val.GetType()),
new CodePrimitiveExpression(a1),
new CodeObjectCreateExpression(typeof(Rune),new CodePrimitiveExpression(a2.ToString()[0])),
new CodePrimitiveExpression(a3));
}
else
{
throw new NotSupportedException($"Cannot generate code for value '{val}'");
}
}

/// <summary>
/// Gets a CodeDOM code block for the left hand side of an assignment operation e.g.:
/// <code>this.label1.Text</code>
Expand Down
2 changes: 2 additions & 0 deletions src/ToCode/ViewToCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
34 changes: 34 additions & 0 deletions src/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Implementation of <see cref="Type.GetElementType"/> that also works for
/// <see cref="IList{T}"/>
/// </summary>
/// <returns>Element type of collection or <see langword="null"/>.</returns>
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;
}
}
}
2 changes: 1 addition & 1 deletion src/UI/Editor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit d187bdc

Please sign in to comment.