use eframe::egui::{self, Color32, CornerRadius, FontId, Pos2, Rect, RichText, Stroke, StrokeKind, Vec2};
use super::facett_theme::Theme;
pub fn status_chip(theme: &Theme, status: &str) -> (&'static str, Color32) {
let s = status.to_ascii_lowercase();
match s.as_str() {
"pass" | "passed" | "ok" | "succeeded" | "done" => ("PASS", super::facett_theme::GREEN),
"fail" | "failed" | "error" => ("FAIL", super::facett_theme::RED),
"ignored" | "ignore" => ("IGN", theme.text_dim),
"stalled" | "stall" => ("STALL", super::facett_theme::AMBER),
"skip" | "skipped" => ("SKIP", Color32::from_rgb(150, 150, 160)),
"running" | "in_progress" => ("RUN", super::facett_theme::AMBER),
_ => ("?", theme.text_dim),
}
}
pub fn status_dot(ui: &mut egui::Ui, theme: &Theme, color: Color32, label: &str, n: usize) {
ui.label(RichText::new("●").color(color));
ui.label(RichText::new(format!("{n} {label}")).color(theme.text));
ui.add_space(6.0);
}
pub fn zebra_row(painter: &egui::Painter, theme: &Theme, rect: Rect, odd: bool) {
if odd {
painter.rect_filled(rect, CornerRadius::same(2), theme.zebra(true));
}
}
pub fn hover_row(painter: &egui::Painter, theme: &Theme, rect: Rect, hovered: bool) {
if hovered {
painter.rect_filled(rect, CornerRadius::same(2), theme.hover());
}
}
pub fn health_pill(painter: &egui::Painter, theme: &Theme, score: f64, top_left: Pos2, w: f32, h: f32) {
let pill = Rect::from_min_size(Pos2::new(top_left.x + 4.0, top_left.y + 9.0), Vec2::new(w - 12.0, h - 18.0));
let col = theme.health_color(score);
painter.rect_filled(pill, CornerRadius::same(7), col.linear_multiply(0.28));
painter.rect_stroke(pill, CornerRadius::same(7), Stroke::new(1.5, col), StrokeKind::Inside);
painter.text(
pill.center(),
egui::Align2::CENTER_CENTER,
format!("{score:.0}"),
FontId::proportional(16.0),
col,
);
}
pub fn sparkline(painter: &egui::Painter, theme: &Theme, series: &[f64], rect: Rect) {
if series.len() < 2 {
return;
}
let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
for &v in series {
lo = lo.min(v);
hi = hi.max(v);
}
let span = (hi - lo).max(1.0);
let n = series.len();
let pts: Vec<Pos2> = series
.iter()
.enumerate()
.map(|(i, &v)| {
let t = i as f32 / (n - 1) as f32;
let x = rect.min.x + t * rect.width();
let norm = ((v - lo) / span) as f32;
let y = rect.max.y - norm * rect.height();
Pos2::new(x, y)
})
.collect();
for w in pts.windows(2) {
painter.line_segment([w[0], w[1]], Stroke::new(1.4, theme.point));
}
if let Some(last) = pts.last() {
painter.circle_filled(*last, 1.8, theme.point);
}
}
pub fn graph_node(
painter: &egui::Painter,
theme: &Theme,
rect: Rect,
label: &str,
stroke: Color32,
selected: bool,
) {
let fill = if selected {
theme.accent.linear_multiply(0.18)
} else {
theme.node_fill
};
painter.rect_filled(rect, CornerRadius::same(6), fill);
painter.rect_stroke(
rect,
CornerRadius::same(6),
Stroke::new(if selected { 2.5 } else { 1.5 }, stroke),
StrokeKind::Inside,
);
painter.text(
rect.center(),
egui::Align2::CENTER_CENTER,
label,
FontId::proportional(13.0),
theme.text,
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_chip_vocabulary_is_palette_aware() {
let d = Theme::default();
assert_eq!(status_chip(&d, "pass").0, "PASS");
assert_eq!(status_chip(&d, "PASS").0, "PASS"); assert_eq!(status_chip(&d, "failed").0, "FAIL");
assert_eq!(status_chip(&d, "skip").0, "SKIP");
assert_eq!(status_chip(&d, "???").1, d.text_dim);
assert_eq!(
status_chip(&Theme::hugin_noir(), "???").1,
Theme::hugin_noir().text_dim,
"unknown chip colour shifts with the palette",
);
assert_eq!(status_chip(&d, "pass").1, super::super::facett_theme::GREEN);
}
}