Skip to main content

agpu/widget/
progress.rs

1//! Progress bar widget.
2
3use crate::core::{Color, Rect};
4use crate::ontology::{
5    AgentAction, AgentCapability, Discoverable, SemanticRole, UiNode, WidgetSchema,
6};
7use crate::paint::Painter;
8use crate::widget::Widget;
9
10/// A horizontal progress bar (determinate or indeterminate).
11pub struct ProgressBar {
12    pub id: String,
13    /// Progress fraction 0.0–1.0.  `None` = indeterminate.
14    pub progress: Option<f32>,
15    bg_color: Option<Color>,
16    fg_color: Option<Color>,
17    corner_radius: Option<f32>,
18}
19
20impl ProgressBar {
21    /// Create a determinate progress bar.
22    #[must_use]
23    pub fn new(id: impl Into<String>, progress: f32) -> Self {
24        Self {
25            id: id.into(),
26            progress: Some(progress.clamp(0.0, 1.0)),
27            bg_color: None,
28            fg_color: None,
29            corner_radius: None,
30        }
31    }
32
33    /// Create an indeterminate (spinning/pulsing) progress bar.
34    #[must_use]
35    pub fn indeterminate(id: impl Into<String>) -> Self {
36        Self {
37            id: id.into(),
38            progress: None,
39            bg_color: None,
40            fg_color: None,
41            corner_radius: None,
42        }
43    }
44
45    #[must_use]
46    pub fn bg(mut self, color: Color) -> Self {
47        self.bg_color = Some(color);
48        self
49    }
50
51    #[must_use]
52    pub fn fg(mut self, color: Color) -> Self {
53        self.fg_color = Some(color);
54        self
55    }
56
57    #[must_use]
58    pub fn rounded(mut self, radius: f32) -> Self {
59        self.corner_radius = Some(radius);
60        self
61    }
62}
63
64impl Widget for ProgressBar {
65    fn draw(&self, painter: &mut dyn Painter, area: Rect) {
66        let radius = self.corner_radius.unwrap_or(3.0);
67        // Track
68        let track_bg = self.bg_color.unwrap_or(Color::rgba(0.2, 0.2, 0.25, 1.0));
69        painter.fill_rect(area, track_bg, radius);
70
71        // Fill
72        let fill_width = match self.progress {
73            Some(p) => area.width * p,
74            None => area.width * 0.3, // simple placeholder for indeterminate
75        };
76        let fill = Rect::new(area.x, area.y, fill_width, area.height);
77        let fill_color = self.fg_color.unwrap_or(Color::rgba(0.2, 0.6, 1.0, 1.0));
78        painter.fill_rect(fill, fill_color, radius);
79    }
80
81    fn ui_node(&self) -> UiNode {
82        UiNode::new("ProgressBar", SemanticRole::Progress).with_id(&self.id)
83    }
84}
85
86impl Discoverable for ProgressBar {
87    fn schema(&self) -> WidgetSchema {
88        WidgetSchema::new(
89            "ProgressBar",
90            "A progress indicator",
91            SemanticRole::Progress,
92        )
93    }
94
95    fn capabilities(&self) -> Vec<AgentCapability> {
96        vec![]
97    }
98
99    fn actions(&self) -> Vec<AgentAction> {
100        vec![AgentAction::simple(
101            "set_progress",
102            "Set progress 0.0–1.0",
103            true,
104        )]
105    }
106
107    fn semantic_role(&self) -> SemanticRole {
108        SemanticRole::Progress
109    }
110
111    fn agent_state(&self) -> serde_json::Value {
112        serde_json::json!({
113            "progress": self.progress,
114            "determinate": self.progress.is_some(),
115        })
116    }
117
118    fn execute_action(
119        &mut self,
120        action: &str,
121        params: &serde_json::Value,
122    ) -> Result<serde_json::Value, String> {
123        match action {
124            "set_progress" => {
125                if let Some(v) = params.get("value").and_then(|v| v.as_f64()) {
126                    let p = (v as f32).clamp(0.0, 1.0);
127                    self.progress = Some(p);
128                    Ok(serde_json::json!({ "progress": p }))
129                } else {
130                    Err("Missing 'value' parameter".into())
131                }
132            }
133            _ => Err(format!("Unknown action: {action}")),
134        }
135    }
136
137    fn agent_id(&self) -> Option<&str> {
138        Some(&self.id)
139    }
140}