Skip to content

Commit 7a591b7

Browse files
committed
Move and reorganize custom renderable documentation into new "How-To" and "Tutorials" sections, add a custom "Pill" widget tutorial, and update related assets and examples.
1 parent fccc35d commit 7a591b7

8 files changed

Lines changed: 539 additions & 15 deletions

File tree

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using Spectre.Console;
2+
using Spectre.Console.Rendering;
3+
4+
namespace Spectre.Docs.Examples.SpectreConsole.HowTo;
5+
6+
internal static class CreatingCustomRenderablesHowTo
7+
{
8+
/// <summary>
9+
/// Create a minimal custom renderable that displays styled text.
10+
/// </summary>
11+
public static void ImplementIRenderable()
12+
{
13+
var label = new Label("Success");
14+
AnsiConsole.Write(label);
15+
AnsiConsole.WriteLine();
16+
}
17+
18+
/// <summary>
19+
/// Implement Measure() to report your widget's size constraints.
20+
/// </summary>
21+
public static void CalculateSizeConstraints()
22+
{
23+
var label = new Label("Processing");
24+
AnsiConsole.Write(label);
25+
AnsiConsole.WriteLine();
26+
}
27+
28+
/// <summary>
29+
/// Implement Render() to yield Segment objects with styled text.
30+
/// </summary>
31+
public static void GenerateSegments()
32+
{
33+
var label = new Label("Warning");
34+
AnsiConsole.Write(label);
35+
AnsiConsole.WriteLine();
36+
}
37+
38+
/// <summary>
39+
/// Apply foreground and background colors to your label.
40+
/// </summary>
41+
public static void ApplyStyles()
42+
{
43+
var success = new Label("OK", Color.White, Color.Green);
44+
var warning = new Label("WARN", Color.Black, Color.Yellow);
45+
var error = new Label("ERROR", Color.White, Color.Red);
46+
47+
AnsiConsole.Write(success);
48+
AnsiConsole.Write(" ");
49+
AnsiConsole.Write(warning);
50+
AnsiConsole.Write(" ");
51+
AnsiConsole.Write(error);
52+
AnsiConsole.WriteLine();
53+
}
54+
55+
/// <summary>
56+
/// Create a container that wraps another renderable.
57+
/// </summary>
58+
public static void WrapOtherRenderables()
59+
{
60+
var status = new LabeledValue(
61+
new Label("Status", Color.White, Color.Blue),
62+
new Text("All systems operational", new Style(Color.Green)));
63+
64+
AnsiConsole.Write(status);
65+
AnsiConsole.WriteLine();
66+
}
67+
68+
/// <summary>
69+
/// Complete example: styled labels with different colors.
70+
/// </summary>
71+
public static void RunAll()
72+
{
73+
AnsiConsole.MarkupLine("[dim]Step 1: Basic label[/]");
74+
ImplementIRenderable();
75+
AnsiConsole.WriteLine();
76+
77+
AnsiConsole.MarkupLine("[dim]Step 2: Styled labels[/]");
78+
ApplyStyles();
79+
AnsiConsole.WriteLine();
80+
81+
AnsiConsole.MarkupLine("[dim]Step 3: Container with label[/]");
82+
WrapOtherRenderables();
83+
}
84+
}
85+
86+
/// <summary>
87+
/// A custom renderable that displays text as a styled badge.
88+
/// </summary>
89+
internal sealed class Label : IRenderable
90+
{
91+
private readonly string _text;
92+
private readonly Style _style;
93+
94+
public Label(string text)
95+
: this(text, Color.Black, Color.Grey)
96+
{
97+
}
98+
99+
public Label(string text, Color foreground, Color background)
100+
{
101+
_text = text;
102+
_style = new Style(foreground, background);
103+
}
104+
105+
public Measurement Measure(RenderOptions options, int maxWidth)
106+
{
107+
// Add 2 for padding (space on each side)
108+
var width = _text.Length + 2;
109+
return new Measurement(width, width);
110+
}
111+
112+
public IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
113+
{
114+
// Render padded text with our style
115+
yield return new Segment($" {_text} ", _style);
116+
}
117+
}
118+
119+
/// <summary>
120+
/// A container that displays a label followed by content.
121+
/// </summary>
122+
internal sealed class LabeledValue : IRenderable
123+
{
124+
private readonly IRenderable _label;
125+
private readonly IRenderable _value;
126+
127+
public LabeledValue(IRenderable label, IRenderable value)
128+
{
129+
_label = label;
130+
_value = value;
131+
}
132+
133+
public Measurement Measure(RenderOptions options, int maxWidth)
134+
{
135+
var labelMeasure = _label.Measure(options, maxWidth);
136+
var remaining = maxWidth - labelMeasure.Max - 1; // -1 for space
137+
var valueMeasure = _value.Measure(options, Math.Max(0, remaining));
138+
139+
var min = labelMeasure.Min + 1 + valueMeasure.Min;
140+
var max = labelMeasure.Max + 1 + valueMeasure.Max;
141+
return new Measurement(min, max);
142+
}
143+
144+
public IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
145+
{
146+
// Render label
147+
var labelMeasure = _label.Measure(options, maxWidth);
148+
foreach (var segment in _label.Render(options, labelMeasure.Max))
149+
{
150+
yield return segment;
151+
}
152+
153+
// Space between label and value
154+
yield return new Segment(" ");
155+
156+
// Render value with remaining width
157+
var remaining = maxWidth - labelMeasure.Max - 1;
158+
foreach (var segment in _value.Render(options, Math.Max(0, remaining)))
159+
{
160+
yield return segment;
161+
}
162+
}
163+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
using Spectre.Console;
2+
using Spectre.Console.Rendering;
3+
using Spectre.Docs.Examples.Showcase;
4+
5+
namespace Spectre.Docs.Examples.SpectreConsole.Tutorials;
6+
7+
/// <summary>
8+
/// A tutorial that builds a custom Pill widget implementing IRenderable.
9+
/// Demonstrates the rendering model, measurements, segments, and capability detection.
10+
/// </summary>
11+
public class CreatingCustomRenderablesTutorial : BaseSample
12+
{
13+
/// <inheritdoc />
14+
public override void Run(IAnsiConsole console)
15+
{
16+
var table = new Table()
17+
.Border(TableBorder.Rounded)
18+
.AddColumn("Status")
19+
.AddColumn("Message");
20+
21+
table.AddRow(new Pill("Success", PillType.Success), new Text("All systems operational"));
22+
table.AddRow(new Pill("Warning", PillType.Warning), new Text("High memory usage detected"));
23+
table.AddRow(new Pill("Error", PillType.Error), new Text("Database connection failed"));
24+
table.AddRow(new Pill("Info", PillType.Info), new Text("Scheduled maintenance at 2:00 AM"));
25+
26+
console.Write(table);
27+
}
28+
29+
/// <summary>Creates a basic Pill class that implements IRenderable.</summary>
30+
public static void CreatePillClass()
31+
{
32+
// The Pill class implements IRenderable, which requires two methods:
33+
// - Measure(): Reports width constraints
34+
// - Render(): Produces styled output segments
35+
}
36+
37+
/// <summary>Implements Measure to calculate the pill's width.</summary>
38+
public static void ImplementMeasure()
39+
{
40+
// Measure returns a Measurement with minimum and maximum width.
41+
// Our pill width = text length + 2 padding spaces + 2 cap characters
42+
var text = "Success";
43+
var width = text.Length + 4;
44+
var measurement = new Measurement(width, width);
45+
AnsiConsole.WriteLine($"Pill width for '{text}': {measurement.Max} cells");
46+
}
47+
48+
/// <summary>Implements Render to yield styled segments.</summary>
49+
public static void ImplementRender()
50+
{
51+
// Render yields Segment objects containing text and style.
52+
// Each segment is an atomic unit of styled output.
53+
var pill = new Pill("Test", PillType.Info);
54+
AnsiConsole.Write(pill);
55+
AnsiConsole.WriteLine();
56+
}
57+
58+
/// <summary>Creates pills with different type variants.</summary>
59+
public static void CreateColoredPills()
60+
{
61+
var success = new Pill("Success", PillType.Success);
62+
var warning = new Pill("Warning", PillType.Warning);
63+
var error = new Pill("Error", PillType.Error);
64+
65+
AnsiConsole.Write(success);
66+
AnsiConsole.Write(" ");
67+
AnsiConsole.Write(warning);
68+
AnsiConsole.Write(" ");
69+
AnsiConsole.Write(error);
70+
AnsiConsole.WriteLine();
71+
}
72+
}
73+
74+
/// <summary>
75+
/// Defines the visual style variants for a Pill widget.
76+
/// </summary>
77+
public enum PillType
78+
{
79+
/// <summary>Green pill for success states.</summary>
80+
Success,
81+
/// <summary>Yellow pill for warning states.</summary>
82+
Warning,
83+
/// <summary>Red pill for error states.</summary>
84+
Error,
85+
/// <summary>Blue pill for informational states.</summary>
86+
Info,
87+
}
88+
89+
/// <summary>
90+
/// A custom renderable that displays text as a colored pill with rounded end caps.
91+
/// </summary>
92+
public sealed class Pill : IRenderable
93+
{
94+
private readonly string _text;
95+
private readonly Style _style;
96+
97+
/// <summary>
98+
/// Creates a new pill with the specified text and type.
99+
/// </summary>
100+
/// <param name="text">The text to display inside the pill.</param>
101+
/// <param name="type">The pill type which determines its color scheme.</param>
102+
public Pill(string text, PillType type)
103+
{
104+
_text = text;
105+
_style = GetStyleForType(type);
106+
}
107+
108+
private static Style GetStyleForType(PillType type) => type switch
109+
{
110+
PillType.Success => new Style(Color.White, Color.Green),
111+
PillType.Warning => new Style(Color.Black, Color.Yellow),
112+
PillType.Error => new Style(Color.White, Color.Red),
113+
PillType.Info => new Style(Color.White, Color.Blue),
114+
_ => new Style(Color.White, Color.Grey),
115+
};
116+
117+
/// <summary>
118+
/// Measures the pill's width in console cells.
119+
/// </summary>
120+
public Measurement Measure(RenderOptions options, int maxWidth)
121+
{
122+
// Width = text + 2 padding spaces + 2 cap characters
123+
var width = _text.Length + 4;
124+
return new Measurement(width, width);
125+
}
126+
127+
/// <summary>
128+
/// Renders the pill as a sequence of styled segments.
129+
/// </summary>
130+
public IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
131+
{
132+
// Use rounded half-circles if Unicode is supported, otherwise spaces
133+
const string LeftCap = "\uE0B6";
134+
const string RightCap = "\uE0B4";
135+
136+
var inverseStyle = new Style(_style.Background);
137+
138+
if (options.Capabilities.Unicode)
139+
{
140+
yield return new Segment(LeftCap, inverseStyle);
141+
yield return new Segment($" {_text} ", _style);
142+
yield return new Segment(RightCap, inverseStyle);
143+
}
144+
else
145+
{
146+
yield return new Segment($" {_text} ", _style);
147+
}
148+
149+
}
150+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Output "anim.svg"
2+
3+
Set DisableCursor true
4+
Set FontSize 22
5+
Set Theme "one dark"
6+
Set TransparentBackground "true"
7+
Set Cols 82
8+
Set Rows 10
9+
10+
Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase creating-custom-renderables-tutorial"
11+
Wait
12+
Screenshot "Spectre.Docs/Content/assets/creating-custom-renderables-tutorial.svg"

Spectre.Docs/Content/assets/cli-homepage.svg

Lines changed: 1 addition & 0 deletions
Loading

Spectre.Docs/Content/assets/creating-custom-renderables-tutorial.svg

Lines changed: 1 addition & 0 deletions
Loading

Spectre.Docs/Content/console/explanation/extending-with-custom-renderables.md

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)