Restructure solution a bit

This commit is contained in:
Patrik Svensson 2020-09-09 08:43:48 +02:00
parent 003e15686f
commit 4f06687104
64 changed files with 221 additions and 199 deletions

View File

@ -42,7 +42,7 @@
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.113"> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.113">
<PrivateAssets>All</PrivateAssets> <PrivateAssets>All</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="2.3.0"> <PackageReference Include="Roslynator.Analyzers" Version="3.0.0">
<PrivateAssets>All</PrivateAssets> <PrivateAssets>All</PrivateAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>

View File

@ -49,7 +49,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel("Hello World") console.Render(new Panel("Hello World")
{ {
Header = new Header("Greeting"), Header = new PanelHeader("Greeting"),
Expand = true, Expand = true,
Padding = new Padding(2, 2), Padding = new Padding(2, 2),
}); });
@ -70,7 +70,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel("Hello World") console.Render(new Panel("Hello World")
{ {
Header = new Header("Greeting").LeftAligned(), Header = new PanelHeader("Greeting").LeftAligned(),
Expand = true, Expand = true,
}); });
@ -90,7 +90,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel("Hello World") console.Render(new Panel("Hello World")
{ {
Header = new Header("Greeting").Centered(), Header = new PanelHeader("Greeting").Centered(),
Expand = true, Expand = true,
}); });
@ -110,7 +110,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel("Hello World") console.Render(new Panel("Hello World")
{ {
Header = new Header("Greeting").RightAligned(), Header = new PanelHeader("Greeting").RightAligned(),
Expand = true, Expand = true,
}); });
@ -130,7 +130,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel("Hello World") console.Render(new Panel("Hello World")
{ {
Header = new Header("Greeting"), Header = new PanelHeader("Greeting"),
Expand = true, Expand = true,
}); });

View File

@ -5,7 +5,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Contains extension methods for <see cref="IHasBorder"/>. /// Contains extension methods for <see cref="IHasBorder"/>.
/// </summary> /// </summary>
public static class BorderExtensions public static class HasBorderExtensions
{ {
/// <summary> /// <summary>
/// Do not display a border. /// Do not display a border.

View File

@ -27,7 +27,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(text)); throw new ArgumentNullException(nameof(text));
} }
return SetHeader(panel, new Header(text, style, alignment)); return SetHeader(panel, new PanelHeader(text, style, alignment));
} }
/// <summary> /// <summary>
@ -36,7 +36,7 @@ namespace Spectre.Console
/// <param name="panel">The panel.</param> /// <param name="panel">The panel.</param>
/// <param name="header">The header to use.</param> /// <param name="header">The header to use.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static Panel SetHeader(this Panel panel, Header header) public static Panel SetHeader(this Panel panel, PanelHeader header)
{ {
if (panel is null) if (panel is null)
{ {

View File

@ -266,8 +266,8 @@ namespace Spectre.Console.Rendering
/// </summary> /// </summary>
/// <param name="segment">The segment to split.</param> /// <param name="segment">The segment to split.</param>
/// <param name="overflow">The overflow strategy to use.</param> /// <param name="overflow">The overflow strategy to use.</param>
/// <param name="encoding">The encodign to use.</param> /// <param name="encoding">The encoding to use.</param>
/// <param name="width">The maxiumum width.</param> /// <param name="width">The maximum width.</param>
/// <returns>A list of segments that has been split.</returns> /// <returns>A list of segments that has been split.</returns>
public static List<Segment> SplitOverflow(Segment segment, Overflow? overflow, Encoding encoding, int width) public static List<Segment> SplitOverflow(Segment segment, Overflow? overflow, Encoding encoding, int width)
{ {
@ -317,7 +317,7 @@ namespace Spectre.Console.Rendering
new Segment(text, style), new Segment(text, style),
Overflow.Ellipsis, Overflow.Ellipsis,
encoding, encoding,
maxWidth).First(); maxWidth)[0];
} }
internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells) internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)

View File

@ -3,20 +3,34 @@ using System.Collections.Generic;
namespace Spectre.Console.Rendering namespace Spectre.Console.Rendering
{ {
internal sealed class SegmentLineEnumerator : IEnumerable<Segment> /// <summary>
/// An enumerator for <see cref="SegmentLine"/> collections.
/// </summary>
public sealed class SegmentLineEnumerator : IEnumerable<Segment>
{ {
private readonly List<SegmentLine> _lines; private readonly List<SegmentLine> _lines;
public SegmentLineEnumerator(List<SegmentLine> lines) /// <summary>
/// Initializes a new instance of the <see cref="SegmentLineEnumerator"/> class.
/// </summary>
/// <param name="lines">The lines to enumerate.</param>
public SegmentLineEnumerator(IEnumerable<SegmentLine> lines)
{ {
_lines = lines; if (lines is null)
{
throw new System.ArgumentNullException(nameof(lines));
} }
_lines = new List<SegmentLine>(lines);
}
/// <inheritdoc/>
public IEnumerator<Segment> GetEnumerator() public IEnumerator<Segment> GetEnumerator()
{ {
return new SegmentLineIterator(_lines); return new SegmentLineIterator(_lines);
} }
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return GetEnumerator(); return GetEnumerator();

View File

@ -3,29 +3,48 @@ using System.Collections.Generic;
namespace Spectre.Console.Rendering namespace Spectre.Console.Rendering
{ {
internal sealed class SegmentLineIterator : IEnumerator<Segment> /// <summary>
/// An iterator for <see cref="SegmentLine"/> collections.
/// </summary>
public sealed class SegmentLineIterator : IEnumerator<Segment>
{ {
private readonly List<SegmentLine> _lines; private readonly List<SegmentLine> _lines;
private int _currentLine; private int _currentLine;
private int _currentIndex; private int _currentIndex;
private bool _lineBreakEmitted; private bool _lineBreakEmitted;
/// <summary>
/// Gets the current segment.
/// </summary>
public Segment Current { get; private set; } public Segment Current { get; private set; }
/// <inheritdoc/>
object? IEnumerator.Current => Current; object? IEnumerator.Current => Current;
public SegmentLineIterator(List<SegmentLine> lines) /// <summary>
/// Initializes a new instance of the <see cref="SegmentLineIterator"/> class.
/// </summary>
/// <param name="lines">The lines to iterate.</param>
public SegmentLineIterator(IEnumerable<SegmentLine> lines)
{ {
if (lines is null)
{
throw new System.ArgumentNullException(nameof(lines));
}
_currentLine = 0; _currentLine = 0;
_currentIndex = -1; _currentIndex = -1;
_lines = lines; _lines = new List<SegmentLine>(lines);
Current = Segment.Empty; Current = Segment.Empty;
} }
/// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
} }
/// <inheritdoc/>
public bool MoveNext() public bool MoveNext()
{ {
if (_currentLine > _lines.Count - 1) if (_currentLine > _lines.Count - 1)
@ -88,6 +107,7 @@ namespace Spectre.Console.Rendering
return true; return true;
} }
/// <inheritdoc/>
public void Reset() public void Reset()
{ {
_currentLine = 0; _currentLine = 0;

View File

@ -1,133 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
/// <summary>
/// Represents a table.
/// </summary>
public sealed partial class Table
{
private const int EdgeCount = 2;
// Calculate the widths of each column, including padding, not including borders.
// Ported from Rich by Will McGugan, licensed under MIT.
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
private List<int> CalculateColumnWidths(RenderContext options, int maxWidth)
{
var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth)).ToArray();
var widths = width_ranges.Select(range => range.Max).ToList();
var tableWidth = widths.Sum();
if (tableWidth > maxWidth)
{
var wrappable = _columns.Select(c => !c.NoWrap).ToList();
widths = CollapseWidths(widths, wrappable, maxWidth);
tableWidth = widths.Sum();
// last resort, reduce columns evenly
if (tableWidth > maxWidth)
{
var excessWidth = tableWidth - maxWidth;
widths = Ratio.Reduce(excessWidth, widths.Select(_ => 1).ToList(), widths, widths);
tableWidth = widths.Sum();
}
}
if (tableWidth < maxWidth && ShouldExpand())
{
var padWidths = Ratio.Distribute(maxWidth - tableWidth, widths);
widths = widths.Zip(padWidths, (a, b) => (a, b)).Select(f => f.a + f.b).ToList();
}
return widths;
}
// Reduce widths so that the total is less or equal to the max width.
// Ported from Rich by Will McGugan, licensed under MIT.
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L442
private static List<int> CollapseWidths(List<int> widths, List<bool> wrappable, int maxWidth)
{
var totalWidth = widths.Sum();
var excessWidth = totalWidth - maxWidth;
if (wrappable.AnyTrue())
{
while (totalWidth != 0 && excessWidth > 0)
{
var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, allowWrap: second))
.Where(x => x.allowWrap)
.Max(x => x.width);
var secondMaxColumn = widths.Zip(wrappable, (width, allowWrap) => allowWrap && width != maxColumn ? width : 1).Max();
var columnDifference = maxColumn - secondMaxColumn;
var ratios = widths.Zip(wrappable, (width, allowWrap) => width == maxColumn && allowWrap ? 1 : 0).ToList();
if (!ratios.Any(x => x != 0) || columnDifference == 0)
{
break;
}
var maxReduce = widths.Select(_ => Math.Min(excessWidth, columnDifference)).ToList();
widths = Ratio.Reduce(excessWidth, ratios, maxReduce, widths);
totalWidth = widths.Sum();
excessWidth = totalWidth - maxWidth;
}
}
return widths;
}
private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth)
{
var padding = column.Padding.GetHorizontalPadding();
// Predetermined width?
if (column.Width != null)
{
return (column.Width.Value + padding, column.Width.Value + padding);
}
var columnIndex = _columns.IndexOf(column);
var rows = _rows.Select(row => row[columnIndex]);
var minWidths = new List<int>();
var maxWidths = new List<int>();
// Include columns in measurement
var measure = column.Text.Measure(options, maxWidth);
minWidths.Add(measure.Min);
maxWidths.Add(measure.Max);
foreach (var row in rows)
{
measure = row.Measure(options, maxWidth);
minWidths.Add(measure.Min);
maxWidths.Add(measure.Max);
}
return (minWidths.Count > 0 ? minWidths.Max() : padding,
maxWidths.Count > 0 ? maxWidths.Max() : maxWidth);
}
private int GetExtraWidth(bool includePadding)
{
var hideBorder = !Border.Visible;
var separators = hideBorder ? 0 : _columns.Count - 1;
var edges = hideBorder ? 0 : EdgeCount;
var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0;
if (!PadRightCell)
{
padding -= _columns.Last().Padding.Right;
}
return separators + edges + padding;
}
}
}

View File

@ -10,6 +10,18 @@
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" /> <None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="AnsiConsole.*.cs">
<DependentUpon>AnsiConsole.cs</DependentUpon>
</Compile>
<Compile Update="Color.*.cs">
<DependentUpon>Color.cs</DependentUpon>
</Compile>
<Compile Update="Border.*.cs">
<DependentUpon>Border.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all" /> <PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all" />
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]" /> <PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]" />

View File

@ -1,40 +0,0 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Represents color and text decoration.
/// </summary>
public sealed partial class Style : IEquatable<Style>
{
/// <summary>
/// Creates a new style from the specified foreground color.
/// </summary>
/// <param name="color">The foreground color.</param>
/// <returns>A new <see cref="Style"/> with the specified foreground color.</returns>
public static Style WithForeground(Color color)
{
return new Style(foreground: color);
}
/// <summary>
/// Creates a new style from the specified background color.
/// </summary>
/// <param name="color">The background color.</param>
/// <returns>A new <see cref="Style"/> with the specified background color.</returns>
public static Style WithBackground(Color color)
{
return new Style(background: color);
}
/// <summary>
/// Creates a new style from the specified text decoration.
/// </summary>
/// <param name="decoration">The text decoration.</param>
/// <returns>A new <see cref="Style"/> with the specified text decoration.</returns>
public static Style WithDecoration(Decoration decoration)
{
return new Style(decoration: decoration);
}
}
}

View File

@ -7,7 +7,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Represents color and text decoration. /// Represents color and text decoration.
/// </summary> /// </summary>
public sealed partial class Style : IEquatable<Style> public sealed class Style : IEquatable<Style>
{ {
/// <summary> /// <summary>
/// Gets the foreground color. /// Gets the foreground color.
@ -48,6 +48,36 @@ namespace Spectre.Console
Decoration = decoration ?? Decoration.None; Decoration = decoration ?? Decoration.None;
} }
/// <summary>
/// Creates a new style from the specified foreground color.
/// </summary>
/// <param name="color">The foreground color.</param>
/// <returns>A new <see cref="Style"/> with the specified foreground color.</returns>
public static Style WithForeground(Color color)
{
return new Style(foreground: color);
}
/// <summary>
/// Creates a new style from the specified background color.
/// </summary>
/// <param name="color">The background color.</param>
/// <returns>A new <see cref="Style"/> with the specified background color.</returns>
public static Style WithBackground(Color color)
{
return new Style(background: color);
}
/// <summary>
/// Creates a new style from the specified text decoration.
/// </summary>
/// <param name="decoration">The text decoration.</param>
/// <returns>A new <see cref="Style"/> with the specified text decoration.</returns>
public static Style WithDecoration(Decoration decoration)
{
return new Style(decoration: decoration);
}
/// <summary> /// <summary>
/// Creates a copy of the current <see cref="Style"/>. /// Creates a copy of the current <see cref="Style"/>.
/// </summary> /// </summary>

View File

@ -38,7 +38,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets the header. /// Gets or sets the header.
/// </summary> /// </summary>
public Header? Header { get; set; } public PanelHeader? Header { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Panel"/> class. /// Initializes a new instance of the <see cref="Panel"/> class.

View File

@ -5,7 +5,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Represents a header. /// Represents a header.
/// </summary> /// </summary>
public sealed class Header : IAlignable public sealed class PanelHeader : IAlignable
{ {
/// <summary> /// <summary>
/// Gets the header text. /// Gets the header text.
@ -23,12 +23,12 @@ namespace Spectre.Console
public Justify? Alignment { get; set; } public Justify? Alignment { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Header"/> class. /// Initializes a new instance of the <see cref="PanelHeader"/> class.
/// </summary> /// </summary>
/// <param name="text">The header text.</param> /// <param name="text">The header text.</param>
/// <param name="style">The header style.</param> /// <param name="style">The header style.</param>
/// <param name="alignment">The header alignment.</param> /// <param name="alignment">The header alignment.</param>
public Header(string text, Style? style = null, Justify? alignment = null) public PanelHeader(string text, Style? style = null, Justify? alignment = null)
{ {
Text = text ?? throw new ArgumentNullException(nameof(text)); Text = text ?? throw new ArgumentNullException(nameof(text));
Style = style; Style = style;
@ -40,7 +40,7 @@ namespace Spectre.Console
/// </summary> /// </summary>
/// <param name="style">The header style.</param> /// <param name="style">The header style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public Header SetStyle(Style? style) public PanelHeader SetStyle(Style? style)
{ {
Style = style ?? Style.Plain; Style = style ?? Style.Plain;
return this; return this;
@ -51,7 +51,7 @@ namespace Spectre.Console
/// </summary> /// </summary>
/// <param name="alignment">The header alignment.</param> /// <param name="alignment">The header alignment.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public Header SetAlignment(Justify alignment) public PanelHeader SetAlignment(Justify alignment)
{ {
Alignment = alignment; Alignment = alignment;
return this; return this;

View File

@ -9,8 +9,10 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// A renderable table. /// A renderable table.
/// </summary> /// </summary>
public sealed partial class Table : Renderable, IHasBorder, IExpandable public sealed class Table : Renderable, IHasBorder, IExpandable
{ {
private const int EdgeCount = 2;
private readonly List<TableColumn> _columns; private readonly List<TableColumn> _columns;
private readonly List<List<IRenderable>> _rows; private readonly List<List<IRenderable>> _rows;
@ -329,6 +331,123 @@ namespace Spectre.Console
return result; return result;
} }
// Calculate the widths of each column, including padding, not including borders.
// Ported from Rich by Will McGugan, licensed under MIT.
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
private List<int> CalculateColumnWidths(RenderContext options, int maxWidth)
{
var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth)).ToArray();
var widths = width_ranges.Select(range => range.Max).ToList();
var tableWidth = widths.Sum();
if (tableWidth > maxWidth)
{
var wrappable = _columns.Select(c => !c.NoWrap).ToList();
widths = CollapseWidths(widths, wrappable, maxWidth);
tableWidth = widths.Sum();
// last resort, reduce columns evenly
if (tableWidth > maxWidth)
{
var excessWidth = tableWidth - maxWidth;
widths = Ratio.Reduce(excessWidth, widths.Select(_ => 1).ToList(), widths, widths);
tableWidth = widths.Sum();
}
}
if (tableWidth < maxWidth && ShouldExpand())
{
var padWidths = Ratio.Distribute(maxWidth - tableWidth, widths);
widths = widths.Zip(padWidths, (a, b) => (a, b)).Select(f => f.a + f.b).ToList();
}
return widths;
}
// Reduce widths so that the total is less or equal to the max width.
// Ported from Rich by Will McGugan, licensed under MIT.
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L442
private static List<int> CollapseWidths(List<int> widths, List<bool> wrappable, int maxWidth)
{
var totalWidth = widths.Sum();
var excessWidth = totalWidth - maxWidth;
if (wrappable.AnyTrue())
{
while (totalWidth != 0 && excessWidth > 0)
{
var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, allowWrap: second))
.Where(x => x.allowWrap)
.Max(x => x.width);
var secondMaxColumn = widths.Zip(wrappable, (width, allowWrap) => allowWrap && width != maxColumn ? width : 1).Max();
var columnDifference = maxColumn - secondMaxColumn;
var ratios = widths.Zip(wrappable, (width, allowWrap) => width == maxColumn && allowWrap ? 1 : 0).ToList();
if (!ratios.Any(x => x != 0) || columnDifference == 0)
{
break;
}
var maxReduce = widths.Select(_ => Math.Min(excessWidth, columnDifference)).ToList();
widths = Ratio.Reduce(excessWidth, ratios, maxReduce, widths);
totalWidth = widths.Sum();
excessWidth = totalWidth - maxWidth;
}
}
return widths;
}
private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth)
{
var padding = column.Padding.GetHorizontalPadding();
// Predetermined width?
if (column.Width != null)
{
return (column.Width.Value + padding, column.Width.Value + padding);
}
var columnIndex = _columns.IndexOf(column);
var rows = _rows.Select(row => row[columnIndex]);
var minWidths = new List<int>();
var maxWidths = new List<int>();
// Include columns in measurement
var measure = column.Text.Measure(options, maxWidth);
minWidths.Add(measure.Min);
maxWidths.Add(measure.Max);
foreach (var row in rows)
{
measure = row.Measure(options, maxWidth);
minWidths.Add(measure.Min);
maxWidths.Add(measure.Max);
}
return (minWidths.Count > 0 ? minWidths.Max() : padding,
maxWidths.Count > 0 ? maxWidths.Max() : maxWidth);
}
private int GetExtraWidth(bool includePadding)
{
var hideBorder = !Border.Visible;
var separators = hideBorder ? 0 : _columns.Count - 1;
var edges = hideBorder ? 0 : EdgeCount;
var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0;
if (!PadRightCell)
{
padding -= _columns.Last().Padding.Right;
}
return separators + edges + padding;
}
private bool ShouldExpand() private bool ShouldExpand()
{ {
return Expand || Width != null; return Expand || Width != null;