//! Window decorations, one per KDE era, drawn as SVG.
//!
//! Each decoration is redrawn from scratch in vector form, not copied from a
//! KDE pixmap, and follows the layout of the original: where its buttons sat,
//! how the caption was aligned, which bevels, gradients and frame pieces it
//! had. Colors all come from the [`Palette`], so any decoration can wear any
//! scheme. That is how the neiam schemes reach the chrome.
use std::fmt::Write;
use super::palette::{Palette, Rgb};
use super::svg::{esc, round_path};
#[derive(Clone, Copy, Debug, serde::Serialize)]
pub struct Chrome {
pub id: &'static str,
pub name: &'static str,
/// Palette used when the card's palette is `auto`.
pub default_palette: &'static str,
}
pub static CHROMES: &[Chrome] = &[
Chrome { id: "kde1", name: "KDE 1 · kwm", default_palette: "kde1" },
Chrome { id: "kde2", name: "KDE 2 · KDE2", default_palette: "kde2" },
Chrome { id: "keramik", name: "KDE 3 · Keramik", default_palette: "keramik" },
Chrome { id: "plastik", name: "KDE 3.5 · Plastik", default_palette: "plastik" },
Chrome { id: "oxygen", name: "KDE 4 · Oxygen", default_palette: "oxygen" },
Chrome { id: "breeze", name: "Plasma · Breeze", default_palette: "breeze-dark" },
Chrome { id: "none", name: "No decoration", default_palette: "breeze-dark" },
];
pub const DEFAULT_CHROME: &str = "breeze";
pub fn get(id: &str) -> Option<&'static Chrome> {
CHROMES.iter().find(|c| c.id == id)
}
/// How much the decoration takes off each side of the window, plus the
/// outer corner radii (top pair, bottom pair).
#[derive(Clone, Copy, Debug)]
pub struct Insets {
pub top: f32,
pub left: f32,
pub right: f32,
pub bottom: f32,
pub radius_top: f32,
pub radius_bottom: f32,
/// Narrowest window that still fits the buttons and some caption.
pub min_width: f32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Shadow {
/// Pre-compositing eras: a hard offset block, the way screenshots of
/// themed desktops were dressed up at the time.
Hard,
/// Compositing eras: a soft blurred drop shadow.
Soft,
}
pub fn insets(id: &str) -> Insets {
let i = |top, side, bottom, rt, rb, min_width| Insets {
top,
left: side,
right: side,
bottom,
radius_top: rt,
radius_bottom: rb,
min_width,
};
match id {
"kde1" => i(27.0, 4.0, 4.0, 0.0, 0.0, 220.0),
"kde2" => i(24.0, 3.0, 10.0, 0.0, 0.0, 240.0),
"keramik" => i(25.0, 4.0, 5.0, 7.0, 0.0, 220.0),
"plastik" => i(23.0, 3.0, 3.0, 3.0, 0.0, 240.0),
"oxygen" => i(27.0, 4.0, 4.0, 5.0, 5.0, 220.0),
"breeze" => i(30.0, 0.0, 0.0, 4.0, 4.0, 200.0),
_ => i(0.0, 0.0, 0.0, 8.0, 8.0, 120.0),
}
}
pub fn shadow(id: &str) -> Shadow {
match id {
"kde1" | "kde2" | "keramik" | "plastik" => Shadow::Hard,
_ => Shadow::Soft,
}
}
/// Everything a decoration needs to paint itself. `uid` prefixes gradient
/// ids so several cards can share one HTML page.
pub struct Ctx<'a> {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
pub p: &'a Palette,
pub title: &'a str,
pub uid: &'a str,
}
/// Paint the decoration. The caller fills the content area afterwards.
pub fn draw(id: &str, out: &mut String, c: &Ctx) {
match id {
"kde1" => kde1(out, c),
"kde2" => kde2(out, c),
"keramik" => keramik(out, c),
"plastik" => plastik(out, c),
"oxygen" => oxygen(out, c),
"breeze" => breeze(out, c),
_ => {}
}
}
// ── shared pieces ───────────────────────────────────────────────────────────
const TITLE_FONT: &str = "B612, 'DejaVu Sans', sans-serif";
/// Average advance of the bold caption font, as a fraction of its size; used
/// only to truncate long titles and size Keramik's caption tab.
const TITLE_ADVANCE: f32 = 0.64;
fn fit_title(title: &str, max_w: f32, size: f32) -> String {
let max_chars = (max_w / (size * TITLE_ADVANCE)).floor().max(0.0) as usize;
let n = title.chars().count();
if n <= max_chars {
title.to_string()
} else if max_chars <= 1 {
String::new()
} else {
let mut s: String = title.chars().take(max_chars - 1).collect();
s.push('…');
s
}
}
fn caption(out: &mut String, x: f32, baseline: f32, anchor: &str, size: f32, fill: Rgb, text: &str) {
if text.is_empty() {
return;
}
let _ = write!(
out,
r#"<text x="{x}" y="{baseline}" text-anchor="{anchor}" font-family="{TITLE_FONT}" font-weight="bold" font-size="{size}" fill="{fill}">{}</text>"#,
esc(text)
);
}
/// Classic 3D bevel: `light` along the top/left edges, `dark` along the
/// bottom/right. Swap the colors for a sunken look.
fn bevel(out: &mut String, x: f32, y: f32, w: f32, h: f32, light: Rgb, dark: Rgb) {
let (x0, y0, x1, y1) = (x + 0.5, y + 0.5, x + w - 0.5, y + h - 0.5);
let _ = write!(
out,
r#"<path d="M{x0} {y1}V{y0}H{x1}" fill="none" stroke="{light}"/><path d="M{x1} {y0}V{y1}H{x0}" fill="none" stroke="{dark}"/>"#
);
}
fn linear(out: &mut String, id: &str, horizontal: bool, stops: &[(f32, Rgb)]) {
let (x2, y2) = if horizontal { (1, 0) } else { (0, 1) };
let _ = write!(out, r#"<linearGradient id="{id}" x1="0" y1="0" x2="{x2}" y2="{y2}">"#);
for (off, c) in stops {
let _ = write!(out, r#"<stop offset="{off}" stop-color="{c}"/>"#);
}
out.push_str("</linearGradient>");
}
#[derive(Clone, Copy)]
enum Glyph {
Close,
/// Old-style iconify: a small block at the bottom.
Iconify,
/// Old-style maximize: an outlined box with a thick top edge.
MaximizeBox,
MinimizeChevron,
MaximizeChevron,
Help,
/// Sticky / on-all-desktops.
Pin,
}
/// Draw a button glyph centred on (cx, cy) within a box of side `s`.
fn glyph(out: &mut String, g: Glyph, cx: f32, cy: f32, s: f32, color: Rgb, stroke: f32) {
let r = s * 0.28;
let common = format!(
r#"fill="none" stroke="{color}" stroke-width="{stroke}" stroke-linecap="round" stroke-linejoin="round""#
);
let _ = match g {
Glyph::Close => write!(
out,
r#"<path d="M{} {}L{} {}M{} {}L{} {}" {common}/>"#,
cx - r, cy - r, cx + r, cy + r, cx + r, cy - r, cx - r, cy + r
),
Glyph::Iconify => write!(
out,
r#"<rect x="{}" y="{}" width="{}" height="{}" fill="{color}"/>"#,
cx - r * 0.6, cy + r * 0.2, r * 1.2, r * 0.8
),
Glyph::MaximizeBox => write!(
out,
r#"<rect x="{}" y="{}" width="{}" height="{}" fill="none" stroke="{color}" stroke-width="{stroke}"/><path d="M{} {}H{}" stroke="{color}" stroke-width="{}"/>"#,
cx - r, cy - r, r * 2.0, r * 2.0, cx - r, cy - r + stroke * 0.5, cx + r, stroke * 2.0
),
Glyph::MinimizeChevron => write!(
out,
r#"<path d="M{} {}L{} {}L{} {}" {common}/>"#,
cx - r, cy - r * 0.45, cx, cy + r * 0.55, cx + r, cy - r * 0.45
),
Glyph::MaximizeChevron => write!(
out,
r#"<path d="M{} {}L{} {}L{} {}" {common}/>"#,
cx - r, cy + r * 0.45, cx, cy - r * 0.55, cx + r, cy + r * 0.45
),
Glyph::Help => write!(
out,
r#"<path d="M{} {}A{} {} 0 1 1 {} {}Q{} {} {} {}" {common}/><circle cx="{cx}" cy="{}" r="{}" fill="{color}"/>"#,
cx - r * 0.75, cy - r * 0.45,
r * 0.75, r * 0.7, cx + r * 0.2, cy + r * 0.1,
cx, cy + r * 0.15, cx, cy + r * 0.45,
cy + r * 1.05, stroke * 0.7
),
Glyph::Pin => write!(
out,
r#"<circle cx="{cx}" cy="{cy}" r="{}" fill="{color}"/>"#,
r * 0.45
),
};
}
/// The window-menu button's application icon: a little terminal.
fn app_icon(out: &mut String, x: f32, y: f32, s: f32) {
let r = s * 0.15;
let _ = write!(
out,
r##"<rect x="{x}" y="{}" width="{s}" height="{}" rx="{r}" fill="#2e3436" stroke="#888a85" stroke-width="0.8"/><path d="M{} {}l{} {}l-{} {}M{} {}h{}" fill="none" stroke="#8ae234" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>"##,
y + s * 0.1,
s * 0.8,
x + s * 0.22, y + s * 0.32, s * 0.16, s * 0.16, s * 0.16, s * 0.16,
x + s * 0.5, y + s * 0.66, s * 0.26
);
}
// ── KDE 1 · kwm ─────────────────────────────────────────────────────────────
// Grey bevelled frame, shaded titlebar, square raised buttons: menu and
// sticky on the left, iconify / maximize / close on the right, caption
// centred.
fn raised_button(out: &mut String, x: f32, y: f32, s: f32, p: &Palette) {
let _ = write!(out, r#"<rect x="{x}" y="{y}" width="{s}" height="{s}" fill="{}"/>"#, p.window);
bevel(out, x, y, s, s, p.window.lighten(0.7), p.window.darken(0.55));
}
fn kde1(out: &mut String, c: &Ctx) {
let p = c.p;
let grad = format!("{}-k1t", c.uid);
out.push_str("<defs>");
linear(out, &grad, true, &[(0.0, p.title_bg), (1.0, p.title_blend)]);
out.push_str("</defs>");
let _ = write!(out, r#"<rect x="{}" y="{}" width="{}" height="{}" fill="{}"/>"#, c.x, c.y, c.w, c.h, p.window);
bevel(out, c.x, c.y, c.w, c.h, p.window.lighten(0.8), Rgb(0, 0, 0));
bevel(out, c.x + 1.0, c.y + 1.0, c.w - 2.0, c.h - 2.0, p.window.lighten(0.4), p.window.darken(0.35));
let (tx, ty, tw, th) = (c.x + 4.0, c.y + 4.0, c.w - 8.0, 20.0);
let _ = write!(out, r#"<rect x="{tx}" y="{ty}" width="{tw}" height="{th}" fill="url(#{grad})"/>"#);
let s = 16.0;
let by = ty + 2.0;
let glyph_c = p.window_fg;
raised_button(out, tx + 2.0, by, s, p);
app_icon(out, tx + 4.0, by + 2.0, 12.0);
raised_button(out, tx + 2.0 + s + 1.0, by, s, p);
glyph(out, Glyph::Pin, tx + 2.0 + s * 1.5 + 1.0, by + s / 2.0, s, glyph_c, 1.5);
let right = tx + tw - 2.0;
for (i, g) in [Glyph::Close, Glyph::MaximizeBox, Glyph::Iconify].into_iter().enumerate() {
let bx = right - s * (i as f32 + 1.0) - i as f32;
raised_button(out, bx, by, s, p);
glyph(out, g, bx + s / 2.0, by + s / 2.0, s, glyph_c, 1.5);
}
let room = tw - 2.0 * (3.0 * s + 8.0);
let text = fit_title(c.title, room, 12.0);
caption(out, tx + tw / 2.0, ty + 14.5, "middle", 12.0, p.title_fg, &text);
// Sunken well around the client area.
bevel(out, c.x + 3.0, c.y + 26.0, c.w - 6.0, c.h - 29.0, p.window.darken(0.35), p.window.lighten(0.8));
}
// ── KDE 2 · the KDE2 default decoration ─────────────────────────────────────
// Horizontal blend titlebar, small bevelled buttons, caption to the left,
// and a grab bar along the bottom with resize handles at each end.
fn kde2(out: &mut String, c: &Ctx) {
let p = c.p;
let grad = format!("{}-k2t", c.uid);
out.push_str("<defs>");
linear(out, &grad, true, &[(0.0, p.title_bg), (1.0, p.title_blend)]);
out.push_str("</defs>");
let _ = write!(out, r#"<rect x="{}" y="{}" width="{}" height="{}" fill="{}"/>"#, c.x, c.y, c.w, c.h, p.window);
bevel(out, c.x, c.y, c.w, c.h, p.window.lighten(0.7), p.window.darken(0.6));
let (tx, ty, tw, th) = (c.x + 3.0, c.y + 3.0, c.w - 6.0, 19.0);
let _ = write!(out, r#"<rect x="{tx}" y="{ty}" width="{tw}" height="{th}" fill="url(#{grad})"/>"#);
let s = 15.0;
let by = ty + 2.0;
let glyph_c = p.window_fg;
let face = |out: &mut String, bx: f32| {
let _ = write!(out, r#"<rect x="{bx}" y="{by}" width="{s}" height="{s}" fill="{}"/>"#, p.window.lighten(0.15));
bevel(out, bx, by, s, s, p.window.lighten(0.8), p.window.darken(0.5));
};
face(out, tx + 2.0);
app_icon(out, tx + 3.5, by + 1.5, 12.0);
face(out, tx + 3.0 + s);
glyph(out, Glyph::Pin, tx + 3.0 + s * 1.5, by + s / 2.0, s, glyph_c, 1.4);
let right = tx + tw - 2.0;
for (i, g) in [Glyph::Close, Glyph::MaximizeBox, Glyph::Iconify, Glyph::Help].into_iter().enumerate() {
let bx = right - s * (i as f32 + 1.0) - i as f32;
face(out, bx);
glyph(out, g, bx + s / 2.0, by + s / 2.0, s, glyph_c, 1.4);
}
let left = tx + 2.0 * s + 8.0;
let room = tw - (2.0 * s + 8.0) - (4.0 * s + 10.0);
let text = fit_title(c.title, room, 12.0);
caption(out, left, ty + 14.0, "start", 12.0, p.title_fg, &text);
// Grab bar.
let (gx, gy, gw, gh) = (c.x + 3.0, c.y + c.h - 9.0, c.w - 6.0, 6.0);
let _ = write!(
out,
r#"<rect x="{gx}" y="{gy}" width="{gw}" height="{gh}" fill="{}"/>"#,
p.window.darken(0.06)
);
bevel(out, gx, gy, gw, gh, p.window.lighten(0.7), p.window.darken(0.5));
for hx in [gx + 26.0, gx + gw - 26.0] {
let _ = write!(
out,
r#"<path d="M{} {gy}v{gh}" stroke="{}"/><path d="M{} {gy}v{gh}" stroke="{}"/>"#,
hx - 0.5,
p.window.darken(0.5),
hx + 0.5,
p.window.lighten(0.7)
);
}
}
// ── KDE 3 · Keramik ─────────────────────────────────────────────────────────
// Rounded, glossy titlebar with the caption on a raised tab that pokes above
// the rest of the bar; pale glossy buttons.
fn gloss(out: &mut String, d: &str, uid: &str) {
let _ = write!(out, r#"<path d="{d}" fill="url(#{uid}-gloss)"/>"#);
}
fn keramik(out: &mut String, c: &Ctx) {
let p = c.p;
let bar = format!("{}-kbar", c.uid);
let tab = format!("{}-ktab", c.uid);
let btn = format!("{}-kbtn", c.uid);
out.push_str("<defs>");
linear(out, &bar, false, &[(0.0, p.title_bg.lighten(0.22)), (0.55, p.title_bg), (1.0, p.title_bg.darken(0.1))]);
linear(out, &tab, false, &[(0.0, p.title_bg.lighten(0.35)), (0.6, p.title_bg.lighten(0.05)), (1.0, p.title_bg)]);
linear(out, &btn, false, &[(0.0, p.title_bg.lighten(0.85)), (0.5, p.title_bg.lighten(0.55)), (1.0, p.title_bg.lighten(0.35))]);
let _ = write!(
out,
r#"<linearGradient id="{}-gloss" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="white" stop-opacity="0.45"/><stop offset="0.5" stop-color="white" stop-opacity="0.08"/><stop offset="0.5" stop-color="white" stop-opacity="0"/></linearGradient>"#,
c.uid
);
out.push_str("</defs>");
let lift = 3.0;
let body = round_path(c.x, c.y + lift, c.w, c.h - lift, 7.0, 0.0);
let _ = write!(out, r#"<path d="{body}" fill="{}" stroke="{}"/>"#, p.window, p.title_bg.darken(0.35));
let bar_d = round_path(c.x, c.y + lift, c.w, 22.0 - lift + 3.0, 7.0, 0.0);
let _ = write!(out, r#"<path d="{bar_d}" fill="url(#{bar})" stroke="{}"/>"#, p.title_bg.darken(0.35));
gloss(out, &bar_d, c.uid);
// Caption tab.
let s = 17.0;
let tab_x = c.x + 6.0 + s + 6.0;
let room = c.w - (tab_x - c.x) - (3.0 * (s + 2.0) + 18.0);
let text = fit_title(c.title, room - 24.0, 12.0);
let tab_w = (text.chars().count() as f32 * 12.0 * TITLE_ADVANCE + 24.0).min(room).max(40.0);
let tab_d = round_path(tab_x, c.y, tab_w, 25.0, 6.0, 0.0);
let _ = write!(out, r#"<path d="{tab_d}" fill="url(#{tab})" stroke="{}"/>"#, p.title_bg.darken(0.35));
gloss(out, &tab_d, c.uid);
caption(out, tab_x + 12.0 + 1.0, c.y + 17.0, "start", 12.0, p.title_bg.darken(0.5), &text);
caption(out, tab_x + 12.0, c.y + 16.0, "start", 12.0, p.title_fg, &text);
let by = c.y + lift + 3.0;
let face = |out: &mut String, bx: f32| {
let _ = write!(
out,
r#"<rect x="{bx}" y="{by}" width="{s}" height="{}" rx="3" fill="url(#{btn})" stroke="{}" stroke-width="0.8"/>"#,
s - 1.0,
p.title_bg.darken(0.3)
);
};
face(out, c.x + 6.0);
app_icon(out, c.x + 8.5, by + 2.0, 12.0);
let glyph_c = p.title_bg.darken(0.65);
for (i, g) in [Glyph::Close, Glyph::MaximizeBox, Glyph::Iconify].into_iter().enumerate() {
let bx = c.x + c.w - 6.0 - s * (i as f32 + 1.0) - 2.0 * i as f32;
face(out, bx);
glyph(out, g, bx + s / 2.0, by + (s - 1.0) / 2.0, s, glyph_c, 1.6);
}
}
// ── KDE 3.5 · Plastik ───────────────────────────────────────────────────────
// Slim gradient titlebar with gently rounded top corners, caption left, and
// flat translucent buttons with crisp white glyphs.
fn plastik(out: &mut String, c: &Ctx) {
let p = c.p;
let grad = format!("{}-pt", c.uid);
out.push_str("<defs>");
linear(out, &grad, false, &[(0.0, p.title_blend), (1.0, p.title_bg)]);
out.push_str("</defs>");
let outline = p.window.darken(0.45);
let body = round_path(c.x, c.y, c.w, c.h, 3.0, 0.0);
let _ = write!(out, r#"<path d="{body}" fill="{}" stroke="{outline}"/>"#, p.window);
let bar_d = round_path(c.x + 1.0, c.y + 1.0, c.w - 2.0, 21.0, 2.5, 0.0);
let _ = write!(out, r#"<path d="{bar_d}" fill="url(#{grad})"/>"#);
let _ = write!(
out,
r#"<path d="M{} {}H{}" stroke="white" stroke-opacity="0.35"/>"#,
c.x + 3.0,
c.y + 1.5,
c.x + c.w - 3.0
);
let s = 15.0;
let by = c.y + 4.0;
let face = |out: &mut String, bx: f32| {
let _ = write!(
out,
r#"<rect x="{bx}" y="{by}" width="{s}" height="{s}" rx="2" fill="white" fill-opacity="0.16" stroke="white" stroke-opacity="0.4" stroke-width="0.8"/>"#
);
};
let white = Rgb(255, 255, 255);
app_icon(out, c.x + 5.0, by + 1.5, 12.0);
face(out, c.x + 6.0 + s);
glyph(out, Glyph::Pin, c.x + 6.0 + s * 1.5, by + s / 2.0, s, p.title_fg, 1.4);
for (i, g) in [Glyph::Close, Glyph::MaximizeBox, Glyph::MinimizeChevron, Glyph::Help].into_iter().enumerate() {
let bx = c.x + c.w - 5.0 - s * (i as f32 + 1.0) - 2.0 * i as f32;
face(out, bx);
let gc = if matches!(g, Glyph::Close) { white } else { p.title_fg };
glyph(out, g, bx + s / 2.0, by + s / 2.0, s, gc, 1.5);
}
let left = c.x + 2.0 * s + 14.0;
let room = c.w - (2.0 * s + 14.0) - (4.0 * (s + 2.0) + 10.0);
let text = fit_title(c.title, room, 12.0);
caption(out, left + 1.0, c.y + 16.5, "start", 12.0, p.title_bg.darken(0.45), &text);
caption(out, left, c.y + 15.5, "start", 12.0, p.title_fg, &text);
}
// ── KDE 4 · Oxygen ──────────────────────────────────────────────────────────
// Seamless: the titlebar is the window itself, lit from the top. Caption
// centred; round, softly shaded buttons with thin chevron glyphs.
fn oxygen(out: &mut String, c: &Ctx) {
let p = c.p;
let grad = format!("{}-ow", c.uid);
let btn = format!("{}-ob", c.uid);
let light = if p.window.luma() > 0.5 { p.window.lighten(0.55) } else { p.window.lighten(0.12) };
let stop = (60.0 / c.h).min(1.0);
out.push_str("<defs>");
linear(out, &grad, false, &[(0.0, light), (stop, p.window), (1.0, p.window)]);
linear(out, &btn, false, &[(0.0, p.window.lighten(0.6)), (1.0, p.window.darken(0.12))]);
out.push_str("</defs>");
let body = round_path(c.x, c.y, c.w, c.h, 5.0, 5.0);
let _ = write!(out, r#"<path d="{body}" fill="url(#{grad})" stroke="{}" stroke-opacity="0.35"/>"#, p.window.darken(0.6));
let cy = c.y + 13.5;
let r = 8.5;
let glyph_c = p.window_fg.mix(p.window, 0.25);
app_icon(out, c.x + 7.0, cy - 7.0, 14.0);
for (i, g) in [Glyph::Close, Glyph::MaximizeChevron, Glyph::MinimizeChevron, Glyph::Help].into_iter().enumerate() {
let cx = c.x + c.w - 6.0 - r - (2.0 * r + 4.0) * i as f32;
let _ = write!(
out,
r#"<circle cx="{cx}" cy="{}" r="{r}" fill="{}" fill-opacity="0.25"/><circle cx="{cx}" cy="{cy}" r="{r}" fill="url(#{btn})" stroke="{}" stroke-width="0.8"/>"#,
cy + 1.0,
p.window.darken(0.7),
p.window.darken(0.25)
);
glyph(out, g, cx, cy, 2.0 * r, glyph_c, 1.6);
}
let room = c.w - 2.0 * (4.0 * (2.0 * r + 4.0) + 6.0);
let text = fit_title(c.title, room, 12.0);
caption(out, c.x + c.w / 2.0, c.y + 18.0, "middle", 12.0, p.title_fg.mix(p.window, 0.15), &text);
}
// ── Plasma · Breeze ─────────────────────────────────────────────────────────
// Flat titlebar, caption centred, bare glyphs: minimize / maximize chevrons
// and a close cross. No side borders; a hairline outlines the window.
fn breeze(out: &mut String, c: &Ctx) {
let p = c.p;
let bar = round_path(c.x, c.y, c.w, 30.0, 4.0, 0.0);
let _ = write!(out, r#"<path d="{bar}" fill="{}"/>"#, p.title_bg);
app_icon(out, c.x + 8.0, c.y + 7.0, 16.0);
let s = 18.0;
let cy = c.y + 15.0;
for (i, g) in [Glyph::Close, Glyph::MaximizeChevron, Glyph::MinimizeChevron].into_iter().enumerate() {
let cx = c.x + c.w - 8.0 - s / 2.0 - (s + 6.0) * i as f32;
glyph(out, g, cx, cy, s, p.title_fg, 1.5);
}
let room = c.w - 2.0 * (3.0 * (s + 6.0) + 8.0);
let text = fit_title(c.title, room, 12.0);
caption(out, c.x + c.w / 2.0, c.y + 19.5, "middle", 12.0, p.title_fg, &text);
}
/// Drawn after the content: a hairline around the whole Breeze window.
pub fn overlay(id: &str, out: &mut String, c: &Ctx) {
if id == "breeze" {
let d = round_path(c.x + 0.5, c.y + 0.5, c.w - 1.0, c.h - 1.0, 4.0, 4.0);
let line = if c.p.title_bg.luma() > 0.5 { Rgb(0, 0, 0) } else { Rgb(255, 255, 255) };
let _ = write!(out, r#"<path d="{d}" fill="none" stroke="{line}" stroke-opacity="0.18"/>"#);
}
}
neiam
/bismuth
MOTHER-FUCKING-BISMUTH
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
21.7 KiB · text
History
d76cd01