1use crate::core::{Color, Position, Rect};
4use crate::ontology::{
5 AgentAction, AgentCapability, Discoverable, SemanticRole, UiNode, WidgetSchema,
6};
7use crate::paint::Painter;
8use crate::widget::Widget;
9
10pub struct Slider {
12 pub id: String,
13 pub value: f32,
14 pub min: f32,
15 pub max: f32,
16 pub step: Option<f32>,
17 bg_color: Option<Color>,
18 fg_color: Option<Color>,
19 corner_radius: Option<f32>,
20}
21
22impl Slider {
23 #[must_use]
24 pub fn new(id: impl Into<String>, value: f32, min: f32, max: f32) -> Self {
25 Self {
26 id: id.into(),
27 value: value.clamp(min, max),
28 min,
29 max,
30 step: None,
31 bg_color: None,
32 fg_color: None,
33 corner_radius: None,
34 }
35 }
36
37 #[must_use]
38 pub fn step(mut self, step: f32) -> Self {
39 self.step = Some(step);
40 self
41 }
42
43 pub fn normalized(&self) -> f32 {
45 if (self.max - self.min).abs() < f32::EPSILON {
46 0.0
47 } else {
48 (self.value - self.min) / (self.max - self.min)
49 }
50 }
51
52 #[must_use]
53 pub fn bg(mut self, color: Color) -> Self {
54 self.bg_color = Some(color);
55 self
56 }
57
58 #[must_use]
59 pub fn fg(mut self, color: Color) -> Self {
60 self.fg_color = Some(color);
61 self
62 }
63
64 #[must_use]
65 pub fn rounded(mut self, radius: f32) -> Self {
66 self.corner_radius = Some(radius);
67 self
68 }
69}
70
71impl Widget for Slider {
72 fn draw(&self, painter: &mut dyn Painter, area: Rect) {
73 let track_height = 4.0;
74 let track_y = area.y + (area.height - track_height) * 0.5;
75 let track = Rect::new(area.x, track_y, area.width, track_height);
76 let radius = self.corner_radius.unwrap_or(2.0);
77
78 let track_color = self.bg_color.unwrap_or(Color::rgba(0.3, 0.3, 0.35, 1.0));
79 painter.fill_rect(track, track_color, radius);
80
81 let t = self.normalized();
82 let filled = Rect::new(area.x, track_y, area.width * t, track_height);
83 let fill_color = self.fg_color.unwrap_or(Color::rgba(0.2, 0.5, 1.0, 1.0));
84 painter.fill_rect(filled, fill_color, radius);
85
86 let thumb_radius = 8.0;
87 let thumb_x = area.x + area.width * t;
88 let thumb_center = Position::new(thumb_x, area.y + area.height * 0.5);
89 painter.fill_circle(thumb_center, thumb_radius, Color::WHITE);
90 }
91
92 fn ui_node(&self) -> UiNode {
93 UiNode::new("Slider", SemanticRole::Input).with_id(&self.id)
94 }
95}
96
97impl Discoverable for Slider {
98 fn schema(&self) -> WidgetSchema {
99 WidgetSchema::new(
100 "Slider",
101 "A range slider for value selection",
102 SemanticRole::Input,
103 )
104 }
105
106 fn capabilities(&self) -> Vec<AgentCapability> {
107 vec![
108 AgentCapability::Focusable,
109 AgentCapability::RangeEditable {
110 min: self.min as f64,
111 max: self.max as f64,
112 step: self.step.map(|s| s as f64),
113 },
114 ]
115 }
116
117 fn actions(&self) -> Vec<AgentAction> {
118 vec![AgentAction::simple(
119 "set_value",
120 "Set the slider value",
121 true,
122 )]
123 }
124
125 fn semantic_role(&self) -> SemanticRole {
126 SemanticRole::Input
127 }
128
129 fn agent_state(&self) -> serde_json::Value {
130 serde_json::json!({
131 "value": self.value,
132 "min": self.min,
133 "max": self.max,
134 "normalized": self.normalized(),
135 })
136 }
137
138 fn execute_action(
139 &mut self,
140 action: &str,
141 params: &serde_json::Value,
142 ) -> Result<serde_json::Value, String> {
143 match action {
144 "set_value" => {
145 if let Some(v) = params.get("value").and_then(|v| v.as_f64()) {
146 self.value = (v as f32).clamp(self.min, self.max);
147 Ok(serde_json::json!({ "value": self.value }))
148 } else {
149 Err("Missing 'value' parameter".into())
150 }
151 }
152 _ => Err(format!("Unknown action: {action}")),
153 }
154 }
155
156 fn agent_id(&self) -> Option<&str> {
157 Some(&self.id)
158 }
159}