Something went wrong. Try again.
htop for your network: a real-time IP traffic monitor for the terminal, ported to Zig from iptop
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513// SPDX-FileCopyrightText: © 2026 Francesc Muñoz// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! The color palettes. `current` is the one being drawn with; switching//! themes copies another palette over it, and since every panel reads its//! colors from `current` as it draws, the next frame is in the new colors.
const std = @import("std");
pub const Rgb = struct { r: u8, g: u8, b: u8,
/// `#rrggbb`, checked at compile time for the palettes below. pub fn hex(comptime s: []const u8) Rgb { if (s.len != 7 or s[0] != '#') @compileError("not a #rrggbb color: " ++ s); return .{ .r = byte(s[1..3]), .g = byte(s[3..5]), .b = byte(s[5..7]) }; }
// Written out rather than taken from `std.fmt.parseInt`, whose loop costs // enough compile-time branches that the palettes below run out of them. fn byte(comptime s: *const [2]u8) u8 { return nibble(s[0]) * 16 + nibble(s[1]); }
fn nibble(comptime c: u8) u8 { return switch (c) { '0'...'9' => c - '0', 'a'...'f' => c - 'a' + 10, 'A'...'F' => c - 'A' + 10, else => @compileError("not a hex digit"), }; }
pub fn eql(a: Rgb, b: Rgb) bool { return a.r == b.r and a.g == b.g and a.b == b.b; }
/// The straight-line blend from `from` toward `to`; `t` of 0 is `from` /// and 1 is `to`. pub fn mix(from: Rgb, to: Rgb, t: f64) Rgb { return .{ .r = mixChannel(from.r, to.r, t), .g = mixChannel(from.g, to.g, t), .b = mixChannel(from.b, to.b, t), }; }
fn mixChannel(a: u8, b: u8, t: f64) u8 { const af: f64 = @floatFromInt(a); const bf: f64 = @floatFromInt(b); return @intFromFloat(@round(af + (bf - af) * t)); }};
pub const white: Rgb = .hex("#ffffff");
pub const Palette = struct { bg: Rgb, panel: Rgb, text: Rgb, dim: Rgb, border: Rgb, accent: Rgb, cyan: Rgb, magenta: Rgb, /// Magenta blended half way over the chart surface, which is how the /// dithered (▒) half of a process bar reads beside the solid (█) half. magenta_dim: Rgb, green: Rgb, red: Rgb, orange: Rgb, yellow: Rgb, download: Rgb, upload: Rgb, /// The surfaces behind charts and gauge tracks. chart_bg: Rgb, track: Rgb, /// The neutral grey of the empty area in a chart. chart_track: Rgb, /// Chart gradients; index 0 is the row nearest the peaks. download_gradient: [4]Rgb, upload_gradient: [4]Rgb, /// What the live edge of a chart brightens toward: white on a dark theme, /// a dark color on a light one, so that the edge stays visible either way. head_anchor: Rgb,};
/// The colors a theme gives explicitly. The rest of a `Palette` is derived/// from them unless the theme overrides it.const Def = struct { name: []const u8, colors: struct { bg: Rgb, panel: Rgb, text: Rgb, dim: Rgb, border: Rgb, accent: Rgb, cyan: Rgb, magenta: Rgb, green: Rgb, red: Rgb, orange: Rgb, yellow: Rgb, chart_bg: Rgb, track: Rgb, chart_track: Rgb, }, download: ?Rgb = null, upload: ?Rgb = null, download_gradient: ?[4]Rgb = null, upload_gradient: ?[4]Rgb = null, head_anchor: ?Rgb = null,};
/// Four stops fading `base` toward a dark anchor.fn gradient(base: Rgb, dark: Rgb) [4]Rgb { return .{ base, base.mix(dark, 0.3), base.mix(dark, 0.55), base.mix(dark, 0.78) };}
fn build(def: Def) Palette { const c = def.colors; const download = def.download orelse c.green; const upload = def.upload orelse c.red; return .{ .bg = c.bg, .panel = c.panel, .text = c.text, .dim = c.dim, .border = c.border, .accent = c.accent, .cyan = c.cyan, .magenta = c.magenta, .magenta_dim = c.magenta.mix(c.chart_bg, 0.5), .green = c.green, .red = c.red, .orange = c.orange, .yellow = c.yellow, .download = download, .upload = upload, .chart_bg = c.chart_bg, .track = c.track, .chart_track = c.chart_track, .download_gradient = def.download_gradient orelse gradient(download, c.bg), .upload_gradient = def.upload_gradient orelse gradient(upload, c.bg), .head_anchor = def.head_anchor orelse white, };}
const defs = blk: { @setEvalBranchQuota(100_000); break :blk [_]Def{ .{ .name = "Tokyo Night", .colors = .{ .bg = .hex("#1a1b26"), .panel = .hex("#16161e"), .text = .hex("#c0caf5"), .dim = .hex("#565f89"), .border = .hex("#3b4261"), .accent = .hex("#7aa2f7"), .cyan = .hex("#7dcfff"), .magenta = .hex("#bb9af7"), .green = .hex("#9ece6a"), .red = .hex("#f7768e"), .orange = .hex("#ff9e64"), .yellow = .hex("#e0af68"), .chart_bg = .hex("#1f2335"), .track = .hex("#292e42"), .chart_track = .hex("#41434f"), }, .download_gradient = .{ .hex("#9ece6a"), .hex("#73daca"), .hex("#41a6b5"), .hex("#33635c") }, .upload_gradient = .{ .hex("#f7768e"), .hex("#c75070"), .hex("#8c3a52"), .hex("#552737") }, }, .{ .name = "Catppuccin Mocha", .colors = .{ .bg = .hex("#1e1e2e"), .panel = .hex("#181825"), .text = .hex("#cdd6f4"), .dim = .hex("#6c7086"), .border = .hex("#45475a"), .accent = .hex("#89b4fa"), .cyan = .hex("#94e2d5"), .magenta = .hex("#cba6f7"), .green = .hex("#a6e3a1"), .red = .hex("#f38ba8"), .orange = .hex("#fab387"), .yellow = .hex("#f9e2af"), .chart_bg = .hex("#232336"), .track = .hex("#313244"), .chart_track = .hex("#45475a"), }, }, .{ .name = "Gruvbox Dark", .colors = .{ .bg = .hex("#1d2021"), .panel = .hex("#282828"), .text = .hex("#ebdbb2"), .dim = .hex("#928374"), .border = .hex("#504945"), .accent = .hex("#83a598"), .cyan = .hex("#8ec07c"), .magenta = .hex("#d3869b"), .green = .hex("#b8bb26"), .red = .hex("#fb4934"), .orange = .hex("#fe8019"), .yellow = .hex("#fabd2f"), .chart_bg = .hex("#282828"), .track = .hex("#3c3836"), .chart_track = .hex("#504945"), }, }, .{ .name = "Nord", .colors = .{ .bg = .hex("#2e3440"), .panel = .hex("#272c36"), .text = .hex("#eceff4"), .dim = .hex("#616e88"), .border = .hex("#434c5e"), .accent = .hex("#88c0d0"), .cyan = .hex("#8fbcbb"), .magenta = .hex("#b48ead"), .green = .hex("#a3be8c"), .red = .hex("#bf616a"), .orange = .hex("#d08770"), .yellow = .hex("#ebcb8b"), .chart_bg = .hex("#2b303b"), .track = .hex("#3b4252"), .chart_track = .hex("#4c566a"), }, }, .{ .name = "Dracula", .colors = .{ .bg = .hex("#282a36"), .panel = .hex("#21222c"), .text = .hex("#f8f8f2"), .dim = .hex("#6272a4"), .border = .hex("#44475a"), .accent = .hex("#bd93f9"), .cyan = .hex("#8be9fd"), .magenta = .hex("#ff79c6"), .green = .hex("#50fa7b"), .red = .hex("#ff5555"), .orange = .hex("#ffb86c"), .yellow = .hex("#f1fa8c"), .chart_bg = .hex("#242631"), .track = .hex("#343746"), .chart_track = .hex("#44475a"), }, }, .{ .name = "Rosé Pine", .colors = .{ .bg = .hex("#191724"), .panel = .hex("#1f1d2e"), .text = .hex("#e0def4"), .dim = .hex("#6e6a86"), .border = .hex("#403d52"), .accent = .hex("#c4a7e7"), .cyan = .hex("#9ccfd8"), .magenta = .hex("#c4a7e7"), .green = .hex("#31748f"), .red = .hex("#eb6f92"), .orange = .hex("#ebbcba"), .yellow = .hex("#f6c177"), .chart_bg = .hex("#21202e"), .track = .hex("#2a283e"), .chart_track = .hex("#403d52"), }, .download = .hex("#9ccfd8"), .upload = .hex("#eb6f92"), }, .{ .name = "Solarized Dark", .colors = .{ .bg = .hex("#002b36"), .panel = .hex("#073642"), .text = .hex("#839496"), .dim = .hex("#586e75"), .border = .hex("#0e4a59"), .accent = .hex("#268bd2"), .cyan = .hex("#2aa198"), .magenta = .hex("#d33682"), .green = .hex("#859900"), .red = .hex("#dc322f"), .orange = .hex("#cb4b16"), .yellow = .hex("#b58900"), .chart_bg = .hex("#073642"), .track = .hex("#0a3a47"), .chart_track = .hex("#586e75"), }, }, .{ .name = "One Dark", .colors = .{ .bg = .hex("#282c34"), .panel = .hex("#21252b"), .text = .hex("#abb2bf"), .dim = .hex("#5c6370"), .border = .hex("#3e4451"), .accent = .hex("#61afef"), .cyan = .hex("#56b6c2"), .magenta = .hex("#c678dd"), .green = .hex("#98c379"), .red = .hex("#e06c75"), .orange = .hex("#d19a66"), .yellow = .hex("#e5c07b"), .chart_bg = .hex("#2c313a"), .track = .hex("#3a3f4b"), .chart_track = .hex("#4b5263"), }, }, .{ .name = "Everforest", .colors = .{ .bg = .hex("#2d353b"), .panel = .hex("#272e33"), .text = .hex("#d3c6aa"), .dim = .hex("#859289"), .border = .hex("#3d484d"), .accent = .hex("#7fbbb3"), .cyan = .hex("#83c092"), .magenta = .hex("#d699b6"), .green = .hex("#a7c080"), .red = .hex("#e67e80"), .orange = .hex("#e69875"), .yellow = .hex("#dbbc7f"), .chart_bg = .hex("#323d43"), .track = .hex("#3d484d"), .chart_track = .hex("#56635f"), }, }, .{ .name = "Kanagawa", .colors = .{ .bg = .hex("#1f1f28"), .panel = .hex("#16161d"), .text = .hex("#dcd7ba"), .dim = .hex("#727169"), .border = .hex("#363646"), .accent = .hex("#7e9cd8"), .cyan = .hex("#7aa89f"), .magenta = .hex("#957fb8"), .green = .hex("#98bb6c"), .red = .hex("#e46876"), .orange = .hex("#ffa066"), .yellow = .hex("#e6c384"), .chart_bg = .hex("#2a2a37"), .track = .hex("#363646"), .chart_track = .hex("#54546d"), }, }, .{ .name = "Monokai", .colors = .{ .bg = .hex("#272822"), .panel = .hex("#1e1f1c"), .text = .hex("#f8f8f2"), .dim = .hex("#75715e"), .border = .hex("#49483e"), .accent = .hex("#66d9ef"), .cyan = .hex("#66d9ef"), .magenta = .hex("#ae81ff"), .green = .hex("#a6e22e"), .red = .hex("#f92672"), .orange = .hex("#fd971f"), .yellow = .hex("#e6db74"), .chart_bg = .hex("#2d2e28"), .track = .hex("#3a3b34"), .chart_track = .hex("#49483e"), }, }, .{ .name = "Ayu Dark", .colors = .{ .bg = .hex("#0b0e14"), .panel = .hex("#0d1017"), .text = .hex("#bfbdb6"), .dim = .hex("#565b66"), .border = .hex("#1c212b"), .accent = .hex("#59c2ff"), .cyan = .hex("#95e6cb"), .magenta = .hex("#d2a6ff"), .green = .hex("#aad94c"), .red = .hex("#f07178"), .orange = .hex("#ff8f40"), .yellow = .hex("#ffb454"), .chart_bg = .hex("#0d1017"), .track = .hex("#131721"), .chart_track = .hex("#565b66"), }, }, .{ .name = "Catppuccin Latte", .colors = .{ .bg = .hex("#eff1f5"), .panel = .hex("#e6e9ef"), .text = .hex("#4c4f69"), .dim = .hex("#8c8fa1"), .border = .hex("#bcc0cc"), .accent = .hex("#1e66f5"), .cyan = .hex("#179299"), .magenta = .hex("#8839ef"), .green = .hex("#40a02b"), .red = .hex("#d20f39"), .orange = .hex("#fe640b"), .yellow = .hex("#df8e1d"), .chart_bg = .hex("#e6e9ef"), .track = .hex("#ccd0da"), .chart_track = .hex("#acb0be"), }, .head_anchor = .hex("#1e1e2e"), }, .{ .name = "Rosé Pine Dawn", .colors = .{ .bg = .hex("#faf4ed"), .panel = .hex("#fffaf3"), .text = .hex("#575279"), .dim = .hex("#9893a5"), .border = .hex("#cecacd"), .accent = .hex("#907aa9"), .cyan = .hex("#56949f"), .magenta = .hex("#907aa9"), .green = .hex("#286983"), .red = .hex("#b4637a"), .orange = .hex("#d7827e"), .yellow = .hex("#ea9d34"), .chart_bg = .hex("#fffaf3"), .track = .hex("#dfdad9"), .chart_track = .hex("#cecacd"), }, .download = .hex("#56949f"), .upload = .hex("#b4637a"), .head_anchor = .hex("#26233a"), }, };};
pub const Theme = struct { name: []const u8, palette: Palette,};
pub const themes: [defs.len]Theme = blk: { @setEvalBranchQuota(100_000); var out: [defs.len]Theme = undefined; for (defs, &out) |def, *t| t.* = .{ .name = def.name, .palette = build(def) }; break :blk out;};
/// The palette everything is drawn with.pub var current: Palette = themes[0].palette;var current_index: usize = 0;
pub fn index() usize { return current_index;}
pub fn apply(i: usize) void { if (i >= themes.len) return; current_index = i; current = themes[i].palette;}
/// The theme called `name`, or null for a name no theme has.pub fn find(name: []const u8) ?usize { for (themes, 0..) |t, i| { if (std.mem.eql(u8, t.name, name)) return i; } return null;}
const testing = std.testing;
test "mix blends channel by channel" { const a: Rgb = .hex("#000000"); const b: Rgb = .hex("#ff8040"); try testing.expect(a.mix(b, 0.5).eql(.hex("#804020"))); try testing.expect(a.mix(b, 0).eql(a)); try testing.expect(a.mix(b, 1).eql(b));}
test "derived colors" { const mocha = themes[find("Catppuccin Mocha").?].palette; try testing.expect(mocha.download.eql(mocha.green)); try testing.expect(mocha.upload.eql(mocha.red)); try testing.expect(mocha.download_gradient[0].eql(mocha.green)); try testing.expect(mocha.head_anchor.eql(white));
const latte = themes[find("Catppuccin Latte").?].palette; try testing.expect(latte.head_anchor.eql(.hex("#1e1e2e")));
const pine = themes[find("Rosé Pine").?].palette; try testing.expect(pine.download.eql(.hex("#9ccfd8")));}
test "apply switches the current palette" { defer apply(0); apply(find("Nord").?); try testing.expectEqual(find("Nord").?, index()); try testing.expect(current.bg.eql(.hex("#2e3440"))); apply(themes.len); try testing.expectEqual(find("Nord").?, index());}