teksilo_widgets/styles/
recipe_progress_bar_style.rs1use teksilo_canvas::{Canvas, Rect, Size, SizeProposal};
21use teksilo_core::accessibility::AccessNodeBuilder;
22use teksilo_core::binding::BindingLevel;
23use teksilo_core::build_context::BuildContext;
24use teksilo_core::color_prop::ColorProp;
25use teksilo_core::signal::Prop;
26use teksilo_core::styles::{ProgressBarStyle, ProgressBarStyleConfig, ProgressKind};
27use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget};
28use teksilo_core::widget_id::WidgetId;
29use teksilo_tokens::{CornerRadius, Orientation, SurfaceRole};
30
31pub const PROGRESS_BAR_CORNER_RADIUS: f32 = 2.0;
33
34#[derive(Debug, Clone, Copy, PartialEq)]
36pub struct ProgressBarRecipe {
37 pub corner_radius: f32,
38}
39
40impl Default for ProgressBarRecipe {
41 fn default() -> Self {
42 Self {
43 corner_radius: PROGRESS_BAR_CORNER_RADIUS,
44 }
45 }
46}
47
48#[derive(Debug, Default, Clone, Copy)]
51pub struct RecipeProgressBarStyle {
52 pub recipe: ProgressBarRecipe,
53}
54
55impl RecipeProgressBarStyle {
56 pub fn new(recipe: ProgressBarRecipe) -> Self {
57 Self { recipe }
58 }
59}
60
61impl ProgressBarStyle for RecipeProgressBarStyle {
62 fn make_body(&self, cfg: &ProgressBarStyleConfig, ctx: &mut BuildContext) -> WidgetId {
63 let track = cfg
64 .track_color_override
65 .clone()
66 .unwrap_or_else(|| SurfaceRole::Sunken.into());
67 let fill = cfg
68 .fill_color_override
69 .clone()
70 .unwrap_or_else(|| SurfaceRole::Accent.into());
71 let determinate_value = match &cfg.progress {
72 ProgressKind::Determinate(p) => Some(p.clone()),
73 ProgressKind::Indeterminate => None,
74 };
75 ctx.add(ProgressBarFrame {
76 orientation: cfg.orientation,
77 track,
78 fill,
79 determinate_value,
80 recipe: self.recipe,
81 })
82 }
83}
84
85struct ProgressBarFrame {
95 orientation: Orientation,
96 track: ColorProp,
97 fill: ColorProp,
98 determinate_value: Option<Prop<f32>>,
101 recipe: ProgressBarRecipe,
102}
103
104impl std::fmt::Debug for ProgressBarFrame {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.debug_struct("ProgressBarFrame")
107 .field("orientation", &self.orientation)
108 .finish()
109 }
110}
111
112impl Widget for ProgressBarFrame {
113 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
114 let id = ctx.self_id();
115 let registry = ctx.binding_registry();
116 if let Some(p) = &self.determinate_value {
117 p.register_if_bound(id, registry, BindingLevel::RepaintOnly);
118 }
119 vec![]
120 }
121
122 fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse {
123 Size::new(
126 proposal.width.unwrap_or(0.0),
127 proposal.height.unwrap_or(0.0),
128 )
129 .into()
130 }
131
132 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
133 let radius = CornerRadius::uniform(self.recipe.corner_radius);
134 let track_color = self.track.resolve(ctx.theme, ctx.effective_enabled);
135 canvas.fill_rounded_rect(bounds, radius, track_color);
136
137 if let Some(value_prop) = &self.determinate_value {
138 let value = value_prop.get().clamp(0.0, 1.0);
139 if value > 0.0 {
140 let fill_color = self.fill.resolve(ctx.theme, ctx.effective_enabled);
141 let fill_rect = match self.orientation {
142 Orientation::Horizontal => {
143 Rect::new(bounds.x, bounds.y, bounds.width * value, bounds.height)
144 }
145 Orientation::Vertical => {
146 let fill_h = bounds.height * value;
147 Rect::new(
148 bounds.x,
149 bounds.y + bounds.height - fill_h,
150 bounds.width,
151 fill_h,
152 )
153 }
154 };
155 canvas.fill_rounded_rect(fill_rect, radius, fill_color);
156 }
157 }
158 }
159
160 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
161 builder.set_hidden();
164 }
165}