summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-05 15:05:34 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit6eaa81710fee8ee2191a0b967b069bb01952be2e (patch)
tree051f54ede02517a851538f8a853f52c7bdfa96a9 /src
parent98199e573d678f6b29a5c122836ec393856d7bd8 (diff)
downloadpardes-6eaa81710fee8ee2191a0b967b069bb01952be2e.tar.gz
pardes-6eaa81710fee8ee2191a0b967b069bb01952be2e.zip
theme the selection colors
Diffstat (limited to 'src')
-rw-r--r--src/pardes.zig105
-rw-r--r--src/themes/acme.zig7
-rw-r--r--src/themes/dark.zig8
-rw-r--r--src/themes/helix.zig9
4 files changed, 104 insertions, 25 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index 471817f7..aa2ec398 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -2226,18 +2226,18 @@ pub const bash_rc =
// ---- theme ----
-// per-button selection highlight: distinct muted colors per mouse button
-const SEL_BG = [3][3]u8{ .{ 0x2c, 0x46, 0x74 }, .{ 0x2c, 0x60, 0x38 }, .{ 0x64, 0x2c, 0x58 } };
-const sel_fg = [3]u8{ 0xd2, 0xd2, 0xd2 };
-// modal line/char selection: warm tan, reads as keyboard-driven
-const msel_bg = [3]u8{ 0x4a, 0x3a, 0x1e };
-const msel_fg = [3]u8{ 0xd8, 0xc8, 0xa8 };
-// the OTHER cursors of a multi-selection: the same tan turned down, so the
-// primary is the one that reads as "here" at a glance (helix does the same
-// with its two selection styles). Each secondary's own cursor cell is then
-// painted msel_fg-on-msel_bg — there is only one hardware cursor.
-const msel2_bg = [3]u8{ 0x2e, 0x26, 0x16 };
-const msel2_fg = [3]u8{ 0xa8, 0x9c, 0x84 };
+/// Halfway between two colors, channel by channel. Every selection tint pardes
+/// paints is a mix of theme colors — the per-mouse-button ones pull the theme's
+/// selection toward one of its own accents, a quarter of the way and so a mix
+/// of a mix; the extra cursors' pulls it back toward the page — and renderPane
+/// makes seven of those, which is six more than spelling the same three-channel
+/// loop out at the call site is worth.
+fn mix(a: [3]u8, b: [3]u8) [3]u8 {
+ var out: [3]u8 = undefined;
+ for (&out, a, b) |*c, x, y| c.* = @intCast((@as(u16, x) + y) / 2);
+ return out;
+}
+
const UNDO_MAX = 256;
const EDIT_UNDO_MAX = 64; // terminal snapshots copy the edit buffer; cap tighter
@@ -2249,10 +2249,18 @@ const EDIT_UNDO_MAX = 64; // terminal snapshots copy the edit buffer; cap tighte
/// piece of loud chrome on screen. The overlay/drag greys that were also
/// hardcoded turned out to be the dark theme's own scroll_track/lineno/tag_fg
/// spelled a second time, so they read those fields now instead.
+///
+/// Selection is ONE pair, `sel_bg`/`sel_fg`, and the rest is arithmetic: the
+/// three per-mouse-button tints and the dimmed extra cursors are mixed off it in
+/// renderPane rather than named here. A theme author gets their selection colour
+/// right and would get four more by accident — and every source gen_themes reads
+/// names exactly one selection anyway.
pub const Theme = struct {
name: []const u8,
bg: ?[3]u8,
fg: ?[3]u8,
+ sel_bg: [3]u8,
+ sel_fg: [3]u8,
tag_bg: [3]u8,
tag_fg: [3]u8,
box: [3]u8, // move box, focused pane
@@ -10194,9 +10202,15 @@ pub const Pardes = struct {
}
if (pv.selection) |selection| {
try highlights.ensureUnusedCapacity(arena, selection.quads.len);
+ // a page is not a grid of cells, so this is the one selection that
+ // paints as a translucent wash over rendered pixels rather than as
+ // a cell's background — but it is the same selection, so it is the
+ // same colour. The alpha stays hardcoded: it is what makes the
+ // glyphs underneath readable, and no theme has an opinion on it.
+ const sel = p.theme().sel_bg;
for (selection.quads) |quad| highlights.appendAssumeCapacity(pdf_impl.Highlight.init(
quad,
- .{ 0x55, 0xa7, 0xff, 0x78 },
+ .{ sel[0], sel[1], sel[2], 0x78 },
.selection,
));
}
@@ -11615,6 +11629,11 @@ pub const Pardes = struct {
// scrollbar's second column. `.default` means the host terminal's own
// background, which is what a themeless dark pane wants.
const pane_bg: Color = if (th.bg) |c| .{ .rgb = c } else .default;
+ // ...and the same background as a colour to do arithmetic on, which the
+ // dimmed selections below need. A theme with a null bg cannot say what
+ // the host's own cell looks like, so its tag bar stands in — the
+ // substitution pdfTintColors already makes.
+ const page_rgb = th.bg orelse th.tag_bg;
// text area resets to terminal-default cells (vaxis clear semantics);
// light themes paint the page over it.
@@ -11681,7 +11700,13 @@ pub const Pardes = struct {
if (w < tw) _ = s.print(tx + tw - w, r.y, w, ibuf[0..iw], tag_style);
}
// tag char selection highlight (helix v/x, or a tagline sweep),
- // inclusive [lo, hi] — rendered-tag columns, so no conversion
+ // inclusive [lo, hi] — rendered-tag columns, so no conversion.
+ //
+ // Every selection on screen paints in the LIVE theme (`th`) and not in
+ // `chrome`: a highlight is not attached to any geometry, it appears
+ // under the range you just swept, so it has to arrive with the theme
+ // the way syntax colours do rather than slide in over the chrome
+ // animation's frames.
if (pane.tag_edit and pane.tag_sel) {
const b = tagSelBounds(pane);
var col: usize = b.lo;
@@ -11689,8 +11714,8 @@ pub const Pardes = struct {
while (col <= end and col < tw) : (col += 1) {
const cell = s.at(tx + @as(u16, @intCast(col)), r.y);
cell.default = false;
- cell.style.bg = .{ .rgb = msel_bg };
- cell.style.fg = .{ .rgb = msel_fg };
+ cell.style.bg = .{ .rgb = th.sel_bg };
+ cell.style.fg = .{ .rgb = th.sel_fg };
}
}
// cursor while editing the tag: a rendered-tag column, verbatim
@@ -11774,6 +11799,28 @@ pub const Pardes = struct {
// mouse selections (pane-local coords), one pass per button — later
// buttons win on overlap. A left .done stays highlighted after release;
// middle/right .done are transient (they fire their action on release).
+ //
+ // Which button drew a sweep is worth seeing, so each gets the theme's
+ // selection tinted toward one of the theme's own syntax accents: three
+ // colours that are visibly not each other on a dark theme and on a
+ // light one, without asking a theme to name three more. A QUARTER of
+ // the accent, which is the mix of a mix — at a half, an accent as
+ // bright as the theme's text lands on top of sel_fg and the selected
+ // text stops being readable on a couple of dozen generated themes.
+ // `num` gives way to `comment` when a theme paints numbers and strings
+ // alike — the shipped helix theme does, 24 of the generated ones do —
+ // because two buttons landing on one colour is the whole thing this
+ // avoids.
+ // ponytail: 20 of the 228 themes colour two of these three scopes the
+ // same anyway and still collapse two buttons. Upgrade path is to walk
+ // the theme for a third colour far enough from the other two, rather
+ // than naming the scopes here.
+ const accent2 = if (std.mem.eql(u8, &th.num, &th.str)) th.comment else th.num;
+ const sel_btn = [3][3]u8{
+ mix(th.sel_bg, mix(th.sel_bg, th.kw)),
+ mix(th.sel_bg, mix(th.sel_bg, th.str)),
+ mix(th.sel_bg, mix(th.sel_bg, accent2)),
+ };
for (pane.sel, 0..) |sl, b| {
if (sl.state == .none) continue;
if (sl.state == .done and b != 0) continue;
@@ -11788,8 +11835,8 @@ pub const Pardes = struct {
while (col <= c1 and col < tw) : (col += 1) {
const cell = s.at(tx + @as(u16, @intCast(col)), r.y + row);
cell.default = false;
- cell.style.bg = .{ .rgb = SEL_BG[b] };
- cell.style.fg = .{ .rgb = sel_fg };
+ cell.style.bg = .{ .rgb = sel_btn[b] };
+ cell.style.fg = .{ .rgb = th.sel_fg };
}
}
}
@@ -11804,8 +11851,8 @@ pub const Pardes = struct {
while (col < tw) : (col += 1) {
const cell = s.at(tx + col, r.y + row);
cell.default = false;
- cell.style.bg = .{ .rgb = msel_bg };
- cell.style.fg = .{ .rgb = msel_fg };
+ cell.style.bg = .{ .rgb = th.sel_bg };
+ cell.style.fg = .{ .rgb = th.sel_fg };
}
}
}
@@ -11838,8 +11885,16 @@ pub const Pardes = struct {
.acol = if (pane.vsel.active) pane.vsel.col else pane.cur_col,
} else pane.sels[si];
const bnd = cellBounds(sr);
- const bg = if (primary) msel_bg else msel2_bg;
- const fg = if (primary) msel_fg else msel2_fg;
+ // an extra cursor is the selection colour turned down: the
+ // highlight pulled halfway back to the page, so the primary is
+ // the one that reads as "here" at a glance. On a light theme
+ // that dims toward white rather than toward black, which is the
+ // same statement. The INK stays put — helix's two selection
+ // styles differ in their background too, and dimming both ends
+ // walks the text and its cell toward each other until neither
+ // is readable on the themes whose selection is already close to
+ // the page.
+ const bg = if (primary) th.sel_bg else mix(th.sel_bg, page_rgb);
var ar: i32 = bnd.lo_row;
while (ar <= bnd.hi_row) : (ar += 1) {
const prow = ar - off + @as(i32, BOX_H);
@@ -11851,7 +11906,7 @@ pub const Pardes = struct {
const cell = s.at(tx + @as(u16, @intCast(col)), r.y + @as(u16, @intCast(prow)));
cell.default = false;
cell.style.bg = .{ .rgb = bg };
- cell.style.fg = .{ .rgb = fg };
+ cell.style.fg = .{ .rgb = th.sel_fg };
}
}
if (primary and !preview) continue; // the hardware cursor IS the primary's
@@ -11860,8 +11915,8 @@ pub const Pardes = struct {
if (crow >= BOX_H and crow < @as(i32, r.h) and ccol >= vpfx and ccol < tw) {
const cell = s.at(tx + @as(u16, @intCast(ccol)), r.y + @as(u16, @intCast(crow)));
cell.default = false;
- cell.style.bg = .{ .rgb = msel_fg };
- cell.style.fg = .{ .rgb = msel_bg };
+ cell.style.bg = .{ .rgb = th.sel_fg };
+ cell.style.fg = .{ .rgb = th.sel_bg };
}
}
}
diff --git a/src/themes/acme.zig b/src/themes/acme.zig
index 925cacfb..87183ebc 100644
--- a/src/themes/acme.zig
+++ b/src/themes/acme.zig
@@ -5,10 +5,17 @@
//! darkened for paper: index 7 (the "white" a shell writes most of its output
//! in) is a mid grey, and 15 (bright white) is BLACK, because a program asking
//! for the brightest color on a light page means "make this stand out".
+//!
+//! The selection is acme's own: plan9's DDarkyellow #eeee9e, the colour acme
+//! sweeps text with, and black ink over it — acme changes the paper under a
+//! selection and never the letters. Out of the same draw.h palette the
+//! scrollbar's #99994c (DYellowgreen) is already taken from.
pub const theme = .{
.name = "acme",
.bg = .{ 0xff, 0xff, 0xea },
.fg = .{ 0x00, 0x00, 0x00 },
+ .sel_bg = .{ 0xee, 0xee, 0x9e },
+ .sel_fg = .{ 0x00, 0x00, 0x00 },
.tag_bg = .{ 0xea, 0xff, 0xff },
.tag_fg = .{ 0x00, 0x00, 0x00 },
.box = .{ 0x6e, 0x4f, 0xd0 },
diff --git a/src/themes/dark.zig b/src/themes/dark.zig
index df418982..5b39b1c7 100644
--- a/src/themes/dark.zig
+++ b/src/themes/dark.zig
@@ -3,10 +3,18 @@
//! `bg`/`fg` NULL: the page keeps the host terminal's own default cell, so
//! pardes sits inside whatever the surrounding terminal already looks like and
//! only paints its chrome. Every other theme here dresses the whole page.
+//!
+//! The selection pair is the warm tan pardes used to paint EVERY theme's
+//! selection with, back when those six numbers were module constants. This is
+//! the theme it belongs to: a null page is the host terminal's, and a tan that
+//! reads as keyboard-driven on whatever dark cell that turns out to be is
+//! exactly what it was picked for.
pub const theme = .{
.name = "dark",
.bg = null,
.fg = null,
+ .sel_bg = .{ 0x4a, 0x3a, 0x1e },
+ .sel_fg = .{ 0xd8, 0xc8, 0xa8 },
.tag_bg = .{ 0x2c, 0x2a, 0x3e },
.tag_fg = .{ 0x96, 0x96, 0x96 },
.box = .{ 0x6e, 0x4f, 0xd0 },
diff --git a/src/themes/helix.zig b/src/themes/helix.zig
index 26c156ff..3c2934f7 100644
--- a/src/themes/helix.zig
+++ b/src/themes/helix.zig
@@ -18,6 +18,13 @@
//! #121212 for the bars, 238 #444444 for the thumb, 235 #262626 for an
//! unfocused box. No hue anywhere outside the syntax colors.
//!
+//! The selection pair is the one thing not in that capture, because the capture
+//! has nothing selected in it: it is pardes.toml's own
+//! `"ui.selection" = { bg = "#80a0ff" }`, verbatim. helix names no foreground
+//! there — it tints the cell and lets the syntax colour ride, which at #bdbdbd
+//! on #80a0ff is not something to copy — so the ink is the page's own
+//! near-black, the other end of the theme's own two extremes.
+//!
//! Shape: bg/fg are explicit rather than null, because "just really dark" must
//! not depend on whose terminal it lands in — but `palette` stays null so a
//! shell's ANSI indices still resolve to the host's own 16 colors. helix does
@@ -32,6 +39,8 @@ pub const theme = .{
.name = "helix",
.bg = .{ 0x08, 0x08, 0x08 },
.fg = .{ 0xbd, 0xbd, 0xbd },
+ .sel_bg = .{ 0x80, 0xa0, 0xff },
+ .sel_fg = .{ 0x08, 0x08, 0x08 },
.tag_bg = .{ 0x12, 0x12, 0x12 },
.tag_fg = .{ 0x94, 0x94, 0x94 },
.box = .{ 0x62, 0x62, 0x62 },