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 Tooltip {
12 pub id: String,
13 pub text: String,
14 bg_color: Option<Color>,
15 fg_color: Option<Color>,
16 corner_radius: Option<f32>,
17 font_size: Option<f32>,
18}
19
20impl Tooltip {
21 #[must_use]
22 pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
23 Self {
24 id: id.into(),
25 text: text.into(),
26 bg_color: None,
27 fg_color: None,
28 corner_radius: None,
29 font_size: None,
30 }
31 }
32
33 #[must_use]
34 pub fn bg(mut self, color: Color) -> Self {
35 self.bg_color = Some(color);
36 self
37 }
38
39 #[must_use]
40 pub fn fg(mut self, color: Color) -> Self {
41 self.fg_color = Some(color);
42 self
43 }
44
45 #[must_use]
46 pub fn rounded(mut self, radius: f32) -> Self {
47 self.corner_radius = Some(radius);
48 self
49 }
50
51 #[must_use]
52 pub fn text_size(mut self, size: f32) -> Self {
53 self.font_size = Some(size);
54 self
55 }
56}
57
58impl Widget for Tooltip {
59 fn draw(&self, painter: &mut dyn Painter, area: Rect) {
60 let bg = self.bg_color.unwrap_or(Color::rgba(0.12, 0.12, 0.16, 0.95));
61 let border = Color::rgba(0.35, 0.35, 0.45, 1.0);
62 let radius = self.corner_radius.unwrap_or(4.0);
63 painter.fill_rect(area, bg, radius);
64 painter.stroke_rect(area, border, 1.0, radius);
65
66 let style = TextStyle {
67 font_size: self.font_size.unwrap_or(12.0),
68 color: self.fg_color.unwrap_or(Color::rgba(0.9, 0.9, 0.9, 1.0)),
69 ..TextStyle::default()
70 };
71 let padding = 6.0;
72 painter.text(
73 Position::new(area.x + padding, area.y + padding),
74 &self.text,
75 &style,
76 );
77 }
78
79 fn ui_node(&self) -> UiNode {
80 UiNode::new("Tooltip", SemanticRole::Display).with_id(&self.id)
81 }
82}
83
84impl Discoverable for Tooltip {
85 fn schema(&self) -> WidgetSchema {
86 WidgetSchema::new(
87 "Tooltip",
88 "A tooltip popup showing help text",
89 SemanticRole::Display,
90 )
91 }
92
93 fn capabilities(&self) -> Vec<AgentCapability> {
94 vec![AgentCapability::HasTooltip]
95 }
96
97 fn actions(&self) -> Vec<AgentAction> {
98 vec![AgentAction::simple(
99 "set_text",
100 "Set tooltip text content",
101 true,
102 )]
103 }
104
105 fn semantic_role(&self) -> SemanticRole {
106 SemanticRole::Display
107 }
108
109 fn agent_state(&self) -> serde_json::Value {
110 serde_json::json!({ "text": self.text })
111 }
112
113 fn execute_action(
114 &mut self,
115 action: &str,
116 params: &serde_json::Value,
117 ) -> Result<serde_json::Value, String> {
118 match action {
119 "set_text" => {
120 if let Some(text) = params.get("text").and_then(|v| v.as_str()) {
121 self.text = text.to_string();
122 Ok(serde_json::json!({ "text": self.text }))
123 } else {
124 Err("Missing 'text' parameter".into())
125 }
126 }
127 _ => Err(format!("Unknown action: {action}")),
128 }
129 }
130
131 fn agent_id(&self) -> Option<&str> {
132 Some(&self.id)
133 }
134}