Skip to main content

ferroui_browser/
browser_top_level_impl.rs

1use crate::browser_app_builder::BrowserPlatformOptions;
2use crate::browser_input_handler::{BrowserInputHandler, IInputTopLevel};
3use crate::browser_insets_manager::BrowserInsetsManager;
4use crate::browser_native_control_host::BrowserNativeControlHost;
5use crate::clipboard_impl::ClipboardImpl;
6use crate::cursor::CssCursor;
7use crate::interop::{dom_helper, input_helper, JsObject};
8use crate::js_object_control_handle::JsObjectControlHandle;
9use crate::rendering::RenderTargetBrowserSurface;
10use crate::storage::BrowserLauncher;
11use crate::storage::BrowserStorageProvider;
12use crate::windowing_platform::BrowserWindowingPlatform;
13use ferroui_base::input::platform::{Clipboard, IClipboard};
14use ferroui_base::input::raw::IRawInputEventArgs;
15use ferroui_base::input::text_input::ITextInputMethodImpl;
16use ferroui_base::input::IInputRoot;
17use ferroui_base::platform::storage::{ILauncher, IStorageProvider};
18use ferroui_base::platform::surfaces::IPlatformRenderSurface;
19use ferroui_base::platform::{ICursorImpl, IOptionalFeatureProvider, ISystemNavigationManagerImpl};
20use ferroui_base::reactive::IDisposable;
21use ferroui_base::rendering::composition::Compositor;
22use ferroui_base::{FerroLocator, LocatorExtensions, PixelPoint, Point, Rect, Size};
23use ferroui_controls::platform::{
24    IInputPane, IInsetsManager, INativeControlHostImpl, IPlatformHandle, IPopupImpl, IScreenImpl, ITopLevelImpl,
25    PlatformThemeVariant,
26};
27use ferroui_controls::{AcrylicPlatformCompensationLevels, WindowResizeReason, WindowTransparencyLevel};
28use std::any::{Any, TypeId};
29use std::cell::{Cell, RefCell};
30use std::collections::HashMap;
31use std::rc::{Rc, Weak};
32use std::sync::{Arc, Mutex, PoisonError};
33
34ferroui_base::ferro_thread_local! {
35    static LAST_TOP_LEVEL_ID: Cell<i32> = const { Cell::new(0) };
36    static TOP_LEVELS: RefCell<HashMap<i32, Weak<BrowserTopLevelImpl>>> = RefCell::new(HashMap::new());
37    static GLOBAL_EVENTS_INITIALIZED: Cell<bool> = const { Cell::new(false) };
38}
39
40type Callback<T> = RefCell<Option<Rc<T>>>;
41
42/// The top-level of a view: a canvas inside an element of the page.
43pub struct BrowserTopLevelImpl {
44    container: JsObject,
45    native_control_host: Rc<dyn INativeControlHostImpl>,
46    input_handler: Rc<BrowserInputHandler>,
47    insets_manager: Rc<BrowserInsetsManager>,
48    clipboard: Rc<Clipboard>,
49    storage_provider: Rc<dyn IStorageProvider>,
50    current_cursor: RefCell<String>,
51    surface: RefCell<Option<Rc<RenderTargetBrowserSurface>>>,
52    /// The surfaces as the thread that renders reads them; see `publish_render_surfaces`.
53    render_surfaces: Arc<Mutex<Vec<Arc<dyn IPlatformRenderSurface>>>>,
54    top_level_id: i32,
55    compositor: Rc<Compositor>,
56    handle: Rc<dyn IPlatformHandle>,
57    acrylic_compensation_levels: AcrylicPlatformCompensationLevels,
58
59    input: Callback<dyn Fn(Rc<dyn IRawInputEventArgs>)>,
60    paint: Callback<dyn Fn(Rect)>,
61    resized: Callback<dyn Fn(Size, WindowResizeReason)>,
62    scaling_changed: Callback<dyn Fn(f64)>,
63    transparency_level_changed: Callback<dyn Fn(WindowTransparencyLevel)>,
64    closed: Callback<dyn Fn()>,
65    lost_focus: Callback<dyn Fn()>,
66}
67
68impl BrowserTopLevelImpl {
69    /// The top-level with the given id, while it is alive.
70    pub fn try_get_top_level(id: i32) -> Option<Rc<BrowserTopLevelImpl>> {
71        TOP_LEVELS.with(|top_levels| top_levels.borrow().get(&id).and_then(Weak::upgrade))
72    }
73
74    /// Creates the top-level of `container`.
75    ///
76    /// `native_control_host` is the element native controls are placed in
77    /// and `input_element` the hidden input element of the view.
78    pub fn new(container: JsObject, native_control_host: JsObject, input_element: JsObject) -> Rc<Self> {
79        // Once, before the first top-level: the events and the handlers of the page that are not
80        // bound to a view.
81        if !GLOBAL_EVENTS_INITIALIZED.with(|initialized| initialized.replace(true)) {
82            dom_helper::init_global_dom_events(&BrowserWindowingPlatform::global_this());
83            input_helper::initialize_background_handlers(&BrowserWindowingPlatform::global_this());
84        }
85
86        let top_level_id = LAST_TOP_LEVEL_ID.with(|last| {
87            last.set(last.get() + 1);
88            last.get()
89        });
90
91        let this = Rc::new_cyclic(|this: &Weak<Self>| {
92            TOP_LEVELS.with(|top_levels| top_levels.borrow_mut().insert(top_level_id, this.clone()));
93
94            // The input handler reaches its top-level through a weak reference: the top-level
95            // owns the handler.
96            let input_top_level: Weak<dyn IInputTopLevel> = this.clone();
97            let input_handler =
98                BrowserInputHandler::new(input_top_level, container.clone(), input_element, top_level_id);
99
100            let opts = FerroLocator::current().get_service::<BrowserPlatformOptions>().unwrap_or_default();
101            let surface = RenderTargetBrowserSurface::create(
102                &container,
103                &opts.renderer.rendering_modes(&opts.rendering_mode),
104                top_level_id,
105            );
106            let compositor = surface.compositor();
107
108            Self {
109                handle: Rc::new(JsObjectControlHandle::new(container.clone())),
110                container,
111                native_control_host: BrowserNativeControlHost::new(native_control_host),
112                input_handler,
113                insets_manager: Rc::new(BrowserInsetsManager::new()),
114                clipboard: Clipboard::new(Rc::new(ClipboardImpl::new())),
115                storage_provider: Rc::new(BrowserStorageProvider::new()),
116                current_cursor: RefCell::new(CssCursor::DEFAULT.to_string()),
117                surface: RefCell::new(Some(surface)),
118                render_surfaces: Arc::new(Mutex::new(Vec::new())),
119                top_level_id,
120                compositor,
121                acrylic_compensation_levels: AcrylicPlatformCompensationLevels::new(1.0, 1.0, 1.0),
122                input: RefCell::new(None),
123                paint: RefCell::new(None),
124                resized: RefCell::new(None),
125                scaling_changed: RefCell::new(None),
126                transparency_level_changed: RefCell::new(None),
127                closed: RefCell::new(None),
128                lost_focus: RefCell::new(None),
129            }
130        });
131
132        if let Some(surface) = this.surface() {
133            surface.size_changed({
134                let this = Rc::downgrade(&this);
135                Rc::new(move || {
136                    if let Some(this) = this.upgrade() {
137                        this.on_size_changed();
138                    }
139                })
140            });
141            surface.scaling_changed({
142                let this = Rc::downgrade(&this);
143                Rc::new(move || {
144                    if let Some(this) = this.upgrade() {
145                        this.on_scaling_changed();
146                    }
147                })
148            });
149        }
150
151        this
152    }
153
154    fn on_scaling_changed(&self) {
155        if let Some(surface) = self.surface() {
156            let scaling_changed = self.scaling_changed.borrow().clone();
157            if let Some(scaling_changed) = scaling_changed {
158                scaling_changed(surface.scaling());
159            }
160        }
161    }
162
163    /// Writes the surfaces the top-level has now to the cell the renderer reads.
164    ///
165    /// The reference reads `Surfaces` of the top-level whenever the renderer creates its render
166    /// target, and the list changes: it is empty once the top-level is disposed. (Until the
167    /// render target of the canvas exists the list has the surface, which is not ready; the
168    /// reference has an empty list then.) The renderer cannot reach the top-level here
169    /// (its function may be called by another thread), so the top-level publishes the list when
170    /// it can have changed: when the renderer asks for the function, when the canvas changes
171    /// its size, and when the top-level is disposed.
172    fn publish_render_surfaces(&self) {
173        let surfaces = ITopLevelImpl::surfaces(self);
174        *self.render_surfaces.lock().unwrap_or_else(PoisonError::into_inner) = surfaces;
175    }
176
177    fn on_size_changed(&self) {
178        self.publish_render_surfaces();
179        if let Some(surface) = self.surface() {
180            let resized = self.resized.borrow().clone();
181            if let Some(resized) = resized {
182                resized(surface.client_size(), WindowResizeReason::User);
183            }
184            self.insets_manager.notify_safe_area_padding_changed();
185        }
186    }
187
188    /// The surface the top-level renders to; `None` once disposed.
189    pub fn surface(&self) -> Option<Rc<RenderTargetBrowserSurface>> {
190        self.surface.borrow().clone()
191    }
192
193    /// The id the script side routes its callbacks by.
194    pub fn top_level_id(&self) -> i32 {
195        self.top_level_id
196    }
197
198    /// The input handler of the top-level: what turns the events of the
199    /// page into raw input events.
200    pub fn input_handler(&self) -> &Rc<BrowserInputHandler> {
201        &self.input_handler
202    }
203
204    /// The input root the top-level delivers input to.
205    pub fn input_root(&self) -> Option<Rc<dyn IInputRoot>> {
206        self.input_handler.input_root()
207    }
208
209    /// The keyboard focus of the page left the element of the top-level.
210    // Differs from the original, where the lost-focus notification is never raised.
211    pub(crate) fn on_lost_focus(&self) {
212        let lost_focus = self.lost_focus.borrow().clone();
213        if let Some(lost_focus) = lost_focus {
214            self.input_handler().text_input_method().while_focus_leaves_view(|| lost_focus());
215        }
216    }
217}
218
219impl IInputTopLevel for BrowserTopLevelImpl {
220    fn dispatch_input(&self, args: Rc<dyn IRawInputEventArgs>) {
221        let input = self.input.borrow().clone();
222        if let Some(input) = input {
223            input(args);
224        }
225    }
226
227    fn page_size(&self) -> Size {
228        ITopLevelImpl::client_size(self)
229    }
230}
231
232impl IDisposable for BrowserTopLevelImpl {
233    fn dispose(&self) {
234        let surface = self.surface.borrow_mut().take();
235        if let Some(surface) = surface {
236            surface.dispose();
237        }
238        // The surface is gone: the renderer is handed no surfaces from here on.
239        self.publish_render_surfaces();
240
241        // Differs from the original, which leaves the listeners of the element in place: a
242        // disposed top-level no longer receives the input of the page.
243        self.input_handler.dispose();
244    }
245}
246
247impl IOptionalFeatureProvider for BrowserTopLevelImpl {
248    fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
249        if feature_type == TypeId::of::<dyn IStorageProvider>() {
250            return Some(Rc::new(self.storage_provider.clone()));
251        }
252
253        if feature_type == TypeId::of::<dyn ITextInputMethodImpl>() {
254            let text_input_method: Rc<dyn ITextInputMethodImpl> = self.input_handler.text_input_method().clone();
255            return Some(Rc::new(text_input_method));
256        }
257
258        if feature_type == TypeId::of::<dyn ISystemNavigationManagerImpl>() {
259            let service = FerroLocator::current().get_service::<dyn ISystemNavigationManagerImpl>()?;
260            return Some(Rc::new(service));
261        }
262
263        if feature_type == TypeId::of::<dyn IScreenImpl>() {
264            let service = FerroLocator::current().get_service::<dyn IScreenImpl>()?;
265            return Some(Rc::new(service));
266        }
267
268        if feature_type == TypeId::of::<dyn INativeControlHostImpl>() {
269            return Some(Rc::new(self.native_control_host.clone()));
270        }
271
272        if feature_type == TypeId::of::<dyn IInsetsManager>() {
273            let insets_manager: Rc<dyn IInsetsManager> = self.insets_manager.clone();
274            return Some(Rc::new(insets_manager));
275        }
276
277        if feature_type == TypeId::of::<dyn IClipboard>() {
278            let clipboard: Rc<dyn IClipboard> = self.clipboard.clone();
279            return Some(Rc::new(clipboard));
280        }
281
282        if feature_type == TypeId::of::<dyn IInputPane>() {
283            let input_pane: Rc<dyn IInputPane> = self.input_handler.input_pane().clone();
284            return Some(Rc::new(input_pane));
285        }
286
287        if feature_type == TypeId::of::<dyn ILauncher>() {
288            let launcher: Rc<dyn ILauncher> = Rc::new(BrowserLauncher::new());
289            return Some(Rc::new(launcher));
290        }
291
292        None
293    }
294}
295
296impl ITopLevelImpl for BrowserTopLevelImpl {
297    fn desktop_scaling(&self) -> f64 {
298        self.render_scaling()
299    }
300
301    fn handle(&self) -> Option<Rc<dyn IPlatformHandle>> {
302        Some(self.handle.clone())
303    }
304
305    fn client_size(&self) -> Size {
306        self.surface().map_or(Size::new(1.0, 1.0), |surface| surface.client_size())
307    }
308
309    fn render_scaling(&self) -> f64 {
310        self.surface().map_or(1.0, |surface| surface.scaling())
311    }
312
313    fn surfaces(&self) -> Vec<Arc<dyn IPlatformRenderSurface>> {
314        self.surface().map_or_else(Vec::new, |surface| surface.get_render_surfaces())
315    }
316
317    fn render_surfaces(&self) -> Arc<dyn Fn() -> Vec<Arc<dyn IPlatformRenderSurface>> + Send + Sync> {
318        self.publish_render_surfaces();
319        let surfaces = self.render_surfaces.clone();
320        Arc::new(move || surfaces.lock().unwrap_or_else(PoisonError::into_inner).clone())
321    }
322
323    fn compositor(&self) -> Option<Rc<Compositor>> {
324        Some(self.compositor.clone())
325    }
326
327    fn input(&self) -> Option<Rc<dyn Fn(Rc<dyn IRawInputEventArgs>)>> {
328        self.input.borrow().clone()
329    }
330
331    fn set_input(&self, value: Option<Rc<dyn Fn(Rc<dyn IRawInputEventArgs>)>>) {
332        *self.input.borrow_mut() = value;
333    }
334
335    fn paint(&self) -> Option<Rc<dyn Fn(Rect)>> {
336        self.paint.borrow().clone()
337    }
338
339    fn set_paint(&self, value: Option<Rc<dyn Fn(Rect)>>) {
340        *self.paint.borrow_mut() = value;
341    }
342
343    fn resized(&self) -> Option<Rc<dyn Fn(Size, WindowResizeReason)>> {
344        self.resized.borrow().clone()
345    }
346
347    fn set_resized(&self, value: Option<Rc<dyn Fn(Size, WindowResizeReason)>>) {
348        *self.resized.borrow_mut() = value;
349    }
350
351    fn scaling_changed(&self) -> Option<Rc<dyn Fn(f64)>> {
352        self.scaling_changed.borrow().clone()
353    }
354
355    fn set_scaling_changed(&self, value: Option<Rc<dyn Fn(f64)>>) {
356        *self.scaling_changed.borrow_mut() = value;
357    }
358
359    fn transparency_level_changed(&self) -> Option<Rc<dyn Fn(WindowTransparencyLevel)>> {
360        self.transparency_level_changed.borrow().clone()
361    }
362
363    fn set_transparency_level_changed(&self, value: Option<Rc<dyn Fn(WindowTransparencyLevel)>>) {
364        *self.transparency_level_changed.borrow_mut() = value;
365    }
366
367    fn set_input_root(&self, input_root: Rc<dyn IInputRoot>) {
368        self.input_handler.set_input_root(input_root);
369    }
370
371    fn point_to_client(&self, point: PixelPoint) -> Point {
372        Point::new(point.x as f64, point.y as f64)
373    }
374
375    fn point_to_screen(&self, point: Point) -> PixelPoint {
376        PixelPoint::new(point.x as i32, point.y as i32)
377    }
378
379    fn set_cursor(&self, cursor: Option<Rc<dyn ICursorImpl>>) {
380        let val = cursor
381            .as_ref()
382            .and_then(|cursor| cursor.as_any().downcast_ref::<CssCursor>())
383            .and_then(|cursor| cursor.value().map(str::to_string))
384            .unwrap_or_else(|| CssCursor::DEFAULT.to_string());
385        if *self.current_cursor.borrow() != val {
386            input_helper::set_cursor(&self.container, &val);
387            *self.current_cursor.borrow_mut() = val;
388        }
389    }
390
391    fn closed(&self) -> Option<Rc<dyn Fn()>> {
392        self.closed.borrow().clone()
393    }
394
395    fn set_closed(&self, value: Option<Rc<dyn Fn()>>) {
396        *self.closed.borrow_mut() = value;
397    }
398
399    fn lost_focus(&self) -> Option<Rc<dyn Fn()>> {
400        self.lost_focus.borrow().clone()
401    }
402
403    fn set_lost_focus(&self, value: Option<Rc<dyn Fn()>>) {
404        *self.lost_focus.borrow_mut() = value;
405    }
406
407    fn create_popup(&self) -> Option<Rc<dyn IPopupImpl>> {
408        None
409    }
410
411    fn set_transparency_level_hint(&self, _transparency_levels: &[WindowTransparencyLevel]) {}
412
413    fn transparency_level(&self) -> WindowTransparencyLevel {
414        WindowTransparencyLevel::none()
415    }
416
417    fn acrylic_compensation_levels(&self) -> AcrylicPlatformCompensationLevels {
418        self.acrylic_compensation_levels
419    }
420
421    fn set_frame_theme_variant(&self, _theme_variant: Option<PlatformThemeVariant>) {}
422
423    fn as_any(&self) -> &dyn Any {
424        self
425    }
426}