ANSI terminalSGR + cells
- {{ansiForBrowser}}
+ {{WebUtility.HtmlEncode(leftLabel)}}{{WebUtility.HtmlEncode(leftNative)}}
+ {{PanelBody(leftClass, leftContent)}}
diff --git a/README.md b/README.md index 02ada78..12106dd 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Quick start · Packages · Layout · + Theming · Documentation · Contributing
@@ -62,6 +63,43 @@ and naturally flowing content in HTML.  +## Theme a whole layout + +A theme changes semantic parts together: borders, titles, labels, bullets, guides, gauges, table +headings, and striped rows. Presets can change the drawing characters too; generated palettes build +accessible colour roles from a single seed. + +```csharp +var registry = MarkupRegistry.Empty.WithAnsi().WithHtml(); + +var sheet = new Stack( +[ + new Fields( + [ + new Field(MarkupText.Plain("Name"), MarkupText.Plain("Lyra Vale")), + new Field(MarkupText.Plain("Role"), MarkupText.Plain("Wayfinder")), + ]), + new Gauge(7, 10) { Label = MarkupText.Plain("Trail") }, +]).Bordered(MarkupText.Plain("WAYFINDER'S JOURNAL")); + +var fantasy = sheet.Themed(ThemePalette.Preset("fantasy")!.ToLayoutTheme()); +var housePalette = ThemePalette.Generate( + new RgbColor(34, 211, 238), + ThemeHarmony.Triadic, + contrast: 0.35); +var house = sheet.Themed(housePalette.ToLayoutTheme()); + +var value = BlockLayout.Build(house, width: 46); +var ansi = value.Render(MarkupFormat.Ansi, registry); +var html = value.Render(MarkupFormat.Html, registry); +``` + + + +Use `.Themed(...)` to override the surrounding theme, or `.ThemedUnder(...)` to provide defaults +that a reader's theme can replace. See [themes and palettes](docs/layout.md#themes-and-palettes) for +the built-in presets, light and dark modes, custom glyphs, base16 schemes, JSON, and contrast checks. + ## Why MarkupString? - **One source of truth** — keep content and semantics together, then choose the output format at diff --git a/docs/assets/showcase-box-drawing.png b/docs/assets/showcase-box-drawing.png index 7164b7d..f9afaca 100644 Binary files a/docs/assets/showcase-box-drawing.png and b/docs/assets/showcase-box-drawing.png differ diff --git a/docs/assets/showcase-image-drawing.png b/docs/assets/showcase-image-drawing.png index 6623725..a91a29a 100644 Binary files a/docs/assets/showcase-image-drawing.png and b/docs/assets/showcase-image-drawing.png differ diff --git a/docs/assets/showcase-theming.png b/docs/assets/showcase-theming.png new file mode 100644 index 0000000..fd56dc8 Binary files /dev/null and b/docs/assets/showcase-theming.png differ diff --git a/docs/showcase/Program.cs b/docs/showcase/Program.cs index efa228b..f977ce7 100644 --- a/docs/showcase/Program.cs +++ b/docs/showcase/Program.cs @@ -74,6 +74,31 @@ figureValue, registry); +var character = new Stack( +[ + new Fields( + [ + new Field(P("Name"), P("Lyra Vale")), + new Field(P("Role"), P("Wayfinder")), + new Field(P("Region"), P("Glasswood")), + new Field(P("Status"), P("Ready")), + ]) { Columns = 2, Gap = 3, Leader = P("·") }, + new Rule(P("CURRENT QUEST")), + new Gauge(7, 10) { Label = P("Trail"), Show = GaugeShow.Percent }, + new Bullets([P("Map the moonwell"), P("Return before dawn")]), +]).Bordered(P("WAYFINDER'S JOURNAL")); + +var fantasy = BlockLayout.Build(character.Themed(ThemePalette.Preset("fantasy")!.ToLayoutTheme()), 46); +var generatedPalette = ThemePalette.Generate(new RgbColor(34, 211, 238), ThemeHarmony.Triadic, contrast: 0.35); +var generated = BlockLayout.Build(character.Themed(generatedPalette.ToLayoutTheme()), 46); +WriteThemePage( + Path.Combine(output, "theming.html"), + "One layout, a whole new world", + "Presets and generated palettes recolour every semantic part — and can change the shapes too.", + fantasy, + generated, + registry); + static MarkupText P(string text) => MarkupText.Plain(text); static void WritePage(string path, string title, string subtitle, MarkupText value, MarkupRegistry registry) @@ -81,7 +106,45 @@ static void WritePage(string path, string title, string subtitle, MarkupText val var ansi = value.Render(MarkupFormat.Ansi, registry); var ansiForBrowser = AnsiEscapeParser.Parse(ansi).Render(MarkupFormat.Html, registry); var html = value.Render(MarkupFormat.Html, registry); - var escapedAnsi = WebUtility.HtmlEncode(ansi.Replace("\u001b", "\\e", StringComparison.Ordinal)); + WriteComparisonPage( + path, title, subtitle, "MarkupText → renderer → native output", + "ANSI terminal", "SGR + cells", "terminal", ansiForBrowser, + "HTML browser", "semantic elements", "browser", html); +} + +static void WriteThemePage( + string path, + string title, + string subtitle, + MarkupText preset, + MarkupText generated, + MarkupRegistry registry) +{ + var presetHtml = AnsiEscapeParser.Parse(preset.Render(MarkupFormat.Ansi, registry)).Render(MarkupFormat.Html, registry); + var generatedHtml = AnsiEscapeParser.Parse(generated.Render(MarkupFormat.Ansi, registry)).Render(MarkupFormat.Html, registry); + WriteComparisonPage( + path, title, subtitle, "layout → palette → themed output", + "Fantasy preset", "colours + glyphs", "terminal", presetHtml, + "Generated palette", "triadic harmony", "terminal", generatedHtml); +} + +static void WriteComparisonPage( + string path, + string title, + string subtitle, + string flowHtml, + string leftLabel, + string leftNative, + string leftClass, + string leftContent, + string rightLabel, + string rightNative, + string rightClass, + string rightContent) +{ + static string PanelBody(string cssClass, string content) => cssClass == "terminal" + ? "" + content + "" + : "
{{WebUtility.HtmlEncode(subtitle)}}
{{ansiForBrowser}}
+ {{escapedAnsi}}