[ Guides ]
Colors, Gradients, and Palette Quantization
Style widgets using Color constants, 24-bit RGB values, hex strings (#RGB, #RRGGBB, #RRGGBBAA), smooth Color::lerp interpolation, and terminal ColorMode overrides.
Complete working example
Section titled “Complete working example”#include <koto/koto.hpp>#include <cstdio>#include <string>
using namespace koto;
int main(){ App app; float progress = 0.35f;
app.on_key([&](const KeyEvent& key) { if (key.ch == 'q' || key.is(Key::Escape)) { app.quit(); } else if (key.is(Key::Right)) { progress = (progress + 0.05f > 1.0f) ? 1.0f : progress + 0.05f; } else if (key.is(Key::Left)) { progress = (progress - 0.05f < 0.0f) ? 0.0f : progress - 0.05f; } });
app.on_render([&](Frame& frame) { auto [header_area, gauge_area, swatch_area] = Layout::vertical(frame.area(), { Constraint::fixed(3), Constraint::fixed(3), Constraint::fill(), });
Box() .title("Color & Gradient Demo (Left/Right to adjust, q to quit)") .border_style(BorderStyle::Rounded) .fg("#00E5FF") .draw(frame, header_area);
uint32_t dynamic_fg = Color::lerp(Color::Emerald, Color::Red, progress); int pct = static_cast<int>(progress * 100.0f);
Box().title("Load").border_style(BorderStyle::Single).draw(frame, gauge_area); Gauge() .ratio(progress) .label(std::to_string(pct) + "%") .filled_fg(dynamic_fg) .empty_fg(Color::DarkGray) .draw(frame, gauge_area.inset(1, 1, 1, 1));
Box() .title("RGB Swatch") .filled(true) .bg(Color::rgb(24, 24, 37)) .fg("amber") .draw(frame, swatch_area); });
if (auto res = app.run(); !res) { std::fprintf(stderr, "Error: %.*s\n", static_cast<int>(res.error().message().size()), res.error().message().data()); return 1; } return 0;}Step-by-step explanation
Section titled “Step-by-step explanation”- Implicit
Colorconstruction: Every widget color setter (fg,bg,title_fg,highlight_fg, etc.) acceptskoto::Colorby value. BecauseColorhas implicit constructors fromuint32_t,std::string_view, andconst char*(include/koto/color.hpp:39-41), you can passColor::Emerald,Color::rgb(24, 24, 37),"#00E5FF", or"amber"interchangeably. - Validating user-supplied hex strings: When parsing untrusted config files, call
try_parse_color(str)orColor::try_from_hex(str)to receivestd::expected<uint32_t, ColorParseError>(EmptyString,InvalidLength,InvalidHexDigit) instead of falling back silently toCOLOR_DEFAULT. - Interpolating gradients:
Color::lerp(c1, c2, t)linearly interpolates red, green, and blue channels fortin[0.0f, 1.0f]usingstd::lround. - Automatic terminal downscaling: At startup,
Terminalinitializesm_color_mode = detect_system_color_mode(). DuringFrame::paint_cell(), 24-bit colors are automatically emitted as[38;2;R;G;Bm(TrueColor), quantized viargb_to_ansi256to[38;5;Nm(Ansi256), quantized viargb_to_ansi16to[30..37;90..97m(Ansi16), or suppressed (NoColor) (src/frame.cpp:243-324).
Common mistakes
Section titled “Common mistakes”- Calling non-existent
Color::Hex(0x...)orColor::from_rgb(...): To construct from a packed integer literal, pass theuint32_tdirectly (Color(0x00E5FF)or0x00E5FF). For RGB channels, callColor::rgb(r, g, b)orColor::Rgb(r, g, b). For hex strings, pass"#00E5FF"or callColor::from_hex("#00E5FF"). - Testing TrueColor output when
NO_COLORis set in the environment: IfNO_COLORis non-empty in the environment,detect_system_color_mode()returnsColorMode::NoColor(src/color.cpp:229-233). In unit tests that inspectFrame::raw_output()for SGR color sequences, either use a headlessFrame(rows, cols)(which defaults toColorMode::TrueColorbecausem_term == nullptr) or explicitly callterm.color_mode(ColorMode::TrueColor).