From fde030a699b3d6667d1fa21ae192b5011e3f373f Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 23:50:56 -0300 Subject: Rules and chrome scale with the display's density; dark pages get visible separators and focus tints Density: dp() in gui.zig draws every size given in pixels in logical pixels (SDL's window display scale, rounded, at least one), as acme scales Border, ButtonBorder and Scrollgap: the rules (rule_px), the tag rule, the grip's ring, its dirty gap and mode bars, the rail's gap to the text and its minimum, the thumb's inset, the rim, a bar cursor's minimum width, underline and strikethrough thickness (the atlas is dropped on a scale change), the rule-grab margin, and the Crt's scanline and mask periods (pardesScale in the post prefix). Cell- and tagline-measured sizes stay: the rail's width, Lift's blur and offsets, every motion; iResolution, fragCoord and iMouse stay in the display's pixels, as ghostty's. PARDES_TEST_SCALE forces a density in a harness (a test window is 1x otherwise, whatever display it lands on) and sizes the test font with it. Colours: a separator that stands off its page by less than 1.5:1 is lifted toward the text to 1.6:1 (helix, atelier, paper; a theme with no page, dark, is judged against #121212), and empty_col follows it. forge's own rule already stands at 3.83:1; what hid it was density: 2 device pixels on a 3x screen, now 6 (dp). A focused tag's tint closer than 1.5:1 to the plain tag on a dark page (1.25:1 on a light one) is pushed further the way it leans, as far as its text keeps its own contrast or 4.5: forge 1.23 -> 1.51 (#2d3440 -> #374253), and likewise dusk, orchard, ink, lagoon, clay, harvest, spectrum, solarium, forge_soft; daybreak, atelier, paper to 1.25. dark and helix gain a tint of their own (dark: #484566 under #c8c8c8, 1.54:1; helix: ramp 237 under 250, 1.65:1). Tag caret: past a full row's end, the G3 glide box sits on the tag's right edge, where the layer's caret and the pixel shell's are. Tests: every native theme's separator >= 1.5 and every dark one has a tint >= 1.25 with readable text; forge keeps its own border; the tag caret glide box at a full row; at 2x the spine, tag rule, ring, rail start and gap, and bar cursor double. Legacy-ThemeFile test strips helix's new pair. Goldens: snapshot goldens change in colour rows only, the tints (orchard #283a2f -> #314738 at boot, forge, helix and dark in theme/themesel). GUI goldens: the harness's single Del of the renamed wide pane is refused since ysqnkyoy (a rename counted as unsaved; fixed on the 9P line in wkzlsqvy), so scenes 09-17 were recorded with that pane open and the file was stale since the merge (08 and 18 also moved with ykvnltvs's full-row tag wrap). The goldens here were recorded with that pane closed (as wkzlsqvy will close it); against the parent's binary under the same conditions they differ only in the tints: 01-13 forge's (#2d3440 -> #374253, and the glyph edges on it), 14 ink's; 15-18 are byte-identical. Until wkzlsqvy is merged, a run here fails 09-17. Each commit reproduces its own recorded goldens on this machine (checked at kvtmqkyn). A test window is 1x unless PARDES_TEST_SCALE says otherwise, whatever display it opens on. Prebuilt shaders refreshed from their sources. Captures: .scratch/render/dark-borders/{before,after,compare,scale2,scale3}. --- docs/themes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'docs/themes.md') diff --git a/docs/themes.md b/docs/themes.md index e028e806..2b15051e 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -102,13 +102,13 @@ so existing exported themes remain valid. | Optional role | Purpose | When absent | | --- | --- | --- | -| `tag_active_bg`, `tag_active_fg` | Focused pane tag surface and text | `tag_bg`, `tag_fg` | +| `tag_active_bg`, `tag_active_fg` | Focused pane tag surface and text; a surface closer than 1.25:1 to `tag_bg` is pushed further the way it already leans, as far as its text keeps its own contrast (or 4.5). Every dark native theme sets one | `tag_bg`, `tag_fg` | | `tag_name_fg` | Filename or terminal `Tty` command tint | The corresponding normal or active tag foreground | | `tag_active_name_fg` | Filename or terminal `Tty` tint in active tags | `tag_name_fg`, then the active tag foreground | | `column_box` | Column grip while held, and its drag rail | `num` | | `column_box_dim` | Column drag grip at rest, in every focus state | Equal mix of `column_box` and `tag_bg` | -| `border` | Quiet separators | `scroll_track` | -| `empty_col` | A column with no pane, under its tag (acme: white) | `border` | +| `border` | Quiet separators; one that stands off the page by less than 1.5:1 (a dark rule on a near-black page) is lifted toward the text to 1.6:1. A theme with no page is judged against #121212 | `scroll_track` | +| `empty_col` | A column with no pane, under its tag (acme: white) | `border`, as drawn | | `lineno_active` | Restrained current line number foreground | `lineno` | | `search_bg`, `search_fg` | Search matches, independent of selection | `sel_bg`, `sel_fg` | | `diagnostic_error` | Error text | `fg`, or `tag_fg` for an inherited foreground | @@ -118,7 +118,7 @@ so existing exported themes remain valid. | `tag_sel_bg` | A tag's selection ground | `sel_bg` | | `sweep_bg`, `sweep_fg` | Three triples each: the select, exec and look sweeps' ground and ink | `sel_bg` tinted toward `kw`, `str` and `num`, in `sel_fg` | | `tag_rule` | The one-pixel rule between a pane's tag and its body, quieter than the `rule_px` rules between panes and columns | Halfway between `tag_bg` and the page the shell draws | -| `rule_px` | Pixel shells: the width of the rules between columns, between panes stacked in a column, and under the workspace and column tags, in `border` (the rule between a tag and its body is always one pixel) | 2 | +| `rule_px` | Pixel shells: the width of the rules between columns, between panes stacked in a column, and under the workspace and column tags, in `border` (the rule between a tag and its body is always one pixel), in logical pixels: doubled on a 2x display, as acme scales its Border | 2 | | `box_border`, `box_dirty` | The grip is acme's button: `box` filled when focused, else a two-pixel ring of `box_border` round the tag's ground; `box_dirty` fills it inside either ring while its file is unsaved (a grid, which has no ring, shows a bold `*` on `box_dirty` in the grip's second cell) | `box_dim`; `diagnostic_warning`, then `num` | | `rail_px` | Pixel shells: the scroll column's width, the grip's button over the scrollbar (thumb a pixel narrower), in pixels at a 17px tagline, scaled with it | 12, acme's Scrollwid | -- cgit v1.3 From 09ec515c5e94de741104038cd61f18b2b997e78c Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 06:04:28 -0300 Subject: G5: lapis, 0x4200.cafe's manuscript, as a theme with its ornament A native theme `lapis` (src/themes/lapis.zig) from web/theme/lapis.css: a lapis-deep page, lapis tags with gold names, vellum text, vermilion rubrics, gold rules and a vermilion tag rule, gold selection in lapis ink. Its ornament is the theme's new `decor` (colors.Decor), drawn by a pixel shell inside the chrome it already has (decision 1), all of it off every focus indicator: - page_dots: the site's gold dot grid (#e8c46a22, 22 px), drawn by the cell shader on page-coloured cells alone (flag 0x10000000), so it lies under the text and never under a selection, a tag or the cursor; fixed to the window, a page the panes lie on. - rail_checker: the dither frame's gold and lapis checker in the scroll track (a decor kind, flag 0x08000000), anchored to its rail; the thumb stays solid gold. - tag plaques (tag_border, tag_shadow, tag_stripe): every tag a raised plaque inside its own band, the site's trail: a 2px gold frame and a hard 5px vermilion shadow down and right, both within the band's rows and columns (down, what the slack under the text leaves), so no shadow reaches a body, a grip or the next pane. The focused pane's plaque is the code window's title bar, gold and lapis stripes of 2px, its words on plates of the focused ground; unfocused ones are plain; column and workspace tags take the object-label language (1px frame, 2px shadow toward the page). Drawn as decor under the cells (a group's under range), with blank tag cells see-through and the cells blended premultiplied (pipeline_over) on such frames; opaque windows only. - title_shadow: the titles' vermilion offset shadow under a file name's letters, sampled in the cell shader inside the letters' own cells (flag 0x08000000), none on a cursor. Chrome carries the decor (decor_*, name_ink, active_name_ink); a theme file folds a nested decor literal field by field. Left out, with why, in docs/effects.md: the fonts, the ornaments, a decor crossfade, and in the tty anything but the colours (a tag row has no spare cell for a shadow). An empty column is the page. The effect id is masked to 27 bits (ui.vert/ui.frag) for the fifth flag. Tests: the cell flags land only on page ground and file-name cells, never a cursor, nothing without decor; lapis's plaques stay inside their tag's band, clear of every grip, only the focused one striped; its checker is in the track; no other theme draws any; the separator and focus tests cover lapis. Goldens: theme and themesel (the ring gains lapis after acme). Gates as G4. Feel review: .scratch/render/lapis/ (lapis.png, lapis-2x.png, tags-focused-vs-unfocused-2x.png, a PanelSlide at 60 fps and a quarter). --- docs/effects.md | 29 ++++ docs/themes.md | 1 + shaders/decor.frag.glsl | 13 ++ shaders/prebuilt/decor.frag.glsl | 13 ++ shaders/prebuilt/decor.frag.spv | Bin 6560 -> 7852 bytes shaders/prebuilt/ui.frag.glsl | 32 +++- shaders/prebuilt/ui.frag.spv | Bin 5700 -> 8448 bytes shaders/prebuilt/ui.vert.glsl | 10 +- shaders/prebuilt/ui.vert.spv | Bin 5612 -> 5612 bytes shaders/ui.frag.glsl | 32 +++- shaders/ui.vert.glsl | 10 +- src/colors.zig | 49 +++++- src/detached/wire.zig | 2 +- src/draw.zig | 17 ++ src/gui/gui.zig | 335 ++++++++++++++++++++++++++++++++++++++- src/surface.zig | 20 +++ src/themes/lapis.zig | 92 +++++++++++ test/snapshots/theme.golden | 62 ++++---- test/snapshots/themesel.golden | 126 +++++++-------- 19 files changed, 729 insertions(+), 114 deletions(-) create mode 100644 src/themes/lapis.zig (limited to 'docs/themes.md') diff --git a/docs/effects.md b/docs/effects.md index 8073c50a..8a60d116 100644 --- a/docs/effects.md +++ b/docs/effects.md @@ -38,6 +38,7 @@ why. The verdict is the user's. | G1 lift | `Lift shadow\|rim\|auto` (off), `InactiveDim ` | opt-in until the focus-lift default is decided | lift-shots-2 (off, shadow, rim, auto on acme, dusk, forge); frame series + mp4 per Motion flavour | shadow on acme: keep as is. Round 1 dropped glow and surface (surface lowered the focused text's contrast) and made rim a hairline just above the focused tag (light on a dark page, shade on a light one). auto on a dark page recedes the others (InactiveDim 30) | | G3 cursor | follows `Motion`; `CursorBlink on\|off` (on) | glide on by default through the default flavour (smooth); blink on (decision 6) | .scratch/render/cursor/cursor-flavours-grid.mp4 (and quarter speed), raw/// | pending | | G4 bloom, vignette, grain | `Bloom`, `Vignette`, `Grain` `0..3` (off) | bundled post passes; none touches a tag, grip, notice or the cursor (pardesSpare); each capped per theme (pardesCap) | .scratch/render/{bloom,vignette,grain}/ (levels 0-3 on forge, dusk, acme; crops; mp4 stepping through the levels) | pending | +| G5 lapis | `Theme lapis` (its `decor`) | a native theme with ornament: tag plaques with hard shadows inside their bands (the focused one striped), dots, rail checker, title shadow | .scratch/render/lapis/ (whole window at 1x and 2x, crops, a slide mp4) | pending | ## Motion flavours @@ -114,3 +115,31 @@ is round, starts halfway to a corner and reaches 10, 18 and 28%. Grain is Found on the way: SDL GPU binds at most 4 KiB of a uniform block, so every Shadertoy colour after the palette (iBackgroundColor and on) read zero; the block is now cut in three with ghostty's names and order kept. + +Notes on G5: lapis (src/themes/lapis.zig) is 0x4200.cafe's lapis.css in +pardes's chrome, drawn inside it (decision 1). The tags are the site's +trail and title bars: every tag a plaque in its own band, framed in gold +(2 px, the trail's border) with the trail's hard vermilion shadow (5 px) +down and right, the frame and the shadow both inside the band's rows and +columns, so no shadow reaches a body, a grip or the next pane; down, the +shadow gets what the band's slack under the text leaves (a pixel or three +at a tagline under the body's size, more at a larger one), to the right +its full offset. The focused pane's plaque is the code window's title bar, +gold and lapis stripes of 2 px, its words on plates of the focused ground +(the words' cells stay opaque, the blanks between let the stripes show), +so the focus indicator is plainer to see than any theme's tint, never +less; the unfocused ones are plain. Column and workspace tags take the +quieter object-label language: a 1 px frame and a 2 px shadow of vermilion +toward the page. The plaques are decor drawn under their cells (a group's +under range) with the cells blended premultiplied over them; blank tag +cells on the tag's ground are see-through there. They need an opaque +window: under WindowOpacity 100 the tags are plain. Also from the site: +the colours (lapis-deep page, vellum text, gold names and rules, +vermilion rubrics and tag rule, gold selection in lapis ink), the page's +gold dot grid (22 px, #e8c46a22) on page-coloured cells only, fixed to the +window; the dither frame's checker in the scroll track, anchored to its +rail (the thumb solid gold); the titles' vermilion offset shadow under a +file name's letters. Left out: Crimson Pro and Departure Mono, the +ornaments, a decor crossfade (it snaps while the colours fade). The tty +shows the colours alone: a tag row abuts its body and its neighbour, so +there is no cell for a shadow row or column. diff --git a/docs/themes.md b/docs/themes.md index 2b15051e..38596819 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -121,6 +121,7 @@ so existing exported themes remain valid. | `rule_px` | Pixel shells: the width of the rules between columns, between panes stacked in a column, and under the workspace and column tags, in `border` (the rule between a tag and its body is always one pixel), in logical pixels: doubled on a 2x display, as acme scales its Border | 2 | | `box_border`, `box_dirty` | The grip is acme's button: `box` filled when focused, else a two-pixel ring of `box_border` round the tag's ground; `box_dirty` fills it inside either ring while its file is unsaved (a grid, which has no ring, shows a bold `*` on `box_dirty` in the grip's second cell) | `box_dim`; `diagnostic_warning`, then `num` | | `rail_px` | Pixel shells: the scroll column's width, the grip's button over the scrollbar (thumb a pixel narrower), in pixels at a 17px tagline, scaled with it | 12, acme's Scrollwid | +| `decor` | Pixel shells: a theme's ornament, drawn inside the chrome it has (lapis's): `page_dots` (a dot grid on the page's own ground, `page_dot_alpha` strong every `page_dot_px`), `rail_checker` (a checker in the scroll track, squares of `rail_checker_px`), `tag_border`, `tag_shadow`, `tag_stripe` (every tag a raised plaque inside its own band: a `tag_border_px` frame round its text and a hard `tag_shadow_px` shadow down and right, both inside the band's rows and columns; the focused pane's plaque striped `tag_stripe_px` with its words on plates of the tag's ground; column and workspace tags a quieter one; opaque windows only) and `title_shadow` (the file name's offset shadow, `title_shadow_px`), sizes in logical pixels. None of it touches a focus indicator | none | Native palettes give filenames a distinct hue at approximately the same brightness as the surrounding tag text. This also applies to output names diff --git a/shaders/decor.frag.glsl b/shaders/decor.frag.glsl index 3e2c286f..bb1ef0d6 100644 --- a/shaders/decor.frag.glsl +++ b/shaders/decor.frag.glsl @@ -8,6 +8,8 @@ // colour rides in the cell's ground (a_bg), its coverage in a_fg.r, so the // result is the overlay pipeline's exactly. // +// Checker (flag 0x08000000): two colours in squares, or in stripes, see main. +// // Soft (flag 0x10000000): a rectangle's shadow, rim or glow blurred by a Gaussian, // in closed form (the product of two erf edges), on one quad grown by 3 // sigma: no blur pass. Everything is in the quad's own 0..1 space, so a @@ -51,6 +53,17 @@ void main() { if (v_clip.z >= v_clip.x && (gl_FragCoord.x < v_clip.x || gl_FragCoord.x >= v_clip.z || gl_FragCoord.y < v_clip.y || gl_FragCoord.y >= v_clip.w)) discard; float opacity = clamp(u_ui.background_opacity, 0.0, 1.0); + // Checker (flag 0x08000000): squares of v_uv.x pixels from the quad's + // own corner, so it moves with its rail, in the ground's colour and the + // ink's (lapis's scroll track). + vec2 in_quad = v_cell_uv / max(vec2(dFdx(v_cell_uv.x), dFdy(v_cell_uv.y)), vec2(1e-6)); + if ((v_effect.x & 0x08000000u) != 0u) { + ivec2 q = ivec2(floor(in_quad / max(v_uv.x, 1.0))); + // v_uv.y set: stripes across the quad (lapis's title bar), not squares. + int k = v_uv.y > 0.5 ? q.y : q.x + q.y; + o_col = vec4(((k & 1) == 0 ? v_bg : v_fg) * opacity, 1.0); + return; + } if ((v_effect.x & 0x10000000u) == 0u) { o_col = vec4(v_bg * opacity, v_fg.r); return; diff --git a/shaders/prebuilt/decor.frag.glsl b/shaders/prebuilt/decor.frag.glsl index 3e2c286f..bb1ef0d6 100644 --- a/shaders/prebuilt/decor.frag.glsl +++ b/shaders/prebuilt/decor.frag.glsl @@ -8,6 +8,8 @@ // colour rides in the cell's ground (a_bg), its coverage in a_fg.r, so the // result is the overlay pipeline's exactly. // +// Checker (flag 0x08000000): two colours in squares, or in stripes, see main. +// // Soft (flag 0x10000000): a rectangle's shadow, rim or glow blurred by a Gaussian, // in closed form (the product of two erf edges), on one quad grown by 3 // sigma: no blur pass. Everything is in the quad's own 0..1 space, so a @@ -51,6 +53,17 @@ void main() { if (v_clip.z >= v_clip.x && (gl_FragCoord.x < v_clip.x || gl_FragCoord.x >= v_clip.z || gl_FragCoord.y < v_clip.y || gl_FragCoord.y >= v_clip.w)) discard; float opacity = clamp(u_ui.background_opacity, 0.0, 1.0); + // Checker (flag 0x08000000): squares of v_uv.x pixels from the quad's + // own corner, so it moves with its rail, in the ground's colour and the + // ink's (lapis's scroll track). + vec2 in_quad = v_cell_uv / max(vec2(dFdx(v_cell_uv.x), dFdy(v_cell_uv.y)), vec2(1e-6)); + if ((v_effect.x & 0x08000000u) != 0u) { + ivec2 q = ivec2(floor(in_quad / max(v_uv.x, 1.0))); + // v_uv.y set: stripes across the quad (lapis's title bar), not squares. + int k = v_uv.y > 0.5 ? q.y : q.x + q.y; + o_col = vec4(((k & 1) == 0 ? v_bg : v_fg) * opacity, 1.0); + return; + } if ((v_effect.x & 0x10000000u) == 0u) { o_col = vec4(v_bg * opacity, v_fg.r); return; diff --git a/shaders/prebuilt/decor.frag.spv b/shaders/prebuilt/decor.frag.spv index 3256f2b1..bd565d03 100644 Binary files a/shaders/prebuilt/decor.frag.spv and b/shaders/prebuilt/decor.frag.spv differ diff --git a/shaders/prebuilt/ui.frag.glsl b/shaders/prebuilt/ui.frag.glsl index dca224ba..62dbbfa2 100644 --- a/shaders/prebuilt/ui.frag.glsl +++ b/shaders/prebuilt/ui.frag.glsl @@ -10,6 +10,12 @@ layout(set = 2, binding = 0) uniform sampler2D u_atlas; layout(std140, set = 3, binding = 0) uniform UiUniforms { float background_opacity; + // A theme's ornament (colors.Decor): the page's dots (rgb, strength), + // their geometry (period, solid radius, faded radius, in pixels), and a + // file name's shadow (rgb, offset in pixels). + vec4 decor_dots; + vec4 decor_dot_geom; + vec4 decor_shadow; } u_ui; layout(location = 0) in vec2 v_uv; @@ -43,7 +49,7 @@ void main() { float bgA = (v_effect.x & 0x20000000u) != 0u ? 1.0 : (v_effect.x & 0x40000000u) != 0u ? 0.0 : clamp(u_ui.background_opacity, 0.0, 1.0); - uint effect = v_effect.x & 0x0fffffffu; + uint effect = v_effect.x & 0x07ffffffu; float progress = clamp(uintBitsToFloat(v_effect.y), 0.0, 1.0); uint cellCol = v_effect.w & 0xffffu; uint cellRow = v_effect.w >> 16; @@ -52,7 +58,29 @@ void main() { // Ink over ground, both premultiplied: v_fg*a + (v_bg*bgA)*(1-a), which is // mix() with the ground already premultiplied. At bgA = 1 that is exactly // the mix(v_bg, v_fg, a) at alpha 1 this shader always wrote. - vec4 cell = vec4(mix(v_bg * bgA, v_fg, a), mix(bgA, 1.0, a)); + vec3 ground = v_bg; + // 0x10000000: the page's ground under a pane body, dotted (lapis): a + // dot every period pixels, fixed to the window, under the text. + if ((v_effect.x & 0x10000000u) != 0u) { + float period = u_ui.decor_dot_geom.x; + vec2 d = mod(gl_FragCoord.xy, period) - 0.5 * period; + float dot_a = 1.0 - smoothstep(u_ui.decor_dot_geom.y, u_ui.decor_dot_geom.z, length(d)); + ground = mix(ground, u_ui.decor_dots.rgb, u_ui.decor_dots.a * dot_a); + } + vec4 under = vec4(ground * bgA, bgA); + // Derivatives outside any branch: a pixel's step in the atlas and in + // the cell, for the shadow's offset. + vec2 step_uv = vec2(dFdx(v_uv.x), dFdy(v_uv.y)) * u_ui.decor_shadow.a; + vec2 step_cell = vec2(dFdx(v_cell_uv.x), dFdy(v_cell_uv.y)) * u_ui.decor_shadow.a; + // 0x08000000: a file name's letters cast a shadow down and right onto + // their own cell's ground (lapis), under the ink; what would fall past + // the cell's edge is not drawn. + if ((v_effect.x & 0x08000000u) != 0u) { + vec2 from = v_cell_uv - step_cell; + float s = all(greaterThanEqual(from, vec2(0.0))) ? texture(u_atlas, v_uv - step_uv).r : 0.0; + under = mix(under, vec4(u_ui.decor_shadow.rgb, 1.0), s); + } + vec4 cell = vec4(mix(under.rgb, v_fg, a), mix(under.a, 1.0, a)); // 3 = data dissolve. Changed cells retain the frozen old cell until their // stable threshold, then atomically become the new cell. Exact endpoints diff --git a/shaders/prebuilt/ui.frag.spv b/shaders/prebuilt/ui.frag.spv index c8ee1604..d83d9c55 100644 Binary files a/shaders/prebuilt/ui.frag.spv and b/shaders/prebuilt/ui.frag.spv differ diff --git a/shaders/prebuilt/ui.vert.glsl b/shaders/prebuilt/ui.vert.glsl index 5551f3f1..de22adee 100644 --- a/shaders/prebuilt/ui.vert.glsl +++ b/shaders/prebuilt/ui.vert.glsl @@ -14,9 +14,11 @@ layout(location = 3) in vec3 a_bg; layout(location = 4) in vec4 a_panel_rect; layout(location = 5) in vec4 a_present_rect; // effect id, eased-progress float bits, pane serial, local row/column packed -// into one uint. The FOUR high effect bits are flags — the frozen-old cell -// layer, a see-through cell background, an opaque block-cursor fill and a -// soft shadow (decor.frag.glsl) — so the id is what is left below them; integer transport preserves the exact +// into one uint. The FIVE high effect bits are flags — the frozen-old cell +// layer, a see-through cell background, an opaque block-cursor fill, a soft +// shadow (decor.frag.glsl) or dotted page ground (ui.frag.glsl), and a +// checker (decor) or a file name's shadow (ui) — so the id is what is left +// below them; integer transport preserves the exact // cross-backend hash. layout(location = 6) in uvec4 a_effect; // A clip rectangle in target pixels (x0, y0, x1, y1): the fragment shader @@ -61,7 +63,7 @@ void main() { v_cell_uv = vec2(0.0, 1.0); } - uint effect = a_effect.x & 0x0fffffffu; + uint effect = a_effect.x & 0x07ffffffu; if (effect == 1u || effect == 2u || effect == 5u) { // NDC y points up, so a top-left/bottom-right box has a NEGATIVE y // delta. Preserve that sign or every local y is mirrored/outside. diff --git a/shaders/prebuilt/ui.vert.spv b/shaders/prebuilt/ui.vert.spv index b1e87ec1..af5b0738 100644 Binary files a/shaders/prebuilt/ui.vert.spv and b/shaders/prebuilt/ui.vert.spv differ diff --git a/shaders/ui.frag.glsl b/shaders/ui.frag.glsl index dca224ba..62dbbfa2 100644 --- a/shaders/ui.frag.glsl +++ b/shaders/ui.frag.glsl @@ -10,6 +10,12 @@ layout(set = 2, binding = 0) uniform sampler2D u_atlas; layout(std140, set = 3, binding = 0) uniform UiUniforms { float background_opacity; + // A theme's ornament (colors.Decor): the page's dots (rgb, strength), + // their geometry (period, solid radius, faded radius, in pixels), and a + // file name's shadow (rgb, offset in pixels). + vec4 decor_dots; + vec4 decor_dot_geom; + vec4 decor_shadow; } u_ui; layout(location = 0) in vec2 v_uv; @@ -43,7 +49,7 @@ void main() { float bgA = (v_effect.x & 0x20000000u) != 0u ? 1.0 : (v_effect.x & 0x40000000u) != 0u ? 0.0 : clamp(u_ui.background_opacity, 0.0, 1.0); - uint effect = v_effect.x & 0x0fffffffu; + uint effect = v_effect.x & 0x07ffffffu; float progress = clamp(uintBitsToFloat(v_effect.y), 0.0, 1.0); uint cellCol = v_effect.w & 0xffffu; uint cellRow = v_effect.w >> 16; @@ -52,7 +58,29 @@ void main() { // Ink over ground, both premultiplied: v_fg*a + (v_bg*bgA)*(1-a), which is // mix() with the ground already premultiplied. At bgA = 1 that is exactly // the mix(v_bg, v_fg, a) at alpha 1 this shader always wrote. - vec4 cell = vec4(mix(v_bg * bgA, v_fg, a), mix(bgA, 1.0, a)); + vec3 ground = v_bg; + // 0x10000000: the page's ground under a pane body, dotted (lapis): a + // dot every period pixels, fixed to the window, under the text. + if ((v_effect.x & 0x10000000u) != 0u) { + float period = u_ui.decor_dot_geom.x; + vec2 d = mod(gl_FragCoord.xy, period) - 0.5 * period; + float dot_a = 1.0 - smoothstep(u_ui.decor_dot_geom.y, u_ui.decor_dot_geom.z, length(d)); + ground = mix(ground, u_ui.decor_dots.rgb, u_ui.decor_dots.a * dot_a); + } + vec4 under = vec4(ground * bgA, bgA); + // Derivatives outside any branch: a pixel's step in the atlas and in + // the cell, for the shadow's offset. + vec2 step_uv = vec2(dFdx(v_uv.x), dFdy(v_uv.y)) * u_ui.decor_shadow.a; + vec2 step_cell = vec2(dFdx(v_cell_uv.x), dFdy(v_cell_uv.y)) * u_ui.decor_shadow.a; + // 0x08000000: a file name's letters cast a shadow down and right onto + // their own cell's ground (lapis), under the ink; what would fall past + // the cell's edge is not drawn. + if ((v_effect.x & 0x08000000u) != 0u) { + vec2 from = v_cell_uv - step_cell; + float s = all(greaterThanEqual(from, vec2(0.0))) ? texture(u_atlas, v_uv - step_uv).r : 0.0; + under = mix(under, vec4(u_ui.decor_shadow.rgb, 1.0), s); + } + vec4 cell = vec4(mix(under.rgb, v_fg, a), mix(under.a, 1.0, a)); // 3 = data dissolve. Changed cells retain the frozen old cell until their // stable threshold, then atomically become the new cell. Exact endpoints diff --git a/shaders/ui.vert.glsl b/shaders/ui.vert.glsl index 5551f3f1..de22adee 100644 --- a/shaders/ui.vert.glsl +++ b/shaders/ui.vert.glsl @@ -14,9 +14,11 @@ layout(location = 3) in vec3 a_bg; layout(location = 4) in vec4 a_panel_rect; layout(location = 5) in vec4 a_present_rect; // effect id, eased-progress float bits, pane serial, local row/column packed -// into one uint. The FOUR high effect bits are flags — the frozen-old cell -// layer, a see-through cell background, an opaque block-cursor fill and a -// soft shadow (decor.frag.glsl) — so the id is what is left below them; integer transport preserves the exact +// into one uint. The FIVE high effect bits are flags — the frozen-old cell +// layer, a see-through cell background, an opaque block-cursor fill, a soft +// shadow (decor.frag.glsl) or dotted page ground (ui.frag.glsl), and a +// checker (decor) or a file name's shadow (ui) — so the id is what is left +// below them; integer transport preserves the exact // cross-backend hash. layout(location = 6) in uvec4 a_effect; // A clip rectangle in target pixels (x0, y0, x1, y1): the fragment shader @@ -61,7 +63,7 @@ void main() { v_cell_uv = vec2(0.0, 1.0); } - uint effect = a_effect.x & 0x0fffffffu; + uint effect = a_effect.x & 0x07ffffffu; if (effect == 1u || effect == 2u || effect == 5u) { // NDC y points up, so a top-left/bottom-right box has a NEGATIVE y // delta. Preserve that sign or every local y is mirrored/outside. diff --git a/src/colors.zig b/src/colors.zig index 4a18bba9..b44e84f9 100644 --- a/src/colors.zig +++ b/src/colors.zig @@ -123,6 +123,42 @@ pub fn mix(a: [3]u8, b: [3]u8) [3]u8 { return out; } +/// A theme's ornament, drawn by a pixel shell inside the chrome it already +/// has (docs/render-pipeline.md §10: no gaps between panes): the lapis +/// theme's. Sizes are logical pixels. None of it touches a focus indicator: +/// the dots lie on the page's own ground, the checker in the scroll track +/// (never its thumb), a tag's plaque inside its band beside its grip (and +/// the focused one only the more striking), and the shadow under a file +/// name's own letters. +pub const Decor = struct { + /// A dot grid on the page's ground, `page_dot_alpha` of 255 strong, a + /// dot one logical pixel across (fading out by a pixel and a half) every + /// `page_dot_px`, fixed to the window. + page_dots: ?[3]u8 = null, + page_dot_alpha: u8 = 0x22, + page_dot_px: u8 = 22, + /// A checker in the scroll track, squares of `rail_checker_px`, this + /// colour and the track's. + rail_checker: ?[3]u8 = null, + rail_checker_px: u8 = 3, + /// Every tag drawn as a raised plaque inside its own band (the site's + /// trail and title bars): a `tag_border` frame round the text, a hard + /// `tag_shadow` offset down and right by `tag_shadow_px`, both within + /// the band's rows and columns, never past them; the focused pane's + /// plaque striped `tag_stripe` and the tag's ground, `tag_stripe_px` + /// each, its words on plates of the ground. Column and workspace tags + /// take a quieter one (a one-pixel frame, a shadow of two, no stripes). + tag_border: ?[3]u8 = null, + tag_border_px: u8 = 2, + tag_shadow: ?[3]u8 = null, + tag_shadow_px: u8 = 5, + tag_stripe: ?[3]u8 = null, + tag_stripe_px: u8 = 2, + /// The file name's offset shadow, down and right, in a pane's tag. + title_shadow: ?[3]u8 = null, + title_shadow_px: u8 = 2, +}; + pub const Theme = struct { name: []const u8, bg: ?[3]u8, @@ -188,6 +224,8 @@ pub const Theme = struct { /// in pixels at a 17px tagline, the thumb one narrower (acme's 12px /// Scrollwid, the default). rail_px: ?u8 = null, + /// A pixel shell's ornament (Decor): lapis's. + decor: ?Decor = null, }; const curated = struct { @@ -209,6 +247,7 @@ const curated = struct { pub const helix = @import("themes/helix.zig"); pub const dark = @import("themes/dark.zig"); pub const acme = @import("themes/acme.zig"); + pub const lapis = @import("themes/lapis.zig"); }; fn fold(comptime C: type) [@typeInfo(C).@"struct".decls.len]Theme { @@ -219,7 +258,15 @@ fn fold(comptime C: type) [@typeInfo(C).@"struct".decls.len]Theme { var t: Theme = undefined; const source = @field(C, d.name).theme; for (@typeInfo(Theme).@"struct".fields) |f| { - @field(t, f.name) = if (@hasField(@TypeOf(source), f.name)) + @field(t, f.name) = if (f.type == ?Decor and @hasField(@TypeOf(source), f.name)) decor: { + // A nested literal is folded field by field too. + var decor: Decor = .{}; + const given = @field(source, f.name); + for (@typeInfo(Decor).@"struct".fields) |g| if (@hasField(@TypeOf(given), g.name)) { + @field(decor, g.name) = @field(given, g.name); + }; + break :decor decor; + } else if (@hasField(@TypeOf(source), f.name)) @field(source, f.name) else if (f.default_value_ptr) |ptr| @as(*const f.type, @ptrCast(@alignCast(ptr))).* diff --git a/src/detached/wire.zig b/src/detached/wire.zig index 53a1a70d..9f9c716a 100644 --- a/src/detached/wire.zig +++ b/src/detached/wire.zig @@ -13,7 +13,7 @@ pub const version: u16 = 8; /// The Chrome palette is carried field by field as its declaration lists /// them, so any change to it changes the wire: bump `version`, then this. -const chrome_layout: u64 = 0xc91196fcdef144e2; +const chrome_layout: u64 = 0xf852cb229ae38f51; comptime { @setEvalBranchQuota(10_000); var hash = std.hash.Fnv1a_64.init(); diff --git a/src/draw.zig b/src/draw.zig index b8cf4e7d..5f14ae21 100644 --- a/src/draw.zig +++ b/src/draw.zig @@ -470,7 +470,24 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface { .grip_dirty = boxDirty(p), .tag_rule = p.theme().tag_rule, .rail_px = @intCast(@min(255, @as(u32, p.theme().rail_px orelse 12) * p.settings.grip_width / 100)), + .name_ink = chrome.tag_name_fg, + .active_name_ink = if (p.settings.focus_tint) chrome.tag_active_name_fg else chrome.tag_name_fg, }; + if (p.theme().decor) |decor| { + s.chrome.decor_dots = decor.page_dots; + s.chrome.decor_dot_alpha = decor.page_dot_alpha; + s.chrome.decor_dot_px = decor.page_dot_px; + s.chrome.decor_checker = decor.rail_checker; + s.chrome.decor_checker_px = decor.rail_checker_px; + s.chrome.decor_box_border = decor.tag_border; + s.chrome.decor_box_border_px = decor.tag_border_px; + s.chrome.decor_box_shadow = decor.tag_shadow; + s.chrome.decor_box_shadow_px = decor.tag_shadow_px; + s.chrome.decor_box_stripe = decor.tag_stripe; + s.chrome.decor_box_stripe_px = decor.tag_stripe_px; + s.chrome.decor_title = decor.title_shadow; + s.chrome.decor_title_px = decor.title_shadow_px; + } place(p, s); // Separators have their own role; a scrollbar need not frame the page. const whole = s.regions[0].rect; diff --git a/src/gui/gui.zig b/src/gui/gui.zig index bde9aecd..092bb3bf 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -507,6 +507,12 @@ const clear_bg_bit: u32 = 0x4000_0000; const opaque_bg_bit: u32 = 0x2000_0000; /// Decor that is a soft shadow, not a flat fill (decor.frag.glsl). const soft_shadow_bit: u32 = 0x1000_0000; +/// Decor that is a checker (decor.frag.glsl). +const checker_bit: u32 = 0x0800_0000; +/// A cell on the page's ground under the theme's dots, and a file name's +/// cell that casts the theme's shadow (ui.frag.glsl, Chrome.decor_*). +const dotted_bit: u32 = 0x1000_0000; +const title_bit: u32 = 0x0800_0000; const SavedImagePlace = struct { key: pardes.ImageCacheKey, @@ -1787,6 +1793,10 @@ const Gui = struct { device: *c.SDL_GPUDevice, swapchain_format: c.SDL_GPUTextureFormat, pipeline: *c.SDL_GPUGraphicsPipeline, + /// The cell pipeline, blending premultiplied over what is drawn under: + /// for a frame with a theme's tag plaques, whose blank tag cells let + /// them show (tagPlaques). + pipeline_over: *c.SDL_GPUGraphicsPipeline, decor_pipeline: *c.SDL_GPUGraphicsPipeline, overlay_pipeline: *c.SDL_GPUGraphicsPipeline, image_pipeline: *c.SDL_GPUGraphicsPipeline, @@ -2068,6 +2078,9 @@ const Group = struct { image_count: u32 = 0, decor_start: u32 = 0, decor_count: u32 = 0, + /// The first `under_count` of the group's decor are drawn before its + /// cells, under them (a theme's tag plaques, whose cells let them show). + under_count: u32 = 0, }; /// The frame's draw groups, bottom to top: tier 0 (the page and the panes @@ -2455,6 +2468,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u const atlas_xfer = c.SDL_CreateGPUTransferBuffer(device, &ax_info) orelse return error.GpuCreate; const pipeline = try makePipeline(device, swapchain_format, frag_spv, 1, std.mem.zeroes(c.SDL_GPUColorTargetBlendState)); + const pipeline_over = try makePipeline(device, swapchain_format, frag_spv, 1, premultipliedBlend()); const decor_pipeline = try makePipeline(device, swapchain_format, decor_frag_spv, 0, backgroundLayerBlend()); const overlay_pipeline = try makeOverlayPipeline(device, swapchain_format); const image_pipeline = try makeImagePipeline(device, swapchain_format); @@ -2485,6 +2499,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u .device = device, .swapchain_format = swapchain_format, .pipeline = pipeline, + .pipeline_over = pipeline_over, .decor_pipeline = decor_pipeline, .overlay_pipeline = overlay_pipeline, .image_pipeline = image_pipeline, @@ -2523,6 +2538,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u // regrown to; the image ones are released below. defer { c.SDL_ReleaseGPUGraphicsPipeline(device, g.pipeline); + c.SDL_ReleaseGPUGraphicsPipeline(device, g.pipeline_over); c.SDL_ReleaseGPUGraphicsPipeline(device, g.decor_pipeline); c.SDL_ReleaseGPUGraphicsPipeline(device, g.overlay_pipeline); c.SDL_ReleaseGPUTexture(device, g.atlas_tex); @@ -5418,6 +5434,20 @@ fn renderFrame( defer if (building) build_zone.end(); const layout = fixedCellLayout(g); const chrome = &surface.chrome; + cell_decor = CellDecor.of(chrome); + // Plaques need an opaque window: with WindowOpacity under 100 the cells + // are drawn as they are, and the plaques not at all. + cell_decor.plaques_off = backgroundOpacity(g.applied_window_opacity) < 1; + if (chrome.decor_box_border != null and !cell_decor.plaques_off) { + cell_decor.grounds = .{ chrome.tag_bg, chrome.tag_focus_bg }; + for (surface.regionList()) |region| { + if (region.kind != .tag and region.kind != .column_tag and region.kind != .workspace_tag) continue; + if (cell_decor.nplaques == cell_decor.plaques.len) break; + const plaque = plaqueBox(g, chrome, surface.regionList(), region, layout, win_w, win_h) orelse continue; + cell_decor.plaques[cell_decor.nplaques] = plaque.band; + cell_decor.nplaques += 1; + } + } var groups = makeGroups(surface.panelTracks(), surface.hasPanelDiff()); // Cursors stand still while panes move, as they always have; while the // focused one glides or blinks, it is the overlay's quad instead. One @@ -5696,9 +5726,16 @@ fn renderFrame( const whole = c.SDL_Rect{ .x = 0, .y = 0, .w = @intCast(sw), .h = @intCast(sh) }; const decor_base = cell_total + shifted; for (groups.items[0..groups.len], 0..) |group, index| { + if (group.under_count != 0 and g.vbuf != null) { + c.SDL_BindGPUGraphicsPipeline(rp, g.decor_pipeline); + pushLayerOpacity(cmd, rp, background_opacity); + const binding = c.SDL_GPUBufferBinding{ .buffer = g.vbuf.?, .offset = (decor_base + group.decor_start) * @sizeOf(CellInstance) }; + c.SDL_BindGPUVertexBuffers(rp, 0, &binding, 1); + c.SDL_DrawGPUPrimitives(rp, 6, group.under_count, 0, 0); + } const has_shifted = index == 0 and shifted != 0; if ((group.cell_count != 0 or has_shifted) and g.vbuf != null) { - c.SDL_BindGPUGraphicsPipeline(rp, g.pipeline); + c.SDL_BindGPUGraphicsPipeline(rp, if (cell_decor.nplaques != 0) g.pipeline_over else g.pipeline); const samp_binding = c.SDL_GPUTextureSamplerBinding{ .texture = g.atlas_tex, .sampler = g.atlas_sampler }; c.SDL_BindGPUFragmentSamplers(rp, 0, &samp_binding, 1); if (group.cell_count != 0) { @@ -5716,13 +5753,13 @@ fn renderFrame( } } if (group.image_count != 0) drawNativeImagesGpu(g, rp, surface, group, whole); - if (group.decor_count != 0 and g.vbuf != null) { + if (group.decor_count > group.under_count and g.vbuf != null) { c.SDL_BindGPUGraphicsPipeline(rp, g.decor_pipeline); // Ink keeps its colour at any WindowOpacity. pushLayerOpacity(cmd, rp, if (index == groups.ink) 1 else background_opacity); - const binding = c.SDL_GPUBufferBinding{ .buffer = g.vbuf.?, .offset = (decor_base + group.decor_start) * @sizeOf(CellInstance) }; + const binding = c.SDL_GPUBufferBinding{ .buffer = g.vbuf.?, .offset = (decor_base + group.decor_start + group.under_count) * @sizeOf(CellInstance) }; c.SDL_BindGPUVertexBuffers(rp, 0, &binding, 1); - c.SDL_DrawGPUPrimitives(rp, 6, group.decor_count, 0, 0); + c.SDL_DrawGPUPrimitives(rp, 6, group.decor_count - group.under_count, 0, 0); } } drawOverlayGpu(g, cmd, rp, overlay_count); @@ -6024,6 +6061,9 @@ fn emitTagLayer(g: *Gui, instances: [*]CellInstance, next: *u32, layer: *const p const y = layer.viewport.y + @as(u16, @intCast(index)); const top = @as(f32, @floatFromInt(y)) * bh; emitInstance(g, instances, next.*, 0, y, .{ .w = band_right - left, .h = bh, .x_off = left, .y_off = slide }, win_w, win_h, track, .body, (&background)[0..1], 0, null, old_layer, false, page); + // A theme's plaque is drawn under the band: the band's own ground + // lets it show (tagPlaques). + if (cell_decor.nplaques != 0 and cell_decor.inPlaque(left + 0.5, top + 0.5)) instances[next.*].effect |= clear_bg_bit; if (slide != 0) clipInstanceTop(&instances[next.*], top, win_h); next.* += 1; const cells = layer.cells[index * layer.cols ..][0..layer.cols]; @@ -6229,6 +6269,10 @@ fn emitSurfaceCell( // this underlay at tagline height leaves page-colored gutters between // adjacent workspace, column and pane tags at TaglineSize < 100. emitInstance(g, instances, next.*, col, row, body_layout, win_w, win_h, track, .body, (&band_base)[0..1], 0, null, old_layer, false, page); + // Over a theme's plaque (not a grip's button) the underlay lets it + // show (tagPlaques). + if (cell_decor.nplaques != 0 and !here.grip and cell_decor.inPlaque((@as(f32, @floatFromInt(col)) + 0.5) * body_layout.w + body_layout.x_off, @as(f32, @floatFromInt(row)) * body_layout.h + body_layout.y_off + 0.5)) + instances[next.*].effect |= clear_bg_bit; next.* += 1; // Tag glyphs keep their font size inside a full physical-width grip. // A pane's grip cells are the cell shells' form (a dirty `*`): the @@ -6553,6 +6597,21 @@ fn emitInstance( // A block cursor is foreground UI ink, even though its color is carried // in the reversed cell background. Keep it visible at WindowOpacity 0. if (is_cursor) instances[idx].effect |= opaque_bg_bit; + // The theme's ornament (cell_decor): dots on the page's own ground, a + // shadow under a file name's letters. Never on a cursor. + if (!is_cursor) { + // A blank cell on a tag's ground inside a plaque: the plaque shows. + if (cell_decor.nplaques != 0 and resolved.role == .tagline and cellCodepoint(&line[at]) == ' ' and + (std.mem.eql(u8, &resolved.bg, &cell_decor.grounds[0]) or std.mem.eql(u8, &resolved.bg, &cell_decor.grounds[1])) and + (cell_decor.inPlaque(px0 + 0.5, py0 + 0.5) or cell_decor.inPlaque(px0 + layout.w - 0.5, py0 + 0.5))) + instances[idx].effect |= clear_bg_bit; + if (cell_decor.dots) |dotted| { + if (!resolved.clear_bg and std.mem.eql(u8, &resolved.bg, &dotted)) instances[idx].effect |= dotted_bit; + } + if (cell_decor.title) |inks| { + if (resolved.role == .tagline and (std.mem.eql(u8, &resolved.fg, &inks[0]) or std.mem.eql(u8, &resolved.fg, &inks[1]))) instances[idx].effect |= title_bit; + } + } } test "block cursor alone overrides transparent cell background" { @@ -6569,6 +6628,107 @@ test "block cursor alone overrides transparent cell background" { } } +test "lapis dots only the page's own ground and shadows only a file name, never a cursor" { + var g: Gui = undefined; + g.words = .{}; + g.cell_w = 10; + g.cell_h = 20; + g.tagline_width = 8; + g.tagline_height = 16; + g.space_slot = .{ .u = 0, .v = 0 }; + const page: [3]u8 = .{ 0x0a, 0x10, 0x30 }; + const name: [3]u8 = .{ 0xe8, 0xc4, 0x6a }; + var chrome: pardes.Chrome = .{ .page = page, .decor_dots = .{ 1, 2, 3 }, .decor_dot_alpha = 0x22, .decor_title = .{ 4, 5, 6 }, .name_ink = name, .active_name_ink = name }; + cell_decor = CellDecor.of(&chrome); + defer cell_decor = .{}; + const on_page: pardes.Cell = .{ .default = false, .style = .{ .fg = .{ .rgb = .{ 200, 200, 200 } }, .bg = .{ .rgb = page } } }; + const selected: pardes.Cell = .{ .default = false, .style = .{ .fg = .{ .rgb = .{ 0, 0, 0 } }, .bg = .{ .rgb = name } } }; + const title: pardes.Cell = .{ .default = false, .style = .{ .fg = .{ .rgb = name }, .bg = .{ .rgb = .{ 0x0f, 0x1a, 0x4a } }, .font_role = .tagline } }; + const plain_tag: pardes.Cell = .{ .default = false, .style = .{ .fg = .{ .rgb = .{ 0x9a, 0xa6, 0xd9 } }, .bg = .{ .rgb = .{ 0x0f, 0x1a, 0x4a } }, .font_role = .tagline } }; + var instances: [1]CellInstance = undefined; + const Case = struct { cell: pardes.Cell, role: pardes.FontRole, cursor: bool, dotted: bool, shadowed: bool }; + for ([_]Case{ + .{ .cell = on_page, .role = .body, .cursor = false, .dotted = true, .shadowed = false }, + .{ .cell = on_page, .role = .body, .cursor = true, .dotted = false, .shadowed = false }, + .{ .cell = selected, .role = .body, .cursor = false, .dotted = false, .shadowed = false }, + .{ .cell = title, .role = .tagline, .cursor = false, .dotted = false, .shadowed = true }, + .{ .cell = title, .role = .tagline, .cursor = true, .dotted = false, .shadowed = false }, + .{ .cell = plain_tag, .role = .tagline, .cursor = false, .dotted = false, .shadowed = false }, + }) |case| { + emitInstance(&g, &instances, 0, 0, 0, fixedCellLayout(&g), 800, 480, null, case.role, (&case.cell)[0..1], 0, null, false, case.cursor, Ground.opaqueRgb(page)); + try std.testing.expectEqual(case.dotted, instances[0].effect & dotted_bit != 0); + try std.testing.expectEqual(case.shadowed, instances[0].effect & title_bit != 0); + } + // A theme without decor marks nothing. + chrome = .{ .page = page }; + cell_decor = CellDecor.of(&chrome); + emitInstance(&g, &instances, 0, 0, 0, fixedCellLayout(&g), 800, 480, null, .body, (&on_page)[0..1], 0, null, false, false, Ground.opaqueRgb(page)); + try std.testing.expectEqual(@as(u32, 0), instances[0].effect & (dotted_bit | title_bit)); +} + +test "lapis plaques stay in their tag's band, clear of every grip; only the focused one is striped" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); + defer core.deinit(); + _ = try core.newShell(1, ""); + try std.testing.expect(pardes.layout.splitColumn(core, 0, 1, false)); + try std.testing.expect(core.executeBuiltinLine(0, "Theme lapis")); + for (0..40) |_| core.update(.tick); + pardes.test_api.sync(core); + const s = try core.render(core.scratch.allocator()); + try std.testing.expect(s.chrome.decor_box_border != null and s.chrome.decor_checker != null and s.chrome.decor_dots != null and s.chrome.decor_title != null); + var g: Gui = undefined; + g.decor = .empty; + defer g.decor.deinit(gpa); + g.cell_w = 16; + g.cell_h = 32; + g.tagline_width = 12; + g.tagline_height = 22; + display_scale = 1; + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, s, &groups, fixedCellLayout(&g), 1280, 768, true); + const layout = fixedCellLayout(&g); + const under = g.decor.items[groups.items[0].decor_start..][0..groups.items[0].under_count]; + try std.testing.expect(under.len != 0); + var stripes: usize = 0; + var shadows: usize = 0; + for (under) |item| { + const r: [4]f32 = .{ (item.x0 + 1) / 2 * 1280, (1 - item.y0) / 2 * 768, (item.x1 + 1) / 2 * 1280, (1 - item.y1) / 2 * 768 }; + // Inside one tag's band, whole: never on a body, a grip or the + // next pane. + const home = for (s.regionList()) |region| { + if (region.kind != .tag and region.kind != .column_tag and region.kind != .workspace_tag) continue; + const band = plaqueBox(&g, &s.chrome, s.regionList(), region, layout, 1280, 768) orelse continue; + if (r[0] >= band.band[0] - 0.01 and r[1] >= band.band[1] - 0.01 and r[2] <= band.band[2] + 0.01 and r[3] <= band.band[3] + 0.01) break region; + } else return error.PlaqueOutsideItsBand; + for (s.regionList()) |grip| { + if (grip.kind != .grip and grip.kind != .column_grip) continue; + const gx0 = @as(f32, @floatFromInt(grip.rect.x)) * 16; + const gx1 = @as(f32, @floatFromInt(grip.rect.x + grip.rect.w)) * 16; + const gy0 = @as(f32, @floatFromInt(grip.rect.y)) * 32; + const gy1 = @as(f32, @floatFromInt(grip.rect.y + grip.rect.h)) * 32; + try std.testing.expect(r[2] <= gx0 + 0.01 or r[0] >= gx1 - 0.01 or r[3] <= gy0 + 0.01 or r[1] >= gy1 - 0.01); + } + if (item.effect & checker_bit != 0) { + try std.testing.expect(home.kind == .tag and home.active); + stripes += 1; + } + const rgb: [3]u8 = .{ @intFromFloat(@round(item.br * 255)), @intFromFloat(@round(item.bg * 255)), @intFromFloat(@round(item.bb * 255)) }; + if (std.mem.eql(u8, &rgb, &s.chrome.decor_box_shadow.?)) shadows += 1; + } + try std.testing.expectEqual(@as(usize, 1), stripes); + try std.testing.expect(shadows >= 2); + // The scroll track is chequered. + var checkers: usize = 0; + for (g.decor.items[groups.items[0].decor_start + groups.items[0].under_count ..]) |item| checkers += @intFromBool(item.effect & checker_bit != 0); + try std.testing.expect(checkers >= 1); + // No other theme draws any of it. + try std.testing.expect(core.executeBuiltinLine(0, "Theme forge")); + for (0..40) |_| core.update(.tick); + const plain = try core.render(core.scratch.allocator()); + try std.testing.expect(plain.chrome.decor_box_border == null and plain.chrome.decor_checker == null and plain.chrome.decor_dots == null and plain.chrome.decor_title == null); +} + fn cellCodepoint(cell: *const pardes.Cell) u32 { const grapheme = cell.grapheme(); return if (cell.default or grapheme.len == 0) ' ' else firstCp(grapheme); @@ -7352,6 +7512,20 @@ fn makePipeline(device: *c.SDL_GPUDevice, color_format: c.SDL_GPUTextureFormat, return c.SDL_CreateGPUGraphicsPipeline(device, &info) orelse error.GpuCreate; } +/// Premultiplied over: an opaque cell writes itself exactly, a see-through +/// one only its ink. +fn premultipliedBlend() c.SDL_GPUColorTargetBlendState { + var blend = std.mem.zeroes(c.SDL_GPUColorTargetBlendState); + blend.src_color_blendfactor = c.SDL_GPU_BLENDFACTOR_ONE; + blend.dst_color_blendfactor = c.SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.color_blend_op = c.SDL_GPU_BLENDOP_ADD; + blend.src_alpha_blendfactor = c.SDL_GPU_BLENDFACTOR_ONE; + blend.dst_alpha_blendfactor = c.SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.alpha_blend_op = c.SDL_GPU_BLENDOP_ADD; + blend.enable_blend = true; + return blend; +} + fn backgroundLayerBlend() c.SDL_GPUColorTargetBlendState { var blend = std.mem.zeroes(c.SDL_GPUColorTargetBlendState); blend.src_color_blendfactor = c.SDL_GPU_BLENDFACTOR_SRC_ALPHA; @@ -7742,6 +7916,29 @@ fn addDecor(g: *Gui, gpa: std.mem.Allocator, x0_in: f32, y0_in: f32, x1_in: f32, try g.decor.append(gpa, instance); } +/// A checker (decor.frag.glsl): squares of `px` window pixels in `a` and +/// `b` from the quad's corner (lapis's scroll track); `stripes`, bands of +/// `px` across it instead (lapis's focused title bar), `a` first. +fn addChecker(g: *Gui, gpa: std.mem.Allocator, x0: f32, y0: f32, x1: f32, y1: f32, a: [3]u8, b: [3]u8, px: f32, track: ?pardes.animation.Track, win_w: f32, win_h: f32) !void { + return addPattern(g, gpa, x0, y0, x1, y1, a, b, px, false, track, win_w, win_h); +} + +fn addPattern(g: *Gui, gpa: std.mem.Allocator, x0: f32, y0: f32, x1: f32, y1: f32, a: [3]u8, b: [3]u8, px: f32, stripes: bool, track: ?pardes.animation.Track, win_w: f32, win_h: f32) !void { + const first = g.decor.items.len; + try addDecor(g, gpa, x0, y0, x1, y1, a, 1, track, win_w, win_h); + if (g.decor.items.len == first) return; + const instance = &g.decor.items[first]; + instance.u0 = px; + instance.u1 = px; + instance.v0 = if (stripes) 1 else 0; + instance.v1 = if (stripes) 1 else 0; + instance.fr = @as(f32, @floatFromInt(b[0])) / 255.0; + instance.fg = @as(f32, @floatFromInt(b[1])) / 255.0; + instance.fb = @as(f32, @floatFromInt(b[2])) / 255.0; + instance.effect |= checker_bit; +} + + /// How a lift of 0 to 1 stands off the page: the shadow's blur and offset /// in cell heights, and its darkest, where it leaves the caster's edge. /// Within §8.2's budget: sigma 0.4-0.8 cell, at most 30% darker at the edge. @@ -8062,6 +8259,13 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g for (groups.items[0..groups.len], 0..) |*group, index| { group.decor_start = @intCast(g.decor.items.len); defer group.decor_count = @as(u32, @intCast(g.decor.items.len)) - group.decor_start; + // Under the cells: a theme's tag plaques, this group's panes' (and + // the column and workspace tags with the still ones). + if (index <= groups.tracks) { + const closing = if (group.track) |active| active.phase == .closing else false; + try tagPlaques(g, gpa, if (closing) surface.previous_regions else regions, groups, index, chrome, layout, win_w, win_h, group.track); + } + group.under_count = @as(u32, @intCast(g.decor.items.len)) - group.decor_start; // The grips' buttons before any rule: the rules over their rows cut // them, and the band round them never covers a rule. if (index <= groups.tracks) { @@ -8226,7 +8430,10 @@ fn paneDecor(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, gr // the track edges it. const column = scrollColumn(g, chrome, rail.rect.x, layout); try addDecor(g, gpa, x0, y0, x0 + layout.w, y1, page, 1, track, win_w, win_h); - try addDecor(g, gpa, column[0], y0, column[1], y1, chrome.scroll_track, 1, track, win_w, win_h); + if (chrome.decor_checker) |gold| + try addChecker(g, gpa, column[0], y0, column[1], y1, chrome.scroll_track, gold, dp(@floatFromInt(@max(1, chrome.decor_checker_px))), track, win_w, win_h) + else + try addDecor(g, gpa, column[0], y0, column[1], y1, chrome.scroll_track, 1, track, win_w, win_h); if (rail.thumb_h != 0) { const thumb_y0 = @as(f32, @floatFromInt(rail.rect.y + rail.thumb_y)) * layout.h; const thumb_y1 = if (rail.thumb_y + rail.thumb_h == rail.rect.h) y1 else @as(f32, @floatFromInt(rail.rect.y + rail.thumb_y + rail.thumb_h)) * layout.h; @@ -8269,11 +8476,126 @@ fn updateDisplayScale(window: *c.SDL_Window) void { display_scale = std.math.clamp(display_scale, 1, 4); } +/// A theme's ornament as the cell shader draws it (Chrome.decor_*), for the +/// frame being built: renderFrame sets it, writing a cell marks the cells +/// it falls on, and pushLayerOpacity hands the shader its colours and +/// sizes. ponytail: one window per process, as display_scale. +var cell_decor: CellDecor = .{}; +const CellDecor = struct { + /// The page's ground, when the theme dots it. + dots: ?[3]u8 = null, + /// A file name's inks in a tag, when the theme shadows it. + title: ?[2][3]u8 = null, + /// UiUniforms after background_opacity (ui.frag.glsl): the dots' colour + /// and strength, their period and radii, the shadow's colour and offset. + uniforms: [12]f32 = @splat(0), + /// The tag plaques' bands (window pixels) and the tag grounds: a blank + /// tag cell there lets its plaque show through (tagPlaques). + plaques: [pardes.MAX_TAG_LAYERS][4]f32 = undefined, + nplaques: usize = 0, + grounds: [2][3]u8 = .{ .{ 0, 0, 0 }, .{ 0, 0, 0 } }, + plaques_off: bool = false, + + fn inPlaque(decor: *const CellDecor, x: f32, y: f32) bool { + for (decor.plaques[0..decor.nplaques]) |band| + if (x >= band[0] and x < band[2] and y >= band[1] and y < band[3]) return true; + return false; + } + + fn of(chrome: *const pardes.Chrome) CellDecor { + var out: CellDecor = .{}; + if (chrome.decor_dots) |rgb| if (chrome.page) |page| { + out.dots = page; + out.uniforms[0..4].* = .{ unit(rgb[0]), unit(rgb[1]), unit(rgb[2]), unit(chrome.decor_dot_alpha) }; + out.uniforms[4..8].* = .{ dp(@floatFromInt(@max(2, chrome.decor_dot_px))), 0.75 * display_scale, 1.25 * display_scale, 0 }; + }; + if (chrome.decor_title) |rgb| { + out.title = .{ chrome.name_ink, chrome.active_name_ink }; + out.uniforms[8..12].* = .{ unit(rgb[0]), unit(rgb[1]), unit(rgb[2]), dp(@floatFromInt(@max(1, chrome.decor_title_px))) }; + } + return out; + } + + fn unit(v: u8) f32 { + return @as(f32, @floatFromInt(v)) / 255; + } +}; + /// A logical pixel size in the display's pixels: rounded, at least one. fn dp(logical: f32) f32 { return @max(1, @round(logical * display_scale)); } +/// A theme's tag plaques (Chrome.decor_box_*), drawn under the cells of +/// group `index`: each pane tag, and with the still panes the column and +/// workspace tags, becomes a framed box inside its own band with a hard +/// shadow down and right that stays inside the band too, so it never falls +/// on a body, a grip or the next pane. The focused pane's box is striped, +/// its words on plates of the tag's ground (their cells stay opaque; the +/// spaces between let the stripes through, emitInstance); a column or the +/// workspace takes a quieter box, a one-pixel frame and a two-pixel shadow. +fn tagPlaques(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, groups: *const Groups, index: usize, chrome: *const pardes.Chrome, layout: CellLayout, win_w: f32, win_h: f32, track: ?pardes.animation.Track) !void { + const border_rgb = chrome.decor_box_border orelse return; + if (cell_decor.plaques_off) return; + const page = chrome.page orelse bg_default; + for (regions) |tag| { + const pane = tag.kind == .tag; + if (!pane and tag.kind != .column_tag and tag.kind != .workspace_tag) continue; + if (tag.rect.w == 0 or tag.rect.h == 0) continue; + if (pane and groupOf(groups, tag.serial) != index) continue; + if (!pane and index != 0) continue; + const plaque = plaqueBox(g, chrome, regions, tag, layout, win_w, win_h) orelse continue; + const b = plaque.border; + const sh = plaque.shadow; + const fill = if (tag.active) chrome.tag_focus_bg else chrome.tag_bg; + // The band's own ground, then the shadow, the frame and the fill. + try addDecor(g, gpa, plaque.band[0], plaque.band[1], plaque.band[2], plaque.band[3], page, 1, track, win_w, win_h); + const shadow_rgb = chrome.decor_box_shadow orelse border_rgb; + try addDecor(g, gpa, plaque.box[0] + sh[0], plaque.box[1] + sh[1], plaque.box[2] + sh[0], plaque.box[3] + sh[1], if (pane) shadow_rgb else mixRgb(shadow_rgb, page), 1, track, win_w, win_h); + try addDecor(g, gpa, plaque.box[0], plaque.box[1], plaque.box[2], plaque.box[3], border_rgb, 1, track, win_w, win_h); + const inner: [4]f32 = .{ plaque.box[0] + b, plaque.box[1] + b, plaque.box[2] - b, plaque.box[3] - b }; + if (pane and tag.active and chrome.decor_box_stripe != null) + try addPattern(g, gpa, inner[0], inner[1], inner[2], inner[3], chrome.decor_box_stripe.?, fill, dp(@floatFromInt(@max(1, chrome.decor_box_stripe_px))), true, track, win_w, win_h) + else + try addDecor(g, gpa, inner[0], inner[1], inner[2], inner[3], fill, 1, track, win_w, win_h); + } +} + +const Plaque = struct { band: [4]f32, box: [4]f32, border: f32, shadow: [2]f32 }; + +/// A tag's plaque in window pixels: its band (the rows, from after the +/// grip to the end), the framed box round the tagline's text, and the +/// frame's and the shadow's widths, the shadow cut to what the band's slack +/// below the text and its right end leave. Null where there is no room. +fn plaqueBox(g: *const Gui, chrome: *const pardes.Chrome, regions: []const pardes.Region, tag: pardes.Region, layout: CellLayout, win_w: f32, win_h: f32) ?Plaque { + const pane = tag.kind == .tag; + const first = tag.rect.y; + const last = tag.rect.y + tag.rect.h - 1; + // From its start, right of its grip: the gap cell before its text is + // the plaque's margin, so its frame is never under the first letter. + _ = regions; + const x0 = @as(f32, @floatFromInt(tag.rect.x)) * layout.w; + const x1_cells = @as(f32, @floatFromInt(tag.rect.x + tag.rect.w)) * layout.w; + const x1 = if (win_w - x1_cells < layout.w) @max(x1_cells, win_w) else x1_cells; + const row_top = @as(f32, @floatFromInt(first)) * layout.h; + const row_bottom = @as(f32, @floatFromInt(last + 1)) * layout.h; + const text_top = row_top + @as(f32, @floatFromInt(taglineBandOffset(first, win_h, g.cell_h, g.tagline_height))); + const text_bottom = @as(f32, @floatFromInt(last)) * layout.h + @as(f32, @floatFromInt(taglineBandOffset(last, win_h, g.cell_h, g.tagline_height) + g.tagline_height)); + const border = @min(if (pane) dp(@floatFromInt(@max(1, chrome.decor_box_border_px))) else dp(1), @floor(text_top - row_top)); + if (border < 1) return null; + // The shadow's full offset to the right; down, what the slack under the + // text leaves (a pixel or three at a tagline under the body's size). + const want: f32 = if (pane) dp(@floatFromInt(chrome.decor_box_shadow_px)) else dp(2); + const drop = std.math.clamp(@floor(row_bottom - text_bottom - border), 0, want); + if (x1 - x0 < 4 * border + want) return null; + return .{ + .band = .{ x0, row_top, x1, row_bottom }, + .box = .{ x0, text_top - border, x1 - want, text_bottom + border }, + .border = border, + .shadow = .{ want, drop }, + }; +} + /// The two short bars on each row of a grip with no mode glyph there, /// centered in its anchor. Only the panes of group `index`. fn gripDecor(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, cells: []const pardes.Cell, groups: *const Groups, index: usize, chrome: *const pardes.Chrome, layout: CellLayout, cols: u16, rows: u16, win_w: f32, win_h: f32, track: ?pardes.animation.Track) !void { @@ -8903,7 +9225,8 @@ fn uploadOverlayGpu(g: *Gui, cmd: *c.SDL_GPUCommandBuffer, vertex_count: u32) bo } fn pushLayerOpacity(cmd: *c.SDL_GPUCommandBuffer, pass: ?*c.SDL_GPURenderPass, opacity: f32) void { - const uniforms = [4]f32{ opacity, 0, 0, 0 }; + var uniforms = [4]f32{ opacity, 0, 0, 0 } ++ @as([12]f32, @splat(0)); + uniforms[4..].* = cell_decor.uniforms; c.SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms, @sizeOf(@TypeOf(uniforms))); // The source alpha is geometric/image coverage, not window opacity. Its // alpha contribution is multiplied by this constant separately, so a diff --git a/src/surface.zig b/src/surface.zig index 523cba43..5b850a8b 100644 --- a/src/surface.zig +++ b/src/surface.zig @@ -428,6 +428,26 @@ pub const Chrome = struct { /// The scroll column's width, button and scrollbar alike, in pixels at /// a 17px tagline (acme's 12 at its font); a pixel shell scales it. rail_px: u8 = 12, + /// The theme's ornament (colors.Decor), a pixel shell's alone: the dot + /// grid on the page, the scroll track's checker, the tags' plaques + /// (frame, shadow, the focused one's stripes) and the file name's shadow, each null when the theme has none, sizes + /// in logical pixels. `name_ink` and `active_name_ink` are the file + /// name's colours in a tag, which the shadow goes under. + decor_dots: ?[3]u8 = null, + decor_dot_alpha: u8 = 0, + decor_dot_px: u8 = 22, + decor_checker: ?[3]u8 = null, + decor_checker_px: u8 = 3, + decor_box_border: ?[3]u8 = null, + decor_box_border_px: u8 = 2, + decor_box_shadow: ?[3]u8 = null, + decor_box_shadow_px: u8 = 5, + decor_box_stripe: ?[3]u8 = null, + decor_box_stripe_px: u8 = 2, + decor_title: ?[3]u8 = null, + decor_title_px: u8 = 2, + name_ink: [3]u8 = .{ 0, 0, 0 }, + active_name_ink: [3]u8 = .{ 0, 0, 0 }, }; /// The canonical interface: what a frame of pardes IS. The tty shell writes diff --git a/src/themes/lapis.zig b/src/themes/lapis.zig new file mode 100644 index 00000000..46623fc2 --- /dev/null +++ b/src/themes/lapis.zig @@ -0,0 +1,92 @@ +//! Lapis: 0x4200.cafe's illuminated manuscript (web/theme/lapis.css) as a +//! pardes theme. Ultramarine vellum, gold leaf and vermilion rubrics: the +//! page is lapis-deep, the tags lapis with their names in gold, the text +//! vellum. The site's ornament comes with it as the theme's `decor`, drawn +//! inside the existing chrome (docs/render-pipeline.md §10, decision 1): +//! a gold dot grid on the page under the pane bodies, a gold and lapis +//! checker in the scroll column, every tag a gold-framed plaque with the +//! trail's hard vermilion shadow (inside its own band: in a tiled layout a +//! shadow past it would fall on the next pane or its grip, and no ornament +//! may cover a focus indicator), the focused one striped like the code +//! window's title bar, and the file name's vermilion offset shadow. +pub const theme = .{ + .name = "lapis", + // --lapis-deep, --vellum + .bg = .{ 0x0a, 0x10, 0x30 }, + .fg = .{ 0xee, 0xe6, 0xd2 }, + // A gold highlight in lapis ink, as the directory's current entry. + .sel_bg = .{ 0xe8, 0xc4, 0x6a }, + .sel_fg = .{ 0x0f, 0x1a, 0x4a }, + // --lapis, --vellum-dim + .tag_bg = .{ 0x0f, 0x1a, 0x4a }, + .tag_fg = .{ 0x9a, 0xa6, 0xd9 }, + .tag_name_fg = .{ 0xe8, 0xc4, 0x6a }, + .tag_active_name_fg = .{ 0xf2, 0xd2, 0x80 }, + // The plaques' hover lapis, taken a step further: 1.5:1 off the tag. + .tag_active_bg = .{ 0x25, 0x39, 0x7d }, + .tag_active_fg = .{ 0xee, 0xe6, 0xd2 }, + .box = .{ 0xe8, 0xc4, 0x6a }, + .box_dim = .{ 0x3a, 0x4a, 0x8a }, + .box_border = .{ 0x9a, 0xa6, 0xd9 }, + .box_dirty = .{ 0xff, 0x6a, 0x4a }, + .column_box = .{ 0xff, 0x6a, 0x4a }, + .column_box_dim = .{ 0x8a, 0x3a, 0x3a }, + // Rubrics: vermilion keywords, gilded strings, the code window's green + // for numbers, vellum-dim comments. + .kw = .{ 0xff, 0x6a, 0x4a }, + .str = .{ 0xe8, 0xc4, 0x6a }, + .num = .{ 0xe7, 0xff, 0xe0 }, + .comment = .{ 0x9a, 0xa6, 0xd9 }, + .lineno = .{ 0x5a, 0x6a, 0xa8 }, + .lineno_active = .{ 0x6a, 0x7a, 0xb8 }, + // --lapis-ink under a gold thumb (the page's scrollbar-color). + .scroll_track = .{ 0x05, 0x0a, 0x24 }, + .scroll_thumb = .{ 0xe8, 0xc4, 0x6a }, + // Gold rules, a vermilion hairline between a tag and its text. + .border = .{ 0xe8, 0xc4, 0x6a }, + // An empty column is the page, not a slab of gold leaf. + .empty_col = .{ 0x0a, 0x10, 0x30 }, + .tag_rule = .{ 0xd4, 0x41, 0x2f }, + .search_bg = .{ 0x6a, 0x2a, 0x1c }, + .search_fg = .{ 0xff, 0xd0, 0xb0 }, + .diagnostic_error = .{ 0xff, 0x6a, 0x4a }, + .diagnostic_warning = .{ 0xe8, 0xc4, 0x6a }, + .diagnostic_info = .{ 0x9a, 0xc6, 0xff }, + .diagnostic_hint = .{ 0xa8, 0xe0, 0xc0 }, + .decor = .{ + .page_dots = .{ 0xe8, 0xc4, 0x6a }, + // #e8c46a22 on the site: a dot a pixel and a half across every 22. + .page_dot_alpha = 0x22, + .page_dot_px = 22, + .rail_checker = .{ 0xe8, 0xc4, 0x6a }, + .rail_checker_px = 3, + // The trail: a 2px gold frame and a 5px vermilion shadow; the code + // window's title bar: gold and lapis stripes of 2px. + .tag_border = .{ 0xe8, 0xc4, 0x6a }, + .tag_border_px = 2, + .tag_shadow = .{ 0xd4, 0x41, 0x2f }, + .tag_shadow_px = 5, + .tag_stripe = .{ 0xe8, 0xc4, 0x6a }, + .tag_stripe_px = 2, + .title_shadow = .{ 0xd4, 0x41, 0x2f }, + .title_shadow_px = 2, + }, + .palette = .{ + .{ 0x0a, 0x10, 0x30 }, + .{ 0xff, 0x6a, 0x4a }, + .{ 0xa8, 0xe0, 0xc0 }, + .{ 0xe8, 0xc4, 0x6a }, + .{ 0x9a, 0xc6, 0xff }, + .{ 0xd8, 0xa0, 0xe8 }, + .{ 0x8a, 0xd8, 0xd8 }, + .{ 0xee, 0xe6, 0xd2 }, + .{ 0x9a, 0xa6, 0xd9 }, + .{ 0xff, 0x9a, 0x6a }, + .{ 0xc8, 0xf0, 0xd8 }, + .{ 0xff, 0xde, 0x90 }, + .{ 0xc0, 0xdc, 0xff }, + .{ 0xe8, 0xc0, 0xf4 }, + .{ 0xb0, 0xec, 0xec }, + .{ 0xff, 0xf8, 0xe8 }, + }, +}; diff --git a/test/snapshots/theme.golden b/test/snapshots/theme.golden index 4acf715d..a6bd6e60 100644 --- a/test/snapshots/theme.golden +++ b/test/snapshots/theme.golden @@ -645,34 +645,34 @@ |30: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, == snap generated grid=100x31 cursor=4,6 == style generated grid=100x31 -|0: 0-50 #e0e0e0,#1e262e, 51-59 #e0e0e0,#303840, 60-70 #e0e0e0,#1e262e, 71-99 d,#1e262e, -|1: 0-1 #ffffff,#4f7b96, 2-2 d,#1e262e, 3-34 #e0e0e0,#1e262e, 35-49 d,#1e262e, 50-51 #ffffff,#4f7b96, 52-52 d,#1e262e, 53-84 #e0e0e0,#1e262e, 85-99 d,#1e262e, -|2: 0-1 #000000,#ffa060, 2-2 d,#1e262e, 3-49 #e0e0e0,#1e262e, 50-51 #ffffff,#303840, 52-52 d,#1e262e, 53-99 #e0e0e0,#1e262e, -|3: 0-2 d,#1e262e, 3-20 #e0e0e0,#1e262e, 21-52 d,#1e262e, 53-70 #e0e0e0,#1e262e, 71-99 d,#1e262e, -|4: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|5: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|6: 0-0 d,#303840, 1-49 d,#161e26, 50-50 d,#303840, 51-99 d,#161e26, -|7: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|8: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|9: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|10: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|11: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|12: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|13: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|14: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|15: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|16: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|17: 0-1 #ffffff,#303840, 2-2 d,#1e262e, 3-49 #e0e0e0,#1e262e, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|18: 0-2 d,#1e262e, 3-20 #e0e0e0,#1e262e, 21-49 d,#1e262e, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|19: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|20: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|21: 0-0 d,#303840, 1-49 d,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|22: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|23: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|24: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|25: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|26: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|27: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|28: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|29: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|30: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|0: 0-50 #9aa6d9,#0f1a4a, 51-59 #0f1a4a,#e8c46a, 60-70 #9aa6d9,#0f1a4a, 71-99 d,#0f1a4a, +|1: 0-1 #ffffff,#8a3a3a, 2-2 d,#25397d, 3-34 #eee6d2,#25397d, 35-49 d,#25397d, 50-51 #ffffff,#8a3a3a, 52-52 d,#0f1a4a, 53-84 #9aa6d9,#0f1a4a, 85-99 d,#0f1a4a, +|2: 0-1 #000000,#e8c46a, 2-2 d,#25397d, 3-29 #eee6d2,#25397d, 30-37 #f2d280,#25397d, 38-49 #eee6d2,#25397d, 50-51 #ffffff,#6a78b1, 52-52 d,#0f1a4a, 53-79 #9aa6d9,#0f1a4a, 80-87 #e8c46a,#0f1a4a, 88-99 #9aa6d9,#0f1a4a, +|3: 0-2 d,#25397d, 3-20 #eee6d2,#25397d, 21-49 d,#25397d, 50-52 d,#0f1a4a, 53-70 #9aa6d9,#0f1a4a, 71-99 d,#0f1a4a, +|4: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|5: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|6: 0-0 d,#e8c46a, 1-49 d,#0a1030, 50-50 d,#e8c46a, 51-99 d,#0a1030, +|7: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|8: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|9: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|10: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|11: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|12: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|13: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|14: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|15: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|16: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|17: 0-1 #ffffff,#6a78b1, 2-2 d,#0f1a4a, 3-29 #9aa6d9,#0f1a4a, 30-37 #e8c46a,#0f1a4a, 38-49 #9aa6d9,#0f1a4a, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|18: 0-2 d,#0f1a4a, 3-20 #9aa6d9,#0f1a4a, 21-49 d,#0f1a4a, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|19: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|20: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|21: 0-0 d,#e8c46a, 1-49 d,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|22: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|23: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|24: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|25: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|26: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|27: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|28: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|29: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, +|30: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030, diff --git a/test/snapshots/themesel.golden b/test/snapshots/themesel.golden index 455808ab..8d453978 100644 --- a/test/snapshots/themesel.golden +++ b/test/snapshots/themesel.golden @@ -529,7 +529,7 @@ |27: 19 Theme helix |28: 20 Theme dark |29: 21 Theme acme -|30: 22 Theme acid +|30: 22 Theme lapis == style step-helix grid=100x31 |0: 0-70 #949494,#121212, 71-99 d,#121212, |1: 0-1 #ffffff,#826f46, 2-2 d,#3a3a3a, 3-34 #bdbdbd,#3a3a3a, 35-99 d,#3a3a3a, @@ -561,7 +561,7 @@ |27: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808,b 7-17 #bdbdbd,#080808, 18-99 d,#080808, |28: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, |29: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, -|30: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, +|30: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-99 d,#080808, == snap step-dark grid=100x31 cursor=7,27 |17: 10 Theme solarium Theme |18: 11 Theme spectrum @@ -575,8 +575,8 @@ |26: 19 Theme helix |27: 20 Theme dark |28: 21 Theme acme -|29: 22 Theme acid -|30: 23 Theme acme_helix +|29: 22 Theme lapis +|30: 23 Theme acid == style step-dark grid=100x31 |0: 0-70 #969696,#2c2a3e, 71-99 d,#2c2a3e, |1: 0-1 #ffffff,#867a4e, 2-2 d,#484566, 3-34 #c8c8c8,#484566, 35-99 d,#484566, @@ -621,9 +621,9 @@ |25: 19 Theme helix |26: 20 Theme dark |27: 21 Theme acme -|28: 22 Theme acid -|29: 23 Theme acme_helix -|30: 24 Theme adwaita_dark +|28: 22 Theme lapis +|29: 23 Theme acid +|30: 24 Theme acme_helix == style step-acme grid=100x31 |0: 0-70 #000000,#eaffff, 71-99 d,#eaffff, |1: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, @@ -653,9 +653,9 @@ |25: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea, |26: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, |27: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|28: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, -|30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-24 #000000,#ffffea, 25-99 d,#ffffea, +|28: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea, +|29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, +|30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, == snap step-generated grid=100x31 cursor=7,27 |17: 12 Theme harvest Theme |18: 13 Theme clay @@ -667,42 +667,42 @@ |24: 19 Theme helix |25: 20 Theme dark |26: 21 Theme acme -|27: 22 Theme acid -|28: 23 Theme acme_helix -|29: 24 Theme adwaita_dark -|30: 25 Theme adwaita_light +|27: 22 Theme lapis +|28: 23 Theme acid +|29: 24 Theme acme_helix +|30: 25 Theme adwaita_dark == style step-generated grid=100x31 -|0: 0-70 #e0e0e0,#1e262e, 71-99 d,#1e262e, -|1: 0-1 #ffffff,#4f7b96, 2-2 d,#1e262e, 3-34 #e0e0e0,#1e262e, 35-99 d,#1e262e, -|2: 0-1 #ffffff,#303840, 2-2 d,#1e262e, 3-59 #e0e0e0,#1e262e, 60-99 d,#1e262e, -|3: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|4: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|5: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|6: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|7: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|8: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|9: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|10: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|11: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-12 #e0e0e0,#161e26, 13-99 d,#161e26, -|12: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|13: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|14: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|15: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|16: 0-1 #000000,#ffa060, 2-2 d,#1e262e, 3-61 #e0e0e0,#1e262e, 62-99 d,#1e262e, -|17: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-19 #e0e0e0,#161e26, 20-92 d,#161e26, 93-93 d,#1e262e, 94-98 #e0e0e0,#1e262e, 99-99 d,#1e262e, -|18: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, -|19: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-23 #e0e0e0,#161e26, 24-99 d,#161e26, -|20: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-22 #e0e0e0,#161e26, 23-99 d,#161e26, -|21: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-25 #e0e0e0,#161e26, 26-99 d,#161e26, -|22: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-99 d,#161e26, -|23: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-34 #e0e0e0,#161e26, 35-99 d,#161e26, -|24: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-17 #e0e0e0,#161e26, 18-99 d,#161e26, -|25: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, -|26: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, -|27: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26,b 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, -|28: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-22 #e0e0e0,#161e26, 23-99 d,#161e26, -|29: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-24 #e0e0e0,#161e26, 25-99 d,#161e26, -|30: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-25 #e0e0e0,#161e26, 26-99 d,#161e26, +|0: 0-70 #9aa6d9,#0f1a4a, 71-99 d,#0f1a4a, +|1: 0-1 #ffffff,#8a3a3a, 2-2 d,#25397d, 3-34 #eee6d2,#25397d, 35-99 d,#25397d, +|2: 0-1 #ffffff,#6a78b1, 2-2 d,#0f1a4a, 3-32 #9aa6d9,#0f1a4a, 33-37 #e8c46a,#0f1a4a, 38-59 #9aa6d9,#0f1a4a, 60-99 d,#0f1a4a, +|3: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-12 #eee6d2,#0a1030, 13-99 d,#0a1030, +|4: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-12 #eee6d2,#0a1030, 13-99 d,#0a1030, +|5: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-12 #eee6d2,#0a1030, 13-99 d,#0a1030, +|6: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-12 #eee6d2,#0a1030, 13-99 d,#0a1030, +|7: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-12 #eee6d2,#0a1030, 13-99 d,#0a1030, +|8: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-12 #eee6d2,#0a1030, 13-99 d,#0a1030, +|9: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-12 #eee6d2,#0a1030, 13-99 d,#0a1030, +|10: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-12 #eee6d2,#0a1030, 13-99 d,#0a1030, +|11: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-12 #eee6d2,#0a1030, 13-99 d,#0a1030, +|12: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-13 #eee6d2,#0a1030, 14-99 d,#0a1030, +|13: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-13 #eee6d2,#0a1030, 14-99 d,#0a1030, +|14: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-13 #eee6d2,#0a1030, 14-99 d,#0a1030, +|15: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-13 #eee6d2,#0a1030, 14-99 d,#0a1030, +|16: 0-1 #000000,#e8c46a, 2-2 d,#25397d, 3-32 #eee6d2,#25397d, 33-39 #f2d280,#25397d, 40-61 #eee6d2,#25397d, 62-99 d,#25397d, +|17: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-19 #eee6d2,#0a1030, 20-92 d,#0a1030, 93-93 d,#0f1a4a, 94-98 #9aa6d9,#0f1a4a, 99-99 d,#0f1a4a, +|18: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-16 #eee6d2,#0a1030, 17-99 d,#0a1030, +|19: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-23 #eee6d2,#0a1030, 24-99 d,#0a1030, +|20: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-22 #eee6d2,#0a1030, 23-99 d,#0a1030, +|21: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-25 #eee6d2,#0a1030, 26-99 d,#0a1030, +|22: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-99 d,#0a1030, +|23: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-34 #eee6d2,#0a1030, 35-99 d,#0a1030, +|24: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-17 #eee6d2,#0a1030, 18-99 d,#0a1030, +|25: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-16 #eee6d2,#0a1030, 17-99 d,#0a1030, +|26: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-16 #eee6d2,#0a1030, 17-99 d,#0a1030, +|27: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #6a7ab8,#0a1030,b 7-17 #eee6d2,#0a1030, 18-99 d,#0a1030, +|28: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-16 #eee6d2,#0a1030, 17-99 d,#0a1030, +|29: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-22 #eee6d2,#0a1030, 23-99 d,#0a1030, +|30: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-24 #eee6d2,#0a1030, 25-99 d,#0a1030, == snap step-back grid=100x31 cursor=7,26 == style step-back grid=100x31 |0: 0-70 #000000,#eaffff, 71-99 d,#eaffff, @@ -732,25 +732,25 @@ |24: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea, |25: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, |26: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|27: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, -|28: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, -|29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-24 #000000,#ffffea, 25-99 d,#ffffea, -|30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea, +|27: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea, +|28: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, +|29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, +|30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-24 #000000,#ffffea, 25-99 d,#ffffea, == snap tail grid=100x31 cursor=7,29 -|17: 234 Theme wolf_alabaster_dark -|18: 235 Theme wolf_alabaster_dark_bg -|19: 236 Theme wolf_alabaster_dark_mono -|20: 237 Theme wolf_alabaster_light -|21: 238 Theme wolf_alabaster_light_bg -|22: 239 Theme wolf_alabaster_light_mono -|23: 240 Theme yellowed -|24: 241 Theme yo -|25: 242 Theme yo_berry -|26: 243 Theme yo_light -|27: 244 Theme zed_onedark -|28: 245 Theme zed_onelight -|29: 246 Theme zenburn -|30: 247 +|17: 235 Theme wolf_alabaster_dark +|18: 236 Theme wolf_alabaster_dark_bg +|19: 237 Theme wolf_alabaster_dark_mono +|20: 238 Theme wolf_alabaster_light +|21: 239 Theme wolf_alabaster_light_bg +|22: 240 Theme wolf_alabaster_light_mono +|23: 241 Theme yellowed +|24: 242 Theme yo +|25: 243 Theme yo_berry +|26: 244 Theme yo_light +|27: 245 Theme zed_onedark +|28: 246 Theme zed_onelight +|29: 247 Theme zenburn +|30: 248 == snap picked grid=100x31 cursor=7,3 |16: 14 line 14 |17: 15 line 15 -- cgit v1.3