use crate::{
app::SharedContext,
assets::AssetHandlerRegistry,
file_upload::NativeFileHover,
ipc::UserWindowEvent,
query::QueryEngine,
shortcut::{HotKey, HotKeyState, ShortcutHandle, ShortcutRegistryError},
webview::PendingWebview,
AssetRequest, Config, WindowCloseBehaviour, WryEventHandler,
};
use dioxus_core::{Callback, VirtualDom};
use std::{
cell::Cell,
future::{Future, IntoFuture},
pin::Pin,
rc::{Rc, Weak},
sync::Arc,
};
use tao::{
event::Event,
event_loop::EventLoopWindowTarget,
window::{Fullscreen as WryFullscreen, Window, WindowId},
};
use wry::{RequestAsyncResponder, WebView};
#[cfg(target_os = "ios")]
use tao::platform::ios::WindowExtIOS;
pub fn window() -> DesktopContext {
dioxus_core::consume_context()
}
pub type DesktopContext = Rc<DesktopService>;
pub type WeakDesktopContext = Weak<DesktopService>;
pub struct DesktopService {
pub webview: WebView,
pub window: Arc<Window>,
pub(crate) shared: Rc<SharedContext>,
pub(super) query: QueryEngine,
pub(crate) asset_handlers: AssetHandlerRegistry,
pub(crate) file_hover: NativeFileHover,
pub(crate) close_behaviour: Rc<Cell<WindowCloseBehaviour>>,
#[cfg(target_os = "ios")]
pub(crate) views: Rc<std::cell::RefCell<Vec<*mut objc::runtime::Object>>>,
}
impl std::ops::Deref for DesktopService {
type Target = Window;
fn deref(&self) -> &Self::Target {
&self.window
}
}
impl DesktopService {
pub(crate) fn new(
webview: WebView,
window: Arc<Window>,
shared: Rc<SharedContext>,
asset_handlers: AssetHandlerRegistry,
file_hover: NativeFileHover,
close_behaviour: WindowCloseBehaviour,
) -> Self {
Self {
window,
webview,
shared,
asset_handlers,
file_hover,
close_behaviour: Rc::new(Cell::new(close_behaviour)),
query: Default::default(),
#[cfg(target_os = "ios")]
views: Default::default(),
}
}
pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> PendingDesktopContext {
let (window, context) = PendingWebview::new(dom, cfg);
self.shared
.proxy
.send_event(UserWindowEvent::NewWindow)
.unwrap();
self.shared.pending_webviews.borrow_mut().push(window);
context
}
pub fn drag(&self) {
if self.window.fullscreen().is_none() {
_ = self.window.drag_window();
}
}
pub fn toggle_maximized(&self) {
self.window.set_maximized(!self.window.is_maximized())
}
pub fn set_close_behavior(&self, behaviour: WindowCloseBehaviour) {
self.close_behaviour.set(behaviour);
}
pub fn close(&self) {
let _ = self
.shared
.proxy
.send_event(UserWindowEvent::CloseWindow(self.id()));
}
pub fn close_window(&self, id: WindowId) {
let _ = self
.shared
.proxy
.send_event(UserWindowEvent::CloseWindow(id));
}
pub fn set_fullscreen(&self, fullscreen: bool) {
if let Some(handle) = &self.window.current_monitor() {
self.window.set_fullscreen(
fullscreen.then_some(WryFullscreen::Borderless(Some(handle.clone()))),
);
}
}
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) {
if let Err(e) = self.webview.zoom(level) {
tracing::warn!("Set webview zoom failed: {e}");
}
}
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,
) -> WryEventHandler {
self.shared.event_handlers.add(self.window.id(), handler)
}
pub fn remove_wry_event_handler(&self, id: WryEventHandler) {
self.shared.event_handlers.remove(id)
}
pub fn create_shortcut(
&self,
hotkey: HotKey,
callback: impl FnMut(HotKeyState) + 'static,
) -> Result<ShortcutHandle, ShortcutRegistryError> {
self.shared
.shortcut_manager
.add_shortcut(hotkey, Box::new(callback))
}
pub fn remove_shortcut(&self, id: ShortcutHandle) {
self.shared.shortcut_manager.remove_shortcut(id)
}
pub fn remove_all_shortcuts(&self) {
self.shared.shortcut_manager.remove_all()
}
pub fn register_asset_handler(
&self,
name: String,
handler: impl Fn(AssetRequest, RequestAsyncResponder) + 'static,
) {
self.asset_handlers
.register_handler(name, Callback::new(move |(req, resp)| handler(req, resp)))
}
pub fn remove_asset_handler(&self, name: &str) -> Option<()> {
self.asset_handlers.remove_handler(name).map(|_| ())
}
#[cfg(target_os = "ios")]
pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
let window = &self.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.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];
}
}
}
}
#[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
}
pub struct PendingDesktopContext {
pub(crate) receiver: futures_channel::oneshot::Receiver<DesktopContext>,
}
impl PendingDesktopContext {
pub async fn resolve(self) -> DesktopContext {
self.try_resolve()
.await
.expect("Failed to resolve pending desktop context")
}
pub async fn try_resolve(self) -> Result<DesktopContext, futures_channel::oneshot::Canceled> {
self.receiver.await
}
}
impl IntoFuture for PendingDesktopContext {
type Output = DesktopContext;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(self.resolve())
}
}