use crate::Theme;
use egui::{Response, Ui, Widget};
use egui_cha::ViewCtx;
pub struct CapacityGauge {
progress: f32,
warning_threshold: f32,
danger_threshold: f32,
label: Option<String>,
display: DisplayMode,
width: Option<f32>,
height: Option<f32>,
animate: bool,
}
#[derive(Debug, Clone)]
enum DisplayMode {
None,
Percentage,
Fraction { current: u64, total: u64 },
Custom(String),
}
impl CapacityGauge {
pub fn new(progress: f32) -> Self {
Self {
progress: progress.clamp(0.0, 1.0),
warning_threshold: 0.7,
danger_threshold: 0.9,
label: None,
display: DisplayMode::None,
width: None,
height: None,
animate: false,
}
}
pub fn from_fraction(current: u64, total: u64) -> Self {
let progress = if total == 0 {
0.0
} else {
current as f32 / total as f32
};
Self::new(progress).show_fraction(current, total)
}
pub fn thresholds(mut self, warning: f32, danger: f32) -> Self {
self.warning_threshold = warning.clamp(0.0, 1.0);
self.danger_threshold = danger.clamp(0.0, 1.0);
self
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn show_percentage(mut self) -> Self {
self.display = DisplayMode::Percentage;
self
}
pub fn show_fraction(mut self, current: u64, total: u64) -> Self {
self.display = DisplayMode::Fraction { current, total };
self
}
pub fn text(mut self, text: impl Into<String>) -> Self {
self.display = DisplayMode::Custom(text.into());
self
}
pub fn width(mut self, width: f32) -> Self {
self.width = Some(width);
self
}
pub fn height(mut self, height: f32) -> Self {
self.height = Some(height);
self
}
pub fn animate(mut self, animate: bool) -> Self {
self.animate = animate;
self
}
pub fn show(self, ui: &mut Ui) -> Response {
self.show_internal(ui)
}
pub fn show_with<Msg>(self, ctx: &mut ViewCtx<'_, Msg>, on_click: impl FnOnce() -> Msg) {
let response = self.show_internal(ctx.ui);
if response.clicked() {
ctx.emit(on_click());
}
}
fn show_internal(self, ui: &mut Ui) -> Response {
let theme = Theme::current(ui.ctx());
let fill_color = if self.progress >= self.danger_threshold {
theme.state_danger
} else if self.progress >= self.warning_threshold {
theme.state_warning
} else {
theme.state_success
};
let text = match self.display {
DisplayMode::None => None,
DisplayMode::Percentage => Some(format!("{:.0}%", self.progress * 100.0)),
DisplayMode::Fraction { current, total } => Some(format!("{} / {}", current, total)),
DisplayMode::Custom(s) => Some(s),
};
let height = self.height.unwrap_or(theme.spacing_md);
let mut bar = egui::ProgressBar::new(self.progress)
.fill(fill_color)
.desired_height(height)
.animate(self.animate);
if let Some(w) = self.width {
bar = bar.desired_width(w);
}
if let Some(t) = text {
bar = bar.text(t);
}
if let Some(label_text) = self.label {
ui.horizontal(|ui| {
ui.label(&label_text);
ui.add(bar)
})
.inner
} else {
ui.add(bar)
}
}
}
impl Widget for CapacityGauge {
fn ui(self, ui: &mut Ui) -> Response {
self.show(ui)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_fraction() {
let gauge = CapacityGauge::from_fraction(750, 1000);
assert!((gauge.progress - 0.75).abs() < 0.001);
}
#[test]
fn test_from_fraction_zero_total() {
let gauge = CapacityGauge::from_fraction(100, 0);
assert_eq!(gauge.progress, 0.0);
}
#[test]
fn test_progress_clamping() {
let gauge = CapacityGauge::new(1.5);
assert_eq!(gauge.progress, 1.0);
let gauge = CapacityGauge::new(-0.5);
assert_eq!(gauge.progress, 0.0);
}
}