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">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="2.3.0">
<PackageReference Include="Roslynator.Analyzers" Version="3.0.0">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>

View File

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

View File

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

View File

@ -27,7 +27,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(text));
}
return SetHeader(panel, new Header(text, style, alignment));
return SetHeader(panel, new PanelHeader(text, style, alignment));
}
/// <summary>
@ -36,7 +36,7 @@ namespace Spectre.Console
/// <param name="panel">The panel.</param>
/// <param name="header">The header to use.</param>
/// <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)
{

View File

@ -266,8 +266,8 @@ namespace Spectre.Console.Rendering
/// </summary>
/// <param name="segment">The segment to split.</param>
/// <param name="overflow">The overflow strategy to use.</param>
/// <param name="encoding">The encodign to use.</param>
/// <param name="width">The maxiumum width.</param>
/// <param name="encoding">The encoding to use.</param>
/// <param name="width">The maximum width.</param>
/// <returns>A list of segments that has been split.</returns>
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),
Overflow.Ellipsis,
encoding,
maxWidth).First();
maxWidth)[0];
}
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
{
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;
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()
{
return new SegmentLineIterator(_lines);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();

View File

@ -3,29 +3,48 @@ using System.Collections.Generic;
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 int _currentLine;
private int _currentIndex;
private bool _lineBreakEmitted;
/// <summary>
/// Gets the current segment.
/// </summary>
public Segment Current { get; private set; }
/// <inheritdoc/>
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;
_currentIndex = -1;
_lines = lines;
_lines = new List<SegmentLine>(lines);
Current = Segment.Empty;
}
/// <inheritdoc/>
public void Dispose()
{
}
/// <inheritdoc/>
public bool MoveNext()
{
if (_currentLine > _lines.Count - 1)
@ -88,6 +107,7 @@ namespace Spectre.Console.Rendering
return true;
}
/// <inheritdoc/>
public void Reset()
{
_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" />
</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>
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all" />
<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>
/// Represents color and text decoration.
/// </summary>
public sealed partial class Style : IEquatable<Style>
public sealed class Style : IEquatable<Style>
{
/// <summary>
/// Gets the foreground color.
@ -48,6 +48,36 @@ namespace Spectre.Console
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>
/// Creates a copy of the current <see cref="Style"/>.
/// </summary>

View File

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

View File

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

View File

@ -9,8 +9,10 @@ namespace Spectre.Console
/// <summary>
/// A renderable table.
/// </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<List<IRenderable>> _rows;
@ -329,6 +331,123 @@ namespace Spectre.Console
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()
{
return Expand || Width != null;