Skip to main content

ferroui_native/
top_level_impl.rs

1//! The top-level implementation shared by windows, popups and embeddable
2//! top-levels, its native handle and the receiver of the native top-level
3//! events.
4
5use crate::clipboard_data_transfer::ClipboardDataTransfer;
6use crate::clipboard_read_session::ClipboardReadSession;
7use crate::cursor::FerroNativeCursor;
8use crate::deferred_framebuffer::DeferredFramebuffer;
9use crate::ferro_native_drag_source::{data_transfer_from_handle, free_data_transfer_handle};
10use crate::ferro_native_text_input_method::FerroNativeTextInputMethod;
11use crate::frn_automation_peer::FrnAutomationPeer;
12use crate::helpers::*;
13use crate::interop::*;
14use crate::ferro_native_gl_platform_graphics::GlPlatformSurface;
15use crate::metal::MetalPlatformSurface;
16use crate::native_control_host_impl::NativeControlHostImpl;
17use crate::platform_behavior_inhibition::PlatformBehaviorInhibition;
18use ferroui_base::input::platform::{IClipboard, PlatformDataTransfer};
19use ferroui_base::input::raw::{
20    IDragDropDevice, RawDragEvent, IRawInputEventArgs, RawKeyEventArgs, RawMouseWheelEventArgs, RawPointerEventArgs, RawPointerEventType,
21    RawPointerGestureEventArgs, RawPointerPoint, RawTextInputEventArgs,
22};
23use ferroui_base::input::text_input::ITextInputMethodImpl;
24use ferroui_base::input::{
25    DragDropEffects, IDataTransfer, CaptureSource, IInputDevice, IInputRoot, IKeyboardDevice, IPointer, KeyDeviceType, MouseDevice, PenDevice,
26    StandardCursorType,
27};
28use ferroui_base::platform::surfaces::{
29    FramebufferLockProperties, IFramebufferPlatformSurface, IFramebufferRenderTarget, IPlatformRenderSurface,
30    IPlatformRenderSurfaceRenderTarget,
31};
32use ferroui_base::platform::{
33    ICursorFactory, ICursorImpl, ILockedFramebuffer, IMacOSTopLevelPlatformHandle, IPlatformBehaviorInhibition,
34    RenderTargetSceneInfo,
35};
36use ferroui_base::reactive::IDisposable;
37use ferroui_base::threading::{Dispatcher, DispatcherPriority};
38use ferroui_base::utilities::ThreadBound;
39use ferroui_base::{FerroLocator, LocatorExtensions, PixelPoint, Point, Rect, Ref, Size, Vector, Visual};
40use ferroui_controls::automation::peers::{AutomationPeer, ControlAutomationPeer};
41use ferroui_controls::platform::{
42    INativeControlHostImpl, IPlatformHandle, IPopupImpl, IScreenImpl, ITopLevelImpl, PlatformThemeVariant,
43};
44use ferroui_controls::{
45    AcrylicPlatformCompensationLevels, Control, TopLevel, WindowResizeReason, WindowTransparencyLevel,
46};
47use ferroui_microcom::{ComPtr, HResult};
48use std::any::{Any, TypeId};
49use std::cell::{Cell, RefCell};
50use std::ffi::{c_void, CStr};
51use std::rc::Rc;
52use std::sync::atomic::{AtomicBool, Ordering};
53use std::sync::{Arc, Mutex, MutexGuard};
54
55/// The platform handle of a top-level of this backend.
56///
57/// A handle obtained from `ITopLevelImpl::handle` of this backend can be
58/// downcast (through `as_any`) to this type, which implements
59/// [`IMacOSTopLevelPlatformHandle`].
60pub struct MacOSTopLevelHandle {
61    native: ComPtr<IFrnTopLevel>,
62    window_base: Option<ComPtr<IFrnWindowBase>>,
63    handle: isize,
64    handle_descriptor: &'static str,
65}
66
67impl MacOSTopLevelHandle {
68    /// The handle of a top-level without a window; describes its `NSView`.
69    pub(crate) fn from_top_level(native: ComPtr<IFrnTopLevel>) -> Rc<Self> {
70        let mut this = Self { native, window_base: None, handle: 0, handle_descriptor: "NSView" };
71        this.handle = this.ns_view();
72        Rc::new(this)
73    }
74
75    /// The handle of a window or popup; describes its `NSWindow`.
76    pub(crate) fn from_window_base(native: ComPtr<IFrnWindowBase>) -> Rc<Self> {
77        let top_level = ComPtr::<IFrnTopLevel>::from_ref(&native);
78        let mut this =
79            Self { native: top_level, window_base: Some(native), handle: 0, handle_descriptor: "NSWindow" };
80        this.handle = this.ns_window();
81        Rc::new(this)
82    }
83
84    pub(crate) fn native(&self) -> &ComPtr<IFrnTopLevel> {
85        &self.native
86    }
87
88    pub(crate) fn window_base(&self) -> Option<&ComPtr<IFrnWindowBase>> {
89        self.window_base.as_ref()
90    }
91}
92
93impl IMacOSTopLevelPlatformHandle for MacOSTopLevelHandle {
94    fn ns_view(&self) -> isize {
95        self.native.obtain_ns_view_handle().check() as isize
96    }
97
98    fn get_ns_view_retained(&self) -> isize {
99        self.native.obtain_ns_view_handle_retained().check() as isize
100    }
101
102    fn ns_window(&self) -> isize {
103        self.window_base.as_ref().map_or(0, |native| native.obtain_ns_window_handle().check() as isize)
104    }
105
106    fn get_ns_window_retained(&self) -> isize {
107        self.window_base.as_ref().map_or(0, |native| native.obtain_ns_window_handle_retained().check() as isize)
108    }
109}
110
111impl IPlatformHandle for MacOSTopLevelHandle {
112    fn handle(&self) -> isize {
113        self.handle
114    }
115
116    fn handle_descriptor(&self) -> Option<&str> {
117        Some(self.handle_descriptor)
118    }
119
120    fn as_any(&self) -> &dyn Any {
121        self
122    }
123}
124
125/// What the concrete top-level types (window, popup, embeddable top-level)
126/// provide to the shared code: the base object and the members the
127/// reference implementation overrides per type.
128pub(crate) trait TopLevelParent: 'static {
129    /// The shared top-level state.
130    fn top_level(&self) -> &Rc<TopLevelImpl>;
131
132    /// Releases the native top-level.
133    fn dispose_top_level(&self) {
134        self.top_level().dispose();
135    }
136
137    /// Lets a window handle a pointer event on its chrome; `true` swallows
138    /// the event.
139    fn chrome_hit_test(&self, _e: &RawPointerEventArgs) -> bool {
140        false
141    }
142
143    /// Creates a popup that belongs to the top-level.
144    fn create_popup_core(&self) -> Option<Rc<dyn IPopupImpl>>;
145
146    fn set_frame_theme_variant_core(&self, _theme_variant: Option<PlatformThemeVariant>) {
147        // no-op
148    }
149
150    fn try_get_feature_core(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
151        self.top_level().try_get_feature(feature_type)
152    }
153}
154
155/// The state and behaviour every top-level of this backend shares.
156///
157/// Deviation (DEVIATIONS.md, Native backend): in the reference the top-level
158/// is its own software (framebuffer) render surface. Here that is
159/// [`TopLevelFramebufferSurface`]: a render surface is shared with the thread
160/// that renders, and the top-level is an object of the UI thread.
161pub struct TopLevelImpl {
162    input_root: RefCell<Option<Rc<dyn IInputRoot>>>,
163    native_control_host: RefCell<Option<Rc<NativeControlHostImpl>>>,
164    platform_behavior_inhibition: RefCell<Option<Rc<PlatformBehaviorInhibition>>>,
165    input_method: RefCell<Option<Rc<FerroNativeTextInputMethod>>>,
166
167    mouse: Rc<MouseDevice>,
168    pen: Rc<PenDevice>,
169
170    keyboard: Option<Rc<dyn IKeyboardDevice>>,
171    cursor_factory: Option<Rc<dyn ICursorFactory>>,
172
173    factory: ComPtr<IFerroNativeFactory>,
174
175    saved_logical_size: Cell<Size>,
176    saved_scaling: Cell<f64>,
177    transparency_level: Cell<WindowTransparencyLevel>,
178
179    handle: RefCell<Option<Rc<MacOSTopLevelHandle>>>,
180
181    /// The render surfaces, in a cell the thread that renders reads too
182    /// (`render_surfaces`): filled by `init`, emptied by `dispose`.
183    surfaces: SharedSurfaces,
184    gl_surface: RefCell<Option<Arc<GlPlatformSurface>>>,
185    metal_surface: RefCell<Option<Arc<MetalPlatformSurface>>>,
186    framebuffer_surface: RefCell<Option<Arc<TopLevelFramebufferSurface>>>,
187
188    input: RefCell<Option<Rc<dyn Fn(Rc<dyn IRawInputEventArgs>)>>>,
189    paint: RefCell<Option<Rc<dyn Fn(Rect)>>>,
190    resized: RefCell<Option<Rc<dyn Fn(Size, WindowResizeReason)>>>,
191    scaling_changed: RefCell<Option<Rc<dyn Fn(f64)>>>,
192    transparency_level_changed: RefCell<Option<Rc<dyn Fn(WindowTransparencyLevel)>>>,
193    closed: RefCell<Option<Rc<dyn Fn()>>>,
194    lost_focus: RefCell<Option<Rc<dyn Fn()>>>,
195}
196
197impl TopLevelImpl {
198    pub(crate) fn new(factory: ComPtr<IFerroNativeFactory>) -> Rc<TopLevelImpl> {
199        let locator = FerroLocator::current();
200        Rc::new(TopLevelImpl {
201            input_root: RefCell::new(None),
202            native_control_host: RefCell::new(None),
203            platform_behavior_inhibition: RefCell::new(None),
204            input_method: RefCell::new(None),
205            mouse: MouseDevice::primary(),
206            pen: PenDevice::new(false),
207            keyboard: locator.get_service::<dyn IKeyboardDevice>(),
208            cursor_factory: locator.get_service::<dyn ICursorFactory>(),
209            factory,
210            saved_logical_size: Cell::new(Size::default()),
211            saved_scaling: Cell::new(0.0),
212            transparency_level: Cell::new(WindowTransparencyLevel::none()),
213            handle: RefCell::new(None),
214            surfaces: Arc::new(Mutex::new(Vec::new())),
215            gl_surface: RefCell::new(None),
216            metal_surface: RefCell::new(None),
217            framebuffer_surface: RefCell::new(None),
218            input: RefCell::new(None),
219            paint: RefCell::new(None),
220            resized: RefCell::new(None),
221            scaling_changed: RefCell::new(None),
222            transparency_level_changed: RefCell::new(None),
223            closed: RefCell::new(None),
224            lost_focus: RefCell::new(None),
225        })
226    }
227
228    pub(crate) fn init(&self, handle: Rc<MacOSTopLevelHandle>) {
229        let native = handle.native().clone();
230        *self.handle.borrow_mut() = Some(handle);
231        self.saved_logical_size.set(self.client_size());
232        self.saved_scaling.set(native.get_scaling().check());
233        *self.native_control_host.borrow_mut() = Some(NativeControlHostImpl::new(native.create_native_control_host().check()));
234        *self.platform_behavior_inhibition.borrow_mut() = self
235            .factory
236            .create_platform_behavior_inhibition()
237            .check()
238            .map(|native| Rc::new(PlatformBehaviorInhibition::new(native)));
239
240        *self.input_method.borrow_mut() = Some(FerroNativeTextInputMethod::new(&native));
241        let gl_surface = GlPlatformSurface::new(native.clone());
242        let metal_surface = MetalPlatformSurface::new(native.clone());
243        let framebuffer_surface =
244            TopLevelFramebufferSurface::new(native, self.saved_logical_size.get(), self.saved_scaling.get());
245        *self.gl_surface.borrow_mut() = Some(gl_surface.clone());
246        *self.metal_surface.borrow_mut() = Some(metal_surface.clone());
247        *self.framebuffer_surface.borrow_mut() = Some(framebuffer_surface.clone());
248        let gl_surface: Arc<dyn IPlatformRenderSurface> = gl_surface;
249        let metal_surface: Arc<dyn IPlatformRenderSurface> = metal_surface;
250        let framebuffer_surface: Arc<dyn IPlatformRenderSurface> = framebuffer_surface;
251        *lock_shared(&self.surfaces) = vec![gl_surface, metal_surface, framebuffer_surface];
252    }
253
254    /// Hands the size and the scaling the top-level saved to its software
255    /// surface, which is what a frame of the thread that renders reads.
256    fn update_surface_metrics(&self) {
257        if let Some(surface) = self.framebuffer_surface.borrow().as_ref() {
258            surface.set_metrics(self.saved_logical_size.get(), self.saved_scaling.get());
259        }
260    }
261
262    /// The top-level of this backend that hosts `visual`, if any.
263    pub(crate) fn find_for_visual(visual: &Visual) -> Option<Rc<TopLevelImpl>> {
264        let platform_impl = TopLevel::get_top_level(Some(visual))?.platform_impl()?;
265        top_level_of(&*platform_impl).cloned()
266    }
267
268    pub(crate) fn begin_dragging_session(
269        &self,
270        effects: FrnDragDropEffects,
271        point: FrnPoint,
272        source: &IFrnClipboardDataSource,
273        callback: &IFrnDndResultCallback,
274        source_handle: *mut c_void,
275    ) {
276        match self.native() {
277            // SAFETY: `source_handle` is an opaque value for native code,
278            // which hands it back in drag events and to the handle
279            // deallocator.
280            Some(native) => unsafe {
281                native.begin_drag_and_drop_operation(effects, point, Some(source), Some(callback), source_handle)
282            }
283            .check(),
284            // Nothing took the handle over.
285            // SAFETY: the handle was made for this call and is not used again.
286            None => unsafe { free_data_transfer_handle(source_handle) },
287        }
288    }
289
290    /// The text input method of the top-level.
291    pub fn input_method(&self) -> Option<Rc<FerroNativeTextInputMethod>> {
292        self.input_method.borrow().clone()
293    }
294
295    pub(crate) fn set_input_method(&self, value: Option<Rc<FerroNativeTextInputMethod>>) {
296        let old = self.input_method.replace(value);
297        drop(old);
298    }
299
300    fn on_drag_event(
301        &self,
302        type_: FrnDragEventType,
303        position: FrnPoint,
304        modifiers: FrnInputModifiers,
305        effects: FrnDragDropEffects,
306        clipboard: Option<&IFrnClipboard>,
307        data_transfer_handle: *mut c_void,
308    ) -> FrnDragDropEffects {
309        let Some(device) = FerroLocator::current().get_service::<dyn IDragDropDevice>() else {
310            return FrnDragDropEffects::None;
311        };
312
313        let Some(input_root) = self.input_root() else {
314            return FrnDragDropEffects::None;
315        };
316
317        // SAFETY: a non-null handle in a drag event is the handle the drag
318        // source of this backend started the operation with.
319        let data_transfer = unsafe { data_transfer_from_handle(data_transfer_handle) };
320
321        // The dragging pasteboard, read for the duration of the event.
322        let clipboard_data_transfer = clipboard.map(|clipboard| {
323            let change_count = clipboard.get_change_count().check();
324            ClipboardDataTransfer::new(ClipboardReadSession::new(ComPtr::from_ref(clipboard), change_count))
325        });
326        struct DisposeOnExit(Option<Rc<PlatformDataTransfer>>);
327        impl Drop for DisposeOnExit {
328            fn drop(&mut self) {
329                if let Some(data_transfer) = self.0.take() {
330                    data_transfer.dispose();
331                }
332            }
333        }
334        let _dispose = DisposeOnExit(clipboard_data_transfer.clone());
335
336        let data_transfer: Rc<dyn IDataTransfer> = match (data_transfer, clipboard_data_transfer) {
337            (Some(data_transfer), _) => data_transfer,
338            (None, Some(clipboard_data_transfer)) => clipboard_data_transfer,
339            // The native side always passes the dragging pasteboard.
340            (None, None) => return FrnDragDropEffects::None,
341        };
342
343        let args = Rc::new(RawDragEvent::new(
344            device,
345            to_raw_drag_event_type(type_),
346            input_root,
347            to_ferro_point(position),
348            data_transfer,
349            DragDropEffects::from_bits_retain(effects.0),
350            to_raw_input_modifiers(modifiers),
351        ));
352        self.invoke_input(args.clone());
353        FrnDragDropEffects(args.effects().bits())
354    }
355
356    pub(crate) fn factory(&self) -> &ComPtr<IFerroNativeFactory> {
357        &self.factory
358    }
359
360    pub(crate) fn input_root(&self) -> Option<Rc<dyn IInputRoot>> {
361        self.input_root.borrow().clone()
362    }
363
364    pub(crate) fn mouse(&self) -> &Rc<MouseDevice> {
365        &self.mouse
366    }
367
368    pub fn desktop_scaling(&self) -> f64 {
369        1.0
370    }
371
372    /// The native top-level; `None` once it is disposed.
373    pub fn native(&self) -> Option<ComPtr<IFrnTopLevel>> {
374        self.handle.borrow().as_ref().map(|handle| handle.native().clone())
375    }
376
377    /// The native window base, when the top-level is a window or a popup
378    /// and is not disposed.
379    pub(crate) fn native_window_base(&self) -> Option<ComPtr<IFrnWindowBase>> {
380        self.handle.borrow().as_ref().and_then(|handle| handle.window_base().cloned())
381    }
382
383    /// The platform handle with its concrete type.
384    pub fn mac_os_handle(&self) -> Option<Rc<MacOSTopLevelHandle>> {
385        self.handle.borrow().clone()
386    }
387
388    pub fn handle(&self) -> Option<Rc<dyn IPlatformHandle>> {
389        self.mac_os_handle().map(|handle| handle as Rc<dyn IPlatformHandle>)
390    }
391
392    pub fn client_size(&self) -> Size {
393        match self.native() {
394            None => Size::default(),
395            Some(native) => to_ferro_size(native.get_client_size().check()),
396        }
397    }
398
399    pub fn render_scaling(&self) -> f64 {
400        self.saved_scaling.get()
401    }
402
403    pub fn surfaces(&self) -> Vec<Arc<dyn IPlatformRenderSurface>> {
404        lock_shared(&self.surfaces).clone()
405    }
406
407    /// The surfaces as the thread that renders asks for them: the function
408    /// reads the cell the top-level publishes its surfaces in, and never
409    /// reaches the top-level itself.
410    ///
411    /// Deviation (DEVIATIONS.md, Native backend): `Surfaces` of the reference
412    /// keeps returning the surfaces of a disposed top-level, whose native
413    /// object then fails every call. Here the surfaces are gone once the
414    /// top-level is disposed: they gave their native top-level back on the
415    /// UI thread, and a render target can no longer be created for them.
416    pub fn render_surfaces(&self) -> Arc<dyn Fn() -> Vec<Arc<dyn IPlatformRenderSurface>> + Send + Sync> {
417        let surfaces = self.surfaces.clone();
418        Arc::new(move || lock_shared(&surfaces).clone())
419    }
420
421    pub fn transparency_level(&self) -> WindowTransparencyLevel {
422        self.transparency_level.get()
423    }
424
425    fn set_transparency_level(&self, value: WindowTransparencyLevel) {
426        if self.transparency_level.get() != value {
427            self.transparency_level.set(value);
428            let changed = self.transparency_level_changed.borrow().clone();
429            if let Some(changed) = changed {
430                changed(value);
431            }
432        }
433    }
434
435    pub fn acrylic_compensation_levels(&self) -> AcrylicPlatformCompensationLevels {
436        AcrylicPlatformCompensationLevels::new(1.0, 0.0, 0.0)
437    }
438
439    fn invoke_input(&self, args: Rc<dyn IRawInputEventArgs>) {
440        let input = self.input.borrow().clone();
441        if let Some(input) = input {
442            input(args);
443        }
444    }
445
446    /// Runs the dispatcher jobs that must not be overtaken by input.
447    fn run_jobs_before_input() {
448        Dispatcher::ui_thread()
449            .run_jobs(Some(DispatcherPriority::from_value(DispatcherPriority::INPUT.value() + 1)));
450    }
451
452    /// The automation peer of the focus root of the top-level, if it is a
453    /// control.
454    pub fn get_automation_peer(&self) -> Option<Ref<AutomationPeer>> {
455        let focus_root = self.input_root()?.focus_root();
456        focus_root.cast::<Control>().map(|c| ControlAutomationPeer::create_peer_for_element(&c))
457    }
458
459    pub fn raw_text_input_event(&self, time_stamp: u64, text: &str) -> bool {
460        let Some(input_root) = self.input_root() else {
461            return false;
462        };
463
464        let Some(keyboard) = self.keyboard.clone() else {
465            return false;
466        };
467
468        Self::run_jobs_before_input();
469
470        let device: Rc<dyn IInputDevice> = keyboard;
471        let args = Rc::new(RawTextInputEventArgs::new(device, time_stamp, input_root, text));
472
473        self.invoke_input(args.clone());
474
475        args.handled()
476    }
477
478    pub fn raw_key_event(
479        &self,
480        type_: FrnRawKeyEventType,
481        time_stamp: u64,
482        modifiers: FrnInputModifiers,
483        key: FrnKey,
484        physical_key: FrnPhysicalKey,
485        key_symbol: Option<String>,
486    ) -> bool {
487        let Some(input_root) = self.input_root() else {
488            return false;
489        };
490
491        let Some(keyboard) = self.keyboard.clone() else {
492            return false;
493        };
494
495        Self::run_jobs_before_input();
496
497        let device: Rc<dyn IInputDevice> = keyboard;
498        let args = Rc::new(RawKeyEventArgs::new(
499            device,
500            time_stamp,
501            input_root,
502            to_raw_key_event_type(type_),
503            to_key(key),
504            to_raw_input_modifiers(modifiers),
505            to_physical_key(physical_key),
506            key_symbol,
507            KeyDeviceType::Keyboard,
508        ));
509
510        self.invoke_input(args.clone());
511
512        args.handled()
513    }
514
515    #[allow(clippy::too_many_arguments)]
516    pub(crate) fn raw_mouse_event(
517        &self,
518        type_: FrnRawMouseEventType,
519        device_type: FrnPointerDeviceType,
520        time_stamp: u64,
521        modifiers: FrnInputModifiers,
522        point: FrnPoint,
523        delta: FrnVector,
524        pressure: f32,
525        x_tilt: f32,
526        y_tilt: f32,
527        chrome_hit_test: &dyn Fn(&RawPointerEventArgs) -> bool,
528    ) {
529        let Some(input_root) = self.input_root() else {
530            return;
531        };
532
533        Self::run_jobs_before_input();
534
535        let mouse: Rc<dyn IInputDevice> = self.mouse.clone();
536        let position = to_ferro_point(point);
537        let delta = Vector::new(delta.x, delta.y);
538        let modifiers = to_raw_input_modifiers(modifiers);
539
540        if type_ == FrnRawMouseEventType::Wheel {
541            self.invoke_input(Rc::new(RawMouseWheelEventArgs::new(
542                mouse, time_stamp, input_root, position, delta, modifiers,
543            )));
544        } else if type_ == FrnRawMouseEventType::Magnify {
545            self.invoke_input(Rc::new(RawPointerGestureEventArgs::new(
546                mouse,
547                time_stamp,
548                input_root,
549                RawPointerEventType::Magnify,
550                position,
551                delta,
552                modifiers,
553            )));
554        } else if type_ == FrnRawMouseEventType::Rotate {
555            self.invoke_input(Rc::new(RawPointerGestureEventArgs::new(
556                mouse,
557                time_stamp,
558                input_root,
559                RawPointerEventType::Rotate,
560                position,
561                delta,
562                modifiers,
563            )));
564        } else if type_ == FrnRawMouseEventType::Swipe {
565            self.invoke_input(Rc::new(RawPointerGestureEventArgs::new(
566                mouse,
567                time_stamp,
568                input_root,
569                RawPointerEventType::Swipe,
570                position,
571                delta,
572                modifiers,
573            )));
574        } else {
575            // A value the toolkit does not know has nothing to be delivered as.
576            let Some(type_) = to_raw_pointer_event_type(type_) else {
577                return;
578            };
579            let device: Rc<dyn IInputDevice> =
580                if device_type == FrnPointerDeviceType::Pen { self.pen.clone() } else { mouse };
581            let mut raw_point = RawPointerPoint::new();
582            raw_point.position = position;
583            raw_point.pressure = pressure;
584            raw_point.x_tilt = x_tilt;
585            raw_point.y_tilt = y_tilt;
586            let e =
587                Rc::new(RawPointerEventArgs::with_point(device, time_stamp, input_root, type_, raw_point, modifiers));
588
589            if !chrome_hit_test(&e) {
590                self.invoke_input(e);
591            }
592        }
593    }
594
595    pub fn invalidate(&self) {
596        if let Some(native) = self.native() {
597            native.invalidate().check();
598        }
599    }
600
601    pub fn set_input_root(&self, input_root: Rc<dyn IInputRoot>) {
602        let old = self.input_root.replace(Some(input_root));
603        drop(old);
604    }
605
606    pub fn point_to_client(&self, point: PixelPoint) -> Point {
607        match self.native() {
608            Some(native) => to_ferro_point(native.point_to_client(pixel_point_to_frn_point(point)).check()),
609            None => Point::default(),
610        }
611    }
612
613    pub fn point_to_screen(&self, point: Point) -> PixelPoint {
614        match self.native() {
615            Some(native) => to_ferro_pixel_point(native.point_to_screen(to_frn_point(point)).check()),
616            None => PixelPoint::default(),
617        }
618    }
619
620    pub fn set_cursor(&self, cursor: Option<Rc<dyn ICursorImpl>>) {
621        let Some(native) = self.native() else {
622            return;
623        };
624
625        let native_cursor = match cursor.as_deref().and_then(FerroNativeCursor::from_cursor_impl) {
626            Some(new_cursor) => new_cursor.cursor(),
627            None => self.cursor_factory.as_ref().and_then(|cursor_factory| {
628                let arrow = cursor_factory.get_cursor(StandardCursorType::Arrow);
629                FerroNativeCursor::from_cursor_impl(&*arrow).and_then(FerroNativeCursor::cursor)
630            }),
631        };
632        native.set_cursor(native_cursor.as_deref()).check();
633    }
634
635    pub fn set_transparency_level_hint(&self, transparency_levels: &[WindowTransparencyLevel]) {
636        for &level in transparency_levels {
637            let mut mode = None;
638
639            if level == WindowTransparencyLevel::none() {
640                mode = Some(FrnWindowTransparencyMode::Opaque);
641            }
642            if level == WindowTransparencyLevel::transparent() {
643                mode = Some(FrnWindowTransparencyMode::Transparent);
644            } else if level == WindowTransparencyLevel::acrylic_blur() {
645                mode = Some(FrnWindowTransparencyMode::Blur);
646            }
647
648            if let Some(mode) = mode {
649                if level != self.transparency_level() {
650                    if let Some(native) = self.native() {
651                        native.set_transparency_mode(mode).check();
652                    }
653                    self.set_transparency_level(level);
654                    return;
655                }
656            }
657        }
658
659        // If we get here, we didn't find a supported level. Use the default of None.
660        if self.transparency_level() != WindowTransparencyLevel::none() {
661            if let Some(native) = self.native() {
662                native.set_transparency_mode(FrnWindowTransparencyMode::Opaque).check();
663            }
664            self.set_transparency_level(WindowTransparencyLevel::none());
665        }
666    }
667
668    /// The optional features of every top-level of this backend.
669    ///
670    /// The features whose contracts are not ported yet (native control host,
671    /// launcher) are absent.
672    pub fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
673        if feature_type == TypeId::of::<dyn ITextInputMethodImpl>() {
674            let input_method: Rc<dyn ITextInputMethodImpl> = self.input_method()?;
675            return Some(Rc::new(input_method));
676        }
677
678        if feature_type == TypeId::of::<dyn INativeControlHostImpl>() {
679            let native_control_host: Rc<dyn INativeControlHostImpl> = self.native_control_host.borrow().clone()?;
680            return Some(Rc::new(native_control_host));
681        }
682
683        if feature_type == TypeId::of::<dyn IClipboard>() {
684            let clipboard = FerroLocator::current().get_required_service::<dyn IClipboard>();
685            return Some(Rc::new(clipboard));
686        }
687
688        if feature_type == TypeId::of::<dyn IPlatformBehaviorInhibition>() {
689            let inhibition: Rc<dyn IPlatformBehaviorInhibition> = self.platform_behavior_inhibition.borrow().clone()?;
690            return Some(Rc::new(inhibition));
691        }
692
693        if feature_type == TypeId::of::<dyn IScreenImpl>() {
694            let screens = FerroLocator::current().get_required_service::<dyn IScreenImpl>();
695            return Some(Rc::new(screens));
696        }
697
698        None
699    }
700
701    /// The screens service, as the top-level feature.
702    pub(crate) fn screens(&self) -> Rc<dyn IScreenImpl> {
703        FerroLocator::current().get_required_service::<dyn IScreenImpl>()
704    }
705
706    pub(crate) fn dispose(&self) {
707        // The surfaces stop being handed out and give back the native
708        // top-level they hold, here on the UI thread: a surface that the
709        // thread that renders drops last cannot release it there.
710        lock_shared(&self.surfaces).clear();
711        let gl_surface = self.gl_surface.borrow_mut().take();
712        if let Some(gl_surface) = gl_surface {
713            gl_surface.close();
714        }
715        let metal_surface = self.metal_surface.borrow_mut().take();
716        if let Some(metal_surface) = metal_surface {
717            metal_surface.close();
718        }
719        let framebuffer_surface = self.framebuffer_surface.borrow_mut().take();
720        if let Some(framebuffer_surface) = framebuffer_surface {
721            framebuffer_surface.close();
722        }
723
724        let handle = self.handle.borrow_mut().take();
725        drop(handle);
726
727        let native_control_host = self.native_control_host.borrow_mut().take();
728        if let Some(native_control_host) = native_control_host {
729            native_control_host.dispose();
730        }
731    }
732
733    /// The host of native controls of the top-level; `None` once it is
734    /// disposed.
735    pub fn native_control_host(&self) -> Option<Rc<dyn INativeControlHostImpl>> {
736        let native_control_host: Rc<dyn INativeControlHostImpl> = self.native_control_host.borrow().clone()?;
737        Some(native_control_host)
738    }
739
740    // --- the native events that only touch the shared state ---------------
741
742    fn on_paint(&self) {
743        Dispatcher::ui_thread().run_jobs(Some(DispatcherPriority::UI_THREAD_RENDER));
744        let s = self.client_size();
745        let paint = self.paint.borrow().clone();
746        if let Some(paint) = paint {
747            paint(Rect::new(0.0, 0.0, s.width, s.height));
748        }
749    }
750
751    fn on_resized(&self, size: &FrnSize, reason: FrnPlatformResizeReason) {
752        if self.native().is_none() {
753            return;
754        }
755
756        let s = Size::new(size.width, size.height);
757        self.saved_logical_size.set(s);
758        self.update_surface_metrics();
759        let resized = self.resized.borrow().clone();
760        if let Some(resized) = resized {
761            resized(s, to_window_resize_reason(reason));
762        }
763    }
764
765    fn on_scaling_changed(&self, scaling: f64) {
766        self.saved_scaling.set(scaling);
767        self.update_surface_metrics();
768        let scaling_changed = self.scaling_changed.borrow().clone();
769        if let Some(scaling_changed) = scaling_changed {
770            scaling_changed(scaling);
771        }
772    }
773
774    fn on_lost_focus(&self) {
775        let lost_focus = self.lost_focus.borrow().clone();
776        if let Some(lost_focus) = lost_focus {
777            lost_focus();
778        }
779
780        // macOS doesn't have the concept of mouse capture. If we're losing the focus during an implicit capture
781        // (standard mouse down), we should release it to avoid mouse events going to an old window.
782        let pointer = self.mouse.pointer();
783        let Some(captured) = pointer.captured() else {
784            return;
785        };
786
787        let captured: Ref<Visual> = captured.upcast();
788        if pointer.capture_source() == CaptureSource::Implicit
789            && Self::find_for_visual(&captured).is_some_and(|owner| std::ptr::eq(&*owner, self))
790        {
791            self.mouse.platform_capture_lost();
792        }
793    }
794}
795
796macro_rules! callback_property {
797    ($getter:ident, $setter:ident, $field:ident, $ty:ty) => {
798        pub fn $getter(&self) -> Option<Rc<$ty>> {
799            self.$field.borrow().clone()
800        }
801
802        pub fn $setter(&self, value: Option<Rc<$ty>>) {
803            let old = self.$field.replace(value);
804            drop(old);
805        }
806    };
807}
808pub(crate) use callback_property;
809
810impl TopLevelImpl {
811    callback_property!(input, set_input, input, dyn Fn(Rc<dyn IRawInputEventArgs>));
812    callback_property!(paint, set_paint, paint, dyn Fn(Rect));
813    callback_property!(resized, set_resized, resized, dyn Fn(Size, WindowResizeReason));
814    callback_property!(scaling_changed, set_scaling_changed, scaling_changed, dyn Fn(f64));
815    callback_property!(
816        transparency_level_changed,
817        set_transparency_level_changed,
818        transparency_level_changed,
819        dyn Fn(WindowTransparencyLevel)
820    );
821    callback_property!(closed, set_closed, closed, dyn Fn());
822    callback_property!(lost_focus, set_lost_focus, lost_focus, dyn Fn());
823}
824
825/// The cell a top-level publishes its render surfaces in.
826type SharedSurfaces = Arc<Mutex<Vec<Arc<dyn IPlatformRenderSurface>>>>;
827
828/// Locks a mutex of the surfaces. A panic of a frame (a failed native call)
829/// leaves nothing half-written behind these locks, so a poisoned lock is
830/// entered all the same.
831fn lock_shared<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
832    mutex.lock().unwrap_or_else(|e| e.into_inner())
833}
834
835/// The native top-level as a render surface holds it.
836///
837/// A render surface is shared with the thread that renders, and the native
838/// top-level (`IFrnTopLevel`) belongs to the UI thread: its members touch the
839/// view, the two that create a render target (`CreateSoftwareRenderTarget`,
840/// `CreateMetalRenderTarget` in `native/FerroUI.Native/src/OSX/TopLevelImpl.mm`)
841/// return `COR_E_INVALIDOPERATION` off the main thread, and the reference
842/// counts of the native objects are plain integers (`ComObject::AddRef` and
843/// `Release` in `native/FerroUI.Native/inc/comimpl.h`), so not even a
844/// reference may be taken or given back by another thread. The pointer is
845/// therefore bound to the UI thread: any other thread sees none.
846pub(crate) struct SurfaceTopLevel(ThreadBound<RefCell<Option<ComPtr<IFrnTopLevel>>>>);
847
848impl SurfaceTopLevel {
849    /// Binds the native top-level to the current thread, the UI thread.
850    pub(crate) fn new(native: ComPtr<IFrnTopLevel>) -> Self {
851        Self(ThreadBound::new(RefCell::new(Some(native))))
852    }
853
854    /// The native top-level; `None` on any thread but the UI thread and once
855    /// it is released.
856    pub(crate) fn get(&self) -> Option<ComPtr<IFrnTopLevel>> {
857        if !self.0.is_on_thread() {
858            return None;
859        }
860        self.0.get().borrow().clone()
861    }
862
863    /// Gives the reference back. Called on the UI thread by the top-level
864    /// when it is disposed: a surface dropped by another thread leaves what
865    /// is bound to the UI thread behind.
866    pub(crate) fn release(&self) {
867        if !self.0.is_on_thread() {
868            return;
869        }
870        let native = self.0.get().borrow_mut().take();
871        drop(native);
872    }
873}
874
875/// What the software surface of a top-level shares with its render targets:
876/// the part of the reference top-level a frame reads.
877struct FramebufferSurfaceState {
878    /// `_syncRoot` of the reference: held while a frame is handed to the
879    /// native render target and while a render target is released. Unlike
880    /// the lock of the reference it cannot be entered twice by one thread;
881    /// nothing under it calls back into the render target.
882    sync_root: Mutex<()>,
883    /// Whether the native top-level is alive (`Native != null` in the
884    /// reference); cleared when the top-level is disposed.
885    alive: AtomicBool,
886    /// `_savedLogicalSize` and `_savedScaling` of the top-level.
887    metrics: Mutex<(Size, f64)>,
888}
889
890impl FramebufferSurfaceState {
891    fn new(logical_size: Size, scaling: f64) -> Arc<Self> {
892        Arc::new(Self {
893            sync_root: Mutex::new(()),
894            alive: AtomicBool::new(true),
895            metrics: Mutex::new((logical_size, scaling)),
896        })
897    }
898}
899
900/// The software (framebuffer) render surface of a top-level.
901///
902/// The surface is shared with the thread that renders. The rule of the
903/// reference is kept: the render target is created on the UI thread only
904/// (the first show and every resize are rendered by the UI thread), and the
905/// frames of the render thread reuse it.
906pub struct TopLevelFramebufferSurface {
907    top_level: SurfaceTopLevel,
908    state: Arc<FramebufferSurfaceState>,
909}
910
911impl TopLevelFramebufferSurface {
912    pub(crate) fn new(native: ComPtr<IFrnTopLevel>, logical_size: Size, scaling: f64) -> Arc<Self> {
913        Arc::new(Self {
914            top_level: SurfaceTopLevel::new(native),
915            state: FramebufferSurfaceState::new(logical_size, scaling),
916        })
917    }
918
919    /// The size and the scaling the next frame is sized with.
920    pub(crate) fn set_metrics(&self, logical_size: Size, scaling: f64) {
921        *lock_shared(&self.state.metrics) = (logical_size, scaling);
922    }
923
924    /// The top-level is disposed: no frame is presented any more and the
925    /// native top-level is given back. Called on the UI thread.
926    pub(crate) fn close(&self) {
927        // Not under `sync_root`, as `Dispose` of the reference top-level: a
928        // frame that is being presented finishes.
929        self.state.alive.store(false, Ordering::Release);
930        self.top_level.release();
931    }
932}
933
934impl IPlatformRenderSurface for TopLevelFramebufferSurface {
935    /// Deviation (DEVIATIONS.md, Native backend): the reference throws
936    /// `RenderTargetNotReadyException` from `CreateFramebufferRenderTarget`
937    /// off the UI thread and the composition target catches it; here the
938    /// surface answers that it is not ready (off the UI thread, and once the
939    /// top-level is disposed), which the composition target treats the same
940    /// way, without a panic to catch.
941    fn is_ready(&self) -> bool {
942        self.top_level.get().is_some()
943    }
944
945    fn as_framebuffer_surface(&self) -> Option<&dyn IFramebufferPlatformSurface> {
946        Some(self)
947    }
948
949    fn as_any(&self) -> &dyn Any {
950        self
951    }
952}
953
954impl IFramebufferPlatformSurface for TopLevelFramebufferSurface {
955    /// # Panics
956    /// Panics when called off the UI thread or when the top-level has no
957    /// native render target (it is closed): the render target is not ready.
958    fn create_framebuffer_render_target(&self) -> Rc<dyn IFramebufferRenderTarget> {
959        if !Dispatcher::ui_thread().check_access() {
960            panic!("The render target is not ready.");
961        }
962
963        let native_render_target =
964            self.top_level.get().and_then(|native| native.create_software_render_target().check());
965
966        let Some(native_render_target) = native_render_target else {
967            panic!("The render target is not ready.");
968        };
969
970        Rc::new(FramebufferRenderTarget::new(self.state.clone(), native_render_target))
971    }
972}
973
974/// The native software render target, as the render target of a top-level
975/// owns it: the only reference to the native object.
976struct SoftwareRenderTargetPtr(ComPtr<IFrnSoftwareRenderTarget>);
977
978// SAFETY: the pointer may be used, and released, by a thread other than the
979// one that received it, because of what the native object is and of how it
980// is held here.
981//
982// The native object is `FrnSoftwareRenderTarget`
983// (`native/FerroUI.Native/src/OSX/rendertarget.mm`). Its one member,
984// `SetFrame`, forwards to `-[IOSurfaceRenderTarget setSwFrame:]`, which runs
985// under `@synchronized (lock)` and is written for both threads
986// (`getNextSurfaceInSafeContext` and `presentSurfaceInSafeContext` branch on
987// `[NSThread isMainThread]`; off the main thread the surface is queued and a
988// callback is posted to the main thread). Its destructor only lets go of the
989// Objective-C render target, whose retain count is atomic.
990//
991// What the native object does not make safe is its reference count:
992// `ComObject::AddRef` and `Release` (`native/FerroUI.Native/inc/comimpl.h`)
993// are plain increments, and `SetFrame` takes and returns a reference to
994// itself (`START_COM_CALL`). That is covered here: native code hands out a
995// new object with one reference (`CreateSoftwareRenderTarget` in
996// `rendertarget.mm`) and keeps none, this wrapper is not `Clone` and never
997// lends the `ComPtr`, and every use of it, the release included, happens
998// under `sync_root` of the surface (or by the last owner of the cell it
999// lives in). So no two threads ever touch the object, or its count, at the
1000// same time. This is the discipline of the reference, which holds one proxy
1001// for the native object and locks `_syncRoot` around `SetFrame` and
1002// `Dispose`.
1003//
1004// `Sync` is not claimed: the wrapper is only reached through a mutex.
1005unsafe impl Send for SoftwareRenderTargetPtr {}
1006
1007/// The software render target of a top-level.
1008///
1009/// It is created by a frame of the UI thread and locked by whichever thread
1010/// renders a frame, so what it holds is shared and locked rather than kept
1011/// in cells.
1012struct FramebufferRenderTarget {
1013    parent: Arc<FramebufferSurfaceState>,
1014    /// Guarded by `sync_root` of the parent, which is taken first.
1015    target: Arc<Mutex<Option<SoftwareRenderTargetPtr>>>,
1016}
1017
1018impl FramebufferRenderTarget {
1019    fn new(parent: Arc<FramebufferSurfaceState>, target: ComPtr<IFrnSoftwareRenderTarget>) -> Self {
1020        Self { parent, target: Arc::new(Mutex::new(Some(SoftwareRenderTargetPtr(target)))) }
1021    }
1022}
1023
1024impl IPlatformRenderSurfaceRenderTarget for FramebufferRenderTarget {}
1025
1026impl IFramebufferRenderTarget for FramebufferRenderTarget {
1027    fn lock(&self, _scene_info: &RenderTargetSceneInfo) -> (Rc<dyn ILockedFramebuffer>, FramebufferLockProperties) {
1028        if lock_shared(&self.target).is_none() {
1029            panic!("Cannot access a disposed object: FramebufferRenderTarget");
1030        }
1031        let (size, scaling) = *lock_shared(&self.parent.metrics);
1032        let w = (size.width * scaling).max(1.0);
1033        let h = (size.height * scaling).max(1.0);
1034        let dpi = scaling * 96.0;
1035
1036        let parent = self.parent.clone();
1037        let current_target = self.target.clone();
1038        let framebuffer = DeferredFramebuffer::new(
1039            Box::new(move |cb: &mut dyn FnMut(&IFrnSoftwareRenderTarget)| {
1040                let _sync_root = lock_shared(&parent.sync_root);
1041                if parent.alive.load(Ordering::Acquire) {
1042                    if let Some(target) = lock_shared(&current_target).as_ref() {
1043                        cb(&*target.0);
1044                    }
1045                }
1046            }),
1047            w as i32,
1048            h as i32,
1049            Vector::new(dpi, dpi),
1050        );
1051        (Rc::new(framebuffer), FramebufferLockProperties::default())
1052    }
1053
1054    fn retains_frame_contents(&self) -> bool {
1055        false
1056    }
1057
1058    fn dispose(&self) {
1059        let _sync_root = lock_shared(&self.parent.sync_root);
1060        let target = lock_shared(&self.target).take();
1061        drop(target);
1062    }
1063}
1064
1065/// Receives the events of a native top-level for the top-level `P`.
1066///
1067/// One type serves every kind of top-level: it implements the window base
1068/// and window event interfaces when `P` provides the matching members.
1069pub(crate) struct TopLevelEvents<P>(pub(crate) Rc<P>);
1070
1071/// Disposes the top-level when the `Closed` callback is done, whether it
1072/// returned or panicked.
1073struct DisposeOnExit<'a, P: TopLevelParent> {
1074    parent: &'a P,
1075    native: Option<ComPtr<IFrnTopLevel>>,
1076}
1077
1078impl<P: TopLevelParent> Drop for DisposeOnExit<'_, P> {
1079    fn drop(&mut self) {
1080        self.parent.dispose_top_level();
1081        drop(self.native.take());
1082    }
1083}
1084
1085fn c_str_to_string(s: Option<&CStr>) -> Option<String> {
1086    s.map(|s| s.to_string_lossy().into_owned())
1087}
1088
1089impl<P: TopLevelParent> IFrnTopLevelEventsImpl for TopLevelEvents<P> {
1090    fn closed(&self) {
1091        crate::callback_base::guard((), || {
1092            let top_level = self.0.top_level();
1093            let _dispose = DisposeOnExit { parent: &*self.0, native: top_level.native() };
1094            let closed = top_level.closed();
1095            if let Some(closed) = closed {
1096                closed();
1097            }
1098        })
1099    }
1100
1101    fn paint(&self) -> Result<(), HResult> {
1102        crate::callback_base::guard(Ok(()), || {
1103            self.0.top_level().on_paint();
1104            Ok(())
1105        })
1106    }
1107
1108    fn resized(&self, size: &FrnSize, reason: FrnPlatformResizeReason) {
1109        crate::callback_base::guard((), || self.0.top_level().on_resized(size, reason))
1110    }
1111
1112    fn raw_mouse_event(
1113        &self,
1114        type_: FrnRawMouseEventType,
1115        device_type: FrnPointerDeviceType,
1116        time_stamp: u64,
1117        modifiers: FrnInputModifiers,
1118        point: FrnPoint,
1119        delta: FrnVector,
1120        pressure: f32,
1121        x_tilt: f32,
1122        y_tilt: f32,
1123    ) {
1124        crate::callback_base::guard((), || {
1125            self.0.top_level().raw_mouse_event(
1126                type_,
1127                device_type,
1128                time_stamp,
1129                modifiers,
1130                point,
1131                delta,
1132                pressure,
1133                x_tilt,
1134                y_tilt,
1135                &|e| self.0.chrome_hit_test(e),
1136            )
1137        })
1138    }
1139
1140    fn raw_key_event(
1141        &self,
1142        type_: FrnRawKeyEventType,
1143        time_stamp: u64,
1144        modifiers: FrnInputModifiers,
1145        key: FrnKey,
1146        physical_key: FrnPhysicalKey,
1147        key_symbol: Option<&CStr>,
1148    ) -> bool {
1149        crate::callback_base::guard(false, || {
1150            self.0.top_level().raw_key_event(
1151                type_,
1152                time_stamp,
1153                modifiers,
1154                key,
1155                physical_key,
1156                c_str_to_string(key_symbol),
1157            )
1158        })
1159    }
1160
1161    fn raw_text_input_event(&self, time_stamp: u64, text: Option<&CStr>) -> bool {
1162        crate::callback_base::guard(false, || {
1163            let text = c_str_to_string(text).unwrap_or_default();
1164            self.0.top_level().raw_text_input_event(time_stamp, &text)
1165        })
1166    }
1167
1168    fn scaling_changed(&self, scaling: f64) {
1169        crate::callback_base::guard((), || self.0.top_level().on_scaling_changed(scaling))
1170    }
1171
1172    fn run_render_priority_jobs(&self) {
1173        crate::callback_base::guard((), || {
1174            Dispatcher::ui_thread().run_jobs(Some(DispatcherPriority::UI_THREAD_RENDER));
1175        })
1176    }
1177
1178    fn lost_focus(&self) {
1179        crate::callback_base::guard((), || self.0.top_level().on_lost_focus())
1180    }
1181
1182    fn get_automation_peer(&self) -> Option<ComPtr<IFrnAutomationPeer>> {
1183        crate::callback_base::guard(None, || {
1184            let native = self.0.top_level().get_automation_peer();
1185
1186            FrnAutomationPeer::wrap_native(native)
1187        })
1188    }
1189
1190    fn drag_event(
1191        &self,
1192        type_: FrnDragEventType,
1193        position: FrnPoint,
1194        modifiers: FrnInputModifiers,
1195        effects: FrnDragDropEffects,
1196        clipboard: Option<&IFrnClipboard>,
1197        data_transfer_handle: *mut c_void,
1198    ) -> FrnDragDropEffects {
1199        crate::callback_base::guard(FrnDragDropEffects::None, || {
1200            self.0.top_level().on_drag_event(type_, position, modifiers, effects, clipboard, data_transfer_handle)
1201        })
1202    }
1203}
1204
1205/// Implements `IOptionalFeatureProvider`, `IDisposable` and `ITopLevelImpl`
1206/// for a type that is a [`TopLevelParent`]; the braces take the `as_*`
1207/// overrides of the type.
1208macro_rules! impl_top_level_contract {
1209    ($ty:ty { $($extra:tt)* }) => {
1210        impl ferroui_base::platform::IOptionalFeatureProvider for $ty {
1211            fn try_get_feature(&self, feature_type: std::any::TypeId) -> Option<std::rc::Rc<dyn std::any::Any>> {
1212                $crate::top_level_impl::TopLevelParent::try_get_feature_core(self, feature_type)
1213            }
1214        }
1215
1216        impl ferroui_base::reactive::IDisposable for $ty {
1217            fn dispose(&self) {
1218                $crate::top_level_impl::TopLevelParent::dispose_top_level(self)
1219            }
1220        }
1221
1222        impl ferroui_controls::platform::ITopLevelImpl for $ty {
1223            fn desktop_scaling(&self) -> f64 {
1224                $crate::top_level_impl::TopLevelParent::top_level(self).desktop_scaling()
1225            }
1226
1227            fn handle(&self) -> Option<std::rc::Rc<dyn ferroui_controls::platform::IPlatformHandle>> {
1228                $crate::top_level_impl::TopLevelParent::top_level(self).handle()
1229            }
1230
1231            fn client_size(&self) -> ferroui_base::Size {
1232                $crate::top_level_impl::TopLevelParent::top_level(self).client_size()
1233            }
1234
1235            fn render_scaling(&self) -> f64 {
1236                $crate::top_level_impl::TopLevelParent::top_level(self).render_scaling()
1237            }
1238
1239            fn surfaces(
1240                &self,
1241            ) -> Vec<std::sync::Arc<dyn ferroui_base::platform::surfaces::IPlatformRenderSurface>> {
1242                $crate::top_level_impl::TopLevelParent::top_level(self).surfaces()
1243            }
1244
1245            fn render_surfaces(
1246                &self,
1247            ) -> std::sync::Arc<
1248                dyn Fn() -> Vec<std::sync::Arc<dyn ferroui_base::platform::surfaces::IPlatformRenderSurface>>
1249                    + Send
1250                    + Sync,
1251            > {
1252                $crate::top_level_impl::TopLevelParent::top_level(self).render_surfaces()
1253            }
1254
1255            fn compositor(&self) -> Option<std::rc::Rc<ferroui_base::rendering::composition::Compositor>> {
1256                Some($crate::ferro_native_platform::FerroNativePlatform::compositor())
1257            }
1258
1259            fn input(
1260                &self,
1261            ) -> Option<std::rc::Rc<dyn Fn(std::rc::Rc<dyn ferroui_base::input::raw::IRawInputEventArgs>)>> {
1262                $crate::top_level_impl::TopLevelParent::top_level(self).input()
1263            }
1264
1265            fn set_input(
1266                &self,
1267                value: Option<std::rc::Rc<dyn Fn(std::rc::Rc<dyn ferroui_base::input::raw::IRawInputEventArgs>)>>,
1268            ) {
1269                $crate::top_level_impl::TopLevelParent::top_level(self).set_input(value)
1270            }
1271
1272            fn paint(&self) -> Option<std::rc::Rc<dyn Fn(ferroui_base::Rect)>> {
1273                $crate::top_level_impl::TopLevelParent::top_level(self).paint()
1274            }
1275
1276            fn set_paint(&self, value: Option<std::rc::Rc<dyn Fn(ferroui_base::Rect)>>) {
1277                $crate::top_level_impl::TopLevelParent::top_level(self).set_paint(value)
1278            }
1279
1280            fn resized(
1281                &self,
1282            ) -> Option<std::rc::Rc<dyn Fn(ferroui_base::Size, ferroui_controls::WindowResizeReason)>> {
1283                $crate::top_level_impl::TopLevelParent::top_level(self).resized()
1284            }
1285
1286            fn set_resized(
1287                &self,
1288                value: Option<std::rc::Rc<dyn Fn(ferroui_base::Size, ferroui_controls::WindowResizeReason)>>,
1289            ) {
1290                $crate::top_level_impl::TopLevelParent::top_level(self).set_resized(value)
1291            }
1292
1293            fn scaling_changed(&self) -> Option<std::rc::Rc<dyn Fn(f64)>> {
1294                $crate::top_level_impl::TopLevelParent::top_level(self).scaling_changed()
1295            }
1296
1297            fn set_scaling_changed(&self, value: Option<std::rc::Rc<dyn Fn(f64)>>) {
1298                $crate::top_level_impl::TopLevelParent::top_level(self).set_scaling_changed(value)
1299            }
1300
1301            fn transparency_level_changed(
1302                &self,
1303            ) -> Option<std::rc::Rc<dyn Fn(ferroui_controls::WindowTransparencyLevel)>> {
1304                $crate::top_level_impl::TopLevelParent::top_level(self).transparency_level_changed()
1305            }
1306
1307            fn set_transparency_level_changed(
1308                &self,
1309                value: Option<std::rc::Rc<dyn Fn(ferroui_controls::WindowTransparencyLevel)>>,
1310            ) {
1311                $crate::top_level_impl::TopLevelParent::top_level(self).set_transparency_level_changed(value)
1312            }
1313
1314            fn set_input_root(&self, input_root: std::rc::Rc<dyn ferroui_base::input::IInputRoot>) {
1315                $crate::top_level_impl::TopLevelParent::top_level(self).set_input_root(input_root)
1316            }
1317
1318            fn point_to_client(&self, point: ferroui_base::PixelPoint) -> ferroui_base::Point {
1319                $crate::top_level_impl::TopLevelParent::top_level(self).point_to_client(point)
1320            }
1321
1322            fn point_to_screen(&self, point: ferroui_base::Point) -> ferroui_base::PixelPoint {
1323                $crate::top_level_impl::TopLevelParent::top_level(self).point_to_screen(point)
1324            }
1325
1326            fn set_cursor(&self, cursor: Option<std::rc::Rc<dyn ferroui_base::platform::ICursorImpl>>) {
1327                $crate::top_level_impl::TopLevelParent::top_level(self).set_cursor(cursor)
1328            }
1329
1330            fn closed(&self) -> Option<std::rc::Rc<dyn Fn()>> {
1331                $crate::top_level_impl::TopLevelParent::top_level(self).closed()
1332            }
1333
1334            fn set_closed(&self, value: Option<std::rc::Rc<dyn Fn()>>) {
1335                $crate::top_level_impl::TopLevelParent::top_level(self).set_closed(value)
1336            }
1337
1338            fn lost_focus(&self) -> Option<std::rc::Rc<dyn Fn()>> {
1339                $crate::top_level_impl::TopLevelParent::top_level(self).lost_focus()
1340            }
1341
1342            fn set_lost_focus(&self, value: Option<std::rc::Rc<dyn Fn()>>) {
1343                $crate::top_level_impl::TopLevelParent::top_level(self).set_lost_focus(value)
1344            }
1345
1346            fn create_popup(&self) -> Option<std::rc::Rc<dyn ferroui_controls::platform::IPopupImpl>> {
1347                $crate::top_level_impl::TopLevelParent::create_popup_core(self)
1348            }
1349
1350            fn set_transparency_level_hint(&self, transparency_levels: &[ferroui_controls::WindowTransparencyLevel]) {
1351                $crate::top_level_impl::TopLevelParent::top_level(self)
1352                    .set_transparency_level_hint(transparency_levels)
1353            }
1354
1355            fn transparency_level(&self) -> ferroui_controls::WindowTransparencyLevel {
1356                $crate::top_level_impl::TopLevelParent::top_level(self).transparency_level()
1357            }
1358
1359            fn acrylic_compensation_levels(&self) -> ferroui_controls::AcrylicPlatformCompensationLevels {
1360                $crate::top_level_impl::TopLevelParent::top_level(self).acrylic_compensation_levels()
1361            }
1362
1363            fn set_frame_theme_variant(
1364                &self,
1365                theme_variant: Option<ferroui_controls::platform::PlatformThemeVariant>,
1366            ) {
1367                $crate::top_level_impl::TopLevelParent::set_frame_theme_variant_core(self, theme_variant)
1368            }
1369
1370            fn as_any(&self) -> &dyn std::any::Any {
1371                self
1372            }
1373
1374            $($extra)*
1375        }
1376    };
1377}
1378pub(crate) use impl_top_level_contract;
1379
1380/// The shared top-level state of a top-level implementation of this
1381/// backend; `None` for an implementation of another backend.
1382pub(crate) fn top_level_of(top_level: &dyn ITopLevelImpl) -> Option<&Rc<TopLevelImpl>> {
1383    let any = top_level.as_any();
1384    if let Some(window) = any.downcast_ref::<crate::window_impl::WindowImpl>() {
1385        return Some(window.top_level());
1386    }
1387    if let Some(popup) = any.downcast_ref::<crate::popup_impl::PopupImpl>() {
1388        return Some(popup.top_level());
1389    }
1390    if let Some(embeddable) = any.downcast_ref::<crate::embeddable_top_level_impl::EmbeddableTopLevelImpl>() {
1391        return Some(embeddable.top_level());
1392    }
1393    None
1394}
1395
1396#[cfg(test)]
1397mod tests {
1398    use super::*;
1399    use ferroui_base::input::raw::{RawKeyEventType, RawPointerEventType};
1400    use ferroui_base::input::{FocusManager, InputElement, Key, KeyboardDevice, PhysicalKey, RawInputModifiers};
1401    struct InputRoot {
1402        root: Ref<InputElement>,
1403    }
1404
1405    impl IInputRoot for InputRoot {
1406        fn focus_manager(&self) -> Option<Rc<FocusManager>> {
1407            None
1408        }
1409        fn pointer_over_element(&self) -> Option<Ref<InputElement>> {
1410            None
1411        }
1412        fn set_pointer_over_element(&self, _value: Option<Ref<InputElement>>) {}
1413        fn cursor_element(&self) -> Option<Ref<InputElement>> {
1414            None
1415        }
1416        fn set_cursor_element(&self, _value: Option<Ref<InputElement>>) {}
1417        fn root_element(&self) -> Ref<InputElement> {
1418            self.root.clone()
1419        }
1420        fn focus_root(&self) -> Ref<InputElement> {
1421            self.root.clone()
1422        }
1423        fn pointer_over_invalidated(&self) {}
1424    }
1425
1426    /// A top-level without a native object (as after it is disposed), with
1427    /// a keyboard device, an input root and an input callback that records
1428    /// what it is given. Creating the factory does not need a window server.
1429    fn top_level(with_input_root: bool) -> (Rc<TopLevelImpl>, Rc<RefCell<Vec<Rc<dyn IRawInputEventArgs>>>>) {
1430        let keyboard: Rc<dyn IKeyboardDevice> = KeyboardDevice::new();
1431        FerroLocator::current_mutable().bind::<dyn IKeyboardDevice>().to_constant(keyboard);
1432        let top_level = TopLevelImpl::new(create_ferro_native().expect("factory"));
1433        if with_input_root {
1434            top_level.set_input_root(Rc::new(InputRoot { root: InputElement::new() }));
1435        }
1436        let received = Rc::new(RefCell::new(Vec::new()));
1437        let sink = received.clone();
1438        top_level.set_input(Some(Rc::new(move |e| sink.borrow_mut().push(e))));
1439        (top_level, received)
1440    }
1441
1442    fn mouse(top_level: &TopLevelImpl, type_: FrnRawMouseEventType, device: FrnPointerDeviceType, swallow: bool) {
1443        top_level.raw_mouse_event(
1444            type_,
1445            device,
1446            42,
1447            FrnInputModifiers::Shift | FrnInputModifiers::LeftMouseButton,
1448            FrnPoint { x: 10.5, y: 20.25 },
1449            FrnVector { x: 1.0, y: -2.0 },
1450            0.75,
1451            0.1,
1452            0.2,
1453            &|_| swallow,
1454        );
1455    }
1456
1457    #[test]
1458    fn pointer_events_become_raw_pointer_args() {
1459        let _dispatcher = Dispatcher::unit_test_scope();
1460        let _scope = FerroLocator::enter_scope();
1461        let (top_level, received) = top_level(true);
1462
1463        mouse(&top_level, FrnRawMouseEventType::LeftButtonDown, FrnPointerDeviceType::Mouse, false);
1464
1465        let received = received.borrow();
1466        assert_eq!(received.len(), 1);
1467        let e = received[0].downcast_ref::<RawPointerEventArgs>().expect("pointer args");
1468        assert_eq!(e.type_(), RawPointerEventType::LeftButtonDown);
1469        assert_eq!(e.position(), Point::new(10.5, 20.25));
1470        assert_eq!(e.input_modifiers(), RawInputModifiers::SHIFT | RawInputModifiers::LEFT_MOUSE_BUTTON);
1471        assert_eq!(e.timestamp(), 42);
1472        assert_eq!(e.point().pressure, 0.75);
1473        assert_eq!(e.point().x_tilt, 0.1);
1474        assert_eq!(e.point().y_tilt, 0.2);
1475        assert!(e.device().as_any().is::<MouseDevice>());
1476    }
1477
1478    #[test]
1479    fn pen_events_come_from_the_pen_device() {
1480        let _dispatcher = Dispatcher::unit_test_scope();
1481        let _scope = FerroLocator::enter_scope();
1482        let (top_level, received) = top_level(true);
1483
1484        mouse(&top_level, FrnRawMouseEventType::Move, FrnPointerDeviceType::Pen, false);
1485
1486        let received = received.borrow();
1487        let e = received[0].downcast_ref::<RawPointerEventArgs>().expect("pointer args");
1488        assert_eq!(e.type_(), RawPointerEventType::Move);
1489        assert!(e.device().as_any().is::<PenDevice>());
1490    }
1491
1492    #[test]
1493    fn wheel_and_gesture_events_carry_their_delta() {
1494        let _dispatcher = Dispatcher::unit_test_scope();
1495        let _scope = FerroLocator::enter_scope();
1496        let (top_level, received) = top_level(true);
1497
1498        // Wheel and gestures are never offered to the chrome hit test.
1499        mouse(&top_level, FrnRawMouseEventType::Wheel, FrnPointerDeviceType::Mouse, true);
1500        mouse(&top_level, FrnRawMouseEventType::Magnify, FrnPointerDeviceType::Mouse, true);
1501        mouse(&top_level, FrnRawMouseEventType::Rotate, FrnPointerDeviceType::Mouse, true);
1502        mouse(&top_level, FrnRawMouseEventType::Swipe, FrnPointerDeviceType::Mouse, true);
1503
1504        let received = received.borrow();
1505        assert_eq!(received.len(), 4);
1506        let wheel = received[0].downcast_ref::<RawMouseWheelEventArgs>().expect("wheel args");
1507        assert_eq!(wheel.delta(), Vector::new(1.0, -2.0));
1508        assert_eq!(wheel.position(), Point::new(10.5, 20.25));
1509        for (index, type_) in
1510            [(1, RawPointerEventType::Magnify), (2, RawPointerEventType::Rotate), (3, RawPointerEventType::Swipe)]
1511        {
1512            let gesture = received[index].downcast_ref::<RawPointerGestureEventArgs>().expect("gesture args");
1513            assert_eq!(gesture.type_(), type_);
1514            assert_eq!(gesture.delta(), Vector::new(1.0, -2.0));
1515        }
1516    }
1517
1518    #[test]
1519    fn chrome_hit_test_can_swallow_pointer_events() {
1520        let _dispatcher = Dispatcher::unit_test_scope();
1521        let _scope = FerroLocator::enter_scope();
1522        let (top_level, received) = top_level(true);
1523
1524        mouse(&top_level, FrnRawMouseEventType::LeftButtonDown, FrnPointerDeviceType::Mouse, true);
1525        assert!(received.borrow().is_empty());
1526
1527        // A type the toolkit does not know is dropped.
1528        mouse(&top_level, FrnRawMouseEventType(99), FrnPointerDeviceType::Mouse, false);
1529        assert!(received.borrow().is_empty());
1530    }
1531
1532    #[test]
1533    fn key_and_text_events_report_whether_they_were_handled() {
1534        let _dispatcher = Dispatcher::unit_test_scope();
1535        let _scope = FerroLocator::enter_scope();
1536        let (top_level, received) = top_level(true);
1537
1538        let handled = top_level.raw_key_event(
1539            FrnRawKeyEventType::KeyUp,
1540            7,
1541            FrnInputModifiers::Windows,
1542            FrnKey::FrnKeyA,
1543            FrnPhysicalKey::FrnPhysicalKeyA,
1544            Some("a".to_string()),
1545        );
1546        assert!(!handled);
1547        assert!(!top_level.raw_text_input_event(8, "ä"));
1548
1549        {
1550            let received = received.borrow();
1551            let key = received[0].downcast_ref::<RawKeyEventArgs>().expect("key args");
1552            assert_eq!(key.type_(), RawKeyEventType::KeyUp);
1553            assert_eq!(key.key(), Key::A);
1554            assert_eq!(key.physical_key(), PhysicalKey::A);
1555            assert_eq!(key.modifiers(), RawInputModifiers::META);
1556            assert_eq!(key.key_symbol().as_deref(), Some("a"));
1557            assert_eq!(key.timestamp(), 7);
1558            let text = received[1].downcast_ref::<RawTextInputEventArgs>().expect("text args");
1559            assert_eq!(text.text(), "ä");
1560        }
1561
1562        top_level.set_input(Some(Rc::new(|e| e.set_handled(true))));
1563        assert!(top_level.raw_key_event(
1564            FrnRawKeyEventType::KeyDown,
1565            9,
1566            FrnInputModifiers::FrnInputModifiersNone,
1567            FrnKey::FrnKeyEscape,
1568            FrnPhysicalKey::FrnPhysicalKeyNone,
1569            None,
1570        ));
1571        assert!(top_level.raw_text_input_event(10, "x"));
1572    }
1573
1574    #[test]
1575    fn drag_events_need_a_drag_drop_device() {
1576        let _dispatcher = Dispatcher::unit_test_scope();
1577        let _scope = FerroLocator::enter_scope();
1578        let (top_level, received) = top_level(true);
1579
1580        let effects = top_level.on_drag_event(
1581            FrnDragEventType::Enter,
1582            FrnPoint { x: 1.0, y: 2.0 },
1583            FrnInputModifiers::FrnInputModifiersNone,
1584            FrnDragDropEffects::Copy,
1585            None,
1586            std::ptr::null_mut(),
1587        );
1588        assert_eq!(effects, FrnDragDropEffects::None);
1589        assert!(received.borrow().is_empty());
1590    }
1591
1592    #[test]
1593    fn nothing_is_delivered_without_an_input_root() {
1594        let _dispatcher = Dispatcher::unit_test_scope();
1595        let _scope = FerroLocator::enter_scope();
1596        let (top_level, received) = top_level(false);
1597
1598        mouse(&top_level, FrnRawMouseEventType::Move, FrnPointerDeviceType::Mouse, false);
1599        assert!(!top_level.raw_text_input_event(1, "x"));
1600        assert!(!top_level.raw_key_event(
1601            FrnRawKeyEventType::KeyDown,
1602            1,
1603            FrnInputModifiers::FrnInputModifiersNone,
1604            FrnKey::FrnKeyA,
1605            FrnPhysicalKey::FrnPhysicalKeyA,
1606            None,
1607        ));
1608        assert!(received.borrow().is_empty());
1609    }
1610
1611    #[test]
1612    fn transparency_level_hint_picks_the_first_supported_level() {
1613        let _dispatcher = Dispatcher::unit_test_scope();
1614        let _scope = FerroLocator::enter_scope();
1615        let (top_level, _received) = top_level(false);
1616        let changes = Rc::new(RefCell::new(Vec::new()));
1617        let sink = changes.clone();
1618        top_level.set_transparency_level_changed(Some(Rc::new(move |level| sink.borrow_mut().push(level))));
1619
1620        // Mica and Blur are not supported on macOS; AcrylicBlur is.
1621        top_level.set_transparency_level_hint(&[
1622            WindowTransparencyLevel::mica(),
1623            WindowTransparencyLevel::blur(),
1624            WindowTransparencyLevel::acrylic_blur(),
1625            WindowTransparencyLevel::transparent(),
1626        ]);
1627        assert_eq!(top_level.transparency_level(), WindowTransparencyLevel::acrylic_blur());
1628
1629        // The current level is skipped, so the next supported one is taken.
1630        top_level
1631            .set_transparency_level_hint(&[WindowTransparencyLevel::acrylic_blur(), WindowTransparencyLevel::transparent()]);
1632        assert_eq!(top_level.transparency_level(), WindowTransparencyLevel::transparent());
1633
1634        // Nothing supported: back to None.
1635        top_level.set_transparency_level_hint(&[WindowTransparencyLevel::mica()]);
1636        assert_eq!(top_level.transparency_level(), WindowTransparencyLevel::none());
1637
1638        // Already None: no change is reported.
1639        top_level.set_transparency_level_hint(&[WindowTransparencyLevel::none()]);
1640        assert_eq!(
1641            *changes.borrow(),
1642            [
1643                WindowTransparencyLevel::acrylic_blur(),
1644                WindowTransparencyLevel::transparent(),
1645                WindowTransparencyLevel::none()
1646            ]
1647        );
1648    }
1649
1650    #[test]
1651    fn a_top_level_without_native_object_answers_with_defaults() {
1652        let _dispatcher = Dispatcher::unit_test_scope();
1653        let _scope = FerroLocator::enter_scope();
1654        let (top_level, _received) = top_level(false);
1655
1656        assert_eq!(top_level.desktop_scaling(), 1.0);
1657        assert!(top_level.native().is_none());
1658        assert!(top_level.handle().is_none());
1659        assert_eq!(top_level.client_size(), Size::default());
1660        assert!(top_level.surfaces().is_empty());
1661        assert!((top_level.render_surfaces())().is_empty());
1662        assert_eq!(top_level.point_to_client(PixelPoint::new(3, 4)), Point::default());
1663        assert_eq!(top_level.point_to_screen(Point::new(3.0, 4.0)), PixelPoint::default());
1664        assert_eq!(top_level.acrylic_compensation_levels(), AcrylicPlatformCompensationLevels::new(1.0, 0.0, 0.0));
1665        // No-ops without a native object.
1666        top_level.invalidate();
1667        top_level.set_cursor(None);
1668    }
1669
1670    // Not from upstream: the top-level reports the peer of its focus root to
1671    // native code when the focus root is a control.
1672    #[test]
1673    fn automation_peer_is_the_peer_of_the_focus_root_control() {
1674        let _dispatcher = Dispatcher::unit_test_scope();
1675        let _scope = FerroLocator::enter_scope();
1676        let (top_level, _) = top_level(false);
1677        assert!(top_level.get_automation_peer().is_none());
1678
1679        top_level.set_input_root(Rc::new(InputRoot { root: InputElement::new() }));
1680        assert!(top_level.get_automation_peer().is_none());
1681
1682        let root = ferroui_controls::Border::new();
1683        top_level.set_input_root(Rc::new(InputRoot { root: root.clone().upcast() }));
1684        assert_eq!(top_level.get_automation_peer(), Some(ControlAutomationPeer::create_peer_for_element(&root)));
1685    }
1686
1687    // Not from upstream: the surfaces of a top-level are objects of their
1688    // own, shared with the thread that renders.
1689    #[test]
1690    fn the_render_surfaces_are_shared_between_threads() {
1691        fn assert_send_sync<T: Send + Sync>() {}
1692        assert_send_sync::<TopLevelFramebufferSurface>();
1693        assert_send_sync::<MetalPlatformSurface>();
1694    }
1695
1696    struct CountingRenderTarget(Rc<Cell<u32>>);
1697
1698    impl IFrnSoftwareRenderTargetImpl for CountingRenderTarget {
1699        fn set_frame(&self, _fb: *mut FrnFramebuffer) -> Result<(), HResult> {
1700            self.0.set(self.0.get() + 1);
1701            Ok(())
1702        }
1703    }
1704
1705    fn scene_info() -> RenderTargetSceneInfo {
1706        RenderTargetSceneInfo::new(
1707            ferroui_base::PixelSize::new(1, 1),
1708            1.0,
1709            ferroui_base::rendering::composition::CompositionTransparencyLevel::None,
1710        )
1711    }
1712
1713    // Not from upstream: the framebuffer render target sizes a frame with
1714    // what the top-level saved in the surface.
1715    #[test]
1716    fn a_frame_is_sized_with_the_metrics_of_the_surface() {
1717        let frames = Rc::new(Cell::new(0));
1718        let state = FramebufferSurfaceState::new(Size::new(10.0, 5.0), 2.0);
1719        let target = FramebufferRenderTarget::new(
1720            state.clone(),
1721            IFrnSoftwareRenderTarget::from_impl(CountingRenderTarget(frames.clone())),
1722        );
1723
1724        let (framebuffer, properties) = target.lock(&scene_info());
1725        assert_eq!(framebuffer.size(), ferroui_base::PixelSize::new(20, 10));
1726        assert_eq!(framebuffer.dpi(), Vector::new(192.0, 192.0));
1727        assert_eq!(properties, FramebufferLockProperties::default());
1728        assert!(!target.retains_frame_contents());
1729        assert_eq!(frames.get(), 0);
1730        framebuffer.dispose();
1731        assert_eq!(frames.get(), 1);
1732
1733        *lock_shared(&state.metrics) = (Size::new(0.0, 0.0), 1.0);
1734        let (framebuffer, _) = target.lock(&scene_info());
1735        assert_eq!(framebuffer.size(), ferroui_base::PixelSize::new(1, 1));
1736        framebuffer.dispose();
1737        assert_eq!(frames.get(), 2);
1738    }
1739
1740    #[test]
1741    fn a_frame_is_not_presented_once_the_top_level_is_gone_or_the_target_is_disposed() {
1742        let frames = Rc::new(Cell::new(0));
1743        let state = FramebufferSurfaceState::new(Size::new(4.0, 4.0), 1.0);
1744        let target = FramebufferRenderTarget::new(
1745            state.clone(),
1746            IFrnSoftwareRenderTarget::from_impl(CountingRenderTarget(frames.clone())),
1747        );
1748
1749        // The top-level is disposed while a frame is being drawn.
1750        let (framebuffer, _) = target.lock(&scene_info());
1751        state.alive.store(false, Ordering::Release);
1752        framebuffer.dispose();
1753        assert_eq!(frames.get(), 0);
1754
1755        // The render target is disposed while a frame is being drawn.
1756        state.alive.store(true, Ordering::Release);
1757        let (framebuffer, _) = target.lock(&scene_info());
1758        target.dispose();
1759        framebuffer.dispose();
1760        assert_eq!(frames.get(), 0);
1761    }
1762
1763    #[test]
1764    #[should_panic(expected = "Cannot access a disposed object: FramebufferRenderTarget")]
1765    fn a_disposed_framebuffer_render_target_cannot_be_locked() {
1766        let state = FramebufferSurfaceState::new(Size::new(4.0, 4.0), 1.0);
1767        let target = FramebufferRenderTarget::new(
1768            state,
1769            IFrnSoftwareRenderTarget::from_impl(CountingRenderTarget(Rc::new(Cell::new(0)))),
1770        );
1771        target.dispose();
1772        let _ = target.lock(&scene_info());
1773    }
1774}