123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- use std::rc::Rc;
- use crate::controller::DesktopController;
- use dioxus_core::ScopeState;
- use serde::de::Error;
- use serde_json::Value;
- use std::future::Future;
- use std::future::IntoFuture;
- use std::pin::Pin;
- use wry::application::dpi::LogicalSize;
- use wry::application::event_loop::ControlFlow;
- use wry::application::event_loop::EventLoopProxy;
- #[cfg(target_os = "ios")]
- use wry::application::platform::ios::WindowExtIOS;
- use wry::application::window::Fullscreen as WryFullscreen;
- use UserWindowEvent::*;
- pub type ProxyType = EventLoopProxy<UserWindowEvent>;
- /// Get an imperative handle to the current window
- pub fn use_window(cx: &ScopeState) -> &DesktopContext {
- cx.use_hook(|| cx.consume_context::<DesktopContext>())
- .as_ref()
- .unwrap()
- }
- /// An imperative interface to the current window.
- ///
- /// To get a handle to the current window, use the [`use_window`] hook.
- ///
- ///
- /// # Example
- ///
- /// you can use `cx.consume_context::<DesktopContext>` to get this context
- ///
- /// ```rust, ignore
- /// let desktop = cx.consume_context::<DesktopContext>().unwrap();
- /// ```
- #[derive(Clone)]
- pub struct DesktopContext {
- /// The wry/tao proxy to the current window
- pub proxy: ProxyType,
- pub(super) eval_reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<Value>>>,
- }
- impl DesktopContext {
- pub(crate) fn new(
- proxy: ProxyType,
- eval_reciever: tokio::sync::mpsc::UnboundedReceiver<Value>,
- ) -> Self {
- Self {
- proxy,
- eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)),
- }
- }
- /// 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) {
- let _ = self.proxy.send_event(DragWindow);
- }
- /// set window minimize state
- pub fn set_minimized(&self, minimized: bool) {
- let _ = self.proxy.send_event(Minimize(minimized));
- }
- /// set window maximize state
- pub fn set_maximized(&self, maximized: bool) {
- let _ = self.proxy.send_event(Maximize(maximized));
- }
- /// toggle window maximize state
- pub fn toggle_maximized(&self) {
- let _ = self.proxy.send_event(MaximizeToggle);
- }
- /// set window visible or not
- pub fn set_visible(&self, visible: bool) {
- let _ = self.proxy.send_event(Visible(visible));
- }
- /// close window
- pub fn close(&self) {
- let _ = self.proxy.send_event(CloseWindow);
- }
- /// set window to focus
- pub fn focus(&self) {
- let _ = self.proxy.send_event(FocusWindow);
- }
- /// change window to fullscreen
- pub fn set_fullscreen(&self, fullscreen: bool) {
- let _ = self.proxy.send_event(Fullscreen(fullscreen));
- }
- /// set resizable state
- pub fn set_resizable(&self, resizable: bool) {
- let _ = self.proxy.send_event(Resizable(resizable));
- }
- /// set the window always on top
- pub fn set_always_on_top(&self, top: bool) {
- let _ = self.proxy.send_event(AlwaysOnTop(top));
- }
- /// set cursor visible or not
- pub fn set_cursor_visible(&self, visible: bool) {
- let _ = self.proxy.send_event(CursorVisible(visible));
- }
- /// set cursor grab
- pub fn set_cursor_grab(&self, grab: bool) {
- let _ = self.proxy.send_event(CursorGrab(grab));
- }
- /// set window title
- pub fn set_title(&self, title: &str) {
- let _ = self.proxy.send_event(SetTitle(String::from(title)));
- }
- /// change window to borderless
- pub fn set_decorations(&self, decoration: bool) {
- let _ = self.proxy.send_event(SetDecorations(decoration));
- }
- /// set window zoom level
- pub fn set_zoom_level(&self, scale_factor: f64) {
- let _ = self.proxy.send_event(SetZoomLevel(scale_factor));
- }
- /// modifies the inner size of the window
- pub fn set_inner_size(&self, logical_size: LogicalSize<f64>) {
- let _ = self.proxy.send_event(SetInnerSize(logical_size));
- }
- /// launch print modal
- pub fn print(&self) {
- let _ = self.proxy.send_event(Print);
- }
- /// opens DevTool window
- pub fn devtool(&self) {
- let _ = self.proxy.send_event(DevTool);
- }
- /// run (evaluate) a script in the WebView context
- pub fn eval(&self, script: impl std::string::ToString) {
- let _ = self.proxy.send_event(Eval(script.to_string()));
- }
- /// Push view
- #[cfg(target_os = "ios")]
- pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
- let _ = self.proxy.send_event(PushView(view));
- }
- /// Push view
- #[cfg(target_os = "ios")]
- pub fn pop_view(&self) {
- let _ = self.proxy.send_event(PopView);
- }
- }
- #[derive(Debug)]
- pub enum UserWindowEvent {
- EditsReady,
- Initialize,
- CloseWindow,
- DragWindow,
- FocusWindow,
- /// Set a new Dioxus template for hot-reloading
- ///
- /// Is a no-op in release builds. Must fit the right format for templates
- SetTemplate(String),
- Visible(bool),
- Minimize(bool),
- Maximize(bool),
- MaximizeToggle,
- Resizable(bool),
- AlwaysOnTop(bool),
- Fullscreen(bool),
- CursorVisible(bool),
- CursorGrab(bool),
- SetTitle(String),
- SetDecorations(bool),
- SetZoomLevel(f64),
- SetInnerSize(LogicalSize<f64>),
- Print,
- DevTool,
- Eval(String),
- #[cfg(target_os = "ios")]
- PushView(objc_id::ShareId<objc::runtime::Object>),
- #[cfg(target_os = "ios")]
- PopView,
- }
- impl DesktopController {
- pub(super) fn handle_event(
- &mut self,
- user_event: UserWindowEvent,
- control_flow: &mut ControlFlow,
- ) {
- // currently dioxus-desktop supports a single window only,
- // so we can grab the only webview from the map;
- // on wayland it is possible that a user event is emitted
- // before the webview is initialized. ignore the event.
- let webview = if let Some(webview) = self.webviews.values().next() {
- webview
- } else {
- return;
- };
- let window = webview.window();
- match user_event {
- Initialize | EditsReady => self.try_load_ready_webviews(),
- SetTemplate(template) => self.set_template(template),
- CloseWindow => *control_flow = ControlFlow::Exit,
- DragWindow => {
- // if the drag_window has any errors, we don't do anything
- window.fullscreen().is_none().then(|| window.drag_window());
- }
- Visible(state) => window.set_visible(state),
- Minimize(state) => window.set_minimized(state),
- Maximize(state) => window.set_maximized(state),
- MaximizeToggle => window.set_maximized(!window.is_maximized()),
- Fullscreen(state) => {
- if let Some(handle) = window.current_monitor() {
- window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle))));
- }
- }
- FocusWindow => window.set_focus(),
- Resizable(state) => window.set_resizable(state),
- AlwaysOnTop(state) => window.set_always_on_top(state),
- Eval(code) => {
- let script = format!(
- r#"window.ipc.postMessage(JSON.stringify({{"method":"eval_result", params: (function(){{
- {}
- }})()}}));"#,
- code
- );
- if let Err(e) = webview.evaluate_script(&script) {
- // we can't panic this error.
- log::warn!("Eval script error: {e}");
- }
- }
- CursorVisible(state) => window.set_cursor_visible(state),
- CursorGrab(state) => {
- let _ = window.set_cursor_grab(state);
- }
- SetTitle(content) => window.set_title(&content),
- SetDecorations(state) => window.set_decorations(state),
- SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
- SetInnerSize(logical_size) => window.set_inner_size(logical_size),
- Print => {
- if let Err(e) = webview.print() {
- // we can't panic this error.
- log::warn!("Open print modal failed: {e}");
- }
- }
- DevTool => {
- #[cfg(debug_assertions)]
- webview.open_devtools();
- #[cfg(not(debug_assertions))]
- log::warn!("Devtools are disabled in release builds");
- }
- #[cfg(target_os = "ios")]
- PushView(view) => 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.push(ui_view);
- },
- #[cfg(target_os = "ios")]
- PopView => unsafe {
- use objc::runtime::Object;
- use objc::*;
- assert!(is_main_thread());
- if let Some(view) = self.views.pop() {
- let ui_view_controller = window.ui_view_controller() as *mut Object;
- let _: () = msg_send![ui_view_controller, setView: view];
- }
- },
- }
- }
- }
- /// Get a closure that executes any JavaScript in the WebView context.
- pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult {
- let desktop = use_window(cx).clone();
- cx.use_hook(|| {
- move |script| {
- desktop.eval(script);
- let recv = desktop.eval_reciever.clone();
- EvalResult { reciever: recv }
- }
- })
- }
- /// A future that resolves to the result of a JavaScript evaluation.
- pub struct EvalResult {
- reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<serde_json::Value>>>,
- }
- impl IntoFuture for EvalResult {
- type Output = Result<serde_json::Value, serde_json::Error>;
- type IntoFuture = Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>;
- fn into_future(self) -> Self::IntoFuture {
- Box::pin(async move {
- let mut reciever = self.reciever.lock().await;
- match reciever.recv().await {
- Some(result) => Ok(result),
- None => Err(serde_json::Error::custom("No result returned")),
- }
- }) as Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>
- }
- }
- #[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
- }
|