浏览代码

feat(core) window events, closes #1523 (#1726)

Lucas Fernandes Nogueira 4 年之前
父节点
当前提交
9c10ccf8d3

+ 5 - 0
.changes/emit-window-events.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Emit `tauri://resize`, `tauri://move`, `tauri://close-requested`, `tauri://destroyed`, `tauri://focus`, `tauri://blur` and `tauri://scale-change` events to the window.

+ 5 - 0
.changes/window-events.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Adds `on_window_event` API to the `Window` struct.

+ 6 - 3
core/tauri/src/lib.rs

@@ -63,9 +63,12 @@ pub use {
   self::runtime::flavors::wry::Wry,
   self::runtime::monitor::Monitor,
   self::runtime::webview::{WebviewAttributes, WindowBuilder},
-  self::runtime::window::export::{
-    dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
-    Window,
+  self::runtime::window::{
+    export::{
+      dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
+      Window,
+    },
+    WindowEvent,
   },
   self::state::{State, StateManager},
 };

+ 77 - 15
core/tauri/src/runtime/flavors/wry.rs

@@ -13,7 +13,7 @@ use crate::{
     },
     window::{
       dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
-      DetachedWindow, PendingWindow,
+      DetachedWindow, PendingWindow, WindowEvent,
     },
     Dispatch, Monitor, Params, Runtime,
   },
@@ -21,6 +21,7 @@ use crate::{
 };
 
 use image::{GenericImageView, Pixel};
+use uuid::Uuid;
 use wry::{
   application::{
     dpi::{
@@ -28,7 +29,7 @@ use wry::{
       PhysicalPosition as WryPhysicalPosition, PhysicalSize as WryPhysicalSize,
       Position as WryPosition, Size as WrySize,
     },
-    event::{Event, WindowEvent},
+    event::{Event, WindowEvent as WryWindowEvent},
     event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget},
     monitor::MonitorHandle,
     window::{Fullscreen, Icon as WindowIcon, Window, WindowBuilder as WryWindowBuilder, WindowId},
@@ -51,6 +52,8 @@ use std::{
 type CreateWebviewHandler =
   Box<dyn FnOnce(&EventLoopWindowTarget<Message>) -> crate::Result<WebView> + Send>;
 type MainThreadTask = Box<dyn FnOnce() + Send>;
+type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
+type WindowEventListeners = Arc<Mutex<HashMap<Uuid, WindowEventHandler>>>;
 
 #[repr(C)]
 #[derive(Debug)]
@@ -86,6 +89,29 @@ impl TryFrom<Icon> for WryIcon {
   }
 }
 
+struct WindowEventWrapper(Option<WindowEvent>);
+
+impl<'a> From<&WryWindowEvent<'a>> for WindowEventWrapper {
+  fn from(event: &WryWindowEvent<'a>) -> Self {
+    let event = match event {
+      WryWindowEvent::Resized(size) => WindowEvent::Resized((*size).into()),
+      WryWindowEvent::Moved(position) => WindowEvent::Moved((*position).into()),
+      WryWindowEvent::CloseRequested => WindowEvent::CloseRequested,
+      WryWindowEvent::Destroyed => WindowEvent::Destroyed,
+      WryWindowEvent::Focused(focused) => WindowEvent::Focused(*focused),
+      WryWindowEvent::ScaleFactorChanged {
+        scale_factor,
+        new_inner_size,
+      } => WindowEvent::ScaleFactorChanged {
+        scale_factor: *scale_factor,
+        new_inner_size: (**new_inner_size).into(),
+      },
+      _ => return Self(None),
+    };
+    Self(Some(event))
+  }
+}
+
 impl From<MonitorHandle> for Monitor {
   fn from(monitor: MonitorHandle) -> Monitor {
     Self {
@@ -332,6 +358,7 @@ pub struct WryDispatcher {
   window_id: WindowId,
   proxy: EventLoopProxy<Message>,
   task_tx: Sender<MainThreadTask>,
+  window_event_listeners: WindowEventListeners,
 }
 
 macro_rules! dispatcher_getter {
@@ -375,6 +402,16 @@ impl Dispatch for WryDispatcher {
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
+  fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> Uuid {
+    let id = Uuid::new_v4();
+    self
+      .window_event_listeners
+      .lock()
+      .unwrap()
+      .insert(id, Box::new(f));
+    id
+  }
+
   // GETTERS
 
   fn scale_factor(&self) -> crate::Result<f64> {
@@ -430,11 +467,12 @@ impl Dispatch for WryDispatcher {
     let label = pending.label.clone();
     let proxy = self.proxy.clone();
     let task_tx = self.task_tx.clone();
+    let window_event_listeners = self.window_event_listeners.clone();
     self
       .proxy
       .send_event(Message::CreateWebview(
         Arc::new(Mutex::new(Some(Box::new(move |event_loop| {
-          create_webview(event_loop, proxy, task_tx, pending)
+          create_webview(event_loop, proxy, task_tx, window_event_listeners, pending)
         })))),
         tx,
       ))
@@ -444,6 +482,7 @@ impl Dispatch for WryDispatcher {
       window_id,
       proxy: self.proxy.clone(),
       task_tx: self.task_tx.clone(),
+      window_event_listeners: self.window_event_listeners.clone(),
     };
     Ok(DetachedWindow { label, dispatcher })
   }
@@ -620,6 +659,7 @@ pub struct Wry {
   event_loop: EventLoop<Message>,
   webviews: HashMap<WindowId, WebView>,
   task_tx: Sender<MainThreadTask>,
+  window_event_listeners: WindowEventListeners,
   task_rx: Receiver<MainThreadTask>,
 }
 
@@ -634,6 +674,7 @@ impl Runtime for Wry {
       webviews: Default::default(),
       task_tx,
       task_rx,
+      window_event_listeners: Default::default(),
     })
   }
 
@@ -647,6 +688,7 @@ impl Runtime for Wry {
       &self.event_loop,
       proxy.clone(),
       self.task_tx.clone(),
+      self.window_event_listeners.clone(),
       pending,
     )?;
 
@@ -654,6 +696,7 @@ impl Runtime for Wry {
       window_id: webview.window().id(),
       proxy,
       task_tx: self.task_tx.clone(),
+      window_event_listeners: self.window_event_listeners.clone(),
     };
 
     self.webviews.insert(webview.window().id(), webview);
@@ -664,6 +707,7 @@ impl Runtime for Wry {
   fn run(self) {
     let mut webviews = self.webviews;
     let task_rx = self.task_rx;
+    let window_event_listeners = self.window_event_listeners.clone();
     self.event_loop.run(move |event, event_loop, control_flow| {
       *control_flow = ControlFlow::Wait;
 
@@ -678,20 +722,27 @@ impl Runtime for Wry {
       }
 
       match event {
-        Event::WindowEvent { event, window_id } => match event {
-          WindowEvent::CloseRequested => {
-            webviews.remove(&window_id);
-            if webviews.is_empty() {
-              *control_flow = ControlFlow::Exit;
+        Event::WindowEvent { event, window_id } => {
+          if let Some(event) = WindowEventWrapper::from(&event).0 {
+            for handler in window_event_listeners.lock().unwrap().values() {
+              handler(&event);
             }
           }
-          WindowEvent::Resized(_) => {
-            if let Err(e) = webviews[&window_id].resize() {
-              eprintln!("{}", e);
+          match event {
+            WryWindowEvent::CloseRequested => {
+              webviews.remove(&window_id);
+              if webviews.is_empty() {
+                *control_flow = ControlFlow::Exit;
+              }
             }
+            WryWindowEvent::Resized(_) => {
+              if let Err(e) = webviews[&window_id].resize() {
+                eprintln!("{}", e);
+              }
+            }
+            _ => {}
           }
-          _ => {}
-        },
+        }
         Event::UserEvent(message) => match message {
           Message::Window(id, window_message) => {
             if let Some(webview) = webviews.get_mut(&id) {
@@ -797,6 +848,7 @@ fn create_webview<M: Params<Runtime = Wry>>(
   event_loop: &EventLoopWindowTarget<Message>,
   proxy: EventLoopProxy<Message>,
   task_tx: Sender<MainThreadTask>,
+  window_event_listeners: WindowEventListeners,
   pending: PendingWindow<M>,
 ) -> crate::Result<WebView> {
   let PendingWindow {
@@ -818,13 +870,19 @@ fn create_webview<M: Params<Runtime = Wry>>(
     webview_builder = webview_builder.with_rpc_handler(create_rpc_handler(
       proxy.clone(),
       task_tx.clone(),
+      window_event_listeners.clone(),
       label.clone(),
       handler,
     ));
   }
   if let Some(handler) = file_drop_handler {
-    webview_builder = webview_builder
-      .with_file_drop_handler(create_file_drop_handler(proxy, task_tx, label, handler));
+    webview_builder = webview_builder.with_file_drop_handler(create_file_drop_handler(
+      proxy,
+      task_tx,
+      window_event_listeners,
+      label,
+      handler,
+    ));
   }
   for (scheme, protocol) in webview_attributes.uri_scheme_protocols {
     webview_builder = webview_builder.with_custom_protocol(scheme, move |_window, url| {
@@ -847,6 +905,7 @@ fn create_webview<M: Params<Runtime = Wry>>(
 fn create_rpc_handler<M: Params<Runtime = Wry>>(
   proxy: EventLoopProxy<Message>,
   task_tx: Sender<MainThreadTask>,
+  window_event_listeners: WindowEventListeners,
   label: M::Label,
   handler: WebviewRpcHandler<M>,
 ) -> Box<dyn Fn(&Window, WryRpcRequest) -> Option<RpcResponse> + 'static> {
@@ -857,6 +916,7 @@ fn create_rpc_handler<M: Params<Runtime = Wry>>(
           window_id: window.id(),
           proxy: proxy.clone(),
           task_tx: task_tx.clone(),
+          window_event_listeners: window_event_listeners.clone(),
         },
         label: label.clone(),
       },
@@ -870,6 +930,7 @@ fn create_rpc_handler<M: Params<Runtime = Wry>>(
 fn create_file_drop_handler<M: Params<Runtime = Wry>>(
   proxy: EventLoopProxy<Message>,
   task_tx: Sender<MainThreadTask>,
+  window_event_listeners: WindowEventListeners,
   label: M::Label,
   handler: FileDropHandler<M>,
 ) -> Box<dyn Fn(&Window, WryFileDropEvent) -> bool + 'static> {
@@ -881,6 +942,7 @@ fn create_file_drop_handler<M: Params<Runtime = Wry>>(
           window_id: window.id(),
           proxy: proxy.clone(),
           task_tx: task_tx.clone(),
+          window_event_listeners: window_event_listeners.clone(),
         },
         label: label.clone(),
       },

+ 76 - 1
core/tauri/src/runtime/manager.rs

@@ -18,7 +18,7 @@ use crate::{
       CustomProtocol, FileDropEvent, FileDropHandler, InvokePayload, WebviewRpcHandler,
       WindowBuilder,
     },
-    window::{DetachedWindow, PendingWindow},
+    window::{dpi::PhysicalSize, DetachedWindow, PendingWindow, WindowEvent},
     Icon, Runtime,
   },
   sealed::ParamsBase,
@@ -36,6 +36,14 @@ use std::{
 };
 use uuid::Uuid;
 
+const WINDOW_RESIZED_EVENT: &str = "tauri://resize";
+const WINDOW_MOVED_EVENT: &str = "tauri://move";
+const WINDOW_CLOSE_REQUESTED_EVENT: &str = "tauri://close-requested";
+const WINDOW_DESTROYED_EVENT: &str = "tauri://destroyed";
+const WINDOW_FOCUS_EVENT: &str = "tauri://focus";
+const WINDOW_BLUR_EVENT: &str = "tauri://blur";
+const WINDOW_SCALE_FACTOR_CHANGED_EVENT: &str = "tauri://scale-change";
+
 /// Parse a string representing an internal tauri event into [`Params::Event`]
 ///
 /// # Panics
@@ -482,9 +490,15 @@ impl<P: Params> WindowManager<P> {
 
     Ok(pending)
   }
+
   pub fn attach_window(&self, window: DetachedWindow<P>) -> Window<P> {
     let window = Window::new(self.clone(), window);
 
+    let window_ = window.clone();
+    window.on_window_event(move |event| {
+      let _ = on_window_event(&window_, event);
+    });
+
     // insert the window into our manager
     {
       self
@@ -611,3 +625,64 @@ impl<P: Params> WindowManager<P> {
     self.windows_lock().clone()
   }
 }
+
+fn on_window_event<P: Params>(window: &Window<P>, event: &WindowEvent) -> crate::Result<()> {
+  match event {
+    WindowEvent::Resized(size) => window.emit(
+      &WINDOW_RESIZED_EVENT
+        .parse()
+        .unwrap_or_else(|_| panic!("unhandled event")),
+      Some(size),
+    )?,
+    WindowEvent::Moved(position) => window.emit(
+      &WINDOW_MOVED_EVENT
+        .parse()
+        .unwrap_or_else(|_| panic!("unhandled event")),
+      Some(position),
+    )?,
+    WindowEvent::CloseRequested => window.emit(
+      &WINDOW_CLOSE_REQUESTED_EVENT
+        .parse()
+        .unwrap_or_else(|_| panic!("unhandled event")),
+      Some(()),
+    )?,
+    WindowEvent::Destroyed => window.emit(
+      &WINDOW_DESTROYED_EVENT
+        .parse()
+        .unwrap_or_else(|_| panic!("unhandled event")),
+      Some(()),
+    )?,
+    WindowEvent::Focused(focused) => window.emit(
+      &if *focused {
+        WINDOW_FOCUS_EVENT
+          .parse()
+          .unwrap_or_else(|_| panic!("unhandled event"))
+      } else {
+        WINDOW_BLUR_EVENT
+          .parse()
+          .unwrap_or_else(|_| panic!("unhandled event"))
+      },
+      Some(()),
+    )?,
+    WindowEvent::ScaleFactorChanged {
+      scale_factor,
+      new_inner_size,
+    } => window.emit(
+      &WINDOW_SCALE_FACTOR_CHANGED_EVENT
+        .parse()
+        .unwrap_or_else(|_| panic!("unhandled event")),
+      Some(ScaleFactorChanged {
+        scale_factor: *scale_factor,
+        size: new_inner_size.clone(),
+      }),
+    )?,
+  }
+  Ok(())
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+struct ScaleFactorChanged {
+  scale_factor: f64,
+  size: PhysicalSize<u32>,
+}

+ 8 - 1
core/tauri/src/runtime/mod.rs

@@ -8,6 +8,7 @@ use crate::{
   runtime::window::{DetachedWindow, PendingWindow},
   Icon, Params, WindowBuilder,
 };
+use uuid::Uuid;
 
 pub(crate) mod app;
 pub mod flavors;
@@ -19,7 +20,10 @@ pub mod webview;
 pub mod window;
 
 use monitor::Monitor;
-use window::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
+use window::{
+  dpi::{PhysicalPosition, PhysicalSize, Position, Size},
+  WindowEvent,
+};
 
 /// The webview runtime interface.
 pub trait Runtime: Sized + 'static {
@@ -50,6 +54,9 @@ pub trait Dispatch: Clone + Send + Sized + 'static {
   /// Run a task on the main thread.
   fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()>;
 
+  /// Registers a window event handler.
+  fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> Uuid;
+
   // GETTERS
 
   /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.

+ 37 - 0
core/tauri/src/runtime/window.rs

@@ -23,6 +23,38 @@ use std::hash::{Hash, Hasher};
 /// UI scaling utilities.
 pub mod dpi;
 
+/// An event from a window.
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub enum WindowEvent {
+  /// The size of the window has changed. Contains the client area's new dimensions.
+  Resized(dpi::PhysicalSize<u32>),
+  /// The position of the window has changed. Contains the window's new position.
+  Moved(dpi::PhysicalPosition<i32>),
+  /// The window has been requested to close.
+  CloseRequested,
+  /// The window has been destroyed.
+  Destroyed,
+  /// The window gained or lost focus.
+  ///
+  /// The parameter is true if the window has gained focus, and false if it has lost focus.
+  Focused(bool),
+  ///The window's scale factor has changed.
+  ///
+  /// The following user actions can cause DPI changes:
+  ///
+  /// - Changing the display's resolution.
+  /// - Changing the display's scale factor (e.g. in Control Panel on Windows).
+  /// - Moving the window to a display with a different scale factor.
+  #[non_exhaustive]
+  ScaleFactorChanged {
+    /// The new scale factor.
+    scale_factor: f64,
+    /// The window inner size.
+    new_inner_size: dpi::PhysicalSize<u32>,
+  },
+}
+
 /// A webview window that has yet to be built.
 pub struct PendingWindow<M: Params> {
   /// The label that the window will be named.
@@ -313,6 +345,11 @@ pub(crate) mod export {
       self.window.dispatcher.eval_script(js)
     }
 
+    /// Registers a window event listener.
+    pub fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) {
+      self.window.dispatcher.on_window_event(f);
+    }
+
     // Getters
 
     /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.