ferroui_controls/automation/peers/
control_automation_peer.rs1use super::{AutomationControlType, AutomationLandmarkType, AutomationPeer, AutomationPeerImpl};
2use crate::automation::{
3 AccessibilityView, AutomationElementIdentifiers, AutomationLiveSetting, AutomationProperties, IsOffscreenBehavior,
4};
5use crate::{Control, DataValidationErrors, ToolTip};
6use ferroui_base::data::core::ValueTypes;
7use ferroui_base::utilities::MathUtilities;
8use ferroui_base::{
9 ferro_class, instantiate, AnyValue, BoxedValue, FerroObjectImpl, FerroObjectImplExt,
10 FerroPropertyChangedEventArgs, PixelRect, Rect, Ref, Visual, WeakRef,
11};
12use std::cell::{Cell, RefCell};
13use std::rc::Rc;
14
15#[repr(C)]
20pub struct ControlAutomationPeer {
21 base: AutomationPeer,
22 owner: WeakRef<Control>,
23 children: RefCell<Option<Rc<Vec<Ref<AutomationPeer>>>>>,
24 children_valid: Cell<bool>,
25 parent: RefCell<Option<WeakRef<AutomationPeer>>>,
26 parent_valid: Cell<bool>,
27}
28
29ferro_class! {
30 ControlAutomationPeer: AutomationPeer, virtuals ControlAutomationPeerImpl: AutomationPeerImpl {
31 fn get_children_core(this) -> Option<Vec<Ref<AutomationPeer>>>;
35 fn get_visual_parent(this) -> Option<Ref<Visual>>;
38 }
39}
40ferroui_base::ferro_class_info!(ControlAutomationPeer {});
41
42impl FerroObjectImpl for ControlAutomationPeer {
43 fn constructed(this: &Self) {
44 Self::parent_constructed(this);
45 this.initialize();
46 }
47}
48
49impl ControlAutomationPeerImpl for ControlAutomationPeer {
50 fn get_children_core(this: &Self) -> Option<Vec<Ref<AutomationPeer>>> {
51 let owner = this.owner();
52 let children = owner.visual_children_snapshot()?;
53
54 if children.is_empty() {
55 return None;
56 }
57
58 let mut result = Vec::new();
59
60 for child in children.iter() {
61 if let Some(c) = child.cast::<Control>() {
62 let peer = this.get_or_create(&c);
63 if c.is_visible() {
64 result.push(peer);
65 }
66 }
67 }
68
69 Some(result)
70 }
71
72 fn get_visual_parent(this: &Self) -> Option<Ref<Visual>> {
73 this.try_owner()?.get_visual_parent()
76 }
77}
78
79impl AutomationPeerImpl for ControlAutomationPeer {
80 fn is_element_available_core(this: &Self) -> bool {
81 this.try_owner().is_some()
82 }
83
84 fn bring_into_view_core(this: &Self) {
85 this.owner().bring_into_view();
86 }
87
88 fn get_or_create_children_core(this: &Self) -> Rc<Vec<Ref<AutomationPeer>>> {
89 let children = this.children.borrow().clone().unwrap_or_default();
90
91 if this.children_valid.get() {
92 return children;
93 }
94
95 let new_children = this.get_children_core().unwrap_or_default();
96
97 for peer in children.iter().filter(|peer| !new_children.contains(peer)) {
98 peer.try_set_parent(None);
99 }
100
101 let this_peer: Ref<AutomationPeer> = this.to_ref().upcast();
102 for peer in &new_children {
103 peer.try_set_parent(Some(this_peer.clone()));
104 }
105
106 this.children_valid.set(true);
107 let new_children = Rc::new(new_children);
108 *this.children.borrow_mut() = Some(new_children.clone());
109 new_children
110 }
111
112 fn get_labeled_by_core(this: &Self) -> Option<Ref<AutomationPeer>> {
113 let label = AutomationProperties::get_labeled_by(&this.owner());
114 label.map(|c| this.get_or_create(&c))
115 }
116
117 fn get_name_core(this: &Self) -> Option<String> {
118 let mut result = AutomationProperties::get_name(&this.owner());
119
120 if is_null_or_white_space(&result) {
121 if let Some(labeled_by) = this.get_labeled_by() {
122 result = Some(labeled_by.get_name());
123 }
124 }
125
126 result
127 }
128
129 fn get_help_text_core(this: &Self) -> Option<String> {
130 let owner = this.owner();
131 let mut result = AutomationProperties::get_help_text(&owner);
132
133 if is_null_or_white_space(&result) {
134 let errors = DataValidationErrors::get_errors(&owner);
135 result = errors.map(|errors| {
136 let errors_string_list: Vec<String> =
137 errors.iter().map(|x| ValueTypes::to_display_string(Some(x))).collect();
138 errors_string_list.join(NEW_LINE)
139 });
140 }
141
142 if is_null_or_white_space(&result) {
143 result = ToolTip::get_tip(&owner).and_then(|tip| {
144 let tip: &dyn AnyValue = &*tip;
145 tip.downcast_ref::<String>().cloned()
146 });
147 }
148
149 if is_null_or_white_space(&result) {
151 result = this.get_placeholder_text_core();
152 }
153
154 result
155 }
156
157 fn get_landmark_type_core(this: &Self) -> Option<AutomationLandmarkType> {
158 AutomationProperties::get_landmark_type(&this.owner())
159 }
160
161 fn get_heading_level_core(this: &Self) -> i32 {
162 AutomationProperties::get_heading_level(&this.owner())
163 }
164
165 fn get_parent_core(this: &Self) -> Option<Ref<AutomationPeer>> {
166 this.ensure_connected();
167 this.parent.borrow().as_ref().and_then(|parent| parent.upgrade())
168 }
169
170 fn get_visual_root_core(this: &Self) -> Option<Ref<AutomationPeer>> {
171 let owner = this.try_owner()?;
172 let focus_root = owner.presentation_source()?.input_root().focus_root();
173 let c = focus_root.cast::<Control>()?;
174 Some(Self::create_peer_for_element(&c))
175 }
176
177 fn to_screen_core(this: &Self, rect: Rect) -> Option<Rect> {
178 let root = this.try_owner()?.presentation_source()?.root_visual()?;
179 Some(
180 PixelRect::from_points(root.point_to_screen(rect.top_left()), root.point_to_screen(rect.bottom_right()))
181 .to_rect(1.0),
182 )
183 }
184
185 fn show_context_menu_core(this: &Self) -> bool {
186 let mut c = Some(this.owner());
187
188 while let Some(control) = c {
189 if let Some(context_menu) = control.context_menu() {
190 context_menu.open_at(Some(&control));
191 return true;
192 }
193
194 c = control.parent().and_then(|parent| parent.cast::<Control>());
195 }
196
197 false
198 }
199
200 fn get_live_setting_core(this: &Self) -> AutomationLiveSetting {
201 AutomationProperties::get_live_setting(&this.owner())
202 }
203
204 fn try_set_parent(this: &Self, parent: Option<Ref<AutomationPeer>>) -> bool {
205 *this.parent.borrow_mut() = parent.map(|parent| parent.downgrade());
206 true
207 }
208
209 fn get_accelerator_key_core(this: &Self) -> Option<String> {
210 AutomationProperties::get_accelerator_key(&this.owner())
211 }
212
213 fn get_access_key_core(this: &Self) -> Option<String> {
214 AutomationProperties::get_access_key(&this.owner())
215 }
216
217 fn get_automation_control_type_core(_this: &Self) -> AutomationControlType {
218 AutomationControlType::Custom
219 }
220
221 fn get_automation_id_core(this: &Self) -> Option<String> {
222 let owner = this.owner();
223 AutomationProperties::get_automation_id(&owner).or_else(|| owner.name())
224 }
225
226 fn get_bounding_rectangle_core(this: &Self) -> Rect {
227 Self::get_bounds(&this.owner())
228 }
229
230 fn get_class_name_core(this: &Self) -> String {
231 this.owner().get_type().name().to_owned()
232 }
233
234 fn get_item_status_core(this: &Self) -> Option<String> {
235 AutomationProperties::get_item_status(&this.owner())
236 }
237
238 fn get_item_type_core(this: &Self) -> Option<String> {
239 AutomationProperties::get_item_type(&this.owner())
240 }
241
242 fn has_keyboard_focus_core(this: &Self) -> bool {
243 this.owner().is_focused()
244 }
245
246 fn is_content_element_core(_this: &Self) -> bool {
247 true
248 }
249
250 fn is_control_element_core(_this: &Self) -> bool {
251 true
252 }
253
254 fn is_enabled_core(this: &Self) -> bool {
255 this.owner().is_effectively_enabled()
256 }
257
258 fn is_keyboard_focusable_core(this: &Self) -> bool {
259 this.owner().focusable()
260 }
261
262 fn set_focus_core(this: &Self) {
263 this.owner().focus();
264 }
265
266 fn get_control_type_override_core(this: &Self) -> AutomationControlType {
267 AutomationProperties::get_control_type_override(&this.owner())
268 .unwrap_or_else(|| this.get_automation_control_type_core())
269 }
270
271 fn get_class_name_override_core(this: &Self) -> String {
272 AutomationProperties::get_class_name_override(&this.owner()).unwrap_or_else(|| this.get_class_name_core())
273 }
274
275 fn is_content_element_override_core(this: &Self) -> bool {
276 let view = AutomationProperties::get_accessibility_view(&this.owner());
277 if view == AccessibilityView::Default {
278 this.is_content_element_core()
279 } else {
280 view as i32 >= AccessibilityView::Content as i32
281 }
282 }
283
284 fn is_control_element_override_core(this: &Self) -> bool {
285 let owner = this.owner();
286 if let Some(is_control_element) = AutomationProperties::get_is_control_element_override(&owner) {
287 return is_control_element;
288 }
289
290 let view = AutomationProperties::get_accessibility_view(&owner);
291 if view == AccessibilityView::Default {
292 this.is_control_element_core()
293 } else {
294 view as i32 >= AccessibilityView::Control as i32
295 }
296 }
297
298 fn is_offscreen_core(this: &Self) -> bool {
299 let owner = this.owner();
300 match AutomationProperties::get_is_offscreen_behavior(&owner) {
301 IsOffscreenBehavior::Onscreen => false,
302 IsOffscreenBehavior::Offscreen => true,
303 IsOffscreenBehavior::FromClip => match owner.get_transformed_bounds() {
304 Some(bounds) => MathUtilities::is_zero(bounds.clip.width) || MathUtilities::is_zero(bounds.clip.height),
305 None => true,
306 },
307 IsOffscreenBehavior::Default => {
308 !owner.is_effectively_visible()
309 || !owner.visual_root().map(|root| root.is_effectively_visible()).unwrap_or(false)
310 }
311 }
312 }
313}
314
315const NEW_LINE: &str = if cfg!(windows) { "\r\n" } else { "\n" };
317
318fn is_null_or_white_space(value: &Option<String>) -> bool {
319 value.as_deref().is_none_or(|value| value.chars().all(char::is_whitespace))
320}
321
322impl ControlAutomationPeer {
323 pub fn construct(owner: &Control) -> Self {
326 Self {
327 base: AutomationPeer::construct(),
328 owner: owner.to_ref().downgrade(),
329 children: RefCell::new(None),
330 children_valid: Cell::new(false),
331 parent: RefCell::new(None),
332 parent_valid: Cell::new(false),
333 }
334 }
335
336 pub fn new(owner: &Control) -> Ref<Self> {
338 instantiate(Self::construct(owner))
339 }
340
341 pub fn owner(&self) -> Ref<Control> {
352 self.owner.upgrade().expect("The control of the automation peer no longer exists.")
353 }
354
355 pub fn try_owner(&self) -> Option<Ref<Control>> {
357 self.owner.upgrade()
358 }
359
360 pub fn get_or_create(&self, element: &Control) -> Ref<AutomationPeer> {
363 if self.owner.upgrade().is_some_and(|owner| std::ptr::eq::<Control>(&*owner, element)) {
364 return self.to_ref().upcast();
365 }
366 Self::create_peer_for_element(element)
367 }
368
369 pub fn create_peer_for_element(element: &Control) -> Ref<AutomationPeer> {
376 element.get_or_create_automation_peer()
377 }
378
379 pub fn from_element(element: &Control) -> Option<Ref<AutomationPeer>> {
382 element.get_automation_peer()
383 }
384
385 pub fn invalidate_children(&self) {
388 self.children_valid.set(false);
389 self.raise_children_changed_event();
390 }
391
392 pub fn invalidate_parent(&self) {
394 *self.parent.borrow_mut() = None;
395 self.parent_valid.set(false);
396 }
397
398 fn get_bounds(control: &Control) -> Rect {
399 let Some(root_visual) = control.visual_root() else {
400 return Rect::default();
401 };
402
403 let Some(transform) = control.transform_to_visual(&root_visual) else {
404 return Rect::default();
405 };
406
407 Rect::from_size(control.bounds().size()).transform_to_aabb(transform)
408 }
409
410 fn initialize(&self) {
411 let Some(owner) = self.try_owner() else { return };
412
413 let weak = self.to_ref().downgrade();
416 owner.property_changed(move |e| {
417 if let Some(this) = weak.upgrade() {
418 this.owner_property_changed(e);
419 }
420 });
421
422 let weak = self.to_ref().downgrade();
423 owner.visual_children().add_collection_changed(Rc::new(move |_| {
424 if let Some(this) = weak.upgrade() {
425 this.invalidate_children();
426 }
427 }));
428 }
429
430 fn owner_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
431 let property = e.property();
432
433 if property == Visual::is_visible_property().as_property() {
434 let parent = self.get_visual_parent();
435 if let Some(c) = parent.and_then(|parent| parent.cast::<Control>()) {
436 if let Some(peer) = self.get_or_create(&c).cast::<ControlAutomationPeer>() {
437 peer.invalidate_children();
438 }
439 }
440 } else if property == Visual::bounds_property().as_property()
441 || property == Visual::render_transform_property().as_property()
442 || property == Visual::render_transform_origin_property().as_property()
443 {
444 let Some(owner) = self.try_owner() else { return };
445 self.raise_property_changed_event(
446 AutomationElementIdentifiers::bounding_rectangle_property(),
447 None,
448 Some(Rc::new(Self::get_bounds(&owner)) as BoxedValue),
449 );
450 } else if property == Visual::visual_parent_property().as_property() {
451 self.invalidate_parent();
452 } else if property == AutomationProperties::item_status_property().as_property() {
453 let (old_value, new_value) = e.get_old_and_new_value::<Option<String>>();
454 self.raise_property_changed_event(
455 AutomationElementIdentifiers::item_status_property(),
456 old_value.map(|v| Rc::new(v) as BoxedValue),
457 new_value.map(|v| Rc::new(v) as BoxedValue),
458 );
459 } else if property == AutomationProperties::automation_id_property().as_property() {
460 self.raise_property_changed_event(
461 AutomationElementIdentifiers::automation_id_property(),
462 None,
463 self.get_automation_id().map(|v| Rc::new(v) as BoxedValue),
464 );
465 }
466 }
467
468 fn ensure_connected(&self) {
469 if !self.parent_valid.get() {
470 let mut parent = self.get_visual_parent();
471
472 while let Some(p) = parent {
473 if let Some(c) = p.cast::<Control>() {
474 let parent_peer = self.get_or_create(&c);
475 parent_peer.get_children();
476
477 if let Some(control_peer) = parent_peer.cast::<ControlAutomationPeer>() {
478 parent = control_peer.get_visual_parent();
479 continue;
480 }
481 }
482
483 parent = p.get_visual_parent();
484 }
485
486 self.parent_valid.set(true);
487 }
488 }
489}