facett-grid/src/condfmt.rs
//! **Conditional formatting** — per-column rules that tint a numeric cell. Two
//! flagship styles: **data bars** (an in-cell bar whose length ∝ value) and a
//! **colour scale** heat (low→high lerped through the theme ramp). Both normalise
//! against the column's `numeric_range`, so they're scale-invariant.
use egui::Color32;
use facett_core::Theme;
/// A conditional-format rule attached to a column index.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CondFmt {
/// In-cell horizontal bar, length ∝ normalised value, tinted from `accent`.
DataBar,
/// Cell background lerped low→high through the theme ramp (`text_dim`→`glow`).
ColorScale,
}
/// Normalise `x` into 0..1 over `(lo, hi)`; degenerate range → 0.5.
pub fn normalize(x: f64, lo: f64, hi: f64) -> f32 {
if hi <= lo {
return 0.5;
}
(((x - lo) / (hi - lo)) as f32).clamp(0.0, 1.0)
}
// The colour lerp and the heat ramp are the canonical `facett_core::look`
// primitives; re-exported here so `facett_grid::condfmt::{lerp_color,heat_color}`
// keeps working while there is exactly one implementation (no copy-paste twin).
pub use facett_core::look::color::{heat_color, lerp_color};
/// The data-bar fill colour under `theme` (accent at a moderate alpha).
pub fn bar_color(theme: &Theme) -> Color32 {
Color32::from_rgba_unmultiplied(theme.accent.r(), theme.accent.g(), theme.accent.b(), 90)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_maps_endpoints() {
assert_eq!(normalize(0.0, 0.0, 10.0), 0.0);
assert_eq!(normalize(10.0, 0.0, 10.0), 1.0);
assert_eq!(normalize(5.0, 0.0, 10.0), 0.5);
// degenerate range
assert_eq!(normalize(7.0, 3.0, 3.0), 0.5);
// clamps out-of-range
assert_eq!(normalize(-5.0, 0.0, 10.0), 0.0);
assert_eq!(normalize(99.0, 0.0, 10.0), 1.0);
}
#[test]
fn lerp_color_endpoints_and_mid() {
let a = Color32::from_rgb(0, 0, 0);
let b = Color32::from_rgb(100, 200, 50);
assert_eq!(lerp_color(a, b, 0.0), a);
assert_eq!(lerp_color(a, b, 1.0), b);
let m = lerp_color(a, b, 0.5);
assert_eq!((m.r(), m.g(), m.b()), (50, 100, 25));
}
#[test]
fn heat_color_warms_with_value() {
let th = Theme::default();
let cold = heat_color(&th, 0.0);
let hot = heat_color(&th, 1.0);
assert!(hot.a() > cold.a(), "hotter cells are more opaque");
}
}