facett-core/src/look/navtokens.rs
//! **Navigation-graph overlay tokens** — the colours the dev-mode navgraph overlay
//! ([`crate::navgraph`]) paints with, derived from the v1 semantic [`Palette`] roles.
//!
//! The overlay has no colour literals of its own. Every colour below is a palette role,
//! sometimes with a lightness step or an alpha, so the overlay follows the active theme
//! (light, dark, device) with no per-theme code.
//!
//! # Names and look and feel v2.0
//!
//! Each field has a dotted token name in [`NavOverlayTokens::NAMES`]. The names are
//! chosen to be the keys of a future v2.0 token set (`nav.chip.plate`,
//! `nav.edge.next`, …): when v2.0 exists, the derivation in
//! [`NavOverlayTokens::from_palette`] is replaced by a lookup of these names and no
//! caller changes. v2.0 itself is not designed here.
use egui::Color32;
use super::Palette;
/// The overlay's colours. `Copy`, computed once per frame from the published palette.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NavOverlayTokens {
/// `nav.chip.plate` — the opaque plate behind a number (it must occlude, the same
/// rule as the dev-id chip: a translucent plate superimposes two strings).
pub chip_plate: Color32,
/// `nav.chip.text.declared` — a number whose every step is declared.
pub chip_declared: Color32,
/// `nav.chip.text.guessed` — a number with a `~` (draw-order) step.
pub chip_guessed: Color32,
/// `nav.chip.text.undeclared` — a number with a `?` (missing from its parent's
/// declared list) step.
pub chip_undeclared: Color32,
/// `nav.chip.outline.focused` — the ring around the chip of the focused node.
pub chip_focused: Color32,
/// `nav.edge.contains` — parent → child; faint, tinted per depth (see
/// [`NavOverlayTokens::contains_at_depth`]).
pub edge_contains: Color32,
/// `nav.edge.next` — sibling n → n+1 in the navigation order; bright, arrowed.
pub edge_next: Color32,
/// `nav.edge.spatial` — the arrow-key neighbour; dashed.
pub edge_spatial: Color32,
}
impl NavOverlayTokens {
/// The v2.0 token names, in field order.
pub const NAMES: [&'static str; 8] = [
"nav.chip.plate",
"nav.chip.text.declared",
"nav.chip.text.guessed",
"nav.chip.text.undeclared",
"nav.chip.outline.focused",
"nav.edge.contains",
"nav.edge.next",
"nav.edge.spatial",
];
/// Derive the tokens from the v1 semantic roles.
pub fn from_palette(p: &Palette) -> Self {
Self {
chip_plate: p.surface_extreme.to_color32(),
chip_declared: p.primary.to_color32(),
chip_guessed: p.warn.to_color32(),
chip_undeclared: p.error.to_color32(),
chip_focused: p.accent.to_color32(),
edge_contains: p.outline.to_color32_alpha(150),
edge_next: p.accent.to_color32(),
edge_spatial: p.on_surface_dim.to_color32_alpha(200),
}
}
/// The contains-edge colour for a child at `depth` (1 = root). Deeper edges fade,
/// so the spine reads first. Alpha only; the hue stays the outline role.
pub fn contains_at_depth(&self, depth: u8) -> Color32 {
let k = match depth {
0..=2 => 1.0,
3 => 0.75,
4 => 0.55,
_ => 0.4,
};
self.edge_contains.gamma_multiply(k)
}
}
/// The palette the active theme published ([`crate::look::Theme::apply`]), or the
/// default preset's when none was published. `Palette` is `Copy`, so this is a read.
pub fn published_palette(ctx: &egui::Context) -> Palette {
ctx.data(|d| d.get_temp::<Palette>(egui::Id::new(PALETTE_ID)))
.unwrap_or_else(super::presets::windows_dark_palette)
}
/// Publish the active palette (called by [`crate::look::Theme::apply`]).
pub fn publish_palette(ctx: &egui::Context, p: Palette) {
ctx.data_mut(|d| d.insert_temp(egui::Id::new(PALETTE_ID), p));
}
const PALETTE_ID: &str = "facett_look_palette";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_three_confidences_are_tellable_apart_and_the_plate_is_opaque() {
for p in [
super::super::presets::windows_dark_palette(),
super::super::presets::windows_light_palette(),
super::super::presets::device_palette(),
] {
let t = NavOverlayTokens::from_palette(&p);
assert_eq!(t.chip_plate.a(), 255, "the chip plate must occlude");
assert_ne!(t.chip_declared, t.chip_guessed);
assert_ne!(t.chip_declared, t.chip_undeclared);
assert_ne!(t.chip_guessed, t.chip_undeclared);
assert!(t.contains_at_depth(5).a() < t.contains_at_depth(1).a(), "deeper fades");
}
assert_eq!(NavOverlayTokens::NAMES.len(), 8);
}
}