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
10#[derive(Debug, Clone)]
12pub struct TreeNode {
13 pub id: String,
14 pub label: String,
15 pub children: Vec<TreeNode>,
16 pub expanded: bool,
17}
18
19impl TreeNode {
20 #[must_use]
21 pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
22 Self {
23 id: id.into(),
24 label: label.into(),
25 children: Vec::new(),
26 expanded: false,
27 }
28 }
29
30 #[must_use]
31 pub fn child(mut self, child: TreeNode) -> Self {
32 self.children.push(child);
33 self
34 }
35
36 #[must_use]
37 pub fn expanded(mut self, expanded: bool) -> Self {
38 self.expanded = expanded;
39 self
40 }
41
42 fn visible_count(&self) -> usize {
44 let mut count = 1; if self.expanded {
46 for child in &self.children {
47 count += child.visible_count();
48 }
49 }
50 count
51 }
52
53 fn draw_recursive(
55 &self,
56 painter: &mut dyn Painter,
57 x: f32,
58 y: &mut f32,
59 row_height: f32,
60 area: &Rect,
61 indent: f32,
62 ) {
63 if *y + row_height < area.y || *y > area.y + area.height {
64 *y += row_height;
65 if self.expanded {
66 for child in &self.children {
67 child.draw_recursive(painter, x + indent, y, row_height, area, indent);
68 }
69 }
70 return;
71 }
72
73 let style = TextStyle {
74 font_size: 13.0,
75 color: Color::WHITE,
76 ..TextStyle::default()
77 };
78
79 if !self.children.is_empty() {
81 let arrow = if self.expanded { "▾" } else { "▸" };
82 painter.text(
83 Position::new(x, *y + (row_height - style.font_size) * 0.5),
84 arrow,
85 &style,
86 );
87 }
88
89 painter.text(
90 Position::new(x + 16.0, *y + (row_height - style.font_size) * 0.5),
91 &self.label,
92 &style,
93 );
94
95 *y += row_height;
96
97 if self.expanded {
98 for child in &self.children {
99 child.draw_recursive(painter, x + indent, y, row_height, area, indent);
100 }
101 }
102 }
103}
104
105pub struct TreeView {
107 pub id: String,
108 pub root: TreeNode,
109 bg_color: Option<Color>,
110 fg_color: Option<Color>,
111 corner_radius: Option<f32>,
112 font_size: Option<f32>,
113 is_bold: bool,
114}
115
116impl TreeView {
117 #[must_use]
118 pub fn new(id: impl Into<String>, root: TreeNode) -> Self {
119 Self {
120 id: id.into(),
121 root,
122 bg_color: None,
123 fg_color: None,
124 corner_radius: None,
125 font_size: None,
126 is_bold: false,
127 }
128 }
129
130 #[must_use]
131 pub fn bg(mut self, color: Color) -> Self {
132 self.bg_color = Some(color);
133 self
134 }
135
136 #[must_use]
137 pub fn fg(mut self, color: Color) -> Self {
138 self.fg_color = Some(color);
139 self
140 }
141
142 #[must_use]
143 pub fn rounded(mut self, radius: f32) -> Self {
144 self.corner_radius = Some(radius);
145 self
146 }
147
148 #[must_use]
149 pub fn text_size(mut self, size: f32) -> Self {
150 self.font_size = Some(size);
151 self
152 }
153
154 #[must_use]
155 pub fn bold(mut self) -> Self {
156 self.is_bold = true;
157 self
158 }
159}
160
161impl Widget for TreeView {
162 fn draw(&self, painter: &mut dyn Painter, area: Rect) {
163 let bg = self.bg_color.unwrap_or(Color::rgba(0.1, 0.1, 0.13, 1.0));
164 let radius = self.corner_radius.unwrap_or(3.0);
165 painter.fill_rect(area, bg, radius);
166
167 let row_height = 24.0;
168 let indent = 18.0;
169 let mut y = area.y;
170
171 self.root
172 .draw_recursive(painter, area.x + 4.0, &mut y, row_height, &area, indent);
173 }
174
175 fn ui_node(&self) -> UiNode {
176 UiNode::new("TreeView", SemanticRole::TreeNode).with_id(&self.id)
177 }
178}
179
180impl Discoverable for TreeView {
181 fn schema(&self) -> WidgetSchema {
182 WidgetSchema::new(
183 "TreeView",
184 "A hierarchical tree view",
185 SemanticRole::TreeNode,
186 )
187 }
188
189 fn capabilities(&self) -> Vec<AgentCapability> {
190 vec![
191 AgentCapability::Focusable,
192 AgentCapability::Expandable {
193 expanded: self.root.expanded,
194 },
195 ]
196 }
197
198 fn actions(&self) -> Vec<AgentAction> {
199 vec![
200 AgentAction::simple("expand", "Expand a node", true),
201 AgentAction::simple("collapse", "Collapse a node", true),
202 ]
203 }
204
205 fn semantic_role(&self) -> SemanticRole {
206 SemanticRole::TreeNode
207 }
208
209 fn agent_state(&self) -> serde_json::Value {
210 serde_json::json!({
211 "root_label": self.root.label,
212 "root_expanded": self.root.expanded,
213 "visible_nodes": self.root.visible_count(),
214 })
215 }
216
217 fn execute_action(
218 &mut self,
219 action: &str,
220 _params: &serde_json::Value,
221 ) -> Result<serde_json::Value, String> {
222 match action {
223 "expand" => {
224 self.root.expanded = true;
225 Ok(serde_json::json!({ "expanded": true }))
226 }
227 "collapse" => {
228 self.root.expanded = false;
229 Ok(serde_json::json!({ "expanded": false }))
230 }
231 _ => Err(format!("Unknown action: {action}")),
232 }
233 }
234
235 fn agent_id(&self) -> Option<&str> {
236 Some(&self.id)
237 }
238}