Skip to main content

ferroui_controls/automation/peers/
control_automation_peer.rs

1use 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/// An automation peer which represents a [`Control`].
16///
17/// The peer does not keep its control alive (the control owns the peer); see
18/// [`owner`](Self::owner).
19#[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        /// Gets the child automation peers of the peer, or `None` if it has
32        /// none. The default implementation returns the peers of the visible
33        /// visual children of the owner.
34        fn get_children_core(this) -> Option<Vec<Ref<AutomationPeer>>>;
35        /// Gets the visual the peer of which is the parent of this peer
36        /// (not for use outside the framework).
37        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        // Deviation (DEVIATIONS.md, Automation peers): the peer holds its control weakly, and a
74        // peer whose control is gone has no visual parent (upstream's peer holds the control).
75        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        // Windows uses HelpText for placeholder text; macOS uses a separate property.
150        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
315/// The line separator of the platform.
316const 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    /// Creates the class data of a peer of `owner`; see
324    /// [`ferroui_base::FerroObject::construct`].
325    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    /// Initializes a new peer of `owner`.
337    pub fn new(owner: &Control) -> Ref<Self> {
338        instantiate(Self::construct(owner))
339    }
340
341    /// Gets the owning control.
342    ///
343    /// A control owns its peer, so the peer holds the control weakly.
344    ///
345    /// # Panics
346    ///
347    /// Panics if the control no longer exists: a peer must not be used
348    /// after its control has been released (code that keeps a peer beyond
349    /// the life of the tree keeps the control with it, or asks
350    /// [`try_owner`](Self::try_owner)).
351    pub fn owner(&self) -> Ref<Control> {
352        self.owner.upgrade().expect("The control of the automation peer no longer exists.")
353    }
354
355    /// Gets the owning control, or `None` if it no longer exists.
356    pub fn try_owner(&self) -> Option<Ref<Control>> {
357        self.owner.upgrade()
358    }
359
360    /// Gets or creates an automation peer for a control: this peer for its
361    /// owner, the peer of `element` otherwise.
362    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    /// Gets an existing automation peer of a control, or creates a new
370    /// one if it does not yet exist.
371    ///
372    /// Despite the name (which comes from the analogous WPF API), this
373    /// method does not create a new peer if one already exists: instead it
374    /// returns the existing peer.
375    pub fn create_peer_for_element(element: &Control) -> Ref<AutomationPeer> {
376        element.get_or_create_automation_peer()
377    }
378
379    /// Gets an existing automation peer of a control, or `None` if it does
380    /// not yet exist.
381    pub fn from_element(element: &Control) -> Option<Ref<AutomationPeer>> {
382        element.get_automation_peer()
383    }
384
385    /// Invalidates the peer's children and causes a re-read from
386    /// [`get_children_core`](Self::get_children_core).
387    pub fn invalidate_children(&self) {
388        self.children_valid.set(false);
389        self.raise_children_changed_event();
390    }
391
392    /// Invalidates the peer's parent.
393    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        // The owner holds the handlers and the peer; the handlers hold the
414        // peer weakly.
415        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}