1use crate::core::{Color, Position, Rect, TextStyle};
4use crate::ontology::{
5 AgentAction, AgentCapability, Discoverable, SemanticRole, UiNode, WidgetSchema,
6};
7use crate::paint::Painter;
8use crate::widget::Widget;
9
10pub struct Checkbox {
12 pub id: String,
13 pub label: String,
14 pub checked: bool,
15 bg_color: Option<Color>,
16 fg_color: Option<Color>,
17 corner_radius: Option<f32>,
18 font_size: Option<f32>,
19 is_bold: bool,
20}
21
22impl Checkbox {
23 #[must_use]
24 pub fn new(id: impl Into<String>, label: impl Into<String>, checked: bool) -> Self {
25 Self {
26 id: id.into(),
27 label: label.into(),
28 checked,
29 bg_color: None,
30 fg_color: None,
31 corner_radius: None,
32 font_size: None,
33 is_bold: false,
34 }
35 }
36
37 #[must_use]
38 pub fn bg(mut self, color: Color) -> Self {
39 self.bg_color = Some(color);
40 self
41 }
42
43 #[must_use]
44 pub fn fg(mut self, color: Color) -> Self {
45 self.fg_color = Some(color);
46 self
47 }
48
49 #[must_use]
50 pub fn rounded(mut self, radius: f32) -> Self {
51 self.corner_radius = Some(radius);
52 self
53 }
54
55 #[must_use]
56 pub fn text_size(mut self, size: f32) -> Self {
57 self.font_size = Some(size);
58 self
59 }
60
61 #[must_use]
62 pub fn bold(mut self) -> Self {
63 self.is_bold = true;
64 self
65 }
66}
67
68impl Widget for Checkbox {
69 fn draw(&self, painter: &mut dyn Painter, area: Rect) {
70 let box_size = 16.0;
71 let box_rect = Rect::new(
72 area.x,
73 area.y + (area.height - box_size) * 0.5,
74 box_size,
75 box_size,
76 );
77
78 let border = self.fg_color.unwrap_or(Color::rgba(0.5, 0.5, 0.6, 1.0));
79 let radius = self.corner_radius.unwrap_or(2.0);
80 painter.stroke_rect(box_rect, border, 1.0, radius);
81
82 if self.checked {
83 let inner = Rect::new(
84 box_rect.x + 3.0,
85 box_rect.y + 3.0,
86 box_size - 6.0,
87 box_size - 6.0,
88 );
89 let check_color = self.bg_color.unwrap_or(Color::rgba(0.2, 0.6, 1.0, 1.0));
90 painter.fill_rect(inner, check_color, 1.0);
91 }
92
93 let text_color = self.fg_color.unwrap_or(Color::WHITE);
94 let style = TextStyle {
95 font_size: self.font_size.unwrap_or(14.0),
96 color: text_color,
97 ..TextStyle::default()
98 };
99 painter.text(
100 Position::new(
101 area.x + box_size + 8.0,
102 area.y + (area.height - style.font_size) * 0.5,
103 ),
104 &self.label,
105 &style,
106 );
107 }
108
109 fn ui_node(&self) -> UiNode {
110 UiNode::new("Checkbox", SemanticRole::Input).with_id(&self.id)
111 }
112}
113
114impl Discoverable for Checkbox {
115 fn schema(&self) -> WidgetSchema {
116 WidgetSchema::new("Checkbox", "A toggle checkbox", SemanticRole::Input)
117 }
118
119 fn capabilities(&self) -> Vec<AgentCapability> {
120 vec![
121 AgentCapability::Focusable,
122 AgentCapability::Toggleable {
123 state: self.checked,
124 },
125 ]
126 }
127
128 fn actions(&self) -> Vec<AgentAction> {
129 vec![AgentAction::simple("toggle", "Toggle the checkbox", true)]
130 }
131
132 fn semantic_role(&self) -> SemanticRole {
133 SemanticRole::Input
134 }
135
136 fn agent_state(&self) -> serde_json::Value {
137 serde_json::json!({ "checked": self.checked, "label": self.label })
138 }
139
140 fn execute_action(
141 &mut self,
142 action: &str,
143 _params: &serde_json::Value,
144 ) -> Result<serde_json::Value, String> {
145 match action {
146 "toggle" => {
147 self.checked = !self.checked;
148 Ok(serde_json::json!({ "checked": self.checked }))
149 }
150 _ => Err(format!("Unknown action: {action}")),
151 }
152 }
153
154 fn agent_id(&self) -> Option<&str> {
155 Some(&self.id)
156 }
157}
158
159pub struct Radio {
161 pub id: String,
162 pub label: String,
163 pub selected: bool,
164 bg_color: Option<Color>,
165 fg_color: Option<Color>,
166 font_size: Option<f32>,
167 is_bold: bool,
168}
169
170impl Radio {
171 #[must_use]
172 pub fn new(id: impl Into<String>, label: impl Into<String>, selected: bool) -> Self {
173 Self {
174 id: id.into(),
175 label: label.into(),
176 selected,
177 bg_color: None,
178 fg_color: None,
179 font_size: None,
180 is_bold: false,
181 }
182 }
183
184 #[must_use]
185 pub fn bg(mut self, color: Color) -> Self {
186 self.bg_color = Some(color);
187 self
188 }
189
190 #[must_use]
191 pub fn fg(mut self, color: Color) -> Self {
192 self.fg_color = Some(color);
193 self
194 }
195
196 #[must_use]
197 pub fn text_size(mut self, size: f32) -> Self {
198 self.font_size = Some(size);
199 self
200 }
201
202 #[must_use]
203 pub fn bold(mut self) -> Self {
204 self.is_bold = true;
205 self
206 }
207}
208
209impl Widget for Radio {
210 fn draw(&self, painter: &mut dyn Painter, area: Rect) {
211 let radius = 8.0;
212 let center = Position::new(area.x + radius, area.y + area.height * 0.5);
213
214 let border = self.fg_color.unwrap_or(Color::rgba(0.5, 0.5, 0.6, 1.0));
215 painter.stroke_circle(center, radius, border, 1.0);
216
217 if self.selected {
218 let fill = self.bg_color.unwrap_or(Color::rgba(0.2, 0.6, 1.0, 1.0));
219 painter.fill_circle(center, radius - 3.0, fill);
220 }
221
222 let text_color = self.fg_color.unwrap_or(Color::WHITE);
223 let style = TextStyle {
224 font_size: self.font_size.unwrap_or(14.0),
225 color: text_color,
226 ..TextStyle::default()
227 };
228 painter.text(
229 Position::new(
230 area.x + radius * 2.0 + 8.0,
231 area.y + (area.height - style.font_size) * 0.5,
232 ),
233 &self.label,
234 &style,
235 );
236 }
237
238 fn ui_node(&self) -> UiNode {
239 UiNode::new("Radio", SemanticRole::Input).with_id(&self.id)
240 }
241}
242
243impl Discoverable for Radio {
244 fn schema(&self) -> WidgetSchema {
245 WidgetSchema::new(
246 "Radio",
247 "An exclusive-choice radio button",
248 SemanticRole::Input,
249 )
250 }
251
252 fn capabilities(&self) -> Vec<AgentCapability> {
253 vec![
254 AgentCapability::Focusable,
255 AgentCapability::Selectable {
256 multi_select: false,
257 item_count: 1,
258 },
259 ]
260 }
261
262 fn actions(&self) -> Vec<AgentAction> {
263 vec![AgentAction::simple(
264 "select",
265 "Select this radio option",
266 true,
267 )]
268 }
269
270 fn semantic_role(&self) -> SemanticRole {
271 SemanticRole::Input
272 }
273
274 fn agent_state(&self) -> serde_json::Value {
275 serde_json::json!({ "selected": self.selected, "label": self.label })
276 }
277
278 fn execute_action(
279 &mut self,
280 action: &str,
281 _params: &serde_json::Value,
282 ) -> Result<serde_json::Value, String> {
283 match action {
284 "select" => {
285 self.selected = true;
286 Ok(serde_json::json!({ "selected": true }))
287 }
288 _ => Err(format!("Unknown action: {action}")),
289 }
290 }
291
292 fn agent_id(&self) -> Option<&str> {
293 Some(&self.id)
294 }
295}