use super::{Widget, WidgetBase, WidgetId, LayoutContext, PaintContext, EventContext};
use crate::css::{ClassList, WidgetState};
use crate::event::{Event, EventResult};
use crate::geometry::{BorderRadius, Color, Rect, Size};
use crate::layout::{Constraints, LayoutResult};
use crate::render::Painter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ProgressVariant {
#[default]
Linear,
Striped,
Indeterminate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ProgressSize {
Small,
#[default]
Medium,
Large,
}
impl ProgressSize {
fn height(&self) -> f32 {
match self {
ProgressSize::Small => 2.0,
ProgressSize::Medium => 4.0,
ProgressSize::Large => 8.0,
}
}
}
pub struct Progress {
base: WidgetBase,
value: f32,
variant: ProgressVariant,
size: ProgressSize,
color: Option<Color>,
show_label: bool,
}
impl Progress {
pub fn new() -> Self {
Self {
base: WidgetBase::new().with_class("progress"),
value: 0.0,
variant: ProgressVariant::default(),
size: ProgressSize::default(),
color: None,
show_label: false,
}
}
pub fn value(mut self, value: f32) -> Self {
self.value = value.clamp(0.0, 1.0);
self
}
pub fn variant(mut self, variant: ProgressVariant) -> Self {
self.variant = variant;
self
}
pub fn size(mut self, size: ProgressSize) -> Self {
self.size = size;
self
}
pub fn color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
pub fn show_label(mut self, show: bool) -> Self {
self.show_label = show;
self
}
pub fn class(mut self, class: &str) -> Self {
self.base.classes.add(class);
self
}
pub fn get_value(&self) -> f32 {
self.value
}
pub fn set_value(&mut self, value: f32) {
self.value = value.clamp(0.0, 1.0);
}
}
impl Default for Progress {
fn default() -> Self {
Self::new()
}
}
impl Widget for Progress {
fn id(&self) -> WidgetId {
self.base.id
}
fn type_name(&self) -> &'static str {
"progress"
}
fn element_id(&self) -> Option<&str> {
self.base.element_id.as_deref()
}
fn classes(&self) -> &ClassList {
&self.base.classes
}
fn state(&self) -> WidgetState {
self.base.state
}
fn intrinsic_size(&self, _ctx: &LayoutContext) -> Size {
let height = if self.show_label {
self.size.height() + 20.0
} else {
self.size.height()
};
Size::new(200.0, height)
}
fn layout(&mut self, constraints: Constraints, ctx: &LayoutContext) -> LayoutResult {
let intrinsic = self.intrinsic_size(ctx);
let size = Size::new(
constraints.max_width.min(intrinsic.width.max(constraints.min_width)),
intrinsic.height,
);
self.base.bounds.size = size;
LayoutResult::new(size)
}
fn paint(&self, painter: &mut Painter, rect: Rect, ctx: &PaintContext) {
let theme = ctx.style_ctx.theme;
let bar_height = self.size.height();
let radius = BorderRadius::all(bar_height / 2.0);
let bar_rect = if self.show_label {
Rect::new(rect.x(), rect.y(), rect.width(), bar_height)
} else {
rect
};
painter.fill_rounded_rect(bar_rect, theme.colors.muted, radius);
let fill_color = self.color.unwrap_or(theme.colors.primary);
match self.variant {
ProgressVariant::Linear | ProgressVariant::Striped => {
let fill_width = bar_rect.width() * self.value;
let fill_rect = Rect::new(bar_rect.x(), bar_rect.y(), fill_width, bar_rect.height());
painter.fill_rounded_rect(fill_rect, fill_color, radius);
if self.variant == ProgressVariant::Striped && fill_width > 0.0 {
let stripe_color = Color::WHITE.with_alpha(0.2);
let stripe_width: f32 = 10.0;
let mut x = bar_rect.x();
while x < bar_rect.x() + fill_width {
let stripe_rect = Rect::new(
x,
bar_rect.y(),
(stripe_width / 2.0).min(bar_rect.x() + fill_width - x),
bar_rect.height(),
);
painter.fill_rect(stripe_rect, stripe_color);
x += stripe_width;
}
}
}
ProgressVariant::Indeterminate => {
let segment_width = bar_rect.width() * 0.3;
let segment_x = bar_rect.x() + (bar_rect.width() - segment_width) * 0.3; let segment_rect = Rect::new(segment_x, bar_rect.y(), segment_width, bar_rect.height());
painter.fill_rounded_rect(segment_rect, fill_color, radius);
}
}
if self.show_label && self.variant != ProgressVariant::Indeterminate {
let label = format!("{:.0}%", self.value * 100.0);
let label_y = bar_rect.y() + bar_height + 16.0;
painter.draw_text(
&label,
crate::geometry::Point::new(rect.x() + rect.width() - 30.0, label_y),
theme.colors.foreground,
12.0,
);
}
}
fn handle_event(&mut self, _event: &Event, _ctx: &mut EventContext) -> EventResult {
EventResult::Ignored
}
fn bounds(&self) -> Rect {
self.base.bounds
}
fn set_bounds(&mut self, bounds: Rect) {
self.base.bounds = bounds;
}
}