Skip to main content

ferroui_controls/presentation_source/
presentation_source.rs

1use super::presentation_source_layout::LayoutDiagnosticBridge;
2use super::renderer_factory::{create_renderer, ITopLevelRenderer};
3use crate::platform::ITopLevelImpl;
4use ferroui_base::input::{Cursor, FocusManager, IInputManager, IInputRoot, InputElement, PointerOverPreProcessor};
5use ferroui_base::layout::{LayoutHelper, LayoutManager};
6use ferroui_base::logging::{LogArea, LogEventLevel, Logger};
7use ferroui_base::platform::IPlatformSettings;
8use ferroui_base::reactive::{IDisposable, IObserver};
9use ferroui_base::rendering::{IHitTester, IPresentationSource, IRenderer};
10use ferroui_base::{
11    FerroLocator, IFerroDependencyResolver, LocatorExtensions, PixelPoint, Point, Ref, Size, Visual, WeakRef,
12};
13use std::cell::{Cell, OnceCell, RefCell};
14use std::rc::{Rc, Weak};
15
16/// Connects the root visual of a top-level to its platform implementation,
17/// renderer, layout manager and input.
18pub struct PresentationSource {
19    pub(super) this: Weak<PresentationSource>,
20    pub(super) platform_impl: RefCell<Option<Rc<dyn ITopLevelImpl>>>,
21    pub(super) pointer_over_pre_processor: RefCell<Option<Rc<PointerOverPreProcessor>>>,
22    pointer_over_pre_processor_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
23    pub(super) input_manager: Option<Rc<dyn IInputManager>>,
24    focus_manager: Rc<FocusManager>,
25    root_visual: RefCell<Option<Ref<InputElement>>>,
26    pub(super) focus_root: WeakRef<InputElement>,
27    /// The scaling callback of the platform implementation that was in
28    /// place before the source chained its own.
29    scaling_changed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
30
31    // Cursor
32    pub(super) cursor: RefCell<Option<Rc<Cursor>>>,
33    pub(super) cursor_override: RefCell<Option<Rc<Cursor>>>,
34    pub(super) pointer_over_element: RefCell<Option<Ref<InputElement>>>,
35    pub(super) cursor_element: RefCell<Option<Ref<InputElement>>>,
36    pub(super) cursor_element_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
37
38    // Layout
39    pub(super) layout_manager: Rc<LayoutManager>,
40    pub(super) layout_diagnostic_bridge: RefCell<Option<LayoutDiagnosticBridge>>,
41
42    // Render root
43    pub(super) renderer: OnceCell<Rc<dyn ITopLevelRenderer>>,
44    pub(super) scene_invalidated_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
45    pub(super) hit_tester_override: RefCell<Option<Rc<dyn IHitTester>>>,
46    pub(super) render_scaling: Cell<f64>,
47}
48
49impl PresentationSource {
50    /// Creates the presentation source of a top-level.
51    ///
52    /// `root_visual` becomes the root of the visual tree, `default_focus_visual`
53    /// receives keyboard input when nothing is focused, `platform_impl` is
54    /// the platform implementation of the top-level and the services of the
55    /// source are taken from `dependency_resolver`.
56    pub fn new(
57        root_visual: Ref<InputElement>,
58        default_focus_visual: &Ref<InputElement>,
59        platform_impl: Rc<dyn ITopLevelImpl>,
60        dependency_resolver: &dyn IFerroDependencyResolver,
61    ) -> Rc<PresentationSource> {
62        let input_manager = try_get_service::<dyn IInputManager>(dependency_resolver);
63
64        let source = Rc::new_cyclic(|this: &Weak<PresentationSource>| {
65            let layout_root: Weak<dyn ferroui_base::layout::ILayoutRoot> = this.clone();
66            PresentationSource {
67                this: this.clone(),
68                platform_impl: RefCell::new(Some(platform_impl.clone())),
69                pointer_over_pre_processor: RefCell::new(None),
70                pointer_over_pre_processor_subscription: RefCell::new(None),
71                input_manager,
72                focus_manager: FocusManager::new(),
73                root_visual: RefCell::new(None),
74                focus_root: default_focus_visual.downgrade(),
75                scaling_changed_subscription: RefCell::new(None),
76                cursor: RefCell::new(None),
77                cursor_override: RefCell::new(None),
78                pointer_over_element: RefCell::new(None),
79                cursor_element: RefCell::new(None),
80                cursor_element_subscription: RefCell::new(None),
81                layout_manager: Self::create_layout_manager(layout_root),
82                layout_diagnostic_bridge: RefCell::new(None),
83                renderer: OnceCell::new(),
84                scene_invalidated_subscription: RefCell::new(None),
85                hit_tester_override: RefCell::new(None),
86                render_scaling: Cell::new(1.0),
87            }
88        });
89
90        platform_impl.set_input_root(source.clone());
91        let weak = Rc::downgrade(&source);
92        platform_impl.set_input(Some(Rc::new(move |e| {
93            if let Some(source) = weak.upgrade() {
94                source.handle_input(e);
95            }
96        })));
97
98        source.render_scaling.set(LayoutHelper::validate_scaling(platform_impl.render_scaling()));
99        let weak = Rc::downgrade(&source);
100        *source.scaling_changed_subscription.borrow_mut() = Some(add_scaling_changed(
101            &platform_impl,
102            Rc::new(move |scaling| {
103                if let Some(source) = weak.upgrade() {
104                    source.handle_scaling_changed(scaling);
105                }
106            }),
107        ));
108
109        let pointer_over_pre_processor = Rc::new(PointerOverPreProcessor::new(source.clone()));
110        *source.pointer_over_pre_processor.borrow_mut() = Some(pointer_over_pre_processor.clone());
111        *source.pointer_over_pre_processor_subscription.borrow_mut() =
112            source.input_manager.as_ref().map(|input_manager| input_manager.pre_process().subscribe(pointer_over_pre_processor));
113
114        let renderer = create_renderer(source.clone(), platform_impl.compositor(), platform_impl.render_surfaces());
115        let weak = Rc::downgrade(&source);
116        *source.scene_invalidated_subscription.borrow_mut() = Some(renderer.scene_invalidated(Rc::new(move |e| {
117            if let Some(source) = weak.upgrade() {
118                source.scene_invalidated(e);
119            }
120        })));
121        if source.renderer.set(renderer).is_err() {
122            unreachable!("the renderer is created once");
123        }
124        source.create_layout_diagnostic_bridge();
125
126        source.set_root_visual(Some(root_visual));
127        source
128    }
129
130    /// The platform implementation of the top-level; `None` once the source
131    /// has been disposed.
132    pub fn platform_impl(&self) -> Option<Rc<dyn ITopLevelImpl>> {
133        self.platform_impl.borrow().clone()
134    }
135
136    /// The focus manager of the tree.
137    pub fn focus_manager(&self) -> &Rc<FocusManager> {
138        &self.focus_manager
139    }
140
141    /// The root of the visual tree; `None` after the top-level has closed.
142    pub fn root_visual(&self) -> Option<Ref<InputElement>> {
143        self.root_visual.borrow().clone()
144    }
145
146    /// Sets the root of the visual tree: the old root is detached from the
147    /// source and the new one attached to it.
148    pub fn set_root_visual(&self, value: Option<Ref<InputElement>>) {
149        let old = self.root_visual.borrow().clone();
150        if let Some(old) = old {
151            old.set_presentation_source_for_root_visual(None);
152        }
153        *self.root_visual.borrow_mut() = value.clone();
154
155        if let Some(new) = &value {
156            let this: Rc<dyn IPresentationSource> = self.rc();
157            new.set_presentation_source_for_root_visual(Some(this));
158        }
159        self.typed_renderer().set_root(value.clone().map(Ref::upcast));
160
161        self.focus_manager.set_content_root(value);
162    }
163
164    /// The root input element; the root visual.
165    ///
166    /// # Panics
167    /// Panics after the top-level has closed.
168    pub fn root_element(&self) -> Ref<InputElement> {
169        self.root_visual().expect("the presentation source has no root visual")
170    }
171
172    /// The presentation source whose root visual is `root_visual`, if the
173    /// visual is the root of one (upstream casts the source of the root
174    /// visual to this class).
175    pub fn from_root_visual(root_visual: &ferroui_base::Visual) -> Option<Rc<PresentationSource>> {
176        let host = root_visual.downcast_ref::<crate::top_level_host::TopLevelHost>()?;
177        Some(host.top_level()?.presentation_source().clone())
178    }
179
180    pub(super) fn rc(&self) -> Rc<PresentationSource> {
181        self.this.upgrade().expect("the presentation source is alive while it is used")
182    }
183
184    /// The platform settings of the application.
185    pub fn platform_settings(&self) -> Option<Rc<dyn IPlatformSettings>> {
186        FerroLocator::current().get_service::<dyn IPlatformSettings>()
187    }
188
189    /// Detaches the source from its platform implementation and releases
190    /// the renderer, the layout manager and the input subscriptions.
191    pub fn dispose(&self) {
192        if let Some(platform_impl) = self.platform_impl() {
193            platform_impl.set_input(None);
194        }
195        if let Some(subscription) = self.scaling_changed_subscription.borrow_mut().take() {
196            subscription.dispose();
197        }
198
199        if let Some(bridge) = self.layout_diagnostic_bridge.borrow_mut().take() {
200            bridge.dispose();
201        }
202        ferroui_base::layout::ILayoutManager::dispose(&*self.layout_manager);
203        if let Some(subscription) = self.scene_invalidated_subscription.borrow_mut().take() {
204            subscription.dispose();
205        }
206        // We need to wait for the renderer to complete any in-flight operations
207        IRenderer::dispose(&**self.typed_renderer());
208
209        *self.platform_impl.borrow_mut() = None;
210        let pointer_over_pre_processor = self.pointer_over_pre_processor.borrow_mut().take();
211        if let Some(pointer_over_pre_processor) = pointer_over_pre_processor {
212            pointer_over_pre_processor.on_completed();
213        }
214        if let Some(subscription) = self.pointer_over_pre_processor_subscription.borrow_mut().take() {
215            subscription.dispose();
216        }
217        if let Some(subscription) = self.cursor_element_subscription.borrow_mut().take() {
218            subscription.dispose();
219        }
220    }
221
222    /// The last known position of the pointer, in screen coordinates.
223    pub fn get_last_pointer_position(&self, _top_level: &Visual) -> Option<PixelPoint> {
224        self.pointer_over_pre_processor.borrow().as_ref().and_then(|p| p.last_position())
225    }
226}
227
228impl PresentationSource {
229    pub(super) fn chrome_hit_test_filter(visual: &Visual) -> bool {
230        if !(visual.is_visible() && visual.is_attached_to_visual_tree()) {
231            return false;
232        }
233        if !visual
234            .downcast_ref::<InputElement>()
235            .is_some_and(|element| element.is_effectively_visible() && element.is_hit_test_visible())
236        {
237            return false;
238        }
239
240        // Allow traversal into any container that might contain chrome elements
241        true
242    }
243
244    pub(super) fn get_chrome_role_from_visual(
245        mut visual: Option<Ref<Visual>>,
246    ) -> Option<ferroui_base::input::WindowDecorationsElementRole> {
247        use ferroui_base::input::WindowDecorationsElementRole;
248
249        while let Some(current) = visual {
250            let role = crate::chrome::WindowDecorationProperties::get_element_role(&current);
251            if role != WindowDecorationsElementRole::None {
252                return Some(role);
253            }
254            visual = current.visual_parent();
255        }
256        None
257    }
258}
259
260/// Chains `handler` onto the scaling-changed callback of a platform
261/// implementation (C# `impl.ScalingChanged += handler`). Disposing the
262/// returned handle stops `handler` from being called.
263pub(crate) fn add_scaling_changed(platform_impl: &Rc<dyn ITopLevelImpl>, handler: Rc<dyn Fn(f64)>) -> Rc<dyn IDisposable> {
264    let previous = platform_impl.scaling_changed();
265    let active = Rc::new(Cell::new(true));
266    let is_active = active.clone();
267    platform_impl.set_scaling_changed(Some(Rc::new(move |scaling| {
268        if let Some(previous) = &previous {
269            previous(scaling);
270        }
271        if is_active.get() {
272            handler(scaling);
273        }
274    })));
275    ferroui_base::reactive::Disposable::create(move || active.set(false))
276}
277
278/// Tries to get a service from a dependency resolver, logging a warning if
279/// not found.
280pub(crate) fn try_get_service<T: ?Sized + 'static>(resolver: &dyn IFerroDependencyResolver) -> Option<Rc<T>> {
281    let result = resolver.get_service::<T>();
282
283    if result.is_none() {
284        if let Some(logger) = Logger::try_get(LogEventLevel::Warning, LogArea::CONTROL) {
285            logger.log_with_values(
286                None,
287                "Could not create {Service} : maybe Application.RegisterServices() wasn't called?",
288                &[&std::any::type_name::<T>()],
289            );
290        }
291    }
292
293    result
294}
295
296impl IPresentationSource for PresentationSource {
297    fn root_visual(&self) -> Option<Ref<Visual>> {
298        PresentationSource::root_visual(self).map(Ref::upcast)
299    }
300
301    fn render_scaling(&self) -> f64 {
302        self.render_scaling.get()
303    }
304
305    fn renderer(&self) -> Rc<dyn IRenderer> {
306        self.typed_renderer().clone()
307    }
308
309    fn layout_root(&self) -> Rc<dyn ferroui_base::layout::ILayoutRoot> {
310        self.rc()
311    }
312
313    fn client_size(&self) -> Size {
314        PresentationSource::client_size(self)
315    }
316
317    fn platform_settings(&self) -> Option<Rc<dyn IPlatformSettings>> {
318        PresentationSource::platform_settings(self)
319    }
320
321    fn hit_tester(&self) -> Rc<dyn IHitTester> {
322        PresentationSource::hit_tester(self)
323    }
324
325    fn input_root(&self) -> Rc<dyn IInputRoot> {
326        self.rc()
327    }
328
329    fn point_to_screen(&self, point: Point) -> Option<PixelPoint> {
330        self.platform_impl().map(|platform_impl| platform_impl.point_to_screen(point))
331    }
332
333    fn point_to_client(&self, point: PixelPoint) -> Option<Point> {
334        self.platform_impl().map(|platform_impl| platform_impl.point_to_client(point))
335    }
336}