use crate::browser_activatable_lifetime::BrowserActivatableLifetime;
use crate::browser_app_builder::BrowserPlatformOptions;
use crate::browser_platform_settings::BrowserPlatformSettings;
use crate::browser_runtime_platform::BrowserRuntimePlatform;
use crate::browser_screens::BrowserScreens;
use crate::browser_system_navigation_manager::BrowserSystemNavigationManagerImpl;
use crate::browser_single_threaded_dispatcher_impl::BrowserSingleThreadedDispatcherImpl;
use crate::cursor::CssCursorFactory;
use crate::interop::{ferro_module, JsObject};
use crate::rendering::BrowserSharedRenderLoop;
use crate::win_stubs::IconLoaderStub;
use ferroui_base::input::platform::{KeyGestureFormatInfo, PlatformHotkeyConfiguration};
use ferroui_base::input::{IKeyboardDevice, KeyboardDevice};
use ferroui_base::platform::{ICursorFactory, IPlatformSettings, IRuntimePlatform, ISystemNavigationManagerImpl};
use ferroui_base::threading::Dispatcher;
use ferroui_base::{FerroLocator, LocatorExtensions};
use ferroui_controls::application_lifetimes::IActivatableLifetime;
use ferroui_controls::platform::{
IPlatformIconLoader, IScreenImpl, ITopLevelImpl, ITrayIconImpl, IWindowImpl, IWindowingPlatform,
};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
ferroui_base::ferro_thread_local! {
static GLOBAL_THIS: RefCell<Option<JsObject>> = const { RefCell::new(None) };
static KEYBOARD: RefCell<Option<Rc<KeyboardDevice>>> = const { RefCell::new(None) };
}
#[derive(Default)]
pub struct BrowserWindowingPlatform;
impl BrowserWindowingPlatform {
pub fn global_this() -> JsObject {
match GLOBAL_THIS.with(|global_this| global_this.borrow().clone()) {
Some(global_this) => global_this,
None => panic!("Browser backend wasn't initialized. GlobalThis is null."),
}
}
pub(crate) fn set_global_this(value: JsObject) {
GLOBAL_THIS.with(|global_this| *global_this.borrow_mut() = Some(value));
}
pub fn keyboard() -> Rc<KeyboardDevice> {
match KEYBOARD.with(|keyboard| keyboard.borrow().clone()) {
Some(keyboard) => keyboard,
None => panic!("BrowserWindowingPlatform not registered."),
}
}
#[cfg(test)]
pub(crate) fn set_keyboard_for_unit_tests(keyboard: Option<Rc<KeyboardDevice>>) {
KEYBOARD.with(|slot| *slot.borrow_mut() = keyboard);
}
pub fn register() {
let instance: Rc<dyn IWindowingPlatform> = Rc::new(BrowserWindowingPlatform);
let keyboard = KeyboardDevice::new();
KEYBOARD.with(|slot| *slot.borrow_mut() = Some(keyboard.clone()));
let keyboard_device: Rc<dyn IKeyboardDevice> = keyboard;
let platform_settings = BrowserPlatformSettings::new();
let activatable_lifetime = BrowserActivatableLifetime::new();
let system_navigation_manager = Rc::new(BrowserSystemNavigationManagerImpl::default());
let locator = FerroLocator::current_mutable();
locator
.bind::<dyn IRuntimePlatform>()
.to_singleton::<BrowserRuntimePlatform>(|instance| instance)
.bind::<dyn ICursorFactory>()
.to_singleton::<CssCursorFactory>(|instance| instance)
.bind::<dyn IKeyboardDevice>()
.to_constant(keyboard_device)
.bind::<dyn IPlatformSettings>()
.to_constant(platform_settings.clone())
.bind_to_self(platform_settings)
.bind::<dyn ISystemNavigationManagerImpl>()
.to_constant(system_navigation_manager.clone())
.bind_to_self(system_navigation_manager)
.bind::<dyn IScreenImpl>()
.to_func(|| Some(BrowserScreens::instance() as Rc<dyn IScreenImpl>))
.bind::<BrowserScreens>()
.to_func(|| Some(BrowserScreens::instance()))
.bind::<dyn IWindowingPlatform>()
.to_constant(instance)
.bind::<dyn IPlatformIconLoader>()
.to_singleton::<IconLoaderStub>(|instance| instance)
.bind_to_self_singleton::<PlatformHotkeyConfiguration>()
.bind_to_self(Rc::new(KeyGestureFormatInfo::new(Some(HashMap::new()), "Cmd", "Ctrl", "Alt", "Shift")))
.bind::<dyn IActivatableLifetime>()
.to_constant(activatable_lifetime.clone())
.bind_to_self(activatable_lifetime);
Dispatcher::initialize_ui_thread_dispatcher(BrowserSingleThreadedDispatcherImpl::new());
let options = FerroLocator::current().get_service::<BrowserPlatformOptions>();
if options.as_ref().is_none_or(|options| options.render_thread) {
BrowserSharedRenderLoop::start_render_thread();
}
if let Some(options) = options {
if options.register_ferro_service_worker {
let sw_path = ferro_module::resolve_service_worker_path();
ferro_module::register_service_worker(sw_path, options.ferro_service_worker_scope.as_deref());
}
}
}
}
impl IWindowingPlatform for BrowserWindowingPlatform {
fn create_window(&self) -> Rc<dyn IWindowImpl> {
panic!(
"Browser doesn't support windowing platform. In order to display a single-view content, set ISingleViewApplicationLifetime.MainView."
);
}
fn create_embeddable_top_level(&self) -> Rc<dyn ITopLevelImpl> {
panic!("The method or operation is not implemented.");
}
fn create_embeddable_window(&self) -> Rc<dyn IWindowImpl> {
panic!("Browser doesn't support embeddable windowing platform.");
}
fn create_tray_icon(&self) -> Option<Rc<dyn ITrayIconImpl>> {
None
}
fn get_windows_z_order(&self, _windows: &[Rc<dyn IWindowImpl>], _z_order: &mut [i64]) {
panic!("Specified method is not supported.");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn there_is_no_tray_icon() {
assert!(BrowserWindowingPlatform.create_tray_icon().is_none());
}
#[test]
#[should_panic(expected = "Browser doesn't support windowing platform")]
fn windows_cannot_be_created() {
BrowserWindowingPlatform.create_window();
}
#[test]
#[should_panic(expected = "Browser doesn't support embeddable windowing platform")]
fn embeddable_windows_cannot_be_created() {
BrowserWindowingPlatform.create_embeddable_window();
}
#[test]
#[should_panic(expected = "Specified method is not supported")]
fn the_z_order_of_windows_is_not_available() {
BrowserWindowingPlatform.get_windows_z_order(&[], &mut []);
}
}