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

@ -8,8 +8,8 @@ public interface IRenderHook
/// <summary>
/// Processes the specified renderables.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="options">The render options.</param>
/// <param name="renderables">The renderables to process.</param>
/// <returns>The processed renderables.</returns>
IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables);
IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables);
}

View File

@ -10,13 +10,13 @@ public abstract class JustInTimeRenderable : Renderable
private IRenderable? _rendered;
/// <inheritdoc/>
protected sealed override Measurement Measure(RenderContext context, int maxWidth)
protected sealed override Measurement Measure(RenderOptions context, int maxWidth)
{
return GetInner().Measure(context, maxWidth);
}
/// <inheritdoc/>
protected sealed override IEnumerable<Segment> Render(RenderContext context, int width)
protected sealed override IEnumerable<Segment> Render(RenderOptions context, int width)
{
return GetInner().Render(context, width);
}

View File

@ -1,78 +0,0 @@
namespace Spectre.Console.Rendering;
/// <summary>
/// Represents a render context.
/// </summary>
public sealed class RenderContext
{
private readonly IReadOnlyCapabilities _capabilities;
/// <summary>
/// Gets the current color system.
/// </summary>
public ColorSystem ColorSystem => _capabilities.ColorSystem;
/// <summary>
/// Gets a value indicating whether or not VT/Ansi codes are supported.
/// </summary>
public bool Ansi => _capabilities.Ansi;
/// <summary>
/// Gets a value indicating whether or not unicode is supported.
/// </summary>
public bool Unicode => _capabilities.Unicode;
/// <summary>
/// Gets the current justification.
/// </summary>
public Justify? Justification { get; }
/// <summary>
/// Gets a value indicating whether the context want items to render without
/// line breaks and return a single line where applicable.
/// </summary>
internal bool SingleLine { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RenderContext"/> class.
/// </summary>
/// <param name="capabilities">The capabilities.</param>
/// <param name="justification">The justification.</param>
public RenderContext(IReadOnlyCapabilities capabilities, Justify? justification = null)
: this(capabilities, justification, false)
{
}
private RenderContext(IReadOnlyCapabilities capabilities, Justify? justification = null, bool singleLine = false)
{
_capabilities = capabilities ?? throw new ArgumentNullException(nameof(capabilities));
Justification = justification;
SingleLine = singleLine;
}
/// <summary>
/// Creates a new context with the specified justification.
/// </summary>
/// <param name="justification">The justification.</param>
/// <returns>A new <see cref="RenderContext"/> instance.</returns>
public RenderContext WithJustification(Justify? justification)
{
return new RenderContext(_capabilities, justification, SingleLine);
}
/// <summary>
/// Creates a new context that tell <see cref="IRenderable"/> instances
/// to not care about splitting things in new lines. Whether or not to
/// comply to the request is up to the item being rendered.
/// </summary>
/// <remarks>
/// Use with care since this has the potential to mess things up.
/// Only use this kind of context with items that you know about.
/// </remarks>
/// <returns>A new <see cref="RenderContext"/> instance.</returns>
internal RenderContext WithSingleLine()
{
return new RenderContext(_capabilities, Justification, true);
}
}

View File

@ -0,0 +1,63 @@
namespace Spectre.Console.Rendering;
/// <summary>
/// Represents render options.
/// </summary>
/// <param name="Capabilities">The capabilities.</param>
/// <param name="ConsoleSize">The console size.</param>
public record class RenderOptions(IReadOnlyCapabilities Capabilities, Size ConsoleSize)
{
/// <summary>
/// Gets the current color system.
/// </summary>
public ColorSystem ColorSystem => Capabilities.ColorSystem;
/// <summary>
/// Gets a value indicating whether or not VT/Ansi codes are supported.
/// </summary>
public bool Ansi => Capabilities.Ansi;
/// <summary>
/// Gets a value indicating whether or not unicode is supported.
/// </summary>
public bool Unicode => Capabilities.Unicode;
/// <summary>
/// Gets the current justification.
/// </summary>
public Justify? Justification { get; init; }
/// <summary>
/// Gets the requested height.
/// </summary>
public int? Height { get; init; }
/// <summary>
/// Gets a value indicating whether the context want items to render without
/// line breaks and return a single line where applicable.
/// </summary>
internal bool SingleLine { get; init; }
/// <summary>
/// Creates a <see cref="RenderOptions"/> instance from a <see cref="IAnsiConsole"/>.
/// </summary>
/// <param name="console">The console.</param>
/// <param name="capabilities">The capabilities, or <c>null</c> to use the provided console's capabilities.</param>
/// <returns>A <see cref="RenderOptions"/> representing the provided <see cref="IAnsiConsole"/>.</returns>
public static RenderOptions Create(IAnsiConsole console, IReadOnlyCapabilities? capabilities = null)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
return new RenderOptions(
capabilities ?? console.Profile.Capabilities,
new Size(console.Profile.Width, console.Profile.Height))
{
Justification = null,
Height = null,
SingleLine = false,
};
}
}

View File

@ -44,17 +44,17 @@ public sealed class RenderPipeline
/// <summary>
/// Processes the specified renderables.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="options">The render options.</param>
/// <param name="renderables">The renderables to process.</param>
/// <returns>The processed renderables.</returns>
public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
public IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables)
{
lock (_lock)
{
var current = renderables;
for (var index = _hooks.Count - 1; index >= 0; index--)
{
current = _hooks[index].Process(context, current);
current = _hooks[index].Process(options, current);
}
return current;

View File

@ -7,25 +7,25 @@ public abstract class Renderable : IRenderable
{
/// <inheritdoc/>
[DebuggerStepThrough]
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
Measurement IRenderable.Measure(RenderOptions options, int maxWidth)
{
return Measure(context, maxWidth);
return Measure(options, maxWidth);
}
/// <inheritdoc/>
[DebuggerStepThrough]
IEnumerable<Segment> IRenderable.Render(RenderContext context, int maxWidth)
IEnumerable<Segment> IRenderable.Render(RenderOptions options, int maxWidth)
{
return Render(context, maxWidth);
return Render(options, maxWidth);
}
/// <summary>
/// Measures the renderable object.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="options">The render options.</param>
/// <param name="maxWidth">The maximum allowed width.</param>
/// <returns>The minimum and maximum width of the object.</returns>
protected virtual Measurement Measure(RenderContext context, int maxWidth)
protected virtual Measurement Measure(RenderOptions options, int maxWidth)
{
return new Measurement(maxWidth, maxWidth);
}
@ -33,8 +33,8 @@ public abstract class Renderable : IRenderable
/// <summary>
/// Renders the object.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="options">The render options.</param>
/// <param name="maxWidth">The maximum allowed width.</param>
/// <returns>A collection of segments.</returns>
protected abstract IEnumerable<Segment> Render(RenderContext context, int maxWidth);
protected abstract IEnumerable<Segment> Render(RenderOptions options, int maxWidth);
}

View File

@ -201,8 +201,9 @@ public class Segment
/// </summary>
/// <param name="segments">The segments to split into lines.</param>
/// <param name="maxWidth">The maximum width.</param>
/// <param name="height">The height (if any).</param>
/// <returns>A list of lines.</returns>
public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments, int maxWidth)
public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments, int maxWidth, int? height = null)
{
if (segments is null)
{
@ -294,6 +295,25 @@ public class Segment
lines.Add(line);
}
// Got a height specified?
if (height != null)
{
if (lines.Count >= height)
{
// Remove lines
lines.RemoveRange(height.Value, lines.Count - height.Value);
}
else
{
// Add lines
var missing = height - lines.Count;
for (var i = 0; i < missing; i++)
{
lines.Add(new SegmentLine());
}
}
}
return lines;
}
@ -549,6 +569,21 @@ public class Segment
return cells;
}
internal static List<SegmentLine> MakeWidth(int expectedWidth, List<SegmentLine> lines)
{
foreach (var line in lines)
{
var width = line.CellCount();
if (width < expectedWidth)
{
var diff = expectedWidth - width;
line.Add(new Segment(new string(' ', diff)));
}
}
return lines;
}
internal static List<string> SplitSegment(string text, int maxCellLength)
{
var list = new List<string>();

View File

@ -11,13 +11,8 @@ internal readonly struct SegmentShape
Height = height;
}
public static SegmentShape Calculate(RenderContext context, List<SegmentLine> lines)
public static SegmentShape Calculate(RenderOptions options, List<SegmentLine> lines)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (lines is null)
{
throw new ArgumentNullException(nameof(lines));
@ -36,7 +31,7 @@ internal readonly struct SegmentShape
Math.Max(Height, other.Height));
}
public void Apply(RenderContext context, ref List<SegmentLine> lines)
public void Apply(RenderOptions options, ref List<SegmentLine> lines)
{
foreach (var line in lines)
{