Skip to main content

ferroui_controls/
native_control_host.rs

1use crate::platform::{
2    INativeControlHostControlTopLevelAttachment, INativeControlHostImpl, IPlatformHandle,
3};
4use crate::presentation_source::PresentationSource;
5use crate::{Control, ControlImpl};
6use ferroui_base::input::InputElementImpl;
7use ferroui_base::interactivity::InteractiveImpl;
8use ferroui_base::layout::{LayoutHelper, LayoutableImpl};
9use ferroui_base::platform::IOptionalFeatureProvider;
10use ferroui_base::reactive::{Disposable, IDisposable};
11use ferroui_base::threading::{Dispatcher, DispatcherPriority};
12use ferroui_base::utilities::HandlerList;
13use ferroui_base::{
14    ferro_class, ferro_class_info, ferro_impl_classes, instantiate, FerroObjectImpl, FerroPropertyChangedEventArgs,
15    Point, Rect, Ref, StyledElementImpl, Visual, VisualImpl, VisualTreeAttachmentEventArgs,
16};
17use std::cell::{Cell, RefCell};
18use std::rc::Rc;
19
20/// Hosts a native control of the platform inside the visual tree: the
21/// native control is created for the top-level the host is attached to and
22/// follows the bounds and the visibility of the host.
23#[repr(C)]
24pub struct NativeControlHost {
25    base: Control,
26    current_root: RefCell<Option<Rc<PresentationSource>>>,
27    current_host: RefCell<Option<Rc<dyn INativeControlHostImpl>>>,
28    attachment: RefCell<Option<Rc<dyn INativeControlHostControlTopLevelAttachment>>>,
29    native_control_handle: RefCell<Option<Rc<dyn IPlatformHandle>>>,
30    queued_for_destruction: Cell<bool>,
31    queued_for_move_resize: Cell<bool>,
32    property_changed_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
33    /// The subscriptions to the changes of the render transforms of the
34    /// host and of its visual ancestors (not upstream: see
35    /// `subscribe_render_transforms`).
36    render_transform_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
37    native_control_handle_changed: HandlerList<dyn Fn()>,
38}
39
40ferro_class! {
41    NativeControlHost: Control, virtuals NativeControlHostImpl: ControlImpl {
42        /// Creates the native control as a child of `parent`. The default
43        /// asks the native control host of the platform for its default
44        /// child.
45        fn create_native_control_core(this, parent: Rc<dyn IPlatformHandle>) -> Rc<dyn IPlatformHandle>;
46        /// Destroys the native control. The default destroys a handle that
47        /// can be destroyed.
48        fn destroy_native_control_core(this, control: Rc<dyn IPlatformHandle>);
49    }
50}
51ferro_class_info!(NativeControlHost { new: NativeControlHost::new });
52
53ferro_impl_classes!(
54    NativeControlHost: FerroObjectImpl,
55    StyledElementImpl,
56    LayoutableImpl,
57    InteractiveImpl,
58    InputElementImpl
59);
60
61impl VisualImpl for NativeControlHost {
62    // As upstream, the base implementation is not called.
63    fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
64        *this.current_root.borrow_mut() = PresentationSource::from_root_visual(e.root_visual());
65        let mut visual = Some(this.to_ref().upcast::<Visual>());
66        while let Some(current) = visual {
67            let weak = this.to_ref().downgrade();
68            let subscription = current.property_changed(move |e| {
69                if let Some(this) = weak.upgrade() {
70                    this.property_changed_handler(e);
71                }
72            });
73            this.property_changed_subscriptions.borrow_mut().push(subscription);
74
75            visual = current.get_visual_parent();
76        }
77        this.subscribe_render_transforms();
78
79        this.update_host();
80    }
81
82    // As upstream, the base implementation is not called.
83    fn on_detached_from_visual_tree(this: &Self, _e: &VisualTreeAttachmentEventArgs) {
84        *this.current_root.borrow_mut() = None;
85        let subscriptions = std::mem::take(&mut *this.property_changed_subscriptions.borrow_mut());
86        for subscription in subscriptions {
87            subscription.dispose();
88        }
89        this.unsubscribe_render_transforms();
90        this.update_host();
91    }
92}
93
94impl NativeControlHostImpl for NativeControlHost {
95    fn create_native_control_core(this: &Self, parent: Rc<dyn IPlatformHandle>) -> Rc<dyn IPlatformHandle> {
96        let Some(current_host) = this.current_host.borrow().clone() else {
97            panic!("Operation is not valid due to the current state of the object.");
98        };
99        current_host.create_default_child(parent)
100    }
101
102    fn destroy_native_control_core(_this: &Self, control: Rc<dyn IPlatformHandle>) {
103        if let Some(native_control_host_destroyable_control_handle) =
104            control.as_native_control_host_destroyable_control_handle()
105        {
106            native_control_host_destroyable_control_handle.destroy();
107        }
108    }
109}
110
111impl ControlImpl for NativeControlHost {
112    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
113        crate::automation::peers::NativeControlHostPeer::new(this).upcast()
114    }
115}
116
117fn same_host(a: Option<&Rc<dyn INativeControlHostImpl>>, b: Option<&Rc<dyn INativeControlHostImpl>>) -> bool {
118    match (a, b) {
119        (Some(a), Some(b)) => std::ptr::addr_eq(Rc::as_ptr(a), Rc::as_ptr(b)),
120        (None, None) => true,
121        _ => false,
122    }
123}
124
125fn same_handle(a: Option<&Rc<dyn IPlatformHandle>>, b: Option<&Rc<dyn IPlatformHandle>>) -> bool {
126    match (a, b) {
127        (Some(a), Some(b)) => std::ptr::addr_eq(Rc::as_ptr(a), Rc::as_ptr(b)),
128        (None, None) => true,
129        _ => false,
130    }
131}
132
133impl NativeControlHost {
134    fn static_constructor() {
135        Visual::flow_direction_property()
136            .changed()
137            .add_class_handler::<NativeControlHost>(Self::on_flow_direction_changed);
138    }
139
140    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
141    pub fn construct() -> Self {
142        Self {
143            base: Control::construct(),
144            current_root: RefCell::new(None),
145            current_host: RefCell::new(None),
146            attachment: RefCell::new(None),
147            native_control_handle: RefCell::new(None),
148            queued_for_destruction: Cell::new(false),
149            queued_for_move_resize: Cell::new(false),
150            property_changed_subscriptions: RefCell::new(Vec::new()),
151            render_transform_subscriptions: RefCell::new(Vec::new()),
152            native_control_handle_changed: HandlerList::new(),
153        }
154    }
155
156    /// Creates a native control host.
157    pub fn new() -> Ref<Self> {
158        instantiate(Self::construct())
159    }
160
161    /// The handle of the native control, once it has been created
162    /// (internal upstream).
163    pub fn native_control_handle(&self) -> Option<Rc<dyn IPlatformHandle>> {
164        self.native_control_handle.borrow().clone()
165    }
166
167    fn set_native_control_handle(&self, value: Option<Rc<dyn IPlatformHandle>>) {
168        let changed = !same_handle(self.native_control_handle.borrow().as_ref(), value.as_ref());
169        if changed {
170            let old = self.native_control_handle.replace(value);
171            drop(old);
172            let handlers = self.native_control_handle_changed.snapshot();
173            for (_, handler) in handlers.iter() {
174                handler();
175            }
176        }
177    }
178
179    /// Raised when the handle of the native control changes (internal
180    /// upstream).
181    pub fn native_control_handle_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
182        let token = self.native_control_handle_changed.add(Rc::new(handler));
183        let weak = self.to_ref().downgrade();
184        Disposable::create(move || {
185            if let Some(this) = weak.upgrade() {
186                this.native_control_handle_changed.remove(token);
187            }
188        })
189    }
190
191    fn on_flow_direction_changed(native_control_host: &NativeControlHost, _e: &FerroPropertyChangedEventArgs<'_>) {
192        native_control_host.try_update_native_control_position();
193    }
194
195    fn property_changed_handler(&self, e: &FerroPropertyChangedEventArgs<'_>) {
196        if !e.is_effective_value_change() {
197            return;
198        }
199        if e.property() == Visual::bounds_property().as_property()
200            || e.property() == Visual::is_visible_property().as_property()
201        {
202            self.enqueue_for_move_resize();
203        } else if e.property() == Visual::render_transform_property().as_property() {
204            // Deviation (DEVIATIONS.md, Native control host): see
205            // `subscribe_render_transforms`.
206            self.subscribe_render_transforms();
207            self.enqueue_for_move_resize();
208        } else if e.property() == Visual::render_transform_origin_property().as_property() {
209            self.enqueue_for_move_resize();
210        }
211    }
212
213    /// Subscribes to the changes of the render transforms of the host and
214    /// of its visual ancestors, in place of the previous subscriptions.
215    ///
216    /// Deviation (DEVIATIONS.md, Native control host): upstream
217    /// (`NativeControlHost.PropertyChangedHandler`) moves the native control
218    /// only when the bounds or the visibility of the host or of an ancestor
219    /// change. The position of the native control is the position of the
220    /// host in the root, which the render transforms are part of, so a
221    /// host whose first arrange falls into a transition that slides its
222    /// page in stayed where the first frame of the transition had it. The
223    /// host is moved when a render transform of the chain is set, replaced
224    /// or removed, and when a mutable one changes in place (an animation);
225    /// a host under no transform that changes is not looked at.
226    fn subscribe_render_transforms(&self) {
227        self.unsubscribe_render_transforms();
228        let mut subscriptions = Vec::new();
229        let mut visual = Some(self.to_ref().upcast::<Visual>());
230        while let Some(current) = visual {
231            let render_transform = current.render_transform();
232            if let Some(mutable_transform) =
233                render_transform.as_ref().and_then(|render_transform| render_transform.as_mutable_transform())
234            {
235                let weak = self.to_ref().downgrade();
236                subscriptions.push(mutable_transform.changed(Rc::new(move || {
237                    if let Some(this) = weak.upgrade() {
238                        this.enqueue_for_move_resize();
239                    }
240                })));
241            }
242
243            visual = current.get_visual_parent();
244        }
245        *self.render_transform_subscriptions.borrow_mut() = subscriptions;
246    }
247
248    fn unsubscribe_render_transforms(&self) {
249        let subscriptions = std::mem::take(&mut *self.render_transform_subscriptions.borrow_mut());
250        for subscription in subscriptions {
251            subscription.dispose();
252        }
253    }
254
255    fn update_host(&self) {
256        self.queued_for_move_resize.set(false);
257        let current_host = self
258            .current_root
259            .borrow()
260            .as_ref()
261            .and_then(|current_root| current_root.platform_impl())
262            .and_then(|platform_impl| {
263                let provider: &dyn IOptionalFeatureProvider = &*platform_impl;
264                provider.try_get::<dyn INativeControlHostImpl>()
265            });
266        *self.current_host.borrow_mut() = current_host.clone();
267
268        if let Some(current_host) = &current_host {
269            // If there is an existing attachment, ensure that we are attached to the proper host or destroy the attachment
270            let attachment = self.attachment.borrow().clone();
271            if let Some(attachment) = attachment {
272                if !same_host(attachment.attached_to().as_ref(), Some(current_host)) {
273                    if attachment.is_compatible_with(&**current_host) {
274                        attachment.set_attached_to(Some(current_host.clone()));
275                    } else {
276                        attachment.dispose();
277                        *self.attachment.borrow_mut() = None;
278                    }
279                }
280            }
281
282            // If there is no attachment, but the control exists,
283            // attempt to attach to the current toplevel or destroy the control if it's incompatible
284            // The borrow of the attachment must end before the branch: both
285            // arms set the attachment.
286            let has_attachment = self.attachment.borrow().is_some();
287            let native_control_handle = self.native_control_handle();
288            if let (false, Some(native_control_handle)) = (has_attachment, native_control_handle) {
289                if current_host.is_compatible_with(&*native_control_handle) {
290                    let attachment = current_host.create_new_attachment(native_control_handle);
291                    *self.attachment.borrow_mut() = Some(attachment);
292                } else {
293                    self.destroy_native_control();
294                }
295            }
296
297            // There is no control handle an no attachment, create both
298            if self.native_control_handle().is_none() {
299                let weak = self.to_ref().downgrade();
300                let attachment = current_host.create_new_attachment_with(Rc::new(move |parent| {
301                    let this = weak.upgrade().expect("the native control host is alive while its control is created");
302                    let handle = this.create_native_control_core(parent);
303                    this.set_native_control_handle(Some(handle.clone()));
304                    handle
305                }));
306                *self.attachment.borrow_mut() = Some(attachment);
307            }
308        } else {
309            // Immediately detach the control from the current toplevel if there is an existing attachment
310            let attachment = self.attachment.borrow().clone();
311            if let Some(attachment) = attachment {
312                attachment.set_attached_to(None);
313            }
314
315            // Don't destroy the control immediately, it might be just being reparented to another TopLevel
316            if self.native_control_handle().is_some() && !self.queued_for_destruction.get() {
317                self.queued_for_destruction.set(true);
318                let this = self.to_ref();
319                Dispatcher::ui_thread().post_local(move || this.check_destruction(), DispatcherPriority::BACKGROUND);
320            }
321        }
322
323        let attached_to = self.attachment.borrow().as_ref().and_then(|attachment| attachment.attached_to());
324        if !same_host(attached_to.as_ref(), current_host.as_ref()) {
325            return;
326        }
327
328        self.try_update_native_control_position();
329    }
330
331    fn get_absolute_bounds(&self) -> Option<Rect> {
332        let current_root = self.current_root.borrow().clone();
333        debug_assert!(current_root.is_some());
334
335        let bounds = self.bounds();
336        // Native window is not rendered by the framework
337        let root_visual = current_root.and_then(|current_root| current_root.root_visual());
338        let transform_to_visual = root_visual.and_then(|root_visual| self.transform_to_visual(&root_visual))?;
339        let mut transformed_rect =
340            Rect::from_position_size(Point::default(), bounds.size()).transform_to_aabb(transform_to_visual);
341        // Transformed rect should be pixel-rounded if layout rounding is enabled.
342        // This is important for native controls to align correctly with the visual tree.
343        if self.use_layout_rounding() {
344            let scale = LayoutHelper::get_layout_scale(self);
345            let left = LayoutHelper::round_layout_value(transformed_rect.x, scale);
346            let top = LayoutHelper::round_layout_value(transformed_rect.y, scale);
347            let right = LayoutHelper::round_layout_value(transformed_rect.right(), scale);
348            let bottom = LayoutHelper::round_layout_value(transformed_rect.bottom(), scale);
349            transformed_rect = Rect::from_points(Point::new(left, top), Point::new(right, bottom));
350        }
351
352        Some(transformed_rect)
353    }
354
355    fn enqueue_for_move_resize(&self) {
356        if self.queued_for_move_resize.get() {
357            return;
358        }
359        self.queued_for_move_resize.set(true);
360        let this = self.to_ref();
361        Dispatcher::ui_thread().post_local(move || this.update_host(), DispatcherPriority::AFTER_RENDER);
362    }
363
364    /// Moves the native control to the bounds of the host, or hides it
365    /// when the host is not visible. Returns `false` when there is no
366    /// native control host or the host has no size yet.
367    pub fn try_update_native_control_position(&self) -> bool {
368        if self.current_host.borrow().is_none() {
369            return false;
370        }
371
372        let bounds = self.get_absolute_bounds();
373        let attachment = self.attachment.borrow().clone();
374
375        match bounds {
376            Some(bounds) if self.is_effectively_visible() => {
377                if bounds.width == 0.0 && bounds.height == 0.0 {
378                    return false;
379                }
380                if let Some(attachment) = attachment {
381                    attachment.show_in_bounds(bounds);
382                }
383            }
384            _ => {
385                if let Some(attachment) = attachment {
386                    attachment.hide_with_size(self.bounds().size());
387                }
388            }
389        }
390        true
391    }
392
393    fn check_destruction(&self) {
394        self.queued_for_destruction.set(false);
395        if self.current_root.borrow().is_none() {
396            self.destroy_native_control();
397        }
398    }
399
400    fn destroy_native_control(&self) {
401        if let Some(native_control_handle) = self.native_control_handle() {
402            let attachment = self.attachment.borrow_mut().take();
403            if let Some(attachment) = attachment {
404                attachment.dispose();
405            }
406
407            self.destroy_native_control_core(native_control_handle);
408            self.set_native_control_handle(None);
409        }
410    }
411}