Skip to main content

ferroui_controls/
top_level.rs

1use crate::metadata::TemplatePartAttribute;
2use crate::platform::{
3    IInputPane, IInsetsManager, IPlatformHandle, IScreenImpl, ITopLevelImpl, PlatformThemeVariant, SafeAreaChangedArgs,
4};
5use crate::presentation_source::{try_get_service, ITopLevelRenderer, PresentationSource};
6use crate::primitives::{TemplateAppliedEventArgs, TemplatedControl, TemplatedControlImpl, TemplatedControlImplExt};
7use crate::top_level_host::TopLevelHost;
8use crate::{IToolTipService, ToolTip};
9use ferroui_base::{Point, Vector};
10use crate::{
11    Border, ContentControl, ContentControlImpl, Control, ControlImpl, PlatformInhibitionType, Screens, WindowResizeReason,
12    WindowTransparencyLevel, WindowTransparencyLevelCollection,
13};
14use ferroui_base::data::BindingPriority;
15use ferroui_base::input::platform::{ClipboardType, IClipboard, IPlatformClipboardManagerImpl};
16use ferroui_base::input::raw::{IRawInputEventArgs, RawKeyEventArgs, RawKeyEventType, RawPointerEventArgs, RawPointerEventType};
17use ferroui_base::input::{
18    FocusManager, IAccessKeyHandler, IInputManager, IInputRoot, IKeyboardNavigationHandler, InputElementImpl,
19    KeyEventArgs, KeyModifiers, KeyboardDevice, KeyboardNavigation, KeyboardNavigationMode, NavigationMethod,
20};
21use ferroui_base::interactivity::{
22    Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
23};
24use ferroui_base::layout::{ILayoutManager, LayoutHelper, LayoutManager, Layoutable, LayoutableImpl};
25use ferroui_base::logical_tree::LogicalTreeAttachmentEventArgs;
26use ferroui_base::media::{Brushes, IBrush, MediaContext, SolidColorBrush};
27use ferroui_base::platform::{
28    IOptionalFeatureProvider, IPlatformBehaviorInhibition, IPlatformSettings, ISystemNavigationManagerImpl,
29};
30use ferroui_base::reactive::{AnonymousObserver, Disposable, IDisposable};
31use ferroui_base::rendering::composition::CompositionTransparencyLevel;
32use ferroui_base::rendering::{IHitTester, IRenderer, RendererDiagnostics};
33use ferroui_base::styling::{
34    Container, ContainerSizing, IGlobalStyles, IStyle, IStyleHost, IThemeVariantHost, StyleHostRef, ThemeVariant,
35};
36use ferroui_base::threading::{Dispatcher, DispatcherTask};
37use ferroui_base::utilities::HandlerList;
38use ferroui_base::{
39    ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, AttachedProperty, DirectProperty,
40    FerroLocator, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs,
41    IFerroDependencyResolver, PixelPoint, Rect, Ref, Size, StaticType, StyledElementImpl, StyledProperty,
42    StyledPropertyOptions, Visual, VisualImpl,
43};
44use std::any::Any;
45use std::cell::{Cell, OnceCell, RefCell};
46use std::collections::HashMap;
47use std::rc::Rc;
48
49type PlatformImplBindings = HashMap<usize, Vec<Rc<dyn Fn()>>>;
50
51/// Base class for top-level widgets.
52///
53/// This class acts as a base for top level widget. It handles scheduling
54/// layout, styling and rendering as well as tracking the widget's
55/// [`client_size`](TopLevel::client_size).
56#[repr(C)]
57pub struct TopLevel {
58    base: ContentControl,
59    dependency_resolver: RefCell<Option<Rc<dyn IFerroDependencyResolver>>>,
60    input_manager: RefCell<Option<Rc<dyn IInputManager>>>,
61    tooltip_service: RefCell<Option<Rc<dyn IToolTipService>>>,
62    access_key_handler: RefCell<Option<Rc<dyn IAccessKeyHandler>>>,
63    keyboard_navigation_handler: RefCell<Option<Rc<dyn IKeyboardNavigationHandler>>>,
64    global_styles: RefCell<Option<Rc<dyn IGlobalStyles>>>,
65    global_styles_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
66    application_theme_host: RefCell<Option<Rc<dyn IThemeVariantHost>>>,
67    application_theme_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
68    back_gesture_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
69    resources_changed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
70    platform_impl_bindings: RefCell<PlatformImplBindings>,
71    scaling: Cell<f64>,
72    is_closed: Cell<bool>,
73    client_size: Cell<Size>,
74    frame_size: Cell<Option<Size>>,
75    actual_transparency_level: Cell<WindowTransparencyLevel>,
76    transparency_fallback_border: RefCell<Option<Ref<Border>>>,
77    visual_layer_manager: RefCell<Option<Ref<crate::primitives::VisualLayerManager>>>,
78    screens: RefCell<Option<Rc<Screens>>>,
79    storage_provider: RefCell<Option<Rc<dyn ferroui_base::platform::storage::IStorageProvider>>>,
80    source: OnceCell<Rc<PresentationSource>>,
81    top_level_host: OnceCell<Ref<TopLevelHost>>,
82    platform_impl: RefCell<Option<Rc<dyn ITopLevelImpl>>>,
83    insets_paddings: RefCell<Option<Rc<dyn IDisposable>>>,
84    insets_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
85    opened: HandlerList<dyn Fn()>,
86    closed: HandlerList<dyn Fn()>,
87    scaling_changed: HandlerList<dyn Fn()>,
88    platform_lost_focus: HandlerList<dyn Fn()>,
89    platform_deactivated: HandlerList<dyn Fn()>,
90    platform_position_changed: HandlerList<dyn Fn(PixelPoint)>,
91    opened_popups: RefCell<Vec<Ref<crate::primitives::Popup>>>,
92}
93
94ferro_class! {
95    TopLevel: ContentControl, virtuals TopLevelImpl: ContentControlImpl {
96        /// Handles a closed notification from the platform implementation.
97        fn handle_closed(this);
98        /// Handles a resize notification from the platform implementation:
99        /// `client_size` is the new client size and `reason` the reason for
100        /// the resize.
101        fn handle_resized(this, client_size: Size, reason: WindowResizeReason);
102        /// Raises the `Opened` event.
103        fn on_opened(this);
104        /// Raises the `Closed` event.
105        fn on_closed(this);
106        /// The popups that are currently open directly in this top level,
107        /// in the order they were opened.
108        fn opened_popups(this) -> Vec<Ref<crate::primitives::Popup>>;
109    }
110}
111
112ferro_impl_classes!(TopLevel: LayoutableImpl, InteractiveImpl, InputElementImpl, ControlImpl, ContentControlImpl);
113
114impl FerroObjectImpl for TopLevel {
115    fn constructed(this: &Self) {
116        Self::parent_constructed(this);
117        this.initialize();
118    }
119
120    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
121        Self::parent_on_property_changed(this, change);
122
123        if change.property() == ContentControl::content_property().as_property() {
124            this.invalidate_child_insets_padding();
125        } else {
126            let actions = this.platform_impl_bindings.borrow().get(&property_key(change.property())).cloned();
127            if let Some(actions) = actions {
128                for action in actions {
129                    action();
130                }
131            }
132        }
133    }
134}
135
136impl StyledElementImpl for TopLevel {
137    fn is_logical_root(_this: &Self) -> bool {
138        true
139    }
140
141    fn styling_parent(this: &Self) -> Option<StyleHostRef> {
142        let global_styles = this.global_styles.borrow().clone()?;
143        let host: Rc<dyn IStyleHost> = global_styles;
144        Some(StyleHostRef::Other(host))
145    }
146
147    fn is_theme_variant_root(this: &Self) -> bool {
148        this.application_theme_host.borrow().is_none()
149    }
150}
151
152impl VisualImpl for TopLevel {
153    fn bypass_flow_direction_policies(_this: &Self) -> bool {
154        true
155    }
156
157    fn invalidate_mirror_transform(_this: &Self) {
158        // Do nothing because TopLevel shouldn't apply MirrorTransform on himself.
159    }
160}
161
162impl TemplatedControlImpl for TopLevel {
163    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
164        Self::parent_on_apply_template(this, e);
165
166        {
167            *this.visual_layer_manager.borrow_mut() =
168                e.name_scope().find_as::<crate::primitives::VisualLayerManager>("PART_VisualLayerManager");
169        }
170
171        let Some(platform_impl) = this.platform_impl() else { return };
172
173        *this.transparency_fallback_border.borrow_mut() = e.name_scope().find_as::<Border>("PART_TransparencyFallback");
174        this.handle_transparency_level_changed(platform_impl.transparency_level());
175    }
176}
177
178impl TopLevelImpl for TopLevel {
179    fn handle_closed(this: &Self) {
180        let source = this.presentation_source().clone();
181        source.dispose();
182        this.stop_rendering();
183
184        debug_assert!(this.platform_impl.borrow().is_some());
185        // The PlatformImpl is completely invalid at this point
186        let platform_impl = this.platform_impl.borrow_mut().take();
187        if let Some(platform_impl) = platform_impl {
188            // The implementation may outlive the top-level it no longer
189            // belongs to: let go of the callbacks, one of which holds the
190            // top-level.
191            platform_impl.set_closed(None);
192            platform_impl.set_paint(None);
193            platform_impl.set_resized(None);
194            platform_impl.set_transparency_level_changed(None);
195            platform_impl.set_platform_specific_scene_info_changed(None);
196        }
197        this.scaling.set(1.0);
198
199        for subscription in this.global_styles_subscriptions.borrow_mut().drain(..) {
200            subscription.dispose();
201        }
202        if let Some(subscription) = this.application_theme_subscription.borrow_mut().take() {
203            subscription.dispose();
204        }
205
206        if let Some(subscription) = this.back_gesture_subscription.borrow_mut().take() {
207            subscription.dispose();
208        }
209
210        let this_ref = this.to_ref();
211        let logical_args = LogicalTreeAttachmentEventArgs::new(this_ref.clone().upcast(), this_ref.clone().upcast(), None);
212        this.notify_detached_from_logical_tree(&logical_args);
213
214        source.set_root_visual(None);
215
216        this.on_closed();
217
218        ILayoutManager::dispose(&**source.layout_manager());
219        this.platform_impl_bindings.borrow_mut().clear();
220
221        // The host and the top-level reference each other (visual child and
222        // logical child); nothing collects the pair, so the closed top-level
223        // leaves its host.
224        if let Some(host) = this.top_level_host.get() {
225            host.visual_children().remove(&this_ref.upcast());
226        }
227    }
228
229    fn handle_resized(this: &Self, client_size: Size, _reason: WindowResizeReason) {
230        this.set_client_size(client_size);
231        this.set_width(client_size.width);
232        this.set_height(client_size.height);
233        this.layout_manager().execute_layout_pass();
234        this.renderer().resized(client_size);
235    }
236
237    fn on_opened(this: &Self) {
238        raise(&this.opened);
239    }
240
241    fn on_closed(this: &Self) {
242        raise(&this.closed);
243    }
244
245    fn opened_popups(this: &Self) -> Vec<Ref<crate::primitives::Popup>> {
246        this.opened_popups.borrow().clone()
247    }
248}
249
250/// Raises a plain event through the dispatcher, the way the reference
251/// implementation sends its event invocations.
252fn raise(handlers: &HandlerList<dyn Fn()>) {
253    if handlers.is_empty() {
254        return;
255    }
256    let snapshot = handlers.snapshot();
257    let _ = Dispatcher::ui_thread().invoke_local(move || {
258        for (_, handler) in snapshot.iter() {
259            handler();
260        }
261    });
262}
263
264fn property_key(property: &'static FerroProperty) -> usize {
265    property as *const FerroProperty as usize
266}
267
268fn subscribe(handlers: &HandlerList<dyn Fn()>, owner: &TopLevel, select: fn(&TopLevel) -> &HandlerList<dyn Fn()>, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
269    let token = handlers.add(handler);
270    let weak = owner.to_ref().downgrade();
271    Disposable::create(move || {
272        if let Some(owner) = weak.upgrade() {
273            select(&owner).remove(token);
274        }
275    })
276}
277
278/// Chains `handler` onto the lost-focus callback of a platform
279/// implementation (C# `impl.LostFocus += handler`).
280fn add_lost_focus(platform_impl: &Rc<dyn ITopLevelImpl>, handler: Rc<dyn Fn()>) {
281    let previous = platform_impl.lost_focus();
282    platform_impl.set_lost_focus(Some(Rc::new(move || {
283        if let Some(previous) = &previous {
284            previous();
285        }
286        handler();
287    })));
288}
289
290impl TopLevel {
291    /// The named parts expected in the control template.
292    pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] = &[
293        TemplatePartAttribute::new("PART_TransparencyFallback", <Border as StaticType>::TYPE),
294        TemplatePartAttribute::new(
295            "PART_VisualLayerManager",
296            <crate::primitives::VisualLayerManager as StaticType>::TYPE,
297        ),
298    ];
299
300    /// The layer manager of the control template, once the template is
301    /// applied (private protected upstream: for derived classes).
302    pub fn visual_layer_manager(&self) -> Option<Ref<crate::primitives::VisualLayerManager>> {
303        self.visual_layer_manager.borrow().clone()
304    }
305
306    /// Enables the overlay, popup overlay and text selector layers of the
307    /// layer manager of the control template (private protected upstream:
308    /// for the top-levels that host their popups and overlays themselves).
309    pub fn enable_visual_layer_manager_layers(&self) {
310        if let Some(vlm) = self.visual_layer_manager() {
311            vlm.set_enable_overlay_layer(true);
312            vlm.set_enable_popup_overlay_layer(true);
313            vlm.set_enable_text_selector_layer(true);
314        }
315    }
316}
317
318ferroui_base::ferro_properties! { impl TopLevel, also [
319    TopLevel::system_bar_color_property,
320    TopLevel::auto_safe_area_padding_property,
321] {
322    ferro_property!(
323        /// Defines the `ClientSize` property.
324        pub fn client_size_property() -> DirectProperty<TopLevel, Size> {
325            FerroProperty::register_direct::<TopLevel, _>("ClientSize", |o| o.client_size(), None, Size::default())
326        }
327    );
328
329    ferro_property!(
330        /// Defines the `FrameSize` property.
331        pub fn frame_size_property() -> DirectProperty<TopLevel, Option<Size>> {
332            FerroProperty::register_direct::<TopLevel, _>("FrameSize", |o| o.frame_size(), None, None)
333        }
334    );
335
336    ferro_property!(
337        /// Defines the `TransparencyLevelHint` property.
338        pub fn transparency_level_hint_property() -> StyledProperty<WindowTransparencyLevelCollection> {
339            FerroProperty::register::<TopLevel, _>("TransparencyLevelHint", WindowTransparencyLevelCollection::default())
340        }
341    );
342
343    ferro_property!(
344        /// Defines the `ActualTransparencyLevel` property.
345        pub fn actual_transparency_level_property() -> DirectProperty<TopLevel, WindowTransparencyLevel> {
346            FerroProperty::register_direct::<TopLevel, _>(
347                "ActualTransparencyLevel",
348                |o| o.actual_transparency_level(),
349                None,
350                WindowTransparencyLevel::none(),
351            )
352        }
353    );
354
355    ferro_property!(
356        /// Defines the `TransparencyBackgroundFallback` property.
357        pub fn transparency_background_fallback_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
358            let white: Rc<dyn IBrush> = Brushes::white();
359            FerroProperty::register::<TopLevel, _>("TransparencyBackgroundFallback", Some(white))
360        }
361    );
362} }
363
364impl TopLevel {
365    /// Defines the `ActualThemeVariant` property.
366    pub fn actual_theme_variant_property() -> &'static StyledProperty<Option<ThemeVariant>> {
367        ThemeVariant::actual_theme_variant_property()
368    }
369
370    /// Defines the `RequestedThemeVariant` property.
371    pub fn requested_theme_variant_property() -> &'static StyledProperty<Option<ThemeVariant>> {
372        ThemeVariant::requested_theme_variant_property()
373    }
374
375    ferro_property!(for TopLevel;
376        /// Defines the SystemBarColor attached property.
377        pub fn system_bar_color_property() -> AttachedProperty<Option<Ref<SolidColorBrush>>> {
378            FerroProperty::register_attached_with::<TopLevel, Control, _>(
379                "SystemBarColor",
380                StyledPropertyOptions::new(None).inherits(true),
381            )
382        }
383    );
384
385    ferro_property!(for TopLevel;
386        /// Defines the AutoSafeAreaPadding attached property.
387        pub fn auto_safe_area_padding_property() -> AttachedProperty<bool> {
388            FerroProperty::register_attached::<TopLevel, Control, _>("AutoSafeAreaPadding", true)
389        }
390    );
391
392    ferro_routed_event!(
393        /// Defines the `BackRequested` event.
394        pub fn back_requested_event() -> RoutedEvent<RoutedEventArgs> {
395            RoutedEvent::register::<TopLevel, _>("BackRequested", RoutingStrategies::BUBBLE)
396        }
397    );
398
399    fn static_constructor() {
400        KeyboardNavigation::tab_navigation_property().override_default_value::<TopLevel>(KeyboardNavigationMode::Cycle);
401        Container::sizing_property().override_default_value::<TopLevel>(ContainerSizing::WidthAndHeight);
402
403        Layoutable::affects_measure::<TopLevel>(&[Self::client_size_property().as_property()]);
404
405        Self::system_bar_color_property().changed().add_class_handler::<Control>(|view, e| {
406            let (_, new_value) = e.get_old_and_new_value::<Option<Ref<SolidColorBrush>>>();
407            if let Some(color_brush) = new_value {
408                if let Some(insets_manager) =
409                    view.parent().and_then(|parent| parent.cast::<TopLevel>()).and_then(|tl| tl.insets_manager())
410                {
411                    insets_manager.set_system_bar_color(Some(color_brush.color()));
412                }
413
414                if let Some(insets) = view.downcast_ref::<TopLevel>().and_then(|tl| tl.insets_manager()) {
415                    insets.set_system_bar_color(Some(color_brush.color()));
416                }
417            }
418        });
419
420        Self::auto_safe_area_padding_property().changed().add_class_handler::<Control>(|view, _| {
421            let top_level = view
422                .to_ref()
423                .cast::<TopLevel>()
424                .or_else(|| view.parent().and_then(|parent| parent.cast::<TopLevel>()));
425            if let Some(top_level) = top_level {
426                top_level.invalidate_child_insets_padding();
427            }
428        });
429
430        // The subscription lives as long as the property.
431        let _ = ToolTip::service_enabled_property().changed().subscribe(Self::on_tool_tip_service_enabled_changed);
432    }
433
434    fn on_tool_tip_service_enabled_changed(args: &FerroPropertyChangedEventArgs<'_>) {
435        if args.get_new_value::<bool>() && args.priority() != BindingPriority::Inherited {
436            let Some(visual) = args.sender().downcast_ref::<Visual>() else { return };
437            let Some(top_level) = Self::get_top_level(Some(visual)) else { return };
438            let offset = visual
439                .translate_point(Point::default(), &top_level)
440                .expect("the top-level is an ancestor of the visual");
441            top_level.update_tool_tip(visual.bounds().translate(Vector::new(offset.x, offset.y)));
442        }
443    }
444
445    fn update_tool_tip(&self, dirty_rect: Rect) {
446        let tooltip_service = self.tooltip_service.borrow().clone();
447        let Some(tooltip_service) = tooltip_service else { return };
448        if !self.is_pointer_over() {
449            return;
450        }
451        let source = self.presentation_source();
452        let Some(last_pos) = source.get_last_pointer_position(self) else { return };
453
454        let client_point = Visual::point_to_client(self, last_pos);
455        if dirty_rect.contains(client_point) {
456            let input_root: Rc<dyn IInputRoot> = source.clone();
457            let candidate = self.input_hit_test_with(client_point, false).map(|element| element.upcast::<Visual>());
458            tooltip_service.update(&input_root, candidate);
459        }
460    }
461
462    /// Field initialisation of a top-level over the platform-specific
463    /// implementation `platform_impl`, with the services of the current
464    /// service locator.
465    pub fn construct(platform_impl: Rc<dyn ITopLevelImpl>) -> Self {
466        Self::construct_with_resolver(platform_impl, None)
467    }
468
469    /// Field initialisation of a top-level; `dependency_resolver` is the
470    /// dependency resolver to use, the current service locator when `None`.
471    pub fn construct_with_resolver(
472        platform_impl: Rc<dyn ITopLevelImpl>,
473        dependency_resolver: Option<Rc<dyn IFerroDependencyResolver>>,
474    ) -> Self {
475        Self {
476            base: ContentControl::construct(),
477            dependency_resolver: RefCell::new(dependency_resolver),
478            input_manager: RefCell::new(None),
479            tooltip_service: RefCell::new(None),
480            access_key_handler: RefCell::new(None),
481            keyboard_navigation_handler: RefCell::new(None),
482            global_styles: RefCell::new(None),
483            global_styles_subscriptions: RefCell::new(Vec::new()),
484            application_theme_host: RefCell::new(None),
485            application_theme_subscription: RefCell::new(None),
486            back_gesture_subscription: RefCell::new(None),
487            resources_changed_subscription: RefCell::new(None),
488            platform_impl_bindings: RefCell::new(HashMap::new()),
489            scaling: Cell::new(1.0),
490            is_closed: Cell::new(false),
491            client_size: Cell::new(Size::default()),
492            frame_size: Cell::new(None),
493            actual_transparency_level: Cell::new(WindowTransparencyLevel::none()),
494            transparency_fallback_border: RefCell::new(None),
495            visual_layer_manager: RefCell::new(None),
496            screens: RefCell::new(None),
497            storage_provider: RefCell::new(None),
498            source: OnceCell::new(),
499            top_level_host: OnceCell::new(),
500            platform_impl: RefCell::new(Some(platform_impl)),
501            insets_paddings: RefCell::new(None),
502            insets_subscription: RefCell::new(None),
503            opened: HandlerList::new(),
504            closed: HandlerList::new(),
505            scaling_changed: HandlerList::new(),
506            platform_lost_focus: HandlerList::new(),
507            platform_deactivated: HandlerList::new(),
508            platform_position_changed: HandlerList::new(),
509            opened_popups: RefCell::new(Vec::new()),
510        }
511    }
512
513    /// The constructor body.
514    fn initialize(&self) {
515        let this = self.to_ref();
516        let platform_impl = self.platform_impl().expect(
517            "Could not create window implementation: maybe no windowing subsystem was initialized?",
518        );
519        let dependency_resolver =
520            self.dependency_resolver.borrow_mut().take().unwrap_or_else(FerroLocator::current);
521        let dependency_resolver: &dyn IFerroDependencyResolver = &*dependency_resolver;
522
523        let host_visual = TopLevelHost::new(&this);
524        if self.top_level_host.set(host_visual.clone()).is_err() {
525            unreachable!("the top-level is initialized once");
526        }
527        host_visual.set_parent(this.clone());
528        self.logical_children().add(host_visual.clone().upcast());
529
530        let source = PresentationSource::new(
531            host_visual.upcast(),
532            &this.clone().upcast(),
533            platform_impl.clone(),
534            dependency_resolver,
535        );
536        if self.source.set(source.clone()).is_err() {
537            unreachable!("the top-level is initialized once");
538        }
539        // The handler lives as long as the renderer; it does nothing once
540        // the top-level is gone.
541        let _ = source.typed_renderer().scene_invalidated(Rc::new({
542            let weak = this.downgrade();
543            move |e| {
544                if let Some(this) = weak.upgrade() {
545                    this.update_tool_tip(e.dirty_rect());
546                }
547            }
548        }));
549
550        self.scaling.set(LayoutHelper::validate_scaling(platform_impl.render_scaling()));
551        self.actual_transparency_level.set(platform_impl.transparency_level());
552
553        let renderer = source.typed_renderer();
554        renderer.set_transparency_level(to_composition_transparency_level(self.actual_transparency_level.get()));
555        renderer.set_platform_specific_scene_info(platform_impl.platform_specific_scene_info());
556
557        *self.access_key_handler.borrow_mut() = try_get_service::<dyn IAccessKeyHandler>(dependency_resolver);
558        *self.input_manager.borrow_mut() = try_get_service::<dyn IInputManager>(dependency_resolver);
559        *self.tooltip_service.borrow_mut() = try_get_service::<dyn IToolTipService>(dependency_resolver);
560        *self.keyboard_navigation_handler.borrow_mut() =
561            try_get_service::<dyn IKeyboardNavigationHandler>(dependency_resolver);
562        *self.global_styles.borrow_mut() = try_get_service::<dyn IGlobalStyles>(dependency_resolver);
563        *self.application_theme_host.borrow_mut() = try_get_service::<dyn IThemeVariantHost>(dependency_resolver);
564
565        let weak = this.downgrade();
566        // The closed callback holds the top-level: as with the delegates of
567        // the reference, the platform implementation keeps its top-level
568        // alive until it reports that it has closed. The callbacks are
569        // released in `handle_closed`.
570        platform_impl.set_closed(Some(Rc::new({
571            let this = this.clone();
572            move || this.ensure_closed()
573        })));
574        platform_impl.set_paint(Some(Rc::new({
575            let weak = weak.clone();
576            move |rect| {
577                if let Some(this) = weak.upgrade() {
578                    this.handle_paint(rect);
579                }
580            }
581        })));
582        platform_impl.set_resized(Some(Rc::new({
583            let weak = weak.clone();
584            move |client_size, reason| {
585                if let Some(this) = weak.upgrade() {
586                    this.handle_resized(client_size, reason);
587                }
588            }
589        })));
590        // The handler lives as long as the platform implementation; it does
591        // nothing once the top-level is gone or closed.
592        let _ = crate::presentation_source::add_scaling_changed(
593            &platform_impl,
594            Rc::new({
595                let weak = weak.clone();
596                move |scaling| {
597                    if let Some(this) = weak.upgrade() {
598                        this.handle_scaling_changed(scaling);
599                    }
600                }
601            }),
602        );
603        platform_impl.set_transparency_level_changed(Some(Rc::new({
604            let weak = weak.clone();
605            move |level| {
606                if let Some(this) = weak.upgrade() {
607                    this.handle_transparency_level_changed(level);
608                }
609            }
610        })));
611        platform_impl.set_platform_specific_scene_info_changed(Some(Rc::new({
612            let weak = weak.clone();
613            move |scene_info| {
614                if let Some(this) = weak.upgrade() {
615                    this.handle_platform_specific_scene_info_changed(scene_info);
616                }
617            }
618        })));
619
620        self.create_platform_impl_binding(Self::transparency_level_hint_property(), {
621            let weak = weak.clone();
622            move |hint: WindowTransparencyLevelCollection| {
623                if let Some(platform_impl) = weak.upgrade().and_then(|this| this.platform_impl()) {
624                    platform_impl.set_transparency_level_hint(&hint);
625                }
626            }
627        });
628
629        let keyboard_navigation_handler = self.keyboard_navigation_handler.borrow().clone();
630        if let Some(handler) = keyboard_navigation_handler {
631            handler.set_owner(&this.clone().upcast());
632        }
633        let access_key_handler = self.access_key_handler.borrow().clone();
634        if let Some(handler) = access_key_handler {
635            handler.set_owner(&this.clone().upcast());
636        }
637
638        let global_styles = self.global_styles.borrow().clone();
639        if let Some(global_styles) = &global_styles {
640            let added = global_styles.global_styles_added(Rc::new({
641                let weak = weak.clone();
642                move |styles: &[Rc<dyn IStyle>]| {
643                    if let Some(this) = weak.upgrade() {
644                        this.styles_added(styles);
645                    }
646                }
647            }));
648            let removed = global_styles.global_styles_removed(Rc::new({
649                let weak = weak.clone();
650                move |styles: &[Rc<dyn IStyle>]| {
651                    if let Some(this) = weak.upgrade() {
652                        this.styles_removed(styles);
653                    }
654                }
655            }));
656            self.global_styles_subscriptions.borrow_mut().extend([added, removed]);
657        }
658
659        let application_theme_host = self.application_theme_host.borrow().clone();
660        if let Some(host) = &application_theme_host {
661            let _ = self.set_value_with_priority(
662                Self::actual_theme_variant_property(),
663                host.actual_theme_variant(),
664                BindingPriority::Template,
665            );
666            let weak_host = Rc::downgrade(host);
667            let subscription = host.actual_theme_variant_changed(Rc::new({
668                let weak = weak.clone();
669                move || {
670                    if let (Some(this), Some(host)) = (weak.upgrade(), weak_host.upgrade()) {
671                        this.global_actual_theme_variant_changed(&*host);
672                    }
673                }
674            }));
675            *self.application_theme_subscription.borrow_mut() = Some(subscription);
676        } else {
677            ThemeVariant::update_actual_theme_variant(self);
678        }
679
680        self.create_platform_impl_binding(Self::actual_theme_variant_property(), {
681            let weak = weak.clone();
682            move |variant: Option<ThemeVariant>| {
683                if let Some(platform_impl) = weak.upgrade().and_then(|this| this.platform_impl()) {
684                    platform_impl.set_frame_theme_variant(variant.as_ref().and_then(to_platform_theme_variant));
685                }
686            }
687        });
688
689        self.set_client_size(platform_impl.client_size());
690
691        if let Some(application_resources) = &global_styles {
692            let subscription = application_resources.resources_changed(Rc::new({
693                let weak = weak.clone();
694                move |e| {
695                    if let Some(this) = weak.upgrade() {
696                        this.notify_resources_changed(*e, true);
697                    }
698                }
699            }));
700            *self.resources_changed_subscription.borrow_mut() = Some(subscription);
701        }
702
703        add_lost_focus(
704            &platform_impl,
705            Rc::new({
706                let weak = weak.clone();
707                move || {
708                    if let Some(this) = weak.upgrade() {
709                        this.platform_impl_lost_focus();
710                        for (_, handler) in this.platform_lost_focus.snapshot().iter() {
711                            handler();
712                        }
713                    }
714                }
715            }),
716        );
717
718        // The handlers that others add to the window callbacks of the
719        // platform implementation go through the top-level (see
720        // `platform_deactivated`); a window base replaces these callbacks
721        // with its own, which raise the same lists.
722        if let Some(window_base_impl) = platform_impl.as_window_base_impl() {
723            window_base_impl.set_deactivated(Some(Rc::new({
724                let weak = weak.clone();
725                move || {
726                    if let Some(this) = weak.upgrade() {
727                        this.raise_platform_deactivated();
728                    }
729                }
730            })));
731            window_base_impl.set_position_changed(Some(Rc::new({
732                let weak = weak.clone();
733                move |position| {
734                    if let Some(this) = weak.upgrade() {
735                        this.raise_platform_position_changed(position);
736                    }
737                }
738            })));
739        }
740
741        if let Some(system_navigation_manager) = self.optional_feature::<dyn ISystemNavigationManagerImpl>() {
742            // As in the reference, the handler stays for the life of the
743            // platform implementation.
744            let _ = system_navigation_manager.back_requested(Rc::new({
745                let weak = weak.clone();
746                move |e: &Rc<RoutedEventArgs>| {
747                    if let Some(this) = weak.upgrade() {
748                        e.set_routed_event(Some(Self::back_requested_event()));
749                        let e = e.clone();
750                        let _ = Dispatcher::ui_thread().invoke_local(move || this.raise_event(&*e));
751                    }
752                }
753            }));
754        }
755
756        let input_manager = self.input_manager.borrow().clone();
757        *self.back_gesture_subscription.borrow_mut() = input_manager.map(|input_manager| {
758            input_manager.pre_process().subscribe(Rc::new(AnonymousObserver::new({
759                let weak = weak.clone();
760                move |e: Rc<dyn IRawInputEventArgs>| {
761                    if let Some(this) = weak.upgrade() {
762                        this.back_gesture_pre_process(&*e);
763                    }
764                }
765            })))
766        });
767    }
768
769    fn back_gesture_pre_process(&self, e: &dyn IRawInputEventArgs) {
770        let input_root = self.input_root();
771        if !std::ptr::addr_eq(Rc::as_ptr(e.root()), Rc::as_ptr(&input_root)) {
772            return;
773        }
774
775        let mut back_requested = false;
776
777        if let Some(raw_key_event_args) = e.downcast_ref::<RawKeyEventArgs>() {
778            if raw_key_event_args.type_() == RawKeyEventType::KeyDown {
779                if let Some(platform_settings) = self.platform_settings() {
780                    let hotkeys = platform_settings.hotkey_configuration();
781
782                    let mut key_event = KeyEventArgs::new();
783                    key_event.key_modifiers = KeyModifiers::from_bits_retain(raw_key_event_args.modifiers().bits());
784                    key_event.key = raw_key_event_args.key();
785                    key_event.physical_key = raw_key_event_args.physical_key();
786                    key_event.key_device_type = raw_key_event_args.key_device_type();
787                    key_event.key_symbol = raw_key_event_args.key_symbol();
788
789                    back_requested = hotkeys.back.iter().any(|key| key.matches(Some(&key_event)));
790                }
791            }
792        } else if let Some(pointer_event_args) = e.downcast_ref::<RawPointerEventArgs>() {
793            back_requested = pointer_event_args.type_() == RawPointerEventType::XButton1Down;
794        }
795
796        if back_requested {
797            let back_requested_event_args = Rc::new(RoutedEventArgs::with_event(Self::back_requested_event()));
798            let this = self.to_ref();
799            let args = back_requested_event_args.clone();
800            let _ = Dispatcher::ui_thread().invoke_local(move || this.raise_event(&*args));
801
802            e.set_handled(back_requested_event_args.handled());
803        }
804    }
805
806    /// Fired when the window is opened. Disposing the returned handle
807    /// unsubscribes.
808    pub fn opened(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
809        subscribe(&self.opened, self, |tl| &tl.opened, Rc::new(handler))
810    }
811
812    /// Fired when the window is closed. Disposing the returned handle
813    /// unsubscribes.
814    pub fn closed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
815        subscribe(&self.closed, self, |tl| &tl.closed, Rc::new(handler))
816    }
817
818    /// Fired when the top-level's scaling changes. Disposing the returned
819    /// handle unsubscribes.
820    pub fn scaling_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
821        subscribe(&self.scaling_changed, self, |tl| &tl.scaling_changed, Rc::new(handler))
822    }
823
824    /// Adds a handler to the lost-focus notification of the platform
825    /// implementation (C# `PlatformImpl.LostFocus += handler`); it runs
826    /// after the top-level's own handling. Disposing the returned handle
827    /// removes it.
828    ///
829    /// The callbacks of a platform implementation hold a single function,
830    /// so handlers that come and go (an open popup watching its parent) are
831    /// kept in a list of the top-level instead of being chained onto the
832    /// callback, where a removed handler would stay behind.
833    pub fn platform_lost_focus(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
834        subscribe(&self.platform_lost_focus, self, |tl| &tl.platform_lost_focus, Rc::new(handler))
835    }
836
837    /// Adds a handler to the deactivated notification of a window platform
838    /// implementation (C# `impl.Deactivated += handler`); see
839    /// [`platform_lost_focus`](Self::platform_lost_focus).
840    pub fn platform_deactivated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
841        subscribe(&self.platform_deactivated, self, |tl| &tl.platform_deactivated, Rc::new(handler))
842    }
843
844    /// Adds a handler to the position-changed notification of a window
845    /// platform implementation (C# `impl.PositionChanged += handler`); see
846    /// [`platform_lost_focus`](Self::platform_lost_focus).
847    pub fn platform_position_changed(&self, handler: impl Fn(PixelPoint) + 'static) -> Rc<dyn IDisposable> {
848        let token = self.platform_position_changed.add(Rc::new(handler));
849        let weak = self.to_ref().downgrade();
850        Disposable::create(move || {
851            if let Some(this) = weak.upgrade() {
852                this.platform_position_changed.remove(token);
853            }
854        })
855    }
856
857    pub(crate) fn raise_platform_deactivated(&self) {
858        for (_, handler) in self.platform_deactivated.snapshot().iter() {
859            handler();
860        }
861    }
862
863    pub(crate) fn raise_platform_position_changed(&self, position: PixelPoint) {
864        for (_, handler) in self.platform_position_changed.snapshot().iter() {
865            handler(position);
866        }
867    }
868
869    /// The client size of the window.
870    pub fn client_size(&self) -> Size {
871        self.client_size.get()
872    }
873
874    /// Sets the client size of the window (protected upstream: for derived
875    /// classes).
876    pub fn set_client_size(&self, value: Size) {
877        self.set_and_raise_cell(Self::client_size_property(), &self.client_size, value);
878    }
879
880    /// The total size of the window.
881    pub fn frame_size(&self) -> Option<Size> {
882        self.frame_size.get()
883    }
884
885    /// Sets the total size of the window (protected upstream: for derived
886    /// classes).
887    pub fn set_frame_size(&self, value: Option<Size>) {
888        self.set_and_raise_cell(Self::frame_size_property(), &self.frame_size, value);
889    }
890
891    /// The transparency levels that the top-level should use when possible.
892    /// Accepts multiple values which are applied in a fallback order. For
893    /// instance, with "Mica, Blur" Mica will be applied only on platforms
894    /// where it is possible, and Blur will be used on the rest of them.
895    /// Default value is an empty collection or "None".
896    pub fn transparency_level_hint(&self) -> WindowTransparencyLevelCollection {
897        self.get_value(Self::transparency_level_hint_property())
898    }
899
900    pub fn set_transparency_level_hint(&self, value: WindowTransparencyLevelCollection) {
901        self.set_value(Self::transparency_level_hint_property(), value)
902    }
903
904    /// The achieved transparency level that the platform was able to
905    /// provide.
906    pub fn actual_transparency_level(&self) -> WindowTransparencyLevel {
907        self.actual_transparency_level.get()
908    }
909
910    fn set_actual_transparency_level(&self, value: WindowTransparencyLevel) {
911        self.set_and_raise_cell(Self::actual_transparency_level_property(), &self.actual_transparency_level, value);
912    }
913
914    /// The brush that transparency will blend with when transparency is not
915    /// supported. By default this is a solid white brush.
916    pub fn transparency_background_fallback(&self) -> Option<Rc<dyn IBrush>> {
917        self.get_value(Self::transparency_background_fallback_property())
918    }
919
920    pub fn set_transparency_background_fallback(&self, value: Option<Rc<dyn IBrush>>) {
921        self.set_value(Self::transparency_background_fallback_property(), value)
922    }
923
924    /// The theme variant requested for the top-level and its descendants.
925    pub fn requested_theme_variant(&self) -> Option<ThemeVariant> {
926        self.get_value(Self::requested_theme_variant_property())
927    }
928
929    pub fn set_requested_theme_variant(&self, value: Option<ThemeVariant>) {
930        self.set_value(Self::requested_theme_variant_property(), value)
931    }
932
933    /// Occurs when physical Back Button is pressed or a back navigation has
934    /// been requested.
935    pub fn back_requested(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
936        self.add_handler(Self::back_requested_event(), handler)
937    }
938
939    /// The layout manager of the top-level.
940    pub fn layout_manager(&self) -> Rc<LayoutManager> {
941        self.presentation_source().layout_manager().clone()
942    }
943
944    /// The platform-specific window implementation; `None` once the
945    /// top-level has closed.
946    pub fn platform_impl(&self) -> Option<Rc<dyn ITopLevelImpl>> {
947        self.platform_impl.borrow().clone()
948    }
949
950    /// Tries to get the platform handle for the top-level-derived control.
951    ///
952    /// Returns the handle describing the window, or `None` if the handle
953    /// could not be retrieved.
954    pub fn try_get_platform_handle(&self) -> Option<Rc<dyn IPlatformHandle>> {
955        self.platform_impl().and_then(|platform_impl| platform_impl.handle())
956    }
957
958    /// Calls `on_value` with the value of `property` now and whenever the
959    /// property changes, for as long as the top-level has a platform
960    /// implementation (private protected upstream: for derived classes).
961    pub fn create_platform_impl_binding<T: ferroui_base::PropertyValue>(
962        &self,
963        property: &'static StyledProperty<T>,
964        on_value: impl Fn(T) + 'static,
965    ) {
966        let weak = self.to_ref().downgrade();
967        let update_platform_impl: Rc<dyn Fn()> = Rc::new(move || {
968            if let Some(this) = weak.upgrade() {
969                if this.platform_impl.borrow().is_some() {
970                    on_value(this.get_value(property));
971                }
972            }
973        });
974        self.platform_impl_bindings
975            .borrow_mut()
976            .entry(property_key(property.as_property()))
977            .or_default()
978            .push(update_platform_impl.clone());
979
980        // execute the action now to handle the default value, which may have been overridden
981        update_platform_impl();
982    }
983
984    /// The presentation source of the top-level.
985    pub fn presentation_source(&self) -> &Rc<PresentationSource> {
986        self.source.get().expect("the presentation source is created with the top-level")
987    }
988
989    /// The host of the top-level: the root of its visual tree.
990    pub(crate) fn top_level_host(&self) -> &Ref<TopLevelHost> {
991        self.top_level_host.get().expect("the host is created with the top-level")
992    }
993
994    /// The input root of the top-level.
995    pub fn input_root(&self) -> Rc<dyn IInputRoot> {
996        self.presentation_source().clone()
997    }
998
999    /// The renderer for the window.
1000    pub fn renderer(&self) -> Rc<dyn ITopLevelRenderer> {
1001        self.presentation_source().typed_renderer().clone()
1002    }
1003
1004    /// The hit tester used instead of the renderer's. This is here purely
1005    /// for unit tests that don't want to set up a proper hit-testable
1006    /// visual tree.
1007    pub fn hit_tester_override(&self) -> Option<Rc<dyn IHitTester>> {
1008        self.presentation_source().hit_tester_override()
1009    }
1010
1011    pub fn set_hit_tester_override(&self, value: Option<Rc<dyn IHitTester>>) {
1012        self.presentation_source().set_hit_tester_override(value)
1013    }
1014
1015    /// A value controlling whether the renderer should draw specific
1016    /// diagnostics.
1017    pub fn renderer_diagnostics(&self) -> Rc<RendererDiagnostics> {
1018        self.renderer().diagnostics()
1019    }
1020
1021    /// The last known position of the pointer, in screen coordinates.
1022    pub fn last_pointer_position(&self) -> Option<PixelPoint> {
1023        self.presentation_source().get_last_pointer_position(self)
1024    }
1025
1026    /// The access key handler for the window.
1027    pub fn access_key_handler(&self) -> Option<Rc<dyn IAccessKeyHandler>> {
1028        self.access_key_handler.borrow().clone()
1029    }
1030
1031    /// The keyboard navigation handler of the window (for unit tests).
1032    pub fn tests_keyboard_navigation_handler(&self) -> Option<Rc<dyn IKeyboardNavigationHandler>> {
1033        self.keyboard_navigation_handler.borrow().clone()
1034    }
1035
1036    /// Helper for setting the color of the platform's system bars.
1037    /// `control` is the main view attached to the top-level, or the
1038    /// top-level.
1039    pub fn set_system_bar_color(control: &Control, color: Option<Ref<SolidColorBrush>>) {
1040        control.set_value(Self::system_bar_color_property(), color);
1041    }
1042
1043    /// Helper for getting the color of the platform's system bars.
1044    /// `control` is the main view attached to the top-level, or the
1045    /// top-level.
1046    pub fn get_system_bar_color(control: &Control) -> Option<Ref<SolidColorBrush>> {
1047        control.get_value(Self::system_bar_color_property())
1048    }
1049
1050    /// Enables or disables whenever the top-level should automatically
1051    /// adjust paddings depending on the safe area. `control` is the main
1052    /// view attached to the top-level, or the top-level.
1053    pub fn set_auto_safe_area_padding(control: &Control, value: bool) {
1054        control.set_value(Self::auto_safe_area_padding_property(), value);
1055    }
1056
1057    /// Gets if auto safe area padding is enabled. `control` is the main
1058    /// view attached to the top-level, or the top-level.
1059    pub fn get_auto_safe_area_padding(control: &Control) -> bool {
1060        control.get_value(Self::auto_safe_area_padding_property())
1061    }
1062
1063    /// The scaling factor to use in rendering.
1064    pub fn render_scaling(&self) -> f64 {
1065        self.scaling.get()
1066    }
1067
1068    fn optional_feature<T: ?Sized + 'static>(&self) -> Option<Rc<T>> {
1069        let platform_impl = self.platform_impl()?;
1070        let provider: &dyn IOptionalFeatureProvider = &*platform_impl;
1071        provider.try_get::<T>()
1072    }
1073
1074    /// File System storage service used for file pickers and bookmarks:
1075    /// the provider of the registered storage provider factory, else the
1076    /// one of the platform, else one without pickers. The provider is
1077    /// created on first use and kept.
1078    pub fn storage_provider(&self) -> Rc<dyn ferroui_base::platform::storage::IStorageProvider> {
1079        use ferroui_base::platform::storage::{IStorageProvider, NoopStorageProvider};
1080        use ferroui_base::LocatorExtensions;
1081
1082        if let Some(storage_provider) = self.storage_provider.borrow().clone() {
1083            return storage_provider;
1084        }
1085
1086        let storage_provider = FerroLocator::current()
1087            .get_service::<dyn crate::platform::IStorageProviderFactory>()
1088            .map(|factory| factory.create_provider(&self.to_ref()))
1089            .or_else(|| self.optional_feature::<dyn IStorageProvider>())
1090            .unwrap_or_else(|| Rc::new(NoopStorageProvider));
1091        *self.storage_provider.borrow_mut() = Some(storage_provider.clone());
1092        storage_provider
1093    }
1094
1095    /// The insets manager of the platform, if it has one.
1096    pub fn insets_manager(&self) -> Option<Rc<dyn IInsetsManager>> {
1097        self.optional_feature::<dyn IInsetsManager>()
1098    }
1099
1100    /// The input pane of the platform, if it has one.
1101    pub fn input_pane(&self) -> Option<Rc<dyn IInputPane>> {
1102        self.optional_feature::<dyn IInputPane>()
1103    }
1104
1105    /// The launcher of the platform: starts the default app associated
1106    /// with a file or a URI. A launcher that launches nothing when the
1107    /// platform has none.
1108    pub fn launcher(&self) -> Rc<dyn ferroui_base::platform::storage::ILauncher> {
1109        use ferroui_base::platform::storage::{ILauncher, NoopLauncher};
1110
1111        self.optional_feature::<dyn ILauncher>().unwrap_or_else(|| Rc::new(NoopLauncher))
1112    }
1113
1114    /// The platform screens implementation.
1115    pub fn screens(&self) -> Option<Rc<Screens>> {
1116        if let Some(screens) = self.screens.borrow().clone() {
1117            return Some(screens);
1118        }
1119        let screens = Screens::new(self.optional_feature::<dyn IScreenImpl>()?);
1120        *self.screens.borrow_mut() = Some(screens.clone());
1121        Some(screens)
1122    }
1123
1124    /// The platform's clipboard implementation.
1125    pub fn clipboard(&self) -> Option<Rc<dyn IClipboard>> {
1126        self.try_get_clipboard(ClipboardType::Default)
1127    }
1128
1129    /// Gets the platform's clipboard of the specified type, or `None` if
1130    /// the platform doesn't provide it.
1131    pub fn try_get_clipboard(&self, type_: ClipboardType) -> Option<Rc<dyn IClipboard>> {
1132        if let Some(clipboard_manager) = self.optional_feature::<dyn IPlatformClipboardManagerImpl>() {
1133            return clipboard_manager.try_get_clipboard(type_);
1134        }
1135
1136        if type_ == ClipboardType::Default {
1137            self.optional_feature::<dyn IClipboard>()
1138        } else {
1139            None
1140        }
1141    }
1142
1143    /// The focus manager of the root.
1144    pub fn focus_manager(&self) -> Rc<FocusManager> {
1145        self.presentation_source().focus_manager().clone()
1146    }
1147
1148    /// The platform-specific settings.
1149    fn platform_settings(&self) -> Option<Rc<dyn IPlatformSettings>> {
1150        use ferroui_base::LocatorExtensions;
1151        FerroLocator::current().get_service::<dyn IPlatformSettings>()
1152    }
1153
1154    /// Gets the top-level in which the given visual is hosted.
1155    pub fn get_top_level(visual: Option<&Visual>) -> Option<Ref<TopLevel>> {
1156        let mut visual = visual.map(|visual| visual.to_ref());
1157        while let Some(current) = visual {
1158            if let Some(tl) = current.clone().cast::<TopLevel>() {
1159                return Some(tl);
1160            }
1161            visual = current.visual_parent();
1162        }
1163
1164        None
1165    }
1166
1167    /// Requests a [`PlatformInhibitionType`] to be inhibited. The behavior
1168    /// remains inhibited until the returned value is disposed. The
1169    /// available set of [`PlatformInhibitionType`]s depends on the platform.
1170    /// If a behavior is inhibited on a platform where this type is not
1171    /// supported the request will have no effect.
1172    ///
1173    /// Like the asynchronous method of the reference, the request is sent to
1174    /// the platform before this returns; the returned task completes, from
1175    /// a dispatcher job unless the platform completed at once, when the
1176    /// platform has applied it. Disposing the result starts the request
1177    /// that lifts the inhibition and returns without waiting for the
1178    /// platform to complete it.
1179    pub fn request_platform_inhibition(
1180        &self,
1181        type_: PlatformInhibitionType,
1182        reason: &str,
1183    ) -> DispatcherTask<Rc<dyn IDisposable>> {
1184        let platform_behavior_inhibition = self.optional_feature::<dyn IPlatformBehaviorInhibition>();
1185        let reason = reason.to_owned();
1186
1187        Dispatcher::ui_thread().to_task_scheduler().start_local(async move {
1188            let Some(platform_behavior_inhibition) = platform_behavior_inhibition else {
1189                return Disposable::create(|| {});
1190            };
1191
1192            match type_ {
1193                PlatformInhibitionType::AppSleep => {
1194                    platform_behavior_inhibition.set_inhibit_app_sleep(true, &reason).await;
1195                    Disposable::create(move || {
1196                        let lifted = platform_behavior_inhibition.set_inhibit_app_sleep(false, &reason);
1197                        drop(Dispatcher::ui_thread().to_task_scheduler().start_local(lifted));
1198                    })
1199                }
1200            }
1201        })
1202    }
1203
1204    /// Enqueues a callback to be called on the next animation tick.
1205    pub fn request_animation_frame(&self, action: impl FnOnce(ferroui_base::animation::TimeSpan) + 'static) {
1206        Dispatcher::ui_thread().verify_access();
1207        MediaContext::instance().request_animation_frame(action);
1208    }
1209
1210    fn invalidate_child_insets_padding(&self) {
1211        let Some(child) = self.content().and_then(|content| Control::from_boxed(&content)) else { return };
1212        let Some(insets_manager) = self.insets_manager() else { return };
1213
1214        if let Some(subscription) = self.insets_subscription.borrow_mut().take() {
1215            subscription.dispose();
1216        }
1217        if let Some(insets_paddings) = self.insets_paddings.borrow_mut().take() {
1218            insets_paddings.dispose();
1219        }
1220
1221        if child.get_value(Self::auto_safe_area_padding_property()) {
1222            let weak = self.to_ref().downgrade();
1223            let subscription = insets_manager.safe_area_changed(Rc::new(move |_: &SafeAreaChangedArgs| {
1224                if let Some(this) = weak.upgrade() {
1225                    this.invalidate_child_insets_padding();
1226                }
1227            }));
1228            *self.insets_subscription.borrow_mut() = Some(subscription);
1229            // lower priority, so it can be redefined by user
1230            let insets_paddings = child.set_value_with_priority(
1231                TemplatedControl::padding_property(),
1232                insets_manager.safe_area_padding(),
1233                BindingPriority::Style,
1234            );
1235            *self.insets_paddings.borrow_mut() = insets_paddings;
1236        }
1237    }
1238
1239    /// Handles a paint notification from the platform implementation;
1240    /// `rect` is the dirty area.
1241    fn handle_paint(&self, rect: Rect) {
1242        self.renderer().paint(rect);
1243    }
1244
1245    /// Registers the top-level with the media context, which starts its
1246    /// renderer (private protected upstream: for derived classes).
1247    pub fn start_rendering(&self) {
1248        let renderer: Rc<dyn IRenderer> = self.renderer();
1249        MediaContext::instance().add_top_level(self.media_context_key(), renderer);
1250    }
1251
1252    /// Unregisters the top-level from the media context, which stops its
1253    /// renderer (private protected upstream: for derived classes).
1254    pub fn stop_rendering(&self) {
1255        MediaContext::instance().remove_top_level(self.media_context_key());
1256    }
1257
1258    /// The key that identifies the top-level in the media context.
1259    pub fn media_context_key(&self) -> usize {
1260        self as *const TopLevel as usize
1261    }
1262
1263    /// Runs the top level teardown exactly once, no matter whether it was
1264    /// initiated by the platform via the closed notification or by a
1265    /// managed dispose (private protected upstream: for derived classes).
1266    pub fn ensure_closed(&self) {
1267        if self.is_closed.replace(true) {
1268            return;
1269        }
1270        self.handle_closed();
1271    }
1272
1273    /// Handles a window scaling change notification from the platform
1274    /// implementation.
1275    fn handle_scaling_changed(&self, scaling: f64) {
1276        if self.is_closed.get() {
1277            return;
1278        }
1279        self.scaling.set(LayoutHelper::validate_scaling(scaling));
1280        LayoutHelper::invalidate_self_and_children_measure(self);
1281        raise(&self.scaling_changed);
1282
1283        self.invalidate_child_insets_padding();
1284    }
1285
1286    fn handle_transparency_level_changed(&self, transparency_level: WindowTransparencyLevel) {
1287        let border = self.transparency_fallback_border.borrow().clone();
1288        if let Some(border) = border {
1289            if transparency_level == WindowTransparencyLevel::none() {
1290                border.set_background(self.transparency_background_fallback());
1291            } else {
1292                border.set_background(None);
1293            }
1294        }
1295
1296        self.set_actual_transparency_level(transparency_level);
1297        self.renderer().set_transparency_level(to_composition_transparency_level(transparency_level));
1298    }
1299
1300    fn handle_platform_specific_scene_info_changed(&self, scene_info: Option<std::sync::Arc<dyn Any + Send + Sync>>) {
1301        self.renderer().set_platform_specific_scene_info(scene_info);
1302    }
1303
1304    fn global_actual_theme_variant_changed(&self, sender: &dyn IThemeVariantHost) {
1305        let _ = self.set_value_with_priority(
1306            Self::actual_theme_variant_property(),
1307            sender.actual_theme_variant(),
1308            BindingPriority::Template,
1309        );
1310    }
1311
1312    /// Records a popup opened directly in this top level (internal
1313    /// upstream).
1314    pub fn add_opened_popup(&self, popup: &Ref<crate::primitives::Popup>) {
1315        self.opened_popups.borrow_mut().push(popup.clone());
1316    }
1317
1318    /// Forgets a popup opened directly in this top level (internal
1319    /// upstream).
1320    pub fn remove_opened_popup(&self, popup: &Ref<crate::primitives::Popup>) {
1321        let mut opened_popups = self.opened_popups.borrow_mut();
1322        if let Some(index) = opened_popups.iter().position(|p| p == popup) {
1323            opened_popups.remove(index);
1324        }
1325    }
1326
1327    fn platform_impl_lost_focus(&self) {
1328        let focused = self.focus_manager().get_focused_element();
1329        let focused = Self::get_top_level(focused.as_deref().map(|element| -> &Visual { element }));
1330
1331        if focused.is_some_and(|focused| std::ptr::eq(&*focused, self)) {
1332            if let Some(keyboard_device) = KeyboardDevice::instance() {
1333                keyboard_device.set_focused_element_with(None, NavigationMethod::Unspecified, KeyModifiers::NONE, false);
1334            }
1335        }
1336    }
1337}
1338
1339impl Drop for TopLevel {
1340    fn drop(&mut self) {
1341        if let Some(subscription) = self.resources_changed_subscription.borrow_mut().take() {
1342            subscription.dispose();
1343        }
1344    }
1345}
1346
1347/// The frame theme variant of a theme variant: light and dark map to
1348/// themselves, a custom variant to what it inherits from, and the default
1349/// variant to nothing (follow the system).
1350fn to_platform_theme_variant(theme_variant: &ThemeVariant) -> Option<PlatformThemeVariant> {
1351    if *theme_variant == ThemeVariant::light() {
1352        Some(PlatformThemeVariant::Light)
1353    } else if *theme_variant == ThemeVariant::dark() {
1354        Some(PlatformThemeVariant::Dark)
1355    } else if let Some(inherit_variant) = theme_variant.inherit_variant() {
1356        to_platform_theme_variant(&inherit_variant)
1357    } else {
1358        None
1359    }
1360}
1361
1362fn to_composition_transparency_level(level: WindowTransparencyLevel) -> CompositionTransparencyLevel {
1363    if level == WindowTransparencyLevel::transparent() {
1364        return CompositionTransparencyLevel::Transparent;
1365    }
1366    if level == WindowTransparencyLevel::blur() {
1367        return CompositionTransparencyLevel::Blur;
1368    }
1369    if level == WindowTransparencyLevel::acrylic_blur() {
1370        return CompositionTransparencyLevel::AcrylicBlur;
1371    }
1372    if level == WindowTransparencyLevel::mica() {
1373        return CompositionTransparencyLevel::Mica;
1374    }
1375    CompositionTransparencyLevel::None
1376}
1377
1378/// The top-level as something that tells when it has been closed.
1379struct TopLevelCloseable(Ref<TopLevel>);
1380
1381impl ferroui_base::input::ICloseable for TopLevelCloseable {
1382    fn closed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
1383        self.0.closed(move || handler())
1384    }
1385}
1386
1387impl TopLevel {
1388    /// The top-level as something that tells when it has been closed.
1389    pub fn as_closeable(&self) -> Rc<dyn ferroui_base::input::ICloseable> {
1390        Rc::new(TopLevelCloseable(self.to_ref()))
1391    }
1392}