use teksilo_canvas::{Canvas, Rect, Size, SizeProposal};
use teksilo_core::accessibility::AccessNodeBuilder;
use teksilo_core::binding::BindingLevel;
use teksilo_core::build_context::BuildContext;
use teksilo_core::color_prop::ColorProp;
use teksilo_core::signal::Prop;
use teksilo_core::styles::{ProgressBarStyle, ProgressBarStyleConfig, ProgressKind};
use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget};
use teksilo_core::widget_id::WidgetId;
use teksilo_tokens::{CornerRadius, Orientation, SurfaceRole};
pub const PROGRESS_BAR_CORNER_RADIUS: f32 = 2.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ProgressBarRecipe {
pub corner_radius: f32,
}
impl Default for ProgressBarRecipe {
fn default() -> Self {
Self {
corner_radius: PROGRESS_BAR_CORNER_RADIUS,
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct RecipeProgressBarStyle {
pub recipe: ProgressBarRecipe,
}
impl RecipeProgressBarStyle {
pub fn new(recipe: ProgressBarRecipe) -> Self {
Self { recipe }
}
}
impl ProgressBarStyle for RecipeProgressBarStyle {
fn make_body(&self, cfg: &ProgressBarStyleConfig, ctx: &mut BuildContext) -> WidgetId {
let track = cfg
.track_color_override
.clone()
.unwrap_or_else(|| SurfaceRole::Sunken.into());
let fill = cfg
.fill_color_override
.clone()
.unwrap_or_else(|| SurfaceRole::Accent.into());
let determinate_value = match &cfg.progress {
ProgressKind::Determinate(p) => Some(p.clone()),
ProgressKind::Indeterminate => None,
};
ctx.add(ProgressBarFrame {
orientation: cfg.orientation,
track,
fill,
determinate_value,
recipe: self.recipe,
})
}
}
struct ProgressBarFrame {
orientation: Orientation,
track: ColorProp,
fill: ColorProp,
determinate_value: Option<Prop<f32>>,
recipe: ProgressBarRecipe,
}
impl std::fmt::Debug for ProgressBarFrame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProgressBarFrame")
.field("orientation", &self.orientation)
.finish()
}
}
impl Widget for ProgressBarFrame {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
let id = ctx.self_id();
let registry = ctx.binding_registry();
if let Some(p) = &self.determinate_value {
p.register_if_bound(id, registry, BindingLevel::RepaintOnly);
}
vec![]
}
fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse {
Size::new(
proposal.width.unwrap_or(0.0),
proposal.height.unwrap_or(0.0),
)
.into()
}
fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
let radius = CornerRadius::uniform(self.recipe.corner_radius);
let track_color = self.track.resolve(ctx.theme, ctx.effective_enabled);
canvas.fill_rounded_rect(bounds, radius, track_color);
if let Some(value_prop) = &self.determinate_value {
let value = value_prop.get().clamp(0.0, 1.0);
if value > 0.0 {
let fill_color = self.fill.resolve(ctx.theme, ctx.effective_enabled);
let fill_rect = match self.orientation {
Orientation::Horizontal => {
Rect::new(bounds.x, bounds.y, bounds.width * value, bounds.height)
}
Orientation::Vertical => {
let fill_h = bounds.height * value;
Rect::new(
bounds.x,
bounds.y + bounds.height - fill_h,
bounds.width,
fill_h,
)
}
};
canvas.fill_rounded_rect(fill_rect, radius, fill_color);
}
}
}
fn accessibility(&self, builder: &mut AccessNodeBuilder) {
builder.set_hidden();
}
}