ferroui_controls/presentation_source/
presentation_source.rs1use 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
16pub 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 scaling_changed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
30
31 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 pub(super) layout_manager: Rc<LayoutManager>,
40 pub(super) layout_diagnostic_bridge: RefCell<Option<LayoutDiagnosticBridge>>,
41
42 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 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 pub fn platform_impl(&self) -> Option<Rc<dyn ITopLevelImpl>> {
133 self.platform_impl.borrow().clone()
134 }
135
136 pub fn focus_manager(&self) -> &Rc<FocusManager> {
138 &self.focus_manager
139 }
140
141 pub fn root_visual(&self) -> Option<Ref<InputElement>> {
143 self.root_visual.borrow().clone()
144 }
145
146 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 pub fn root_element(&self) -> Ref<InputElement> {
169 self.root_visual().expect("the presentation source has no root visual")
170 }
171
172 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 pub fn platform_settings(&self) -> Option<Rc<dyn IPlatformSettings>> {
186 FerroLocator::current().get_service::<dyn IPlatformSettings>()
187 }
188
189 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 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 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 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(¤t);
251 if role != WindowDecorationsElementRole::None {
252 return Some(role);
253 }
254 visual = current.visual_parent();
255 }
256 None
257 }
258}
259
260pub(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
278pub(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}