diff --git a/examples/Borders/Program.cs b/examples/Borders/Program.cs
index dd8412d..2854107 100644
--- a/examples/Borders/Program.cs
+++ b/examples/Borders/Program.cs
@@ -21,7 +21,7 @@ namespace BordersExample
static IRenderable CreatePanel(string name, BoxBorder border)
{
return new Panel($"This is a panel with\nthe [yellow]{name}[/] border.")
- .Header($" {name} ", Style.Parse("blue"), Justify.Center)
+ .Header($" [blue]{name}[/] ", Justify.Center)
.Border(border)
.BorderStyle(Style.Parse("grey"));
}
@@ -53,7 +53,7 @@ namespace BordersExample
table.AddRow("Cell", "Cell");
return new Panel(table)
- .Header($" {name} ", Style.Parse("blue"), Justify.Center)
+ .Header($" [blue]{name}[/] ", Justify.Center)
.NoBorder();
}
diff --git a/examples/Cursor/Program.cs b/examples/Cursor/Program.cs
index ebfdfbf..e2645c2 100644
--- a/examples/Cursor/Program.cs
+++ b/examples/Cursor/Program.cs
@@ -1,4 +1,3 @@
-using System;
using Spectre.Console;
namespace Cursor
diff --git a/examples/Panels/Program.cs b/examples/Panels/Program.cs
index de8e2fa..d43c110 100644
--- a/examples/Panels/Program.cs
+++ b/examples/Panels/Program.cs
@@ -20,16 +20,14 @@ namespace PanelExample
new Panel(new Text("Left adjusted\nLeft").LeftAligned())
.Expand()
.SquareBorder()
- .Header("Left")
- .HeaderStyle("red"));
+ .Header("[red]Left[/]"));
// Centered ASCII panel with text
AnsiConsole.Render(
new Panel(new Text("Centered\nCenter").Centered())
.Expand()
.AsciiBorder()
- .Header("Center")
- .HeaderStyle("green")
+ .Header("[green]Center[/]")
.HeaderAlignment(Justify.Center));
// Right adjusted, rounded panel with text
@@ -37,8 +35,7 @@ namespace PanelExample
new Panel(new Text("Right adjusted\nRight").RightAligned())
.Expand()
.RoundedBorder()
- .Header("Right")
- .HeaderStyle("blue")
+ .Header("[blue]Right[/]")
.HeaderAlignment(Justify.Right));
}
}
diff --git a/examples/Rules/Program.cs b/examples/Rules/Program.cs
index 438a717..e65ac97 100644
--- a/examples/Rules/Program.cs
+++ b/examples/Rules/Program.cs
@@ -10,18 +10,21 @@ namespace EmojiExample
WrapInPanel(
new Rule()
.RuleStyle(Style.Parse("yellow"))
+ .AsciiBorder()
.LeftAligned());
// Left aligned title
WrapInPanel(
new Rule("[blue]Left aligned[/]")
.RuleStyle(Style.Parse("red"))
+ .DoubleBorder()
.LeftAligned());
// Centered title
WrapInPanel(
new Rule("[green]Centered[/]")
.RuleStyle(Style.Parse("green"))
+ .HeavyBorder()
.Centered());
// Right aligned title
diff --git a/src/Spectre.Console.Tests/Unit/PanelTests.cs b/src/Spectre.Console.Tests/Unit/PanelTests.cs
index 354229a..1faa679 100644
--- a/src/Spectre.Console.Tests/Unit/PanelTests.cs
+++ b/src/Spectre.Console.Tests/Unit/PanelTests.cs
@@ -316,14 +316,14 @@ namespace Spectre.Console.Tests.Unit
var panel = new Panel(grid)
.Expand().RoundedBorder()
.BorderStyle(new Style().Foreground(Color.Grey))
- .Header("Short paths ", new Style().Foreground(Color.Grey));
+ .Header("[grey]Short paths[/]");
// When
console.Render(panel);
// Then
console.Lines.Count.ShouldBe(4);
- console.Lines[0].ShouldBe("╭─Short paths ─────────────────────────────────────────────────────────────────────╮");
+ console.Lines[0].ShouldBe("╭─Short paths──────────────────────────────────────────────────────────────────────╮");
console.Lines[1].ShouldBe("│ at System.Runtime.CompilerServices.TaskAwaiter. │");
console.Lines[2].ShouldBe("│ HandleNonSuccessAndDebuggerNotification(Task task) │");
console.Lines[3].ShouldBe("╰──────────────────────────────────────────────────────────────────────────────────╯");
diff --git a/src/Spectre.Console.Tests/Unit/RuleTests.cs b/src/Spectre.Console.Tests/Unit/RuleTests.cs
index 3ebb653..a39113b 100644
--- a/src/Spectre.Console.Tests/Unit/RuleTests.cs
+++ b/src/Spectre.Console.Tests/Unit/RuleTests.cs
@@ -19,6 +19,34 @@ namespace Spectre.Console.Tests.Unit
console.Lines[0].ShouldBe("────────────────────────────────────────");
}
+ [Fact]
+ public void Should_Render_Default_Rule_With_Specified_Box()
+ {
+ // Given
+ var console = new PlainConsole(width: 40);
+
+ // When
+ console.Render(new Rule().DoubleBorder());
+
+ // Then
+ console.Lines.Count.ShouldBe(1);
+ console.Lines[0].ShouldBe("════════════════════════════════════════");
+ }
+
+ [Fact]
+ public void Should_Render_With_Specified_Box()
+ {
+ // Given
+ var console = new PlainConsole(width: 40);
+
+ // When
+ console.Render(new Rule("Hello World").DoubleBorder());
+
+ // Then
+ console.Lines.Count.ShouldBe(1);
+ console.Lines[0].ShouldBe("═════════════ Hello World ══════════════");
+ }
+
[Fact]
public void Should_Render_Default_Rule_With_Title_Centered_By_Default()
{
diff --git a/src/Spectre.Console/Extensions/Obsolete/ObsoletePanelExtensions.cs b/src/Spectre.Console/Extensions/Obsolete/ObsoletePanelExtensions.cs
index f5d2dd4..0af65b2 100644
--- a/src/Spectre.Console/Extensions/Obsolete/ObsoletePanelExtensions.cs
+++ b/src/Spectre.Console/Extensions/Obsolete/ObsoletePanelExtensions.cs
@@ -30,10 +30,8 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(text));
}
- style ??= panel.Header?.Style;
alignment ??= panel.Header?.Alignment;
-
- return SetHeader(panel, new PanelHeader(text, style, alignment));
+ return SetHeader(panel, new PanelHeader(text, alignment));
}
///
@@ -54,5 +52,18 @@ namespace Spectre.Console
panel.Header = header;
return panel;
}
+
+ ///
+ /// Sets the panel header style.
+ ///
+ /// The panel.
+ /// The header style.
+ /// The same instance so that multiple calls can be chained.
+ [Obsolete("Use markup in header instead.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Panel HeaderStyle(this Panel panel, Style style)
+ {
+ return panel;
+ }
}
}
diff --git a/src/Spectre.Console/Extensions/PanelExtensions.cs b/src/Spectre.Console/Extensions/PanelExtensions.cs
index a817b5c..e1901dc 100644
--- a/src/Spectre.Console/Extensions/PanelExtensions.cs
+++ b/src/Spectre.Console/Extensions/PanelExtensions.cs
@@ -12,10 +12,9 @@ namespace Spectre.Console
///
/// The panel.
/// The header text.
- /// The header style.
/// The header alignment.
/// The same instance so that multiple calls can be chained.
- public static Panel Header(this Panel panel, string text, Style? style = null, Justify? alignment = null)
+ public static Panel Header(this Panel panel, string text, Justify? alignment = null)
{
if (panel is null)
{
@@ -27,42 +26,8 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(text));
}
- style ??= panel.Header?.Style;
alignment ??= panel.Header?.Alignment;
-
- return Header(panel, new PanelHeader(text, style, alignment));
- }
-
- ///
- /// Sets the panel header style.
- ///
- /// The panel.
- /// The header style.
- /// The same instance so that multiple calls can be chained.
- public static Panel HeaderStyle(this Panel panel, Style style)
- {
- if (panel is null)
- {
- throw new ArgumentNullException(nameof(panel));
- }
-
- if (style is null)
- {
- throw new ArgumentNullException(nameof(style));
- }
-
- if (panel.Header != null)
- {
- // Update existing style
- panel.Header.Style = style;
- }
- else
- {
- // Create header
- Header(panel, string.Empty, style, null);
- }
-
- return panel;
+ return Header(panel, new PanelHeader(text, alignment));
}
///
@@ -86,7 +51,7 @@ namespace Spectre.Console
else
{
// Create header
- Header(panel, string.Empty, null, alignment);
+ Header(panel, string.Empty, alignment);
}
return panel;
diff --git a/src/Spectre.Console/IHasBoxBorder.cs b/src/Spectre.Console/IHasBoxBorder.cs
index bb5f97b..70daa98 100644
--- a/src/Spectre.Console/IHasBoxBorder.cs
+++ b/src/Spectre.Console/IHasBoxBorder.cs
@@ -3,7 +3,7 @@ namespace Spectre.Console
///
/// Represents something that has a box border.
///
- public interface IHasBoxBorder : IHasBorder
+ public interface IHasBoxBorder
{
///
/// Gets or sets the box.
diff --git a/src/Spectre.Console/Widgets/Panel.cs b/src/Spectre.Console/Widgets/Panel.cs
index e429176..483d1b6 100644
--- a/src/Spectre.Console/Widgets/Panel.cs
+++ b/src/Spectre.Console/Widgets/Panel.cs
@@ -9,7 +9,7 @@ namespace Spectre.Console
///
/// A renderable panel.
///
- public sealed class Panel : Renderable, IHasBoxBorder, IExpandable, IPaddable
+ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable, IPaddable
{
private const int EdgeWidth = 2;
@@ -123,62 +123,35 @@ namespace Spectre.Console
}
// Panel bottom
- AddBottomBorder(result, border, borderStyle, panelWidth);
-
- return result;
- }
-
- private static void AddBottomBorder(List result, BoxBorder border, Style borderStyle, int panelWidth)
- {
result.Add(new Segment(border.GetPart(BoxBorderPart.BottomLeft), borderStyle));
result.Add(new Segment(border.GetPart(BoxBorderPart.Bottom).Repeat(panelWidth - EdgeWidth), borderStyle));
result.Add(new Segment(border.GetPart(BoxBorderPart.BottomRight), borderStyle));
result.Add(Segment.LineBreak);
+
+ return result;
}
- private void AddTopBorder(List segments, RenderContext context, BoxBorder border, Style borderStyle, int panelWidth)
+ private void AddTopBorder(List result, RenderContext context, BoxBorder border, Style borderStyle, int panelWidth)
{
- segments.Add(new Segment(border.GetPart(BoxBorderPart.TopLeft), borderStyle));
-
- if (Header != null)
+ var rule = new Rule
{
- var leftSpacing = 0;
- var rightSpacing = 0;
+ Style = borderStyle,
+ Border = border,
+ TitlePadding = 1,
+ TitleSpacing = 0,
+ Title = Header?.Text,
+ Alignment = Header?.Alignment ?? Justify.Left,
+ };
- var headerWidth = panelWidth - (EdgeWidth * 2);
- var header = Segment.TruncateWithEllipsis(Header.Text, Header.Style ?? borderStyle, context, headerWidth);
+ // Top left border
+ result.Add(new Segment(border.GetPart(BoxBorderPart.TopLeft), borderStyle));
- var excessWidth = headerWidth - header.CellCount(context);
- if (excessWidth > 0)
- {
- switch (Header.Alignment ?? Justify.Left)
- {
- case Justify.Left:
- leftSpacing = 0;
- rightSpacing = excessWidth;
- break;
- case Justify.Right:
- leftSpacing = excessWidth;
- rightSpacing = 0;
- break;
- case Justify.Center:
- leftSpacing = excessWidth / 2;
- rightSpacing = (excessWidth / 2) + (excessWidth % 2);
- break;
- }
- }
+ // Top border (and header text if specified)
+ result.AddRange(((IRenderable)rule).Render(context, panelWidth - 2).Where(x => !x.IsLineBreak));
- segments.Add(new Segment(border.GetPart(BoxBorderPart.Top).Repeat(leftSpacing + 1), borderStyle));
- segments.Add(header);
- segments.Add(new Segment(border.GetPart(BoxBorderPart.Top).Repeat(rightSpacing + 1), borderStyle));
- }
- else
- {
- segments.Add(new Segment(border.GetPart(BoxBorderPart.Top).Repeat(panelWidth - EdgeWidth), borderStyle));
- }
-
- segments.Add(new Segment(border.GetPart(BoxBorderPart.TopRight), borderStyle));
- segments.Add(Segment.LineBreak);
+ // Top right border
+ result.Add(new Segment(border.GetPart(BoxBorderPart.TopRight), borderStyle));
+ result.Add(Segment.LineBreak);
}
}
}
diff --git a/src/Spectre.Console/Widgets/PanelHeader.cs b/src/Spectre.Console/Widgets/PanelHeader.cs
index 0c327f8..4c609bb 100644
--- a/src/Spectre.Console/Widgets/PanelHeader.cs
+++ b/src/Spectre.Console/Widgets/PanelHeader.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
namespace Spectre.Console
{
@@ -12,11 +13,6 @@ namespace Spectre.Console
///
public string Text { get; }
- ///
- /// Gets or sets the panel header style.
- ///
- public Style? Style { get; set; }
-
///
/// Gets or sets the panel header alignment.
///
@@ -26,12 +22,10 @@ namespace Spectre.Console
/// Initializes a new instance of the class.
///
/// The panel header text.
- /// The panel header style.
/// The panel header alignment.
- public PanelHeader(string text, Style? style = null, Justify? alignment = null)
+ public PanelHeader(string text, Justify? alignment = null)
{
Text = text ?? throw new ArgumentNullException(nameof(text));
- Style = style;
Alignment = alignment;
}
@@ -40,9 +34,10 @@ namespace Spectre.Console
///
/// The panel header style.
/// The same instance so that multiple calls can be chained.
+ [Obsolete("Use markup instead.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public PanelHeader SetStyle(Style? style)
{
- Style = style ?? Style.Plain;
return this;
}
@@ -51,14 +46,10 @@ namespace Spectre.Console
///
/// The panel header style.
/// The same instance so that multiple calls can be chained.
+ [Obsolete("Use markup instead.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public PanelHeader SetStyle(string style)
{
- if (style is null)
- {
- throw new ArgumentNullException(nameof(style));
- }
-
- Style = Style.Parse(style);
return this;
}
diff --git a/src/Spectre.Console/Widgets/Rule.cs b/src/Spectre.Console/Widgets/Rule.cs
index 168cbb0..ead2acc 100644
--- a/src/Spectre.Console/Widgets/Rule.cs
+++ b/src/Spectre.Console/Widgets/Rule.cs
@@ -9,7 +9,7 @@ namespace Spectre.Console
///
/// A renderable horizontal rule.
///
- public sealed class Rule : Renderable, IAlignable
+ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
{
///
/// Gets or sets the rule title markup text.
@@ -26,6 +26,12 @@ namespace Spectre.Console
///
public Justify? Alignment { get; set; }
+ ///
+ public BoxBorder Border { get; set; } = BoxBorder.Square;
+
+ internal int TitlePadding { get; set; } = 2;
+ internal int TitleSpacing { get; set; } = 1;
+
///
/// Initializes a new instance of the class.
///
@@ -45,21 +51,23 @@ namespace Spectre.Console
///
protected override IEnumerable Render(RenderContext context, int maxWidth)
{
- if (Title == null || maxWidth <= 6)
+ var extraLength = (2 * TitlePadding) + (2 * TitleSpacing);
+
+ if (Title == null || maxWidth <= extraLength)
{
- return GetLineWithoutTitle(maxWidth);
+ return GetLineWithoutTitle(context, maxWidth);
}
// Get the title and make sure it fits.
- var title = GetTitleSegments(context, Title, maxWidth - 6);
- if (Segment.CellCount(context, title) > maxWidth - 6)
+ var title = GetTitleSegments(context, Title, maxWidth - extraLength);
+ if (Segment.CellCount(context, title) > maxWidth - extraLength)
{
// Truncate the title
- title = Segment.TruncateWithEllipsis(title, context, maxWidth - 6);
+ title = Segment.TruncateWithEllipsis(title, context, maxWidth - extraLength);
if (!title.Any())
{
// We couldn't fit the title at all.
- return GetLineWithoutTitle(maxWidth);
+ return GetLineWithoutTitle(context, maxWidth);
}
}
@@ -74,9 +82,11 @@ namespace Spectre.Console
return segments;
}
- private IEnumerable GetLineWithoutTitle(int maxWidth)
+ private IEnumerable GetLineWithoutTitle(RenderContext context, int maxWidth)
{
- var text = new string('─', maxWidth);
+ var border = Border.GetSafeBorder(context.LegacyConsole || !context.Unicode);
+ var text = border.GetPart(BoxBorderPart.Top).Repeat(maxWidth);
+
return new[]
{
new Segment(text, Style ?? Style.Plain),
@@ -84,49 +94,51 @@ namespace Spectre.Console
};
}
- private (Segment Left, Segment Right) GetLineSegments(RenderContext context, int maxWidth, IEnumerable title)
+ private IEnumerable GetTitleSegments(RenderContext context, string title, int width)
{
- var alignment = Alignment ?? Justify.Center;
+ title = title.NormalizeLineEndings().Replace("\n", " ").Trim();
+ var markup = new Markup(title, Style);
+ return ((IRenderable)markup).Render(context.WithSingleLine(), width);
+ }
+ private (Segment Left, Segment Right) GetLineSegments(RenderContext context, int width, IEnumerable title)
+ {
var titleLength = Segment.CellCount(context, title);
+ var border = Border.GetSafeBorder(context.LegacyConsole || !context.Unicode);
+ var borderPart = border.GetPart(BoxBorderPart.Top);
+
+ var alignment = Alignment ?? Justify.Center;
if (alignment == Justify.Left)
{
- var left = new Segment(new string('─', 2) + " ", Style ?? Style.Plain);
+ var left = new Segment(borderPart.Repeat(TitlePadding) + new string(' ', TitleSpacing), Style ?? Style.Plain);
- var rightLength = maxWidth - titleLength - left.CellCount(context) - 1;
- var right = new Segment(" " + new string('─', rightLength), Style ?? Style.Plain);
+ var rightLength = width - titleLength - left.CellCount(context) - TitleSpacing;
+ var right = new Segment(new string(' ', TitleSpacing) + borderPart.Repeat(rightLength), Style ?? Style.Plain);
return (left, right);
}
else if (alignment == Justify.Center)
{
- var leftLength = ((maxWidth - titleLength) / 2) - 1;
- var left = new Segment(new string('─', leftLength) + " ", Style ?? Style.Plain);
+ var leftLength = ((width - titleLength) / 2) - TitleSpacing;
+ var left = new Segment(borderPart.Repeat(leftLength) + new string(' ', TitleSpacing), Style ?? Style.Plain);
- var rightLength = maxWidth - titleLength - left.CellCount(context) - 1;
- var right = new Segment(" " + new string('─', rightLength), Style ?? Style.Plain);
+ var rightLength = width - titleLength - left.CellCount(context) - TitleSpacing;
+ var right = new Segment(new string(' ', TitleSpacing) + borderPart.Repeat(rightLength), Style ?? Style.Plain);
return (left, right);
}
else if (alignment == Justify.Right)
{
- var right = new Segment(" " + new string('─', 2), Style ?? Style.Plain);
+ var right = new Segment(new string(' ', TitleSpacing) + borderPart.Repeat(TitlePadding), Style ?? Style.Plain);
- var leftLength = maxWidth - titleLength - right.CellCount(context) - 1;
- var left = new Segment(new string('─', leftLength) + " ", Style ?? Style.Plain);
+ var leftLength = width - titleLength - right.CellCount(context) - TitleSpacing;
+ var left = new Segment(borderPart.Repeat(leftLength) + new string(' ', TitleSpacing), Style ?? Style.Plain);
return (left, right);
}
throw new NotSupportedException("Unsupported alignment.");
}
-
- private IEnumerable GetTitleSegments(RenderContext context, string title, int width)
- {
- title = title.NormalizeLineEndings().Replace("\n", " ").Trim();
- var markup = new Markup(title, Style);
- return ((IRenderable)markup).Render(context.WithSingleLine(), width - 6);
- }
}
}