use serde::{Deserialize, Serialize};
use crate::tree::AXTree;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AXSnapshot {
pub label: String,
pub timestamp_ms: u64,
pub url: String,
pub document_title: String,
pub tree: AXTree,
pub focus: FocusSnapshot,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FocusSnapshot {
pub active_backend_node_id: Option<i64>,
pub ax_node_id: Option<String>,
pub selector: Option<String>,
pub visible: bool,
pub in_viewport: bool,
pub focus_indicator: Option<FocusIndicatorStatus>,
pub bounding_box: Option<Rect>,
pub obscured_by: Option<String>,
#[serde(default)]
pub aria_hidden_chain: bool,
#[serde(default)]
pub inert_chain: bool,
#[serde(default)]
pub hidden_by_style: bool,
#[serde(default)]
pub media_control_missing_name: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FocusIndicatorStatus {
Detected,
NotDetected,
Ambiguous,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct Rect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
impl Rect {
pub fn intersects_viewport(&self, viewport_width: f32, viewport_height: f32) -> bool {
self.x < viewport_width
&& self.y < viewport_height
&& self.x + self.width > 0.0
&& self.y + self.height > 0.0
}
}
impl AXSnapshot {
pub fn new(
label: impl Into<String>,
url: impl Into<String>,
document_title: impl Into<String>,
timestamp_ms: u64,
tree: AXTree,
focus: FocusSnapshot,
) -> Self {
Self {
label: label.into(),
timestamp_ms,
url: url.into(),
document_title: document_title.into(),
tree,
focus,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rect_intersects_viewport() {
let r = Rect {
x: 10.0,
y: 10.0,
width: 100.0,
height: 50.0,
};
assert!(r.intersects_viewport(800.0, 600.0));
let off = Rect {
x: 900.0,
y: 0.0,
width: 50.0,
height: 50.0,
};
assert!(!off.intersects_viewport(800.0, 600.0));
}
#[test]
fn snapshot_constructs_with_defaults() {
let snap = AXSnapshot::new(
"initial",
"https://example.com",
"Example",
0,
AXTree::new(),
FocusSnapshot::default(),
);
assert_eq!(snap.label, "initial");
assert!(snap.focus.focus_indicator.is_none());
}
}