Add Layout widget (#1041)

* Add width to panels
* Add height to panels
* Replace RenderContext with RenderOptions
* Remove exclusivity from alternative buffer
* Add Layout widget
* Add Align widget
This commit is contained in:
Patrik Svensson
2022-11-15 10:12:17 +01:00
committed by GitHub
parent 9ce3b99cd6
commit c3ec6a7363
137 changed files with 2651 additions and 387 deletions

View File

@@ -89,4 +89,51 @@ internal static class Aligner
throw new NotSupportedException("Unknown alignment");
}
}
public static void AlignHorizontally<T>(T segments, HorizontalAlignment alignment, int maxWidth)
where T : List<Segment>
{
var width = Segment.CellCount(segments);
if (width >= maxWidth)
{
return;
}
switch (alignment)
{
case HorizontalAlignment.Left:
{
var diff = maxWidth - width;
segments.Add(Segment.Padding(diff));
break;
}
case HorizontalAlignment.Right:
{
var diff = maxWidth - width;
segments.Insert(0, Segment.Padding(diff));
break;
}
case HorizontalAlignment.Center:
{
// Left side.
var diff = (maxWidth - width) / 2;
segments.Insert(0, Segment.Padding(diff));
// Right side
segments.Add(Segment.Padding(diff));
var remainder = (maxWidth - width) % 2;
if (remainder != 0)
{
segments.Add(Segment.Padding(remainder));
}
break;
}
default:
throw new NotSupportedException("Unknown alignment");
}
}
}

View File

@@ -0,0 +1,22 @@
namespace Spectre.Console;
/// <summary>
/// Represents something that can be used to resolve ratios.
/// </summary>
internal interface IRatioResolvable
{
/// <summary>
/// Gets the ratio.
/// </summary>
int Ratio { get; }
/// <summary>
/// Gets the size.
/// </summary>
int? Size { get; }
/// <summary>
/// Gets the minimum size.
/// </summary>
int MinimumSize { get; }
}

View File

@@ -0,0 +1,15 @@
#if NETSTANDARD2_0
using System.ComponentModel;
namespace System.Runtime.CompilerServices
{
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit
{
}
}
#endif

View File

@@ -5,6 +5,78 @@ namespace Spectre.Console;
internal static class Ratio
{
public static List<int> Resolve(int total, IEnumerable<IRatioResolvable> edges)
{
static (int Div, float Mod) DivMod(float x, float y)
{
return ((int)(x / y), x % y);
}
static int? GetEdgeWidth(IRatioResolvable edge)
{
if (edge.Size != null && edge.Size < edge.MinimumSize)
{
return edge.MinimumSize;
}
return edge.Size;
}
var sizes = edges.Select(x => GetEdgeWidth(x)).ToArray();
while (sizes.Any(s => s == null))
{
// Get all edges and map them back to their index.
// Ignore edges which have a explicit size.
var flexibleEdges = sizes.Zip(edges, (a, b) => (Size: a, Edge: b))
.Enumerate()
.Select(x => (x.Index, x.Item.Size, x.Item.Edge))
.Where(x => x.Size == null)
.ToList();
// Get the remaining space
var remaining = total - sizes.Select(size => size ?? 0).Sum();
if (remaining <= 0)
{
// No more room for flexible edges.
return sizes
.Zip(edges, (size, edge) => (Size: size, Edge: edge))
.Select(zip => zip.Size ?? zip.Edge.MinimumSize)
.Select(size => size > 0 ? size : 1)
.ToList();
}
var portion = (float)remaining / flexibleEdges.Sum(x => Math.Max(1, x.Edge.Ratio));
var invalidate = false;
foreach (var (index, size, edge) in flexibleEdges)
{
if (portion * edge.Ratio <= edge.MinimumSize)
{
sizes[index] = edge.MinimumSize;
// New fixed size will invalidate calculations,
// so we need to repeat the process
invalidate = true;
break;
}
}
if (!invalidate)
{
var remainder = 0f;
foreach (var flexibleEdge in flexibleEdges)
{
var (div, mod) = DivMod((portion * flexibleEdge.Edge.Ratio) + remainder, 1);
remainder = mod;
sizes[flexibleEdge.Index] = div;
}
}
}
return sizes.Select(x => x ?? 1).ToList();
}
public static List<int> Reduce(int total, List<int> ratios, List<int> maximums, List<int> values)
{
ratios = ratios.Zip(maximums, (a, b) => (ratio: a, max: b)).Select(a => a.max > 0 ? a.ratio : 0).ToList();

View File

@@ -4,7 +4,7 @@ internal sealed class HtmlEncoder : IAnsiConsoleEncoder
{
public string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderables)
{
var context = new RenderContext(new EncoderCapabilities(ColorSystem.TrueColor));
var context = RenderOptions.Create(console, new EncoderCapabilities(ColorSystem.TrueColor));
var builder = new StringBuilder();
builder.Append("<pre style=\"font-size:90%;font-family:consolas,'Courier New',monospace\">\n");

View File

@@ -4,7 +4,7 @@ internal sealed class TextEncoder : IAnsiConsoleEncoder
{
public string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderables)
{
var context = new RenderContext(new EncoderCapabilities(ColorSystem.TrueColor));
var context = RenderOptions.Create(console, new EncoderCapabilities(ColorSystem.TrueColor));
var builder = new StringBuilder();
foreach (var renderable in renderables)