Skip to content

Commit d48846f

Browse files
Show how to theme a complete layout (#44)
1 parent a872663 commit d48846f

7 files changed

Lines changed: 117 additions & 14 deletions

File tree

‎README.md‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<a href="#quick-start">Quick start</a> ·
2323
<a href="#packages">Packages</a> ·
2424
<a href="#unicode-aware-layout">Layout</a> ·
25+
<a href="#theme-a-whole-layout">Theming</a> ·
2526
<a href="#documentation">Documentation</a> ·
2627
<a href="#contributing">Contributing</a>
2728
</p>
@@ -62,6 +63,43 @@ and naturally flowing content in HTML.
6263

6364
![The same MarkupString figure rendered as ANSI terminal art and an HTML image](docs/assets/showcase-image-drawing.png)
6465

66+
## Theme a whole layout
67+
68+
A theme changes semantic parts together: borders, titles, labels, bullets, guides, gauges, table
69+
headings, and striped rows. Presets can change the drawing characters too; generated palettes build
70+
accessible colour roles from a single seed.
71+
72+
```csharp
73+
var registry = MarkupRegistry.Empty.WithAnsi().WithHtml();
74+
75+
var sheet = new Stack(
76+
[
77+
new Fields(
78+
[
79+
new Field(MarkupText.Plain("Name"), MarkupText.Plain("Lyra Vale")),
80+
new Field(MarkupText.Plain("Role"), MarkupText.Plain("Wayfinder")),
81+
]),
82+
new Gauge(7, 10) { Label = MarkupText.Plain("Trail") },
83+
]).Bordered(MarkupText.Plain("WAYFINDER'S JOURNAL"));
84+
85+
var fantasy = sheet.Themed(ThemePalette.Preset("fantasy")!.ToLayoutTheme());
86+
var housePalette = ThemePalette.Generate(
87+
new RgbColor(34, 211, 238),
88+
ThemeHarmony.Triadic,
89+
contrast: 0.35);
90+
var house = sheet.Themed(housePalette.ToLayoutTheme());
91+
92+
var value = BlockLayout.Build(house, width: 46);
93+
var ansi = value.Render(MarkupFormat.Ansi, registry);
94+
var html = value.Render(MarkupFormat.Html, registry);
95+
```
96+
97+
![The same MarkupString layout rendered with a fantasy preset and a generated triadic palette](docs/assets/showcase-theming.png)
98+
99+
Use `.Themed(...)` to override the surrounding theme, or `.ThemedUnder(...)` to provide defaults
100+
that a reader's theme can replace. See [themes and palettes](docs/layout.md#themes-and-palettes) for
101+
the built-in presets, light and dark modes, custom glyphs, base16 schemes, JSON, and contrast checks.
102+
65103
## Why MarkupString?
66104

67105
- **One source of truth** — keep content and semantics together, then choose the output format at
-169 Bytes
Loading
-169 Bytes
Loading

‎docs/assets/showcase-theming.png‎

182 KB
Loading

‎docs/showcase/Program.cs‎

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,77 @@
7474
figureValue,
7575
registry);
7676

77+
var character = new Stack(
78+
[
79+
new Fields(
80+
[
81+
new Field(P("Name"), P("Lyra Vale")),
82+
new Field(P("Role"), P("Wayfinder")),
83+
new Field(P("Region"), P("Glasswood")),
84+
new Field(P("Status"), P("Ready")),
85+
]) { Columns = 2, Gap = 3, Leader = P("·") },
86+
new Rule(P("CURRENT QUEST")),
87+
new Gauge(7, 10) { Label = P("Trail"), Show = GaugeShow.Percent },
88+
new Bullets([P("Map the moonwell"), P("Return before dawn")]),
89+
]).Bordered(P("WAYFINDER'S JOURNAL"));
90+
91+
var fantasy = BlockLayout.Build(character.Themed(ThemePalette.Preset("fantasy")!.ToLayoutTheme()), 46);
92+
var generatedPalette = ThemePalette.Generate(new RgbColor(34, 211, 238), ThemeHarmony.Triadic, contrast: 0.35);
93+
var generated = BlockLayout.Build(character.Themed(generatedPalette.ToLayoutTheme()), 46);
94+
WriteThemePage(
95+
Path.Combine(output, "theming.html"),
96+
"One layout, a whole new world",
97+
"Presets and generated palettes recolour every semantic part — and can change the shapes too.",
98+
fantasy,
99+
generated,
100+
registry);
101+
77102
static MarkupText P(string text) => MarkupText.Plain(text);
78103

79104
static void WritePage(string path, string title, string subtitle, MarkupText value, MarkupRegistry registry)
80105
{
81106
var ansi = value.Render(MarkupFormat.Ansi, registry);
82107
var ansiForBrowser = AnsiEscapeParser.Parse(ansi).Render(MarkupFormat.Html, registry);
83108
var html = value.Render(MarkupFormat.Html, registry);
84-
var escapedAnsi = WebUtility.HtmlEncode(ansi.Replace("\u001b", "\\e", StringComparison.Ordinal));
109+
WriteComparisonPage(
110+
path, title, subtitle, "MarkupText <span>→</span> renderer <span>→</span> native output",
111+
"ANSI terminal", "SGR + cells", "terminal", ansiForBrowser,
112+
"HTML browser", "semantic elements", "browser", html);
113+
}
114+
115+
static void WriteThemePage(
116+
string path,
117+
string title,
118+
string subtitle,
119+
MarkupText preset,
120+
MarkupText generated,
121+
MarkupRegistry registry)
122+
{
123+
var presetHtml = AnsiEscapeParser.Parse(preset.Render(MarkupFormat.Ansi, registry)).Render(MarkupFormat.Html, registry);
124+
var generatedHtml = AnsiEscapeParser.Parse(generated.Render(MarkupFormat.Ansi, registry)).Render(MarkupFormat.Html, registry);
125+
WriteComparisonPage(
126+
path, title, subtitle, "layout <span>→</span> palette <span>→</span> themed output",
127+
"Fantasy preset", "colours + glyphs", "terminal", presetHtml,
128+
"Generated palette", "triadic harmony", "terminal", generatedHtml);
129+
}
130+
131+
static void WriteComparisonPage(
132+
string path,
133+
string title,
134+
string subtitle,
135+
string flowHtml,
136+
string leftLabel,
137+
string leftNative,
138+
string leftClass,
139+
string leftContent,
140+
string rightLabel,
141+
string rightNative,
142+
string rightClass,
143+
string rightContent)
144+
{
145+
static string PanelBody(string cssClass, string content) => cssClass == "terminal"
146+
? "<pre class=\"terminal\">" + content + "</pre>"
147+
: "<div class=\"browser\">" + content + "</div>";
85148

86149
File.WriteAllText(path, $$"""
87150
<!doctype html>
@@ -117,26 +180,24 @@ static void WritePage(string path, string title, string subtitle, MarkupText val
117180
.browser .ms-figure-image { max-width: 220px; margin: 0 20px 14px 0; filter: drop-shadow(0 10px 20px rgba(0, 0, 0, .35)); }
118181
.browser .ms-figure { font-family: "DejaVu Sans", sans-serif; }
119182
.browser .ms-text { line-height: 1.65; }
120-
.source { display: none; }
121183
</style>
122184
</head>
123185
<body>
124186
<main>
125187
<header>
126188
<div><h1>{{WebUtility.HtmlEncode(title)}}</h1><p class="subtitle">{{WebUtility.HtmlEncode(subtitle)}}</p></div>
127-
<div class="flow">MarkupText <span>→</span> renderer <span>→</span> native output</div>
189+
<div class="flow">{{flowHtml}}</div>
128190
</header>
129191
<section class="comparison">
130192
<article class="panel">
131-
<div class="panel-head"><i class="dot"></i><span class="label">ANSI terminal</span><span class="native">SGR + cells</span></div>
132-
<pre class="terminal">{{ansiForBrowser}}</pre>
193+
<div class="panel-head"><i class="dot"></i><span class="label">{{WebUtility.HtmlEncode(leftLabel)}}</span><span class="native">{{WebUtility.HtmlEncode(leftNative)}}</span></div>
194+
{{PanelBody(leftClass, leftContent)}}
133195
</article>
134196
<article class="panel">
135-
<div class="panel-head"><i class="dot"></i><span class="label">HTML browser</span><span class="native">semantic elements</span></div>
136-
<div class="browser">{{html}}</div>
197+
<div class="panel-head"><i class="dot"></i><span class="label">{{WebUtility.HtmlEncode(rightLabel)}}</span><span class="native">{{WebUtility.HtmlEncode(rightNative)}}</span></div>
198+
{{PanelBody(rightClass, rightContent)}}
137199
</article>
138200
</section>
139-
<pre class="source">{{escapedAnsi}}</pre>
140201
</main>
141202
</body>
142203
</html>

‎docs/showcase/README.md‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# README showcase captures
22

3-
The two README screenshots are generated from MarkupString values rather than recreated by hand.
3+
The README screenshots are generated from MarkupString values rather than recreated by hand.
44
The checked-in captures use this supported Linux x64 environment:
55

66
- the .NET SDK selected by the repository's `global.json`;
@@ -23,7 +23,9 @@ present. After restoring the repository once, run from its root:
2323
docs/showcase/capture.sh
2424
```
2525

26-
Each page places the same value's ANSI rendering beside its semantic HTML rendering. The ANSI side
27-
is parsed back only to make its terminal styling visible in the browser capture; its text and SGR
28-
styling come from `Render(MarkupFormat.Ansi)`. The capture script applies rounded alpha masks and
29-
transparent padding so the dark cards sit cleanly on both GitHub README themes.
26+
The format pages place the same value's ANSI rendering beside its semantic HTML rendering. The
27+
theme page places the same layout under a preset and a generated palette. ANSI is parsed back only
28+
to make its terminal styling visible in the browser capture; its text and SGR styling come from
29+
`Render(MarkupFormat.Ansi)`. The capture script applies rounded alpha masks and transparent padding
30+
so the dark cards sit cleanly on both GitHub README themes, and omits volatile PNG timestamps so a
31+
repeat capture is byte-for-byte identical.

‎docs/showcase/capture.sh‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ capture() {
5858
"$raw"
5959

6060
magick composite -compose CopyOpacity "$mask" "$raw" "$clipped"
61-
magick "$clipped" -bordercolor none -border 18 "$asset_dir/$asset"
61+
magick "$clipped" -bordercolor none -border 18 \
62+
-define png:exclude-chunk=date,time "$asset_dir/$asset"
6263
}
6364

6465
capture box-drawing.html showcase-box-drawing.png
6566
capture image-drawing.html showcase-image-drawing.png
67+
capture theming.html showcase-theming.png

0 commit comments

Comments
 (0)