use crate::uia::{
element::UiAutomationElement,
pattern::{PatternCreator, PatternError},
};
use windows::{
core::BSTR,
Win32::{
Foundation::POINT,
UI::Accessibility::{
IUIAutomationTextPattern, IUIAutomationTextPattern2, IUIAutomationTextRange,
IUIAutomationTextRangeArray, TextPatternRangeEndpoint_End,
TextPatternRangeEndpoint_Start, TextUnit_Character, TextUnit_Document, TextUnit_Format,
TextUnit_Line, TextUnit_Page, TextUnit_Paragraph, TextUnit_Word, UIA_TextPattern2Id,
UIA_TextPatternId, UIA_PATTERN_ID,
},
},
};
use windows_core::Interface;
pub struct UiAutomationTextPattern(IUIAutomationTextPattern);
impl TryFrom<IUIAutomationTextPattern> for UiAutomationTextPattern {
type Error = PatternError;
fn try_from(value: IUIAutomationTextPattern) -> Result<Self, Self::Error> {
Ok(Self(value))
}
}
impl TryFrom<IUIAutomationTextPattern2> for UiAutomationTextPattern {
type Error = PatternError;
fn try_from(value: IUIAutomationTextPattern2) -> Result<Self, Self::Error> {
Ok(Self(value.cast().map_err(|e| -> PatternError {
format!("Can't convert type. ({})", e).into()
})?))
}
}
impl PatternCreator<IUIAutomationTextPattern> for UiAutomationTextPattern {
const PATTERN: UIA_PATTERN_ID = UIA_TextPatternId;
}
impl UiAutomationTextPattern {
pub fn document_range(&self) -> UiAutomationTextRange {
unsafe { UiAutomationTextRange::obtain(&self.0.DocumentRange().unwrap()) }
}
pub fn get_selection(&self) -> Vec<UiAutomationTextRange> {
if let Ok(array) = unsafe { self.0.GetSelection() } {
return array.to_vec();
}
vec![]
}
pub fn supported_text_selection(&self) -> SupportedTextSelection {
match unsafe { self.0.SupportedTextSelection() }.unwrap().0 {
1 => SupportedTextSelection::Single,
2 => SupportedTextSelection::Multiple,
_ => SupportedTextSelection::None,
}
}
pub fn get_visible_ranges(&self) -> Vec<UiAutomationTextRange> {
unsafe {
if let Ok(array) = self.0.GetVisibleRanges() {
return array.to_vec();
}
}
vec![]
}
pub fn range_from_child(&self, child: &UiAutomationElement) -> Option<UiAutomationTextRange> {
if let Ok(c) = unsafe { self.0.RangeFromChild(child.get_raw()) } {
return Some(UiAutomationTextRange::obtain(&c));
}
None
}
pub fn range_from_point(&self, x: i32, y: i32) -> Option<UiAutomationTextRange> {
if let Ok(x) = unsafe { self.0.RangeFromPoint(POINT { x, y }) } {
return Some(UiAutomationTextRange::obtain(&x));
}
None
}
}
pub struct UiAutomationTextPattern2(IUIAutomationTextPattern2, UiAutomationTextPattern);
impl TryFrom<IUIAutomationTextPattern2> for UiAutomationTextPattern2 {
type Error = PatternError;
fn try_from(value: IUIAutomationTextPattern2) -> Result<Self, Self::Error> {
let p = value.clone().try_into()?;
Ok(Self(value, p))
}
}
impl PatternCreator<IUIAutomationTextPattern2> for UiAutomationTextPattern2 {
const PATTERN: UIA_PATTERN_ID = UIA_TextPattern2Id;
}
impl UiAutomationTextPattern2 {
pub fn get_caret_range(&self) -> Option<(bool, UiAutomationTextRange)> {
unsafe {
let mut active = std::mem::zeroed();
if let Ok(range) = self.0.GetCaretRange(&mut active) {
return Some((active.as_bool(), UiAutomationTextRange::obtain(&range)));
}
None
}
}
pub fn range_from_annotation(&self, annotation: &UiAutomationElement) -> UiAutomationTextRange {
let range = unsafe { self.0.RangeFromAnnotation(annotation.get_raw()).unwrap() };
UiAutomationTextRange::obtain(&range)
}
}
unsafe impl Send for UiAutomationTextPattern2 {}
unsafe impl Sync for UiAutomationTextPattern2 {}
impl std::ops::Deref for UiAutomationTextPattern2 {
type Target = UiAutomationTextPattern;
fn deref(&self) -> &Self::Target {
&self.1
}
}
trait TextRangeArray {
fn to_vec(&self) -> Vec<UiAutomationTextRange>;
}
impl TextRangeArray for IUIAutomationTextRangeArray {
fn to_vec(&self) -> Vec<UiAutomationTextRange> {
let mut v = vec![];
unsafe {
for i in 0..self.Length().unwrap() {
if let Ok(item) = self.GetElement(i) {
v.push(UiAutomationTextRange::obtain(&item));
}
}
}
v
}
}
#[derive(Clone, Debug)]
pub struct UiAutomationTextRange(IUIAutomationTextRange);
impl UiAutomationTextRange {
pub(crate) fn obtain(range: &IUIAutomationTextRange) -> Self {
Self(range.clone())
}
pub fn compare(&self, range: &UiAutomationTextRange) -> bool {
unsafe { self.0.Compare(&range.0) }
.unwrap_or(false.into())
.as_bool()
}
pub fn add_to_selection(&self) {
unsafe { self.0.AddToSelection() }.unwrap_or(())
}
pub fn compare_endpoints(
&self,
src_start_endpoint: bool,
range: &UiAutomationTextRange,
target_start_endpoint: bool,
) -> i32 {
let src_endpoint = if src_start_endpoint {
TextPatternRangeEndpoint_Start
} else {
TextPatternRangeEndpoint_End
};
let target_endpoint = if target_start_endpoint {
TextPatternRangeEndpoint_Start
} else {
TextPatternRangeEndpoint_End
};
unsafe {
self.0
.CompareEndpoints(src_endpoint, &range.0, target_endpoint)
}
.unwrap_or(0)
}
pub fn expand_to_enclosing_unit(&self, text_unit: TextUnit) {
let unit = match text_unit {
TextUnit::Character => TextUnit_Character,
TextUnit::Format => TextUnit_Format,
TextUnit::Word => TextUnit_Word,
TextUnit::Line => TextUnit_Line,
TextUnit::Paragraph => TextUnit_Paragraph,
TextUnit::Page => TextUnit_Page,
TextUnit::Document => TextUnit_Document,
};
unsafe { self.0.ExpandToEnclosingUnit(unit) }.unwrap_or(())
}
pub fn get_text(&self, max_length: i32) -> String {
unsafe { self.0.GetText(max_length) }
.unwrap_or(BSTR::new())
.to_string()
}
}
unsafe impl Sync for UiAutomationTextRange {}
unsafe impl Send for UiAutomationTextRange {}
pub enum TextUnit {
Character,
Format,
Word,
Line,
Paragraph,
Page,
Document,
}
pub enum SupportedTextSelection {
None,
Single,
Multiple,
}