123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- use crate::{
- app::SharedContext,
- assets::AssetHandlerRegistry,
- file_upload::NativeFileHover,
- ipc::UserWindowEvent,
- query::QueryEngine,
- shortcut::{HotKey, HotKeyState, ShortcutHandle, ShortcutRegistryError},
- webview::WebviewInstance,
- AssetRequest, Config, WryEventHandler,
- };
- use dioxus_core::{
- prelude::{Callback, ScopeId},
- VirtualDom,
- };
- use std::rc::{Rc, Weak};
- 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;
- /// Get an imperative handle to the current window without using a hook
- ///
- /// ## Panics
- ///
- /// This function will panic if it is called outside of the context of a Dioxus App.
- pub fn window() -> DesktopContext {
- dioxus_core::prelude::consume_context()
- }
- /// A handle to the [`DesktopService`] that can be passed around.
- pub type DesktopContext = Rc<DesktopService>;
- /// A weak handle to the [`DesktopService`] to ensure safe passing.
- /// The problem without this is that the tao window is never dropped and therefore cannot be closed.
- /// This was due to the Rc that had still references because of multiple copies when creating a webview.
- pub type WeakDesktopContext = Weak<DesktopService>;
- /// An imperative interface to the current window.
- ///
- /// To get a handle to the current window, use the [`window`] function.
- ///
- ///
- /// # Example
- ///
- /// you can use `cx.consume_context::<DesktopContext>` to get this context
- ///
- /// ```rust, ignore
- /// let desktop = cx.consume_context::<DesktopContext>().unwrap();
- /// ```
- pub struct DesktopService {
- /// The wry/tao proxy to the current window
- pub webview: WebView,
- /// The tao window itself
- pub window: Window,
- pub(crate) shared: Rc<SharedContext>,
- /// The receiver for queries about the current window
- pub(super) query: QueryEngine,
- pub(crate) asset_handlers: AssetHandlerRegistry,
- pub(crate) file_hover: NativeFileHover,
- #[cfg(target_os = "ios")]
- pub(crate) views: Rc<std::cell::RefCell<Vec<*mut objc::runtime::Object>>>,
- }
- /// A smart pointer to the current window.
- 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: Window,
- shared: Rc<SharedContext>,
- asset_handlers: AssetHandlerRegistry,
- file_hover: NativeFileHover,
- ) -> Self {
- Self {
- window,
- webview,
- shared,
- asset_handlers,
- file_hover,
- query: Default::default(),
- #[cfg(target_os = "ios")]
- views: Default::default(),
- }
- }
- /// Create a new window using the props and window builder
- ///
- /// Returns the webview handle for the new window.
- ///
- /// You can use this to control other windows from the current window.
- ///
- /// Be careful to not create a cycle of windows, or you might leak memory.
- pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> WeakDesktopContext {
- let window = WebviewInstance::new(cfg, dom, self.shared.clone());
- let cx = window.dom.in_runtime(|| {
- ScopeId::ROOT
- .consume_context::<Rc<DesktopService>>()
- .unwrap()
- });
- self.shared
- .proxy
- .send_event(UserWindowEvent::NewWindow)
- .unwrap();
- self.shared.pending_webviews.borrow_mut().push(window);
- Rc::downgrade(&cx)
- }
- /// trigger the drag-window event
- ///
- /// Moves the window with the left mouse button until the button is released.
- ///
- /// you need use it in `onmousedown` event:
- /// ```rust, ignore
- /// onmousedown: move |_| { desktop.drag_window(); }
- /// ```
- pub fn drag(&self) {
- if self.window.fullscreen().is_none() {
- _ = self.window.drag_window();
- }
- }
- /// Toggle whether the window is maximized or not
- pub fn toggle_maximized(&self) {
- self.window.set_maximized(!self.window.is_maximized())
- }
- /// Close this window
- pub fn close(&self) {
- let _ = self
- .shared
- .proxy
- .send_event(UserWindowEvent::CloseWindow(self.id()));
- }
- /// Close a particular window, given its ID
- pub fn close_window(&self, id: WindowId) {
- let _ = self
- .shared
- .proxy
- .send_event(UserWindowEvent::CloseWindow(id));
- }
- /// change window to fullscreen
- 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()))),
- );
- }
- }
- /// launch print modal
- pub fn print(&self) {
- if let Err(e) = self.webview.print() {
- tracing::warn!("Open print modal failed: {e}");
- }
- }
- /// Set the zoom level of the webview
- pub fn set_zoom_level(&self, level: f64) {
- if let Err(e) = self.webview.zoom(level) {
- tracing::warn!("Set webview zoom failed: {e}");
- }
- }
- /// opens DevTool window
- pub fn devtool(&self) {
- #[cfg(debug_assertions)]
- self.webview.open_devtools();
- #[cfg(not(debug_assertions))]
- tracing::warn!("Devtools are disabled in release builds");
- }
- /// Create a wry event handler that listens for wry events.
- /// This event handler is scoped to the currently active window and will only receive events that are either global or related to the current window.
- ///
- /// The id this function returns can be used to remove the event handler with [`Self::remove_wry_event_handler`]
- 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)
- }
- /// Remove a wry event handler created with [`Self::create_wry_event_handler`]
- pub fn remove_wry_event_handler(&self, id: WryEventHandler) {
- self.shared.event_handlers.remove(id)
- }
- /// Create a global shortcut
- ///
- /// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information.
- 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))
- }
- /// Remove a global shortcut
- pub fn remove_shortcut(&self, id: ShortcutHandle) {
- self.shared.shortcut_manager.remove_shortcut(id)
- }
- /// Remove all global shortcuts
- pub fn remove_all_shortcuts(&self) {
- self.shared.shortcut_manager.remove_all()
- }
- /// Provide a callback to handle asset loading yourself.
- /// If the ScopeId isn't provided, defaults to a global handler.
- /// Note that the handler is namespaced by name, not ScopeId.
- ///
- /// When the component is dropped, the handler is removed.
- ///
- /// See [`crate::use_asset_handler`] for a convenient hook.
- 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)))
- }
- /// Removes an asset handler by its identifier.
- ///
- /// Returns `None` if the handler did not exist.
- pub fn remove_asset_handler(&self, name: &str) -> Option<()> {
- self.asset_handlers.remove_handler(name).map(|_| ())
- }
- /// Push an objc view to the window
- #[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);
- }
- }
- /// Pop an objc view from the window
- #[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
- }
|