mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-04 10:35:27 +08:00 
			
		
		
		
	Add rule widget
Adds a new rule widget. Also fixes some bugs I encountered while testing some unrelated things in an extremely small console.
This commit is contained in:
		
				
					committed by
					
						
						Patrik Svensson
					
				
			
			
				
	
			
			
			
						parent
						
							1410cba6c5
						
					
				
				
					commit
					5a1b8a1710
				
			
							
								
								
									
										31
									
								
								README.jp.md
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								README.jp.md
									
									
									
									
									
								
							@@ -96,21 +96,22 @@ Spectre.Consoleでできることを見るために、
 | 
			
		||||
```
 | 
			
		||||
> dotnet example
 | 
			
		||||
 | 
			
		||||
┌────────────┬───────────────────────────────────────┬───────────────────────────────────────────────────┐
 | 
			
		||||
│ Name       │ Path                                  │ Description                                       │
 | 
			
		||||
├────────────┼───────────────────────────────────────┼───────────────────────────────────────────────────┤
 | 
			
		||||
│ Borders    │ examples/Borders/Borders.csproj       │ Demonstrates the different kind of borders.       │
 | 
			
		||||
│ Calendars  │ examples/Calendars/Calendars.csproj   │ Demonstrates how to render calendars.             │
 | 
			
		||||
│ Colors     │ examples/Colors/Colors.csproj         │ Demonstrates how to use colors in the console.    │
 | 
			
		||||
│ Columns    │ examples/Columns/Columns.csproj       │ Demonstrates how to render data into columns.     │
 | 
			
		||||
│ Emojis     │ examples/Emojis/Emojis.csproj         │ Demonstrates how to render emojis.                │
 | 
			
		||||
│ Exceptions │ examples/Exceptions/Exceptions.csproj │ Demonstrates how to render formatted exceptions.  │
 | 
			
		||||
│ Grids      │ examples/Grids/Grids.csproj           │ Demonstrates how to render grids in a console.    │
 | 
			
		||||
│ Info       │ examples/Info/Info.csproj             │ Displays the capabilities of the current console. │
 | 
			
		||||
│ Links      │ examples/Links/Links.csproj           │ Demonstrates how to render links in a console.    │
 | 
			
		||||
│ Panels     │ examples/Panels/Panels.csproj         │ Demonstrates how to render items in panels.       │
 | 
			
		||||
│ Tables     │ examples/Tables/Tables.csproj         │ Demonstrates how to render tables in a console.   │
 | 
			
		||||
└────────────┴───────────────────────────────────────┴───────────────────────────────────────────────────┘
 | 
			
		||||
╭────────────┬───────────────────────────────────────┬──────────────────────────────────────────────────────╮
 | 
			
		||||
│ Name       │ Path                                  │ Description                                          │
 | 
			
		||||
├────────────┼───────────────────────────────────────┼──────────────────────────────────────────────────────┤
 | 
			
		||||
│ Borders    │ examples/Borders/Borders.csproj       │ Demonstrates the different kind of borders.          │
 | 
			
		||||
│ Calendars  │ examples/Calendars/Calendars.csproj   │ Demonstrates how to render calendars.                │
 | 
			
		||||
│ Colors     │ examples/Colors/Colors.csproj         │ Demonstrates how to use colors in the console.       │
 | 
			
		||||
│ Columns    │ examples/Columns/Columns.csproj       │ Demonstrates how to render data into columns.        │
 | 
			
		||||
│ Emojis     │ examples/Emojis/Emojis.csproj         │ Demonstrates how to render emojis.                   │
 | 
			
		||||
│ Exceptions │ examples/Exceptions/Exceptions.csproj │ Demonstrates how to render formatted exceptions.     │
 | 
			
		||||
│ Grids      │ examples/Grids/Grids.csproj           │ Demonstrates how to render grids in a console.       │
 | 
			
		||||
│ Info       │ examples/Info/Info.csproj             │ Displays the capabilities of the current console.    │
 | 
			
		||||
│ Links      │ examples/Links/Links.csproj           │ Demonstrates how to render links in a console.       │
 | 
			
		||||
│ Panels     │ examples/Panels/Panels.csproj         │ Demonstrates how to render items in panels.          │
 | 
			
		||||
│ Rules      │ examples/Rules/Rules.csproj           │ Demonstrates how to render horizontal rules (lines). │
 | 
			
		||||
│ Tables     │ examples/Tables/Tables.csproj         │ Demonstrates how to render tables in a console.      │
 | 
			
		||||
╰────────────┴───────────────────────────────────────┴──────────────────────────────────────────────────────╯
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
そして、例を実行します
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								README.md
									
									
									
									
									
								
							@@ -108,21 +108,22 @@ Now you can list available examples in this repository:
 | 
			
		||||
```
 | 
			
		||||
> dotnet example
 | 
			
		||||
 | 
			
		||||
┌────────────┬───────────────────────────────────────┬───────────────────────────────────────────────────┐
 | 
			
		||||
│ Name       │ Path                                  │ Description                                       │
 | 
			
		||||
├────────────┼───────────────────────────────────────┼───────────────────────────────────────────────────┤
 | 
			
		||||
│ Borders    │ examples/Borders/Borders.csproj       │ Demonstrates the different kind of borders.       │
 | 
			
		||||
│ Calendars  │ examples/Calendars/Calendars.csproj   │ Demonstrates how to render calendars.             │
 | 
			
		||||
│ Colors     │ examples/Colors/Colors.csproj         │ Demonstrates how to use colors in the console.    │
 | 
			
		||||
│ Columns    │ examples/Columns/Columns.csproj       │ Demonstrates how to render data into columns.     │
 | 
			
		||||
│ Emojis     │ examples/Emojis/Emojis.csproj         │ Demonstrates how to render emojis.                │
 | 
			
		||||
│ Exceptions │ examples/Exceptions/Exceptions.csproj │ Demonstrates how to render formatted exceptions.  │
 | 
			
		||||
│ Grids      │ examples/Grids/Grids.csproj           │ Demonstrates how to render grids in a console.    │
 | 
			
		||||
│ Info       │ examples/Info/Info.csproj             │ Displays the capabilities of the current console. │
 | 
			
		||||
│ Links      │ examples/Links/Links.csproj           │ Demonstrates how to render links in a console.    │
 | 
			
		||||
│ Panels     │ examples/Panels/Panels.csproj         │ Demonstrates how to render items in panels.       │
 | 
			
		||||
│ Tables     │ examples/Tables/Tables.csproj         │ Demonstrates how to render tables in a console.   │
 | 
			
		||||
└────────────┴───────────────────────────────────────┴───────────────────────────────────────────────────┘
 | 
			
		||||
╭────────────┬───────────────────────────────────────┬──────────────────────────────────────────────────────╮
 | 
			
		||||
│ Name       │ Path                                  │ Description                                          │
 | 
			
		||||
├────────────┼───────────────────────────────────────┼──────────────────────────────────────────────────────┤
 | 
			
		||||
│ Borders    │ examples/Borders/Borders.csproj       │ Demonstrates the different kind of borders.          │
 | 
			
		||||
│ Calendars  │ examples/Calendars/Calendars.csproj   │ Demonstrates how to render calendars.                │
 | 
			
		||||
│ Colors     │ examples/Colors/Colors.csproj         │ Demonstrates how to use colors in the console.       │
 | 
			
		||||
│ Columns    │ examples/Columns/Columns.csproj       │ Demonstrates how to render data into columns.        │
 | 
			
		||||
│ Emojis     │ examples/Emojis/Emojis.csproj         │ Demonstrates how to render emojis.                   │
 | 
			
		||||
│ Exceptions │ examples/Exceptions/Exceptions.csproj │ Demonstrates how to render formatted exceptions.     │
 | 
			
		||||
│ Grids      │ examples/Grids/Grids.csproj           │ Demonstrates how to render grids in a console.       │
 | 
			
		||||
│ Info       │ examples/Info/Info.csproj             │ Displays the capabilities of the current console.    │
 | 
			
		||||
│ Links      │ examples/Links/Links.csproj           │ Demonstrates how to render links in a console.       │
 | 
			
		||||
│ Panels     │ examples/Panels/Panels.csproj         │ Demonstrates how to render items in panels.          │
 | 
			
		||||
│ Rules      │ examples/Rules/Rules.csproj           │ Demonstrates how to render horizontal rules (lines). │
 | 
			
		||||
│ Tables     │ examples/Tables/Tables.csproj         │ Demonstrates how to render tables in a console.      │
 | 
			
		||||
╰────────────┴───────────────────────────────────────┴──────────────────────────────────────────────────────╯
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
And to run an example:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using Spectre.Console;
 | 
			
		||||
using Spectre.Console.Rendering;
 | 
			
		||||
 | 
			
		||||
@@ -7,6 +8,8 @@ namespace BordersExample
 | 
			
		||||
    {
 | 
			
		||||
        public static void Main()
 | 
			
		||||
        {
 | 
			
		||||
            Debugger.Launch();
 | 
			
		||||
 | 
			
		||||
            // Render panel borders
 | 
			
		||||
            AnsiConsole.WriteLine();
 | 
			
		||||
            AnsiConsole.MarkupLine("[white bold underline]PANEL BORDERS[/]");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Spectre.Console;
 | 
			
		||||
 | 
			
		||||
namespace ColorExample
 | 
			
		||||
@@ -24,7 +25,7 @@ namespace ColorExample
 | 
			
		||||
 | 
			
		||||
                AnsiConsole.ResetColors();
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
                AnsiConsole.MarkupLine("[bold underline]3-bit Colors[/]");
 | 
			
		||||
                AnsiConsole.Render(new Rule("[yellow bold underline]3-bit Colors[/]").SetStyle("grey").LeftAligned());
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
 | 
			
		||||
                for (var i = 0; i < 8; i++)
 | 
			
		||||
@@ -47,7 +48,7 @@ namespace ColorExample
 | 
			
		||||
 | 
			
		||||
                AnsiConsole.ResetColors();
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
                AnsiConsole.MarkupLine("[bold underline]4-bit Colors[/]");
 | 
			
		||||
                AnsiConsole.Render(new Rule("[yellow bold underline]4-bit Colors[/]").SetStyle("grey").LeftAligned());
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
 | 
			
		||||
                for (var i = 0; i < 16; i++)
 | 
			
		||||
@@ -70,7 +71,7 @@ namespace ColorExample
 | 
			
		||||
 | 
			
		||||
                AnsiConsole.ResetColors();
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
                AnsiConsole.MarkupLine("[bold underline]8-bit Colors[/]");
 | 
			
		||||
                AnsiConsole.Render(new Rule("[yellow bold underline]8-bit Colors[/]").SetStyle("grey").LeftAligned());
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
 | 
			
		||||
                for (var i = 0; i < 16; i++)
 | 
			
		||||
@@ -97,7 +98,7 @@ namespace ColorExample
 | 
			
		||||
 | 
			
		||||
                AnsiConsole.ResetColors();
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
                AnsiConsole.MarkupLine("[bold underline]24-bit Colors[/]");
 | 
			
		||||
                AnsiConsole.Render(new Rule("[yellow bold underline]24-bit Colors[/]").SetStyle("grey").LeftAligned());
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
 | 
			
		||||
                var index = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
 
 | 
			
		||||
@@ -15,17 +15,17 @@ namespace Exceptions
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
                AnsiConsole.Render(new Panel("[u]Default[/]").Expand());
 | 
			
		||||
                AnsiConsole.Render(new Rule("Default").LeftAligned());
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
                AnsiConsole.WriteException(ex);
 | 
			
		||||
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
                AnsiConsole.Render(new Panel("[u]Compact[/]").Expand());
 | 
			
		||||
                AnsiConsole.Render(new Rule("Compact").LeftAligned());
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
                AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks);
 | 
			
		||||
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
                AnsiConsole.Render(new Panel("[u]Custom colors[/]").Expand());
 | 
			
		||||
                AnsiConsole.Render(new Rule("Compact + Custom colors").LeftAligned());
 | 
			
		||||
                AnsiConsole.WriteLine();
 | 
			
		||||
                AnsiConsole.WriteException(ex, new ExceptionSettings
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								examples/Rules/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								examples/Rules/Program.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
using Spectre.Console;
 | 
			
		||||
 | 
			
		||||
namespace EmojiExample
 | 
			
		||||
{
 | 
			
		||||
    public static class Program
 | 
			
		||||
    {
 | 
			
		||||
        public static void Main(string[] args)
 | 
			
		||||
        {
 | 
			
		||||
            // No title
 | 
			
		||||
            Render(new Rule().SetStyle("yellow"));
 | 
			
		||||
 | 
			
		||||
            // Left aligned title
 | 
			
		||||
            Render(new Rule("[white]Left aligned[/]").LeftAligned().SetStyle("red"));
 | 
			
		||||
 | 
			
		||||
            // Centered title
 | 
			
		||||
            Render(new Rule("[silver]Centered[/]").Centered().SetStyle("green"));
 | 
			
		||||
 | 
			
		||||
            // Right aligned title
 | 
			
		||||
            Render(new Rule("[grey]Right aligned[/]").RightAligned().SetStyle("blue"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void Render(Rule rule)
 | 
			
		||||
        {
 | 
			
		||||
            AnsiConsole.Render(new Panel(rule).Expand().SetBorderStyle(Style.Parse("grey")));
 | 
			
		||||
            AnsiConsole.WriteLine();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								examples/Rules/Rules.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								examples/Rules/Rules.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <OutputType>Exe</OutputType>
 | 
			
		||||
    <TargetFramework>netcoreapp3.1</TargetFramework>
 | 
			
		||||
    <IsPackable>false</IsPackable>
 | 
			
		||||
    <Title>Rules</Title>
 | 
			
		||||
    <Description>Demonstrates how to render horizontal rules (lines).</Description>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
							
								
								
									
										125
									
								
								src/Spectre.Console.Tests/Unit/RuleTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/Spectre.Console.Tests/Unit/RuleTests.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using Xunit;
 | 
			
		||||
 | 
			
		||||
namespace Spectre.Console.Tests.Unit
 | 
			
		||||
{
 | 
			
		||||
    public sealed class RuleTests
 | 
			
		||||
    {
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void Should_Render_Default_Rule_Without_Title()
 | 
			
		||||
        {
 | 
			
		||||
            // Given
 | 
			
		||||
            var console = new PlainConsole(width: 40);
 | 
			
		||||
 | 
			
		||||
            // When
 | 
			
		||||
            console.Render(new Rule());
 | 
			
		||||
 | 
			
		||||
            // Then
 | 
			
		||||
            console.Lines.Count.ShouldBe(1);
 | 
			
		||||
            console.Lines[0].ShouldBe("────────────────────────────────────────");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void Should_Render_Default_Rule_With_Title_Centered_By_Default()
 | 
			
		||||
        {
 | 
			
		||||
            // Given
 | 
			
		||||
            var console = new PlainConsole(width: 40);
 | 
			
		||||
 | 
			
		||||
            // When
 | 
			
		||||
            console.Render(new Rule("Hello World"));
 | 
			
		||||
 | 
			
		||||
            // Then
 | 
			
		||||
            console.Lines.Count.ShouldBe(1);
 | 
			
		||||
            console.Lines[0].ShouldBe("───────────── Hello World ──────────────");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void Should_Render_Default_Rule_With_Title_Left_Aligned()
 | 
			
		||||
        {
 | 
			
		||||
            // Given
 | 
			
		||||
            var console = new PlainConsole(width: 40);
 | 
			
		||||
 | 
			
		||||
            // When
 | 
			
		||||
            console.Render(new Rule("Hello World")
 | 
			
		||||
            {
 | 
			
		||||
                Alignment = Justify.Left,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Then
 | 
			
		||||
            console.Lines.Count.ShouldBe(1);
 | 
			
		||||
            console.Lines[0].ShouldBe("── Hello World ─────────────────────────");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void Should_Render_Default_Rule_With_Title_Right_Aligned()
 | 
			
		||||
        {
 | 
			
		||||
            // Given
 | 
			
		||||
            var console = new PlainConsole(width: 40);
 | 
			
		||||
 | 
			
		||||
            // When
 | 
			
		||||
            console.Render(new Rule("Hello World")
 | 
			
		||||
            {
 | 
			
		||||
                Alignment = Justify.Right,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Then
 | 
			
		||||
            console.Lines.Count.ShouldBe(1);
 | 
			
		||||
            console.Lines[0].ShouldBe("───────────────────────── Hello World ──");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void Should_Convert_Line_Breaks_In_Title_To_Spaces()
 | 
			
		||||
        {
 | 
			
		||||
            // Given
 | 
			
		||||
            var console = new PlainConsole(width: 40);
 | 
			
		||||
 | 
			
		||||
            // When
 | 
			
		||||
            console.Render(new Rule("Hello\nWorld\r\n!"));
 | 
			
		||||
 | 
			
		||||
            // Then
 | 
			
		||||
            console.Lines.Count.ShouldBe(1);
 | 
			
		||||
            console.Lines[0].ShouldBe("──────────── Hello World ! ─────────────");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void Should_Truncate_Title()
 | 
			
		||||
        {
 | 
			
		||||
            // Given
 | 
			
		||||
            var console = new PlainConsole(width: 40);
 | 
			
		||||
 | 
			
		||||
            // When
 | 
			
		||||
            console.Render(new Rule("          Hello World    "));
 | 
			
		||||
 | 
			
		||||
            // Then
 | 
			
		||||
            console.Lines.Count.ShouldBe(1);
 | 
			
		||||
            console.Lines[0].ShouldBe("───────────── Hello World ──────────────");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Theory]
 | 
			
		||||
        [InlineData(0, "Hello World Hello World Hello World Hello World Hello World", "")]
 | 
			
		||||
        [InlineData(1, "Hello World Hello World Hello World Hello World Hello World", "─")]
 | 
			
		||||
        [InlineData(2, "Hello World Hello World Hello World Hello World Hello World", "──")]
 | 
			
		||||
        [InlineData(3, "Hello World Hello World Hello World Hello World Hello World", "───")]
 | 
			
		||||
        [InlineData(4, "Hello World Hello World Hello World Hello World Hello World", "────")]
 | 
			
		||||
        [InlineData(5, "Hello World Hello World Hello World Hello World Hello World", "─────")]
 | 
			
		||||
        [InlineData(6, "Hello World Hello World Hello World Hello World Hello World", "──────")]
 | 
			
		||||
        [InlineData(7, "Hello World Hello World Hello World Hello World Hello World", "───────")]
 | 
			
		||||
        [InlineData(8, "Hello World Hello World Hello World Hello World Hello World", "── H… ──")]
 | 
			
		||||
        [InlineData(8, "A", "── A ───")]
 | 
			
		||||
        [InlineData(8, "AB", "── AB ──")]
 | 
			
		||||
        [InlineData(8, "ABC", "── A… ──")]
 | 
			
		||||
        [InlineData(40, "Hello World Hello World Hello World Hello World Hello World", "──── Hello World Hello World Hello… ────")]
 | 
			
		||||
        public void Should_Truncate_Too_Long_Title(int width, string input, string expected)
 | 
			
		||||
        {
 | 
			
		||||
            // Given
 | 
			
		||||
            var console = new PlainConsole(width);
 | 
			
		||||
 | 
			
		||||
            // When
 | 
			
		||||
            console.Render(new Rule(input));
 | 
			
		||||
 | 
			
		||||
            // Then
 | 
			
		||||
            console.Lines.Count.ShouldBe(1);
 | 
			
		||||
            console.Lines[0].ShouldBe(expected);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -44,7 +44,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{C3E2CB
 | 
			
		||||
		..\.github\workflows\publish.yaml = ..\.github\workflows\publish.yaml
 | 
			
		||||
	EndProjectSection
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Calendars", "..\examples\Calendars\Calendars.csproj", "{57691C7D-683D-46E6-AA4F-57A8C5F65D25}"
 | 
			
		||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calendars", "..\examples\Calendars\Calendars.csproj", "{57691C7D-683D-46E6-AA4F-57A8C5F65D25}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules", "..\examples\Rules\Rules.csproj", "{8622A261-02C6-40CA-9797-E3F01ED87D6B}"
 | 
			
		||||
EndProject
 | 
			
		||||
Global
 | 
			
		||||
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
			
		||||
@@ -212,6 +214,18 @@ Global
 | 
			
		||||
		{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Release|x64.Build.0 = Release|Any CPU
 | 
			
		||||
		{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Release|x86.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Release|x86.Build.0 = Release|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Debug|x64.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Debug|x64.Build.0 = Debug|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Debug|x86.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Debug|x86.Build.0 = Debug|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|x64.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|x64.Build.0 = Release|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|x86.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|x86.Build.0 = Release|Any CPU
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
	GlobalSection(SolutionProperties) = preSolution
 | 
			
		||||
		HideSolutionNode = FALSE
 | 
			
		||||
@@ -229,6 +243,7 @@ Global
 | 
			
		||||
		{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
 | 
			
		||||
		{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F}
 | 
			
		||||
		{57691C7D-683D-46E6-AA4F-57A8C5F65D25} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
 | 
			
		||||
		{8622A261-02C6-40CA-9797-E3F01ED87D6B} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
	GlobalSection(ExtensibilityGlobals) = postSolution
 | 
			
		||||
		SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										75
									
								
								src/Spectre.Console/Extensions/RuleExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/Spectre.Console/Extensions/RuleExtensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace Spectre.Console
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Contains extension methods for <see cref="RuleExtensions"/>.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static class RuleExtensions
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Sets the rule title.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="rule">The rule.</param>
 | 
			
		||||
        /// <param name="title">The title.</param>
 | 
			
		||||
        /// <returns>The same instance so that multiple calls can be chained.</returns>
 | 
			
		||||
        public static Rule SetTitle(this Rule rule, string title)
 | 
			
		||||
        {
 | 
			
		||||
            if (rule is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(rule));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (title is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(title));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            rule.Title = title;
 | 
			
		||||
            return rule;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Sets the rule style.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="rule">The rule.</param>
 | 
			
		||||
        /// <param name="style">The rule style string.</param>
 | 
			
		||||
        /// <returns>The same instance so that multiple calls can be chained.</returns>
 | 
			
		||||
        public static Rule SetStyle(this Rule rule, string style)
 | 
			
		||||
        {
 | 
			
		||||
            if (rule is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(rule));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (style is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(style));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return SetStyle(rule, Style.Parse(style));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Sets the rule style.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="rule">The rule.</param>
 | 
			
		||||
        /// <param name="style">The rule style.</param>
 | 
			
		||||
        /// <returns>The same instance so that multiple calls can be chained.</returns>
 | 
			
		||||
        public static Rule SetStyle(this Rule rule, Style style)
 | 
			
		||||
        {
 | 
			
		||||
            if (rule is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(rule));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (style is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(style));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            rule.Style = style;
 | 
			
		||||
            return rule;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -27,6 +27,12 @@ namespace Spectre.Console.Rendering
 | 
			
		||||
        /// </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>
 | 
			
		||||
@@ -34,21 +40,42 @@ namespace Spectre.Console.Rendering
 | 
			
		||||
        /// <param name="legacyConsole">A value indicating whether or not this a legacy console (i.e. cmd.exe).</param>
 | 
			
		||||
        /// <param name="justification">The justification to use when rendering.</param>
 | 
			
		||||
        public RenderContext(Encoding encoding, bool legacyConsole, Justify? justification = null)
 | 
			
		||||
            : this(encoding, legacyConsole, justification, false)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private RenderContext(Encoding encoding, bool legacyConsole, Justify? justification = null, bool singleLine = false)
 | 
			
		||||
        {
 | 
			
		||||
            Encoding = encoding ?? throw new System.ArgumentNullException(nameof(encoding));
 | 
			
		||||
            LegacyConsole = legacyConsole;
 | 
			
		||||
            Justification = justification;
 | 
			
		||||
            Unicode = Encoding == Encoding.UTF8 || Encoding == Encoding.Unicode;
 | 
			
		||||
            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 with the specified justification.</returns>
 | 
			
		||||
        /// <returns>A new <see cref="RenderContext"/> instance.</returns>
 | 
			
		||||
        public RenderContext WithJustification(Justify? justification)
 | 
			
		||||
        {
 | 
			
		||||
            return new RenderContext(Encoding, LegacyConsole, justification);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <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(Encoding, LegacyConsole, Justification, true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using Spectre.Console.Internal;
 | 
			
		||||
 | 
			
		||||
namespace Spectre.Console.Rendering
 | 
			
		||||
@@ -125,48 +127,11 @@ namespace Spectre.Console.Rendering
 | 
			
		||||
        /// <param name="context">The render context.</param>
 | 
			
		||||
        /// <param name="segments">The segments to measure.</param>
 | 
			
		||||
        /// <returns>The number of cells that the segments occupies in the console.</returns>
 | 
			
		||||
        public static int CellLength(RenderContext context, List<Segment> segments)
 | 
			
		||||
        public static int CellLength(RenderContext context, IEnumerable<Segment> segments)
 | 
			
		||||
        {
 | 
			
		||||
            return segments.Sum(segment => segment.CellLength(context));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Truncates the segments to the specified width.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="context">The render context.</param>
 | 
			
		||||
        /// <param name="segments">The segments to truncate.</param>
 | 
			
		||||
        /// <param name="maxWidth">The maximum width that the segments may occupy.</param>
 | 
			
		||||
        /// <returns>A list of segments that has been truncated.</returns>
 | 
			
		||||
        public static List<Segment> Truncate(RenderContext context, IEnumerable<Segment> segments, int maxWidth)
 | 
			
		||||
        {
 | 
			
		||||
            if (context is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(context));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (segments is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(segments));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var result = new List<Segment>();
 | 
			
		||||
 | 
			
		||||
            var totalWidth = 0;
 | 
			
		||||
            foreach (var segment in segments)
 | 
			
		||||
            {
 | 
			
		||||
                var segmentWidth = segment.CellLength(context);
 | 
			
		||||
                if (totalWidth + segmentWidth > maxWidth)
 | 
			
		||||
                {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                result.Add(segment);
 | 
			
		||||
                totalWidth += segmentWidth;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Splits the provided segments into lines.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -387,6 +352,90 @@ namespace Spectre.Console.Rendering
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Truncates the segments to the specified width.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="context">The render context.</param>
 | 
			
		||||
        /// <param name="segments">The segments to truncate.</param>
 | 
			
		||||
        /// <param name="maxWidth">The maximum width that the segments may occupy.</param>
 | 
			
		||||
        /// <returns>A list of segments that has been truncated.</returns>
 | 
			
		||||
        public static List<Segment> Truncate(RenderContext context, IEnumerable<Segment> segments, int maxWidth)
 | 
			
		||||
        {
 | 
			
		||||
            if (context is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(context));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (segments is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(segments));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var result = new List<Segment>();
 | 
			
		||||
 | 
			
		||||
            var totalWidth = 0;
 | 
			
		||||
            foreach (var segment in segments)
 | 
			
		||||
            {
 | 
			
		||||
                var segmentWidth = segment.CellLength(context);
 | 
			
		||||
                if (totalWidth + segmentWidth > maxWidth)
 | 
			
		||||
                {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                result.Add(segment);
 | 
			
		||||
                totalWidth += segmentWidth;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (result.Count == 0 && segments.Any())
 | 
			
		||||
            {
 | 
			
		||||
                var segment = Truncate(context, segments.First(), maxWidth);
 | 
			
		||||
                if (segment != null)
 | 
			
		||||
                {
 | 
			
		||||
                    result.Add(segment);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Truncates the segment to the specified width.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="context">The render context.</param>
 | 
			
		||||
        /// <param name="segment">The segment to truncate.</param>
 | 
			
		||||
        /// <param name="maxWidth">The maximum width that the segment may occupy.</param>
 | 
			
		||||
        /// <returns>A new truncated segment, or <c>null</c>.</returns>
 | 
			
		||||
        public static Segment? Truncate(RenderContext context, Segment segment, int maxWidth)
 | 
			
		||||
        {
 | 
			
		||||
            if (segment is null)
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (segment.CellLength(context) <= maxWidth)
 | 
			
		||||
            {
 | 
			
		||||
                return segment;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var builder = new StringBuilder();
 | 
			
		||||
            foreach (var character in segment.Text)
 | 
			
		||||
            {
 | 
			
		||||
                if (Cell.GetCellLength(context, builder.ToString()) >= maxWidth)
 | 
			
		||||
                {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                builder.Append(character);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (builder.Length == 0)
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new Segment(builder.ToString(), segment.Style);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal static Segment TruncateWithEllipsis(string text, Style style, RenderContext context, int maxWidth)
 | 
			
		||||
        {
 | 
			
		||||
            return SplitOverflow(
 | 
			
		||||
@@ -396,6 +445,46 @@ namespace Spectre.Console.Rendering
 | 
			
		||||
                maxWidth)[0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal static List<Segment> TruncateWithEllipsis(IEnumerable<Segment> segments, RenderContext context, int maxWidth)
 | 
			
		||||
        {
 | 
			
		||||
            if (CellLength(context, segments) <= maxWidth)
 | 
			
		||||
            {
 | 
			
		||||
                return new List<Segment>(segments);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            segments = TrimEnd(Truncate(context, segments, maxWidth - 1));
 | 
			
		||||
            if (!segments.Any())
 | 
			
		||||
            {
 | 
			
		||||
                return new List<Segment>(1);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var result = new List<Segment>(segments);
 | 
			
		||||
            result.Add(new Segment("…", result.Last().Style));
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal static List<Segment> TrimEnd(IEnumerable<Segment> segments)
 | 
			
		||||
        {
 | 
			
		||||
            var stack = new Stack<Segment>();
 | 
			
		||||
            var checkForWhitespace = true;
 | 
			
		||||
            foreach (var segment in segments.Reverse())
 | 
			
		||||
            {
 | 
			
		||||
                if (checkForWhitespace)
 | 
			
		||||
                {
 | 
			
		||||
                    if (segment.IsWhiteSpace)
 | 
			
		||||
                    {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    checkForWhitespace = false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                stack.Push(segment);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return stack.ToList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var cell in cells)
 | 
			
		||||
 
 | 
			
		||||
@@ -66,10 +66,15 @@ namespace Spectre.Console
 | 
			
		||||
 | 
			
		||||
            var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
 | 
			
		||||
            var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
 | 
			
		||||
            if (columnCount == 0)
 | 
			
		||||
            {
 | 
			
		||||
                // Temporary work around for extremely small consoles
 | 
			
		||||
                return new Measurement(maxWidth, maxWidth);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var rows = _items.Count / columnCount;
 | 
			
		||||
            var rows = _items.Count / Math.Max(columnCount, 1);
 | 
			
		||||
            var greatestWidth = 0;
 | 
			
		||||
            for (var row = 0; row < rows; row += columnCount)
 | 
			
		||||
            for (var row = 0; row < rows; row += Math.Max(1, columnCount))
 | 
			
		||||
            {
 | 
			
		||||
                var widths = itemWidths.Skip(row * columnCount).Take(columnCount).ToList();
 | 
			
		||||
                var totalWidth = widths.Sum() + (maxPadding * (widths.Count - 1));
 | 
			
		||||
@@ -89,6 +94,11 @@ namespace Spectre.Console
 | 
			
		||||
 | 
			
		||||
            var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
 | 
			
		||||
            var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
 | 
			
		||||
            if (columnCount == 0)
 | 
			
		||||
            {
 | 
			
		||||
                // Temporary work around for extremely small consoles
 | 
			
		||||
                columnCount = 1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var table = new Table();
 | 
			
		||||
            table.NoBorder();
 | 
			
		||||
 
 | 
			
		||||
@@ -138,7 +138,9 @@ namespace Spectre.Console
 | 
			
		||||
                return Array.Empty<Segment>();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var lines = SplitLines(context, maxWidth);
 | 
			
		||||
            var lines = context.SingleLine
 | 
			
		||||
                ? new List<SegmentLine>(_lines)
 | 
			
		||||
                : SplitLines(context, maxWidth);
 | 
			
		||||
 | 
			
		||||
            // Justify lines
 | 
			
		||||
            var justification = context.Justification ?? Alignment ?? Justify.Left;
 | 
			
		||||
@@ -170,6 +172,11 @@ namespace Spectre.Console
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (context.SingleLine)
 | 
			
		||||
            {
 | 
			
		||||
                return lines.First().Where(segment => !segment.IsLineBreak);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new SegmentLineEnumerator(lines);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										132
									
								
								src/Spectre.Console/Widgets/Rule.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/Spectre.Console/Widgets/Rule.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Spectre.Console.Internal;
 | 
			
		||||
using Spectre.Console.Rendering;
 | 
			
		||||
 | 
			
		||||
namespace Spectre.Console
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// A renderable horizontal rule.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public sealed class Rule : Renderable, IAlignable
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the rule title markup text.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string? Title { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the rule style.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Style? Style { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the rule's title alignment.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Justify? Alignment { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="Rule"/> class.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Rule()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="Rule"/> class.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="title">The rule title markup text.</param>
 | 
			
		||||
        public Rule(string title)
 | 
			
		||||
        {
 | 
			
		||||
            Title = title ?? throw new ArgumentNullException(nameof(title));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
 | 
			
		||||
        {
 | 
			
		||||
            if (Title == null || maxWidth <= 6)
 | 
			
		||||
            {
 | 
			
		||||
                return GetLineWithoutTitle(maxWidth);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get the title and make sure it fits.
 | 
			
		||||
            var title = GetTitleSegments(context, Title, maxWidth - 6);
 | 
			
		||||
            if (Segment.CellLength(context, title) > maxWidth - 6)
 | 
			
		||||
            {
 | 
			
		||||
                // Truncate the title
 | 
			
		||||
                title = Segment.TruncateWithEllipsis(title, context, maxWidth - 6);
 | 
			
		||||
                if (!title.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    // We couldn't fit the title at all.
 | 
			
		||||
                    return GetLineWithoutTitle(maxWidth);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var (left, right) = GetLineSegments(context, maxWidth, title);
 | 
			
		||||
 | 
			
		||||
            var segments = new List<Segment>();
 | 
			
		||||
            segments.Add(left);
 | 
			
		||||
            segments.AddRange(title);
 | 
			
		||||
            segments.Add(right);
 | 
			
		||||
            segments.Add(Segment.LineBreak);
 | 
			
		||||
 | 
			
		||||
            return segments;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IEnumerable<Segment> GetLineWithoutTitle(int maxWidth)
 | 
			
		||||
        {
 | 
			
		||||
            var text = new string('─', maxWidth);
 | 
			
		||||
            return new[]
 | 
			
		||||
            {
 | 
			
		||||
                new Segment(text, Style ?? Style.Plain),
 | 
			
		||||
                Segment.LineBreak,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private (Segment Left, Segment Right) GetLineSegments(RenderContext context, int maxWidth, IEnumerable<Segment> title)
 | 
			
		||||
        {
 | 
			
		||||
            var alignment = Alignment ?? Justify.Center;
 | 
			
		||||
 | 
			
		||||
            var titleLength = Segment.CellLength(context, title);
 | 
			
		||||
 | 
			
		||||
            if (alignment == Justify.Left)
 | 
			
		||||
            {
 | 
			
		||||
                var left = new Segment(new string('─', 2) + " ", Style ?? Style.Plain);
 | 
			
		||||
 | 
			
		||||
                var rightLength = maxWidth - titleLength - left.CellLength(context) - 1;
 | 
			
		||||
                var right = new Segment(" " + new string('─', 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 rightLength = maxWidth - titleLength - left.CellLength(context) - 1;
 | 
			
		||||
                var right = new Segment(" " + new string('─', rightLength), Style ?? Style.Plain);
 | 
			
		||||
 | 
			
		||||
                return (left, right);
 | 
			
		||||
            }
 | 
			
		||||
            else if (alignment == Justify.Right)
 | 
			
		||||
            {
 | 
			
		||||
                var right = new Segment(" " + new string('─', 2), Style ?? Style.Plain);
 | 
			
		||||
 | 
			
		||||
                var leftLength = maxWidth - titleLength - right.CellLength(context) - 1;
 | 
			
		||||
                var left = new Segment(new string('─', leftLength) + " ", Style ?? Style.Plain);
 | 
			
		||||
 | 
			
		||||
                return (left, right);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            throw new NotSupportedException("Unsupported alignment.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IEnumerable<Segment> 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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user