use std::cell::RefCell;
use std::rc::Rc;
use std::rc::Weak;
use crate::create_new_window;
use crate::events::IpcMessage;
use crate::query::QueryEngine;
use crate::shortcut::ShortcutId;
use crate::shortcut::ShortcutRegistry;
use crate::shortcut::ShortcutRegistryError;
use crate::Config;
use crate::WebviewHandler;
use dioxus_core::ScopeState;
use dioxus_core::VirtualDom;
#[cfg(all(feature = "hot-reload", debug_assertions))]
use dioxus_hot_reload::HotReloadMsg;
use slab::Slab;
use wry::application::accelerator::Accelerator;
use wry::application::event::Event;
use wry::application::event_loop::EventLoopProxy;
use wry::application::event_loop::EventLoopWindowTarget;
#[cfg(target_os = "ios")]
use wry::application::platform::ios::WindowExtIOS;
use wry::application::window::Fullscreen as WryFullscreen;
use wry::application::window::Window;
use wry::application::window::WindowId;
use wry::webview::WebView;
pub type ProxyType = EventLoopProxy<UserWindowEvent>;
pub fn use_window(cx: &ScopeState) -> &DesktopContext {
cx.use_hook(|| cx.consume_context::<DesktopContext>())
.as_ref()
.unwrap()
}
pub(crate) type WebviewQueue = Rc<RefCell<Vec<WebviewHandler>>>;
pub struct DesktopService {
pub webview: Rc<WebView>,
pub proxy: ProxyType,
pub(super) query: QueryEngine,
pub(super) pending_windows: WebviewQueue,
pub(crate) event_loop: EventLoopWindowTarget<UserWindowEvent>,
pub(crate) event_handlers: WindowEventHandlers,
pub(crate) shortcut_manager: ShortcutRegistry,
#[cfg(target_os = "ios")]
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
}
pub type DesktopContext = Rc<DesktopService>;
impl std::ops::Deref for DesktopService {
type Target = Window;
fn deref(&self) -> &Self::Target {
self.webview.window()
}
}
impl DesktopService {
pub(crate) fn new(
webview: WebView,
proxy: ProxyType,
event_loop: EventLoopWindowTarget<UserWindowEvent>,
webviews: WebviewQueue,
event_handlers: WindowEventHandlers,
shortcut_manager: ShortcutRegistry,
) -> Self {
Self {
webview: Rc::new(webview),
proxy,
event_loop,
query: Default::default(),
pending_windows: webviews,
event_handlers,
shortcut_manager,
#[cfg(target_os = "ios")]
views: Default::default(),
}
}
pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> Weak<DesktopService> {
let window = create_new_window(
cfg,
&self.event_loop,
&self.proxy,
dom,
&self.pending_windows,
&self.event_handlers,
self.shortcut_manager.clone(),
);
let desktop_context = window
.dom
.base_scope()
.consume_context::<Rc<DesktopService>>()
.unwrap();
let id = window.desktop_context.webview.window().id();
self.proxy
.send_event(UserWindowEvent(EventData::NewWindow, id))
.unwrap();
self.proxy
.send_event(UserWindowEvent(EventData::Poll, id))
.unwrap();
self.pending_windows.borrow_mut().push(window);
Rc::downgrade(&desktop_context)
}
pub fn drag(&self) {
let window = self.webview.window();
if window.fullscreen().is_none() {
window.drag_window().unwrap();
}
}
pub fn toggle_maximized(&self) {
let window = self.webview.window();
window.set_maximized(!window.is_maximized())
}
pub fn close(&self) {
let _ = self
.proxy
.send_event(UserWindowEvent(EventData::CloseWindow, self.id()));
}
pub fn close_window(&self, id: WindowId) {
let _ = self
.proxy
.send_event(UserWindowEvent(EventData::CloseWindow, id));
}
pub fn set_fullscreen(&self, fullscreen: bool) {
if let Some(handle) = self.webview.window().current_monitor() {
self.webview
.window()
.set_fullscreen(fullscreen.then_some(WryFullscreen::Borderless(Some(handle))));
}
}
pub fn print(&self) {
if let Err(e) = self.webview.print() {
tracing::warn!("Open print modal failed: {e}");
}
}
pub fn set_zoom_level(&self, level: f64) {
self.webview.zoom(level);
}
pub fn devtool(&self) {
#[cfg(debug_assertions)]
self.webview.open_devtools();
#[cfg(not(debug_assertions))]
tracing::warn!("Devtools are disabled in release builds");
}
pub fn create_wry_event_handler(
&self,
handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
) -> WryEventHandlerId {
self.event_handlers.add(self.id(), handler)
}
pub fn remove_wry_event_handler(&self, id: WryEventHandlerId) {
self.event_handlers.remove(id)
}
pub fn create_shortcut(
&self,
accelerator: Accelerator,
callback: impl FnMut() + 'static,
) -> Result<ShortcutId, ShortcutRegistryError> {
self.shortcut_manager
.add_shortcut(accelerator, Box::new(callback))
}
pub fn remove_shortcut(&self, id: ShortcutId) {
self.shortcut_manager.remove_shortcut(id)
}
pub fn remove_all_shortcuts(&self) {
self.shortcut_manager.remove_all()
}
#[cfg(target_os = "ios")]
pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
let window = self.webview.window();
unsafe {
use objc::runtime::Object;
use objc::*;
assert!(is_main_thread());
let ui_view = window.ui_view() as *mut Object;
let ui_view_frame: *mut Object = msg_send![ui_view, frame];
let _: () = msg_send![view, setFrame: ui_view_frame];
let _: () = msg_send![view, setAutoresizingMask: 31];
let ui_view_controller = window.ui_view_controller() as *mut Object;
let _: () = msg_send![ui_view_controller, setView: view];
self.views.borrow_mut().push(ui_view);
}
}
#[cfg(target_os = "ios")]
pub fn pop_view(&self) {
let window = self.webview.window();
unsafe {
use objc::runtime::Object;
use objc::*;
assert!(is_main_thread());
if let Some(view) = self.views.borrow_mut().pop() {
let ui_view_controller = window.ui_view_controller() as *mut Object;
let _: () = msg_send![ui_view_controller, setView: view];
}
}
}
}
#[derive(Debug, Clone)]
pub struct UserWindowEvent(pub EventData, pub WindowId);
#[derive(Debug, Clone)]
pub enum EventData {
Poll,
Ipc(IpcMessage),
#[cfg(all(feature = "hot-reload", debug_assertions))]
HotReloadEvent(HotReloadMsg),
NewWindow,
CloseWindow,
}
#[cfg(target_os = "ios")]
fn is_main_thread() -> bool {
use objc::runtime::{Class, BOOL, NO};
use objc::*;
let cls = Class::get("NSThread").unwrap();
let result: BOOL = unsafe { msg_send![cls, isMainThread] };
result != NO
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WryEventHandlerId(usize);
#[derive(Clone, Default)]
pub(crate) struct WindowEventHandlers {
handlers: Rc<RefCell<Slab<WryWindowEventHandlerInner>>>,
}
impl WindowEventHandlers {
pub(crate) fn add(
&self,
window_id: WindowId,
handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
) -> WryEventHandlerId {
WryEventHandlerId(
self.handlers
.borrow_mut()
.insert(WryWindowEventHandlerInner {
window_id,
handler: Box::new(handler),
}),
)
}
pub(crate) fn remove(&self, id: WryEventHandlerId) {
self.handlers.borrow_mut().try_remove(id.0);
}
pub(crate) fn apply_event(
&self,
event: &Event<UserWindowEvent>,
target: &EventLoopWindowTarget<UserWindowEvent>,
) {
for (_, handler) in self.handlers.borrow_mut().iter_mut() {
handler.apply_event(event, target);
}
}
}
struct WryWindowEventHandlerInner {
window_id: WindowId,
handler: WryEventHandlerCallback,
}
type WryEventHandlerCallback =
Box<dyn FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static>;
impl WryWindowEventHandlerInner {
fn apply_event(
&mut self,
event: &Event<UserWindowEvent>,
target: &EventLoopWindowTarget<UserWindowEvent>,
) {
match event {
Event::WindowEvent { window_id, .. }
| Event::MenuEvent {
window_id: Some(window_id),
..
} => {
if *window_id != self.window_id {
return;
}
}
_ => (),
}
(self.handler)(event, target)
}
}
pub fn use_wry_event_handler(
cx: &ScopeState,
handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
) -> &WryEventHandler {
let desktop = use_window(cx);
cx.use_hook(move || {
let desktop = desktop.clone();
let id = desktop.create_wry_event_handler(handler);
WryEventHandler {
handlers: desktop.event_handlers.clone(),
id,
}
})
}
pub struct WryEventHandler {
handlers: WindowEventHandlers,
pub id: WryEventHandlerId,
}
impl WryEventHandler {
pub fn remove(&self) {
self.handlers.remove(self.id);
}
}
impl Drop for WryEventHandler {
fn drop(&mut self) {
self.handlers.remove(self.id);
}
}