| 1 | //! The native port of `rig.js::resolveLook`, shared by the GPUI painter and |
| 2 | //! legibility metrics. The web boundary check guards parity with the reference. |
| 3 | |
| 4 | use super::rig::Role; |
| 5 | |
| 6 | /// How a named shape is filled. |
| 7 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 8 | pub enum Fill { |
| 9 | Solid(u32), |
| 10 | /// The body's two-stop gradient at 135°: first stop top-left. |
| 11 | Gradient([u32; 2]), |
| 12 | } |
| 13 | |
| 14 | /// The light eye/throat apertures the color whale paints. |
| 15 | pub const APERTURE: u32 = 0xfaf8f5; |
| 16 | |
| 17 | /// The contrast rim a surface over an unknown backdrop paints behind the |
| 18 | /// silhouette (see [`keyline_stroke`]): a deep-sea ink, darker than every |
| 19 | /// body stop, so the edge holds on paper, mid-grey and photographs while |
| 20 | /// the body itself holds on dark wallpaper. A light keyline on paper |
| 21 | /// wallpaper measures 1.00:1, so this stays dark. |
| 22 | pub const RIM: u32 = 0x08101c; |
| 23 | /// The rim's opacity. Solid enough to carry 3:1 against mid-grey. |
| 24 | pub const RIM_ALPHA: f64 = 0.95; |
| 25 | |
| 26 | /// The light hairline one device pixel outside the rim: the cursor trick. |
| 27 | /// The dark rim alone sinks into dark and mid-tone wallpaper (a worst median |
| 28 | /// edge of 2.2:1 over real photographs); a light line alone vanishes on |
| 29 | /// paper. Together one of the two always separates the form. A crisp |
| 30 | /// stroke, never blurred: it is a keyline, not a glow. |
| 31 | pub const HALO: u32 = 0xfaf8f5; |
| 32 | /// The hairline's opacity. |
| 33 | pub const HALO_ALPHA: f64 = 0.85; |
| 34 | /// Below this side, in logical pixels, the hairline fills the C's inner |
| 35 | /// opening and reads as a grey badge, so it is left off. The floating |
| 36 | /// companion, the one carrier over an unknown desktop, renders far above it. |
| 37 | pub const HALO_MIN_SIDE: f64 = 32.; |
| 38 | |
| 39 | /// The body gradient stops for a theme. |
| 40 | pub fn body(dark: bool) -> [u32; 2] { |
| 41 | if dark { |
| 42 | [0x3594d8, 0x2b70d5] |
| 43 | } else { |
| 44 | [0x1e8fd8, 0x0b48bb] |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | /// The color fill for a shape role, or `None` for roles that never paint |
| 49 | /// on their own (compound cutouts). |
| 50 | pub fn fill(role: Role, dark: bool) -> Option<Fill> { |
| 51 | Some(match role { |
| 52 | Role::Cutout => return None, |
| 53 | Role::Hole => Fill::Solid(APERTURE), |
| 54 | Role::Pointer => Fill::Solid(if dark { 0x66d6de } else { 0x147888 }), |
| 55 | Role::Accent => Fill::Solid(0xd2a34e), |
| 56 | Role::Water => Fill::Solid(body(dark)[0]), |
| 57 | Role::Tool => Fill::Solid(if dark { 0xfaf8f5 } else { 0x202123 }), |
| 58 | Role::Body => Fill::Gradient(body(dark)), |
| 59 | }) |
| 60 | } |
| 61 | |
| 62 | /// Centered stroke width, in logical pixels, for a window `scale`. |
| 63 | /// |
| 64 | /// The fill covers the inner half of a centered stroke, so the width is |
| 65 | /// twice the visible device-pixel count. That count is `round(scale)` and |
| 66 | /// never less than one, which is one device pixel at 1× and one logical |
| 67 | /// pixel at an integer scale. A non-positive or non-finite scale is 1×. |
| 68 | pub fn keyline_stroke(scale: f64) -> f64 { |
| 69 | let scale = if scale.is_finite() && scale > 0. { |
| 70 | scale |
| 71 | } else { |
| 72 | 1. |
| 73 | }; |
| 74 | 2. * scale.round().max(1.) / scale |
| 75 | } |
| 76 | |
| 77 | /// Centered stroke width for the light hairline: twice the rim's, so after |
| 78 | /// the rim paints over its inner half exactly one more device pixel shows |
| 79 | /// outside the dark one. |
| 80 | pub fn halo_stroke(scale: f64) -> f64 { |
| 81 | 2. * keyline_stroke(scale) |
| 82 | } |
| 83 | |
| 84 | #[cfg(test)] |
| 85 | mod tests { |
| 86 | use super::keyline_stroke; |
| 87 | |
| 88 | #[test] |
| 89 | fn keyline_stroke_is_one_visible_device_pixel_on_the_grid() { |
| 90 | assert_eq!(keyline_stroke(1.), 2.); |
| 91 | assert_eq!(keyline_stroke(2.), 2.); |
| 92 | assert_eq!(keyline_stroke(3.), 2.); |
| 93 | assert!((keyline_stroke(1.25) - 1.6).abs() < 1e-9); |
| 94 | for scale in [0.5, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0] { |
| 95 | let visible = keyline_stroke(scale) / 2. * scale; |
| 96 | let want = scale.round().max(1.); |
| 97 | assert!( |
| 98 | (visible - want).abs() < 1e-9, |
| 99 | "scale {scale}: visible {visible} device px, want {want}" |
| 100 | ); |
| 101 | } |
| 102 | assert_eq!(keyline_stroke(0.), keyline_stroke(1.)); |
| 103 | assert_eq!(keyline_stroke(-2.), keyline_stroke(1.)); |
| 104 | assert_eq!(keyline_stroke(f64::NAN), keyline_stroke(1.)); |
| 105 | } |
| 106 | } |
| 107 |