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
42pub 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 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 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 pub fn new(container: JsObject, native_control_host: JsObject, input_element: JsObject) -> Rc<Self> {
79 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 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 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 pub fn surface(&self) -> Option<Rc<RenderTargetBrowserSurface>> {
190 self.surface.borrow().clone()
191 }
192
193 pub fn top_level_id(&self) -> i32 {
195 self.top_level_id
196 }
197
198 pub fn input_handler(&self) -> &Rc<BrowserInputHandler> {
201 &self.input_handler
202 }
203
204 pub fn input_root(&self) -> Option<Rc<dyn IInputRoot>> {
206 self.input_handler.input_root()
207 }
208
209 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 self.publish_render_surfaces();
240
241 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}