accesskit_atspi_common/
util.rs
1use accesskit::{Point, Rect};
7use accesskit_consumer::{Node, TextPosition, TextRange};
8use atspi_common::{CoordType, Granularity};
9
10use crate::Error;
11
12#[derive(Clone, Copy, Default)]
13pub struct WindowBounds {
14 pub outer: Rect,
15 pub inner: Rect,
16}
17
18impl WindowBounds {
19 pub fn new(outer: Rect, inner: Rect) -> Self {
20 Self { outer, inner }
21 }
22
23 pub(crate) fn accesskit_point_to_atspi_point(
24 &self,
25 point: Point,
26 parent: Option<Node>,
27 coord_type: CoordType,
28 ) -> Point {
29 let origin = self.origin(parent, coord_type);
30 Point::new(origin.x + point.x, origin.y + point.y)
31 }
32
33 pub(crate) fn atspi_point_to_accesskit_point(
34 &self,
35 point: Point,
36 parent: Option<Node>,
37 coord_type: CoordType,
38 ) -> Point {
39 let origin = self.origin(parent, coord_type);
40 Point::new(point.x - origin.x, point.y - origin.y)
41 }
42
43 fn origin(&self, parent: Option<Node>, coord_type: CoordType) -> Point {
44 match coord_type {
45 CoordType::Screen => self.inner.origin(),
46 CoordType::Window => Point::ZERO,
47 CoordType::Parent => {
48 if let Some(parent) = parent {
49 let parent_origin = parent.bounding_box().unwrap_or_default().origin();
50 Point::new(-parent_origin.x, -parent_origin.y)
51 } else {
52 self.inner.origin()
53 }
54 }
55 }
56 }
57}
58
59pub(crate) fn text_position_from_offset<'a>(
60 node: &'a Node,
61 offset: i32,
62) -> Option<TextPosition<'a>> {
63 let index = offset.try_into().ok()?;
64 node.text_position_from_global_usv_index(index)
65}
66
67pub(crate) fn text_range_from_offset<'a>(
68 node: &'a Node,
69 offset: i32,
70 granularity: Granularity,
71) -> Result<TextRange<'a>, Error> {
72 let start_offset = text_position_from_offset(node, offset).ok_or(Error::IndexOutOfRange)?;
73 let start = match granularity {
74 Granularity::Char => start_offset,
75 Granularity::Line if start_offset.is_line_start() => start_offset,
76 Granularity::Line => start_offset.backward_to_line_start(),
77 Granularity::Paragraph if start_offset.is_paragraph_start() => start_offset,
78 Granularity::Paragraph => start_offset.backward_to_paragraph_start(),
79 Granularity::Sentence => return Err(Error::UnsupportedTextGranularity),
80 Granularity::Word if start_offset.is_word_start() => start_offset,
81 Granularity::Word => start_offset.backward_to_word_start(),
82 };
83 let end = match granularity {
84 Granularity::Char if start_offset.is_document_end() => start_offset,
85 Granularity::Char => start.forward_to_character_end(),
86 Granularity::Line => start.forward_to_line_end(),
87 Granularity::Paragraph => start.forward_to_paragraph_end(),
88 Granularity::Sentence => return Err(Error::UnsupportedTextGranularity),
89 Granularity::Word => start.forward_to_word_end(),
90 };
91 let mut range = start.to_degenerate_range();
92 range.set_end(end);
93 Ok(range)
94}
95
96pub(crate) fn text_range_from_offsets<'a>(
97 node: &'a Node,
98 start_offset: i32,
99 end_offset: i32,
100) -> Option<TextRange<'a>> {
101 let start = text_position_from_offset(node, start_offset)?;
102 let end = if end_offset == -1 {
103 node.document_range().end()
104 } else {
105 text_position_from_offset(node, end_offset)?
106 };
107
108 let mut range = start.to_degenerate_range();
109 range.set_end(end);
110 Some(range)
111}
112
113pub(crate) fn text_range_bounds_from_offsets(
114 node: &Node,
115 start_offset: i32,
116 end_offset: i32,
117) -> Option<Rect> {
118 text_range_from_offsets(node, start_offset, end_offset)?
119 .bounding_boxes()
120 .into_iter()
121 .reduce(|rect1, rect2| rect1.union(rect2))
122}