Skip to main content

drmkit_csd/
geometry.rs

1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Where the parts of a decoration go.
5
6use crate::Theme;
7
8/// The button radius, in pixels.
9///
10/// Fixed rather than themed. The three buttons are a fixed idiom — a user
11/// learns where they are once — and a theme that could resize them could put
12/// them outside the title bar it also sized.
13const BUTTON_RADIUS: i32 = 7;
14/// Gap between adjacent buttons.
15const BUTTON_GAP: i32 = 6;
16/// Gap between the rightmost button and the panel's right edge.
17const BUTTON_RIGHT_PAD: i32 = 10;
18
19/// Where everything sits inside a decoration of a given size.
20///
21/// All in decoration-local pixels, with the origin at the top left of the
22/// **decoration**, not of the window — the shadow is drawn outside the panel,
23/// so the panel starts inset by the shadow's extent.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25#[non_exhaustive]
26pub struct DecorationGeometry {
27    /// Panel left edge.
28    pub panel_x: i32,
29    /// Panel top edge.
30    pub panel_y: i32,
31    /// Panel width.
32    pub panel_w: i32,
33    /// Panel height.
34    pub panel_h: i32,
35    /// Title bar height, measured from the panel's top.
36    pub title_bar_height: i32,
37    /// Radius of each window button.
38    pub button_radius: i32,
39    /// Vertical center of every button.
40    pub button_cy: i32,
41    /// Horizontal center of the close button.
42    pub close_cx: i32,
43    /// Of the minimize button.
44    pub minimize_cx: i32,
45    /// Of the maximize button.
46    pub maximize_cx: i32,
47}
48
49/// Lay out a decoration `deco_w` by `deco_h` under `theme`.
50///
51/// Never fails and never panics: a decoration too small to hold a panel gets
52/// a zero-sized one rather than a negative one. A negative width reaching a
53/// rasterizer is either a crash or a very large unsigned number, and the
54/// caller that asked for a 4-pixel decoration under a 24-pixel shadow has
55/// made an ordinary mistake.
56#[must_use]
57pub fn decoration_geometry(theme: &Theme, deco_w: u32, deco_h: u32) -> DecorationGeometry {
58    // A negative extent in a hand-written theme means none, not an inset the
59    // other way -- a panel drawn outside its own decoration is not a thing to
60    // reproduce faithfully.
61    let extent = theme.shadow_extent.max(0);
62
63    let panel_w = (i64::from(deco_w) - 2 * i64::from(extent)).max(0);
64    let panel_h = (i64::from(deco_h) - 2 * i64::from(extent)).max(0);
65    let panel_w = i32::try_from(panel_w).unwrap_or(i32::MAX);
66    let panel_h = i32::try_from(panel_h).unwrap_or(i32::MAX);
67
68    let title_bar_height = theme.title_bar.height.max(0);
69    let button_cy = extent.saturating_add(title_bar_height / 2);
70
71    // Right to left: close is outermost, because it is the one a user reaches
72    // for by feel and the one that must not move when the others are absent.
73    let right_edge = extent
74        .saturating_add(panel_w)
75        .saturating_sub(BUTTON_RIGHT_PAD);
76    let close_cx = right_edge - BUTTON_RADIUS;
77    let step = (2 * BUTTON_RADIUS) + BUTTON_GAP;
78
79    DecorationGeometry {
80        panel_x: extent,
81        panel_y: extent,
82        panel_w,
83        panel_h,
84        title_bar_height,
85        button_radius: BUTTON_RADIUS,
86        button_cy,
87        close_cx,
88        minimize_cx: close_cx - step,
89        maximize_cx: close_cx - (2 * step),
90    }
91}