Skip to main content

agpu/ontology/
capability.rs

1//! Widget capabilities that advertise interaction affordances.
2
3use serde::{Deserialize, Serialize};
4
5/// A capability that a widget instance advertises.
6///
7/// Agents query capabilities to understand what interactions are possible
8/// with a given widget without trial-and-error.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub enum AgentCapability {
11    /// Widget can receive keyboard focus.
12    Focusable,
13    /// Widget responds to mouse clicks.
14    Clickable,
15    /// Widget can be scrolled (vertical, horizontal, or both).
16    Scrollable { vertical: bool, horizontal: bool },
17    /// Widget accepts text input.
18    TextInput {
19        multiline: bool,
20        max_length: Option<usize>,
21    },
22    /// Widget supports item selection (single or multi).
23    Selectable {
24        multi_select: bool,
25        item_count: usize,
26    },
27    /// Widget can be expanded or collapsed.
28    Expandable { expanded: bool },
29    /// Widget supports drag-and-drop as a source.
30    Draggable,
31    /// Widget acts as a drop target.
32    DropTarget,
33    /// Widget supports resizing by the user.
34    Resizable {
35        min_width: Option<f32>,
36        min_height: Option<f32>,
37        max_width: Option<f32>,
38        max_height: Option<f32>,
39    },
40    /// Widget has an animation in progress.
41    Animated { playing: bool },
42    /// Widget supports value editing within a range.
43    RangeEditable {
44        min: f64,
45        max: f64,
46        step: Option<f64>,
47    },
48    /// Widget can be toggled on/off.
49    Toggleable { state: bool },
50    /// Widget supports sorting.
51    Sortable { columns: Vec<String> },
52    /// Widget supports filtering.
53    Filterable,
54    /// Widget supports search.
55    Searchable,
56    /// Widget supports copy-to-clipboard.
57    Copyable,
58    /// Widget provides hover tooltips.
59    HasTooltip,
60    /// Widget supports keyboard shortcuts.
61    HasKeyBindings { bindings: Vec<(String, String)> },
62    /// Widget can be minimized.
63    Minimizable,
64    /// Widget can be maximized.
65    Maximizable,
66    /// Widget can be closed.
67    Closable,
68    /// Widget supports pinch-to-zoom.
69    Zoomable { min_zoom: f32, max_zoom: f32 },
70    /// Custom capability with a name tag.
71    Custom(String),
72}
73
74impl AgentCapability {
75    /// Returns the canonical name of this capability for querying.
76    pub fn name(&self) -> &str {
77        match self {
78            Self::Focusable => "focusable",
79            Self::Clickable => "clickable",
80            Self::Scrollable { .. } => "scrollable",
81            Self::TextInput { .. } => "text-input",
82            Self::Selectable { .. } => "selectable",
83            Self::Expandable { .. } => "expandable",
84            Self::Draggable => "draggable",
85            Self::DropTarget => "drop-target",
86            Self::Resizable { .. } => "resizable",
87            Self::Animated { .. } => "animated",
88            Self::RangeEditable { .. } => "range-editable",
89            Self::Toggleable { .. } => "toggleable",
90            Self::Sortable { .. } => "sortable",
91            Self::Filterable => "filterable",
92            Self::Searchable => "searchable",
93            Self::Copyable => "copyable",
94            Self::HasTooltip => "has-tooltip",
95            Self::HasKeyBindings { .. } => "has-key-bindings",
96            Self::Minimizable => "minimizable",
97            Self::Maximizable => "maximizable",
98            Self::Closable => "closable",
99            Self::Zoomable { .. } => "zoomable",
100            Self::Custom(name) => name.as_str(),
101        }
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn capability_names() {
111        assert_eq!(AgentCapability::Focusable.name(), "focusable");
112        assert_eq!(AgentCapability::Clickable.name(), "clickable");
113        assert_eq!(
114            AgentCapability::Scrollable {
115                vertical: true,
116                horizontal: false
117            }
118            .name(),
119            "scrollable"
120        );
121        assert_eq!(
122            AgentCapability::TextInput {
123                multiline: false,
124                max_length: None
125            }
126            .name(),
127            "text-input"
128        );
129        assert_eq!(AgentCapability::Draggable.name(), "draggable");
130        assert_eq!(AgentCapability::DropTarget.name(), "drop-target");
131        assert_eq!(AgentCapability::Custom("my-cap".into()).name(), "my-cap");
132    }
133
134    #[test]
135    fn capability_serialize_roundtrip() {
136        let caps = vec![
137            AgentCapability::Focusable,
138            AgentCapability::Clickable,
139            AgentCapability::Toggleable { state: true },
140            AgentCapability::RangeEditable {
141                min: 0.0,
142                max: 100.0,
143                step: Some(1.0),
144            },
145            AgentCapability::Custom("special".into()),
146        ];
147        for cap in caps {
148            let json = serde_json::to_string(&cap).unwrap();
149            let cap2: AgentCapability = serde_json::from_str(&json).unwrap();
150            assert_eq!(cap, cap2);
151        }
152    }
153}