use egui::{CornerRadius, Rect, Response, Sense, Ui, WidgetInfo, WidgetType, pos2, vec2};
use super::{animate, mix};
use crate::automation::{self, Details, Role};
use crate::tokens;
pub const MAX_ROWS: usize = 256;
const PULSE_S: f64 = 1.6;
const WIDTHS: [f32; 5] = [1.0, 0.86, 0.94, 0.72, 0.9];
pub struct Skeleton<'a> {
id: &'a str,
label: Option<&'a str>,
rows: usize,
width: Option<f32>,
reduced_motion: bool,
}
impl<'a> Skeleton<'a> {
pub fn rows(id: &'a str, rows: usize) -> Self {
Skeleton { id, label: None, rows: rows.min(MAX_ROWS), width: None, reduced_motion: false }
}
pub fn label(mut self, label: &'a str) -> Self {
self.label = Some(label);
self
}
pub fn width(mut self, width: f32) -> Self {
self.width = width.is_finite().then(|| width.clamp(8.0, 8000.0));
self
}
pub fn reduced_motion(mut self, reduced: bool) -> Self {
self.reduced_motion = reduced;
self
}
pub fn show(self, ui: &mut Ui) -> Response {
let t = tokens::current(ui.ctx());
let row_h = t.control_height;
let bar_h = t.type_scale.body.round();
let w = self.width.unwrap_or_else(|| ui.available_width().clamp(8.0, 8000.0));
let (rect, response) = ui.allocate_exact_size(vec2(w, row_h * self.rows as f32), Sense::hover());
if self.rows > 0 && ui.is_rect_visible(rect) {
let base = t.surface.hover;
let fill = if self.reduced_motion {
base
} else {
animate(ui, rect);
let phase = (ui.input(|i| i.time) / PULSE_S).fract() as f32;
let k = 0.5 - 0.5 * (phase * std::f32::consts::TAU).cos();
mix(base, t.surface.sunken, 0.6 * k)
};
let radius = CornerRadius::same(t.radius.sm);
let clip = ui.clip_rect();
for i in 0..self.rows {
let top = rect.top() + row_h * i as f32 + (row_h - bar_h) / 2.0;
if top > clip.bottom() {
break;
}
if top + bar_h < clip.top() {
continue;
}
let frac = WIDTHS.get(i % WIDTHS.len()).copied().unwrap_or(1.0);
let bar = Rect::from_min_size(pos2(rect.left(), top), vec2(w * frac, bar_h));
ui.painter().rect_filled(bar, radius, fill);
}
}
let installed;
let label = match self.label {
Some(l) => l,
None => {
installed = crate::labels::current(ui.ctx());
installed.controls.loading.as_str()
}
};
response.widget_info(|| WidgetInfo::labeled(WidgetType::ProgressIndicator, true, label));
ui.ctx().accesskit_node_builder(response.id, |node| node.set_busy());
automation::register_with(ui, &response, self.id, label, Role::Progress, Details::default());
response
}
}