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