One call repaints every view, retints the text already on screen, and moves the ANSI table with it, so a player can restyle your whole display without you redrawing anything.
Summary
A theme in TextMode is a named color palette, and switching one is a single call. That call does three things at once: it pushes the new colors into every view that follows the theme, it rewrites the sixteen ANSI colors so indexed content means the new palette, and it walks the cells already on screen translating old colors to new ones. The screen restyles without anyone redrawing it.
The reason it works that way is that a theme is not a struct of thirteen color fields living in TextMode. It is a Core ColorPalette tagged for TextMode, resolved by name through the shared color asset registry, exactly as the Console module resolves its own themes. That has a few practical consequences worth knowing up front. A theme is expressible as a single string, so it survives a JSON config file and a save game. A project can add its own by dropping a palette asset into the project with the right tag and the right entries, with no code at all. And the same palette can be authored in Chroma, the color and palette tool, rather than in a C# file.
Eight themes ship: Classic, Green Phosphor, Amber Phosphor, Lightman, Midnight, Paper, WordPerfect and Amiga. The two phosphor themes are what they sound like, Lightman is a monochrome pale blue on near-black, Midnight is a cool dark scheme, and Paper is the inverted light one. WordPerfect reproduces the light gray on blue of DOS word processors in the DOS text mode colors, and Amiga reproduces the white on blue of the Workbench 1.3 screen with its orange cursor. The others are Scylla’s own designs.
A palette has to earn the right to be a theme. It must lead with the sixteen ANSI colors in ANSI order, because position is the contract there, and it must define every semantic entry the module needs. A palette that fails either check is rejected and the module falls back to Classic with a warning rather than rendering with half a theme.
Use color themes for:
- Letting players pick a display style, including high-contrast or light options.
- Shipping a retro look without hard-coding phosphor green through your drawing code.
- Restyling an entire screen at runtime without a redraw pass.
- Keeping your game’s colors consistent between your own drawing and terminal output.
Features
- A theme is a palette by name. Resolved through the Core color asset registry, so it is a string in a config file rather than a code-defined structure.
- Eight shipped themes. Classic, Green Phosphor, Amber Phosphor, Lightman, Midnight, Paper, WordPerfect and Amiga.
- Project themes with no code. Drop in a tagged palette asset with the right entries and it appears alongside the built-ins.
- Retint on switch. Cells already on screen are translated from the old palette to the new one, so a switch does not require a redraw.
- ANSI table follows. Indexed color moves with the theme, so escape sequences keep painting from the current palette rather than a stale sixteen.
- Semantic colors. Beyond the ANSI sixteen, a scheme carries foreground, background, accent, border, cursor, selection and status colors, so game code can reference intent rather than a specific hue.
- Per-view opt-out. A view can decline to follow the theme, which is how a display styled deliberately by its host keeps its colors.
- Validation with fallback. An unresolvable name or an incomplete palette falls back to Classic and logs, rather than failing startup or rendering wrongly.
The color scheme
TextModeColorScheme is the resolved form of a theme: the values a view and your drawing code actually read.
| Entry ID | What it is for |
|---|---|
Foreground | Ordinary text. What a cleared cell and a default print use. |
Background | The surface behind everything. A transparent value lets the game show through. |
BrightForeground | Emphasized text, headings, the thing you want read first. |
DimForeground | De-emphasized text: hints, disabled entries, background chatter. |
Accent | The theme’s signature color, for highlights and selection marks. |
Border | Frames and rules, so a panel outline is not competing with its contents. |
Cursor | The insertion cursor, kept separate so it can contrast against text. |
SelectionBackground, SelectionForeground | The selected-text pair. Selected cells are drawn in these two colors instead of their own. The built-in themes use their own background on their foreground, so a selection reads as inverse text. |
ErrorForeground, WarningForeground, InfoForeground, SuccessForeground | Status colors, so a log line’s severity does not need a palette lookup in your code. |
Drawing with semantic colors rather than named ANSI ones is why a screen restyles sensibly. ANSIColor.Green is green in every theme; TextModeThemes.Active.SuccessForeground is whatever that theme considers a success color, which in Paper is a dark green on white and in Green Phosphor is a brighter shade of the same phosphor.
Recipes
Let the player pick a theme
Problem. Your options menu should offer the display styles you ship plus anything the player has added, and applying one should take effect immediately.
Solution. Ask for the available names and set by name.
/* Includes the shipped themes and any project palette tagged for TextMode that satisfies the
contract, so a player-supplied palette shows up without a code change. */
var names = TextModeThemes.GetAvailableThemeNames();
/* Setting a theme repaints every following view, moves the ANSI table, and retints what is
already on screen. Returns false when the name could not be resolved. */
if (!TextModeThemes.SetTheme(names[selectedIndex]))
{
/* The module has fallen back to Classic and logged why. */
}
Cycle themes from a key
Problem. You want a quick way to flip through themes while tuning your screens, or a player-facing shortcut.
Solution. CycleTheme steps to the next available theme and returns the one it landed on.
/* Walks the available list in order and wraps. Handy for a debug key and for a demo. */
var applied = TextModeThemes.CycleTheme();
_console.PrintAligned(0, applied, TextAlignment.Center,
TextModeThemes.Active.DimForeground, TextModeThemes.Active.Background);
Invert the theme’s colors
Problem. You want a negative of the current look, for example to flash the screen on a hit or to offer a light version of a dark theme.
Solution. SetInverted shows every color TextMode draws with its complement: theme colors, indexed colors and 24-bit colors alike, on every view at once, including a view shown through the CRT. The inversion stays on when the theme changes, so every theme switched to afterwards is shown inverted too, until SetInverted(false) turns it off.
/* Toggles the negative. The theme, its palette and the text already on screen keep their
own colors; only the display is inverted, so switching back is exact. */
TextModeThemes.SetInverted(!TextModeThemes.Inverted);
Anything you draw next to a view in a theme color, such as a panel behind the grid, can follow along with TextModeThemes.DisplayColor, which returns the color as it currently appears. The terminal demo exposes the inversion as theme inverse.
Draw in colors that follow the theme
Problem. Your status panel uses green for good and red for bad, and under the Paper theme those hard-coded colors are unreadable on a white background.
Solution. Draw with the scheme’s semantic colors instead of named ANSI ones.
var theme = TextModeThemes.Active;
/* Intent rather than hue. Under Paper these resolve to dark tones on a light background;
under Green Phosphor they resolve to shades of the same phosphor. */
_console.Print(2, 1, "REACTOR", theme.Foreground, theme.Background);
_console.Print(12, 1, "NOMINAL", theme.SuccessForeground, theme.Background);
_console.Print(2, 2, "COOLANT", theme.Foreground, theme.Background);
_console.Print(12, 2, "LOW", theme.WarningForeground, theme.Background);
/* Frames use the border color so they recede rather than competing with the text. */
_console.DrawBox(0, 0, PANEL_W, PANEL_H, BoxStyle.Single, theme.Border, theme.Background);
Switch without retinting what is on screen
Problem. You are about to redraw the whole screen anyway, and the retint pass is wasted work.
Solution. Every theme call takes a flag for it.
/* Skips the walk over existing cells. New drawing uses the new colors; the old content keeps
the old ones until you redraw it, which you are about to do. */
TextModeThemes.SetTheme(themeName, remapExisting: false);
RedrawEverything();
Add a project theme with no code
Problem. Your game wants a bespoke palette that matches its art direction, and you would rather not maintain it in C#.
Solution. Author a palette asset and satisfy the contract. The registry finds it.
- Duplicate one of the shipped TextMode palettes, or author a new one in Chroma.
- Keep the sixteen ANSI colors first and in ANSI order. Position is the contract there, not the entry ID.
- Define every semantic entry. A palette missing one is rejected rather than partially applied.
- Tag it for TextMode so the registry offers it as a theme.
- Set its name in the configuration’s Theme group, or pass the name to
SetTheme.
Reset to the configured theme
Problem. A cutscene switched to a special theme and you want to return to whatever the player chose without knowing what it was.
Solution. ResetTheme returns to the configured startup theme.
/* Goes back to the theme named in the configuration, whatever the player set it to. */
TextModeThemes.ResetTheme();
API Sketch
static class TextModeThemes {
string ActiveThemeName { get; }
ColorPalette ActivePalette { get; }
TextModeColorScheme Active { get; }
bool Inverted { get; }
bool SetTheme(string themeName, bool remapExisting = true);
bool SetInverted(bool inverted);
Color DisplayColor(Color color);
Color32 DisplayColor(Color32 color);
bool RefreshActiveTheme(bool remapExisting = true);
string CycleTheme(bool remapExisting = true);
void ResetTheme();
List<string> GetAvailableThemeNames();
}
readonly struct TextModeColorScheme {
Color32 Foreground, Background, BrightForeground, DimForeground;
Color32 Accent, Border, Cursor;
Color32 SelectionBackground, SelectionForeground;
Color32 ErrorForeground, WarningForeground, InfoForeground, SuccessForeground;
}
static class TextModeColorDefaults {
ColorPalette Resolve(string themeName); /* falls back to Classic */
ColorPalette Fallback { get; } /* the hard-coded classic set */
bool HasRequiredEntries(ColorPalette palette);
ReadOnlySpan<string> BuiltInNames;
ReadOnlySpan<string> ANSIEntryNames;
ReadOnlySpan<string> SemanticEntryNames;
}
static class TextModeThemeRemapper {
int BuildMap(ColorPalette from, ColorPalette to, Dictionary<uint, uint> map);
int Remap(CellGrid grid, IReadOnlyDictionary<uint, uint> map);
}
See the API reference for full signatures, remarks, and exception contracts on every member.
Shipped themes
| Theme | Character | Suits |
|---|---|---|
| Scylla TextMode Classic | Light gray on black, full-color ANSI | The default. A neutral terminal look that does not impose a period or a mood. |
| Scylla TextMode Green Phosphor | Monochrome green, brightening rather than desaturating at the top of its ramp | Early terminals, hacker fiction, anything wanting a CRT feel. |
| Scylla TextMode Amber Phosphor | Monochrome amber, same ramp construction | The warmer phosphor alternative; easier on the eye for long reading. |
| Scylla TextMode Lightman | Monochrome pale blue on near-black, with a deeper blue accent | Early home computer screens, and anything that wants the calm of a monochrome terminal without green or amber. |
| Scylla TextMode Midnight | Cool blues on near-black | A modern dark scheme for interfaces that are not pretending to be old hardware. |
| Scylla TextMode Paper | Dark text on a light ground, with an inverted bright ramp | Light mode, and the accessibility option for players who find dark screens hard to read. |
| Scylla TextMode WordPerfect | Light gray on blue in the DOS text mode colors, with bright white for emphasis and blue on gray for selections | Word processors, editors and other applications that should look like late 1980s DOS software. |
| Scylla TextMode Amiga | White on blue with an orange cursor and accent, in the four Workbench 1.3 colors, with vivid Amiga colors for the rest of the ANSI set | Shells, file managers and anything that should look like a late 1980s Amiga. |
Best Practices
- Draw with semantic colors when intent matters.
SuccessForegroundrestyles sensibly across every theme; a hard-coded green does not survive a light theme. - Draw with named ANSI colors when terminal consistency matters. They follow the palette too, and they keep your drawing consistent with anything the terminal renders.
- Reserve truecolor for what must never change. It is invisible to theme changes, which is right for brand colors and wrong for interface chrome.
- Skip the retint when you are about to redraw. The remap walks every cell; there is no point paying for it twice.
- Let the view follow the theme unless its host owns the colors. A display styled deliberately by another system should opt out. That is exactly what the debug console does, by naming no theme.
- Validate a project palette before shipping it. The contract is the sixteen ANSI colors in order plus every semantic entry; a palette missing one silently becomes Classic at runtime.
- Keep the ANSI sixteen faithful for terminal compatibility. Positions are addressed by escape sequences, so creative reordering breaks hosted programs.
- Store the theme by name. It is a string, which means a save file, a config file and a settings menu can all round-trip it without a custom serializer.
- Consider a light theme an accessibility feature, not a novelty. Paper exists so a player who cannot comfortably read light-on-dark has an option.
- Check the return value of
SetTheme. False means the name did not resolve and the module fell back, which is the difference between a working theme picker and one that silently does nothing.
Pitfalls
Related
- 01 Scylla TextMode
- 05 TextMode – Colors and Attributes
- 12 TextMode – Configuration
- 03 TextMode – Views and Grid
- API reference:
Scylla.TextMode.TextModeThemes - API reference:
Scylla.TextMode.TextModeColorScheme - API reference:
Scylla.TextMode.TextModeColorDefaults - API reference:
Scylla.TextMode.TextModeThemeRemapper