mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-08-17 03:40:23 +03:00
refactor(core): add webview events (#8844)
* refactor(core): add webview events * license header * clippy * fix doctests * more doctests * fix JS `listen` with `EventTarget::Any` * typo * update module import * clippy * remove console.log * fix api example * fix documentation for emiTo [skip ci] * actually add RunEvent::WebviewEvent * update migration * lint --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
parent
5618f6d2ff
commit
16e550ec15
9
.changes/api-tauri-event-file-drop-rename.md
Normal file
9
.changes/api-tauri-event-file-drop-rename.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
'@tauri-apps/api': 'patch:breaking'
|
||||||
|
---
|
||||||
|
|
||||||
|
Renamed the following enum variants of `TauriEvent` enum:
|
||||||
|
|
||||||
|
- `TauriEvent.WEBVIEW_FILE_DROP` -> `TauriEvent.FILE_DROP`
|
||||||
|
- `TauriEvent.WEBVIEW_FILE_DROP_HOVER` -> `TauriEvent.FILE_DROP_HOVER`
|
||||||
|
- `TauriEvent.WEBVIEW_FILE_DROP_CANCELLED` -> `TauriEvent.FILE_DROP_CANCELLED`
|
5
.changes/api-webview-window-new-methods.md
Normal file
5
.changes/api-webview-window-new-methods.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@tauri-apps/api': 'patch:feat'
|
||||||
|
---
|
||||||
|
|
||||||
|
Add a new `webviewWindow` module that exports `WebviewWindow` class and related methods such as `getCurrent` and `getAll`.
|
5
.changes/api-webview-window.md
Normal file
5
.changes/api-webview-window.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@tauri-apps/api': 'patch:breaking'
|
||||||
|
---
|
||||||
|
|
||||||
|
Move `WebviewWindow` class from `webview` module to a new `webviewWindow` module.
|
5
.changes/api-window-on-filedrop.md
Normal file
5
.changes/api-window-on-filedrop.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@tauri-apps/api': 'patch:feat'
|
||||||
|
---
|
||||||
|
|
||||||
|
Add `Window.onFileDropEvent` method.
|
5
.changes/core-js-event-anytarget.md
Normal file
5
.changes/core-js-event-anytarget.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'tauri': 'patch:bug'
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix JS event listeners registered using JS `listen` api with `EventTarget::Any` never fired.
|
6
.changes/tauri-runtime-webview-events.md
Normal file
6
.changes/tauri-runtime-webview-events.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'tauri-runtime': 'patch'
|
||||||
|
'tauri-runtime-wry': 'patch'
|
||||||
|
---
|
||||||
|
|
||||||
|
Add `WebviewEvent`, `RunEvent::WebviewEvent` and `WebviewDispatch::on_webview_event`.
|
9
.changes/tauri-webview-events.md
Normal file
9
.changes/tauri-webview-events.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
'tauri': 'patch:feat'
|
||||||
|
---
|
||||||
|
|
||||||
|
Add webview-specific events for multi-webview windows:
|
||||||
|
|
||||||
|
- Add `WebviewEvent` enum
|
||||||
|
- Add `RunEvent::WebviewEvent` variant.
|
||||||
|
- Add `Builder::on_webview_event` and `Webview::on_webview_event` methods.
|
2
.github/workflows/test-core.yml
vendored
2
.github/workflows/test-core.yml
vendored
@ -94,7 +94,7 @@ jobs:
|
|||||||
- name: test (using cross)
|
- name: test (using cross)
|
||||||
if: ${{ matrix.platform.cross }}
|
if: ${{ matrix.platform.cross }}
|
||||||
run: |
|
run: |
|
||||||
cargo install cross --git https://github.com/cross-rs/cross
|
cargo install cross --git https://github.com/cross-rs/cross --locked
|
||||||
cross ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }}
|
cross ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }}
|
||||||
|
|
||||||
- name: test (using cargo)
|
- name: test (using cargo)
|
||||||
|
@ -17,12 +17,12 @@ use tauri_runtime::{
|
|||||||
webview::{DetachedWebview, DownloadEvent, PendingWebview, WebviewIpcHandler},
|
webview::{DetachedWebview, DownloadEvent, PendingWebview, WebviewIpcHandler},
|
||||||
window::{
|
window::{
|
||||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
||||||
CursorIcon, DetachedWindow, FileDropEvent, PendingWindow, RawWindow, WindowBuilder,
|
CursorIcon, DetachedWindow, FileDropEvent, PendingWindow, RawWindow, WebviewEvent,
|
||||||
WindowBuilderBase, WindowEvent, WindowId,
|
WindowBuilder, WindowBuilderBase, WindowEvent, WindowId,
|
||||||
},
|
},
|
||||||
DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, RunEvent,
|
DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, RunEvent,
|
||||||
Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, WebviewDispatch,
|
Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, WebviewDispatch,
|
||||||
WindowDispatch, WindowEventId,
|
WebviewEventId, WindowDispatch, WindowEventId,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@ -121,6 +121,8 @@ pub type WebContextStore = Arc<Mutex<HashMap<Option<PathBuf>, WebContext>>>;
|
|||||||
// window
|
// window
|
||||||
pub type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
|
pub type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
|
||||||
pub type WindowEventListeners = Arc<Mutex<HashMap<WindowEventId, WindowEventHandler>>>;
|
pub type WindowEventListeners = Arc<Mutex<HashMap<WindowEventId, WindowEventHandler>>>;
|
||||||
|
pub type WebviewEventHandler = Box<dyn Fn(&WebviewEvent) + Send>;
|
||||||
|
pub type WebviewEventListeners = Arc<Mutex<HashMap<WebviewEventId, WebviewEventHandler>>>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct WindowIdStore(Arc<Mutex<HashMap<TaoWindowId, WindowId>>>);
|
pub struct WindowIdStore(Arc<Mutex<HashMap<TaoWindowId, WindowId>>>);
|
||||||
@ -172,7 +174,7 @@ pub(crate) fn send_user_message<T: UserEvent>(
|
|||||||
&context.main_thread.window_target,
|
&context.main_thread.window_target,
|
||||||
message,
|
message,
|
||||||
UserMessageContext {
|
UserMessageContext {
|
||||||
webview_id_map: context.webview_id_map.clone(),
|
window_id_map: context.window_id_map.clone(),
|
||||||
windows: context.main_thread.windows.clone(),
|
windows: context.main_thread.windows.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -187,7 +189,7 @@ pub(crate) fn send_user_message<T: UserEvent>(
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Context<T: UserEvent> {
|
pub struct Context<T: UserEvent> {
|
||||||
pub webview_id_map: WindowIdStore,
|
pub window_id_map: WindowIdStore,
|
||||||
main_thread_id: ThreadId,
|
main_thread_id: ThreadId,
|
||||||
pub proxy: TaoEventLoopProxy<Message<T>>,
|
pub proxy: TaoEventLoopProxy<Message<T>>,
|
||||||
main_thread: DispatcherMainThreadContext<T>,
|
main_thread: DispatcherMainThreadContext<T>,
|
||||||
@ -195,6 +197,7 @@ pub struct Context<T: UserEvent> {
|
|||||||
next_window_id: Arc<AtomicU32>,
|
next_window_id: Arc<AtomicU32>,
|
||||||
next_webview_id: Arc<AtomicU32>,
|
next_webview_id: Arc<AtomicU32>,
|
||||||
next_window_event_id: Arc<AtomicU32>,
|
next_window_event_id: Arc<AtomicU32>,
|
||||||
|
next_webview_event_id: Arc<AtomicU32>,
|
||||||
next_webcontext_id: Arc<AtomicU32>,
|
next_webcontext_id: Arc<AtomicU32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +225,10 @@ impl<T: UserEvent> Context<T> {
|
|||||||
self.next_window_event_id.fetch_add(1, Ordering::Relaxed)
|
self.next_window_event_id.fetch_add(1, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_webview_event_id(&self) -> u32 {
|
||||||
|
self.next_webview_event_id.fetch_add(1, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
fn next_webcontext_id(&self) -> u32 {
|
fn next_webcontext_id(&self) -> u32 {
|
||||||
self.next_webcontext_id.fetch_add(1, Ordering::Relaxed)
|
self.next_webcontext_id.fetch_add(1, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
@ -463,16 +470,6 @@ impl<'a> From<&TaoWindowEvent<'a>> for WindowEventWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<WebviewEvent> for WindowEventWrapper {
|
|
||||||
fn from(event: WebviewEvent) -> Self {
|
|
||||||
let event = match event {
|
|
||||||
WebviewEvent::Focused(focused) => WindowEvent::Focused(focused),
|
|
||||||
WebviewEvent::FileDrop(event) => WindowEvent::FileDrop(event),
|
|
||||||
};
|
|
||||||
Self(Some(event))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MonitorHandleWrapper(pub MonitorHandle);
|
pub struct MonitorHandleWrapper(pub MonitorHandle);
|
||||||
|
|
||||||
impl From<MonitorHandleWrapper> for Monitor {
|
impl From<MonitorHandleWrapper> for Monitor {
|
||||||
@ -994,53 +991,6 @@ impl WindowBuilder for WindowBuilderWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FileDropEventWrapper(WryFileDropEvent);
|
|
||||||
|
|
||||||
// on Linux, the paths are percent-encoded
|
|
||||||
#[cfg(any(
|
|
||||||
target_os = "linux",
|
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd"
|
|
||||||
))]
|
|
||||||
fn decode_path(path: PathBuf) -> PathBuf {
|
|
||||||
percent_encoding::percent_decode(path.display().to_string().as_bytes())
|
|
||||||
.decode_utf8_lossy()
|
|
||||||
.into_owned()
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
// on Windows and macOS, we do not need to decode the path
|
|
||||||
#[cfg(not(any(
|
|
||||||
target_os = "linux",
|
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd"
|
|
||||||
)))]
|
|
||||||
fn decode_path(path: PathBuf) -> PathBuf {
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FileDropEventWrapper> for FileDropEvent {
|
|
||||||
fn from(event: FileDropEventWrapper) -> Self {
|
|
||||||
match event.0 {
|
|
||||||
WryFileDropEvent::Hovered { paths, position } => FileDropEvent::Hovered {
|
|
||||||
paths: paths.into_iter().map(decode_path).collect(),
|
|
||||||
position: PhysicalPosition::new(position.0 as f64, position.1 as f64),
|
|
||||||
},
|
|
||||||
WryFileDropEvent::Dropped { paths, position } => FileDropEvent::Dropped {
|
|
||||||
paths: paths.into_iter().map(decode_path).collect(),
|
|
||||||
position: PhysicalPosition::new(position.0 as f64, position.1 as f64),
|
|
||||||
},
|
|
||||||
// default to cancelled
|
|
||||||
// FIXME(maybe): Add `FileDropEvent::Unknown` event?
|
|
||||||
_ => FileDropEvent::Cancelled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "linux",
|
target_os = "linux",
|
||||||
target_os = "dragonfly",
|
target_os = "dragonfly",
|
||||||
@ -1168,13 +1118,30 @@ pub enum WindowMessage {
|
|||||||
RequestRedraw,
|
RequestRedraw,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SynthesizedWindowEvent {
|
||||||
|
Focused(bool),
|
||||||
|
FileDrop(FileDropEvent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SynthesizedWindowEvent> for WindowEventWrapper {
|
||||||
|
fn from(event: SynthesizedWindowEvent) -> Self {
|
||||||
|
let event = match event {
|
||||||
|
SynthesizedWindowEvent::Focused(focused) => WindowEvent::Focused(focused),
|
||||||
|
SynthesizedWindowEvent::FileDrop(event) => WindowEvent::FileDrop(event),
|
||||||
|
};
|
||||||
|
Self(Some(event))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum WebviewMessage {
|
pub enum WebviewMessage {
|
||||||
|
AddEventListener(WebviewEventId, Box<dyn Fn(&WebviewEvent) + Send>),
|
||||||
#[cfg(not(all(feature = "tracing", not(target_os = "android"))))]
|
#[cfg(not(all(feature = "tracing", not(target_os = "android"))))]
|
||||||
EvaluateScript(String),
|
EvaluateScript(String),
|
||||||
#[cfg(all(feature = "tracing", not(target_os = "android")))]
|
#[cfg(all(feature = "tracing", not(target_os = "android")))]
|
||||||
EvaluateScript(String, Sender<()>, tracing::Span),
|
EvaluateScript(String, Sender<()>, tracing::Span),
|
||||||
#[allow(dead_code)]
|
|
||||||
WebviewEvent(WebviewEvent),
|
WebviewEvent(WebviewEvent),
|
||||||
|
SynthesizedWindowEvent(SynthesizedWindowEvent),
|
||||||
Navigate(Url),
|
Navigate(Url),
|
||||||
Print,
|
Print,
|
||||||
Close,
|
Close,
|
||||||
@ -1195,13 +1162,6 @@ pub enum WebviewMessage {
|
|||||||
IsDevToolsOpen(Sender<bool>),
|
IsDevToolsOpen(Sender<bool>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum WebviewEvent {
|
|
||||||
FileDrop(FileDropEvent),
|
|
||||||
Focused(bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type CreateWindowClosure<T> =
|
pub type CreateWindowClosure<T> =
|
||||||
Box<dyn FnOnce(&EventLoopWindowTarget<Message<T>>) -> Result<WindowWrapper> + Send>;
|
Box<dyn FnOnce(&EventLoopWindowTarget<Message<T>>) -> Result<WindowWrapper> + Send>;
|
||||||
|
|
||||||
@ -1250,6 +1210,16 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
|
|||||||
send_user_message(&self.context, Message::Task(Box::new(f)))
|
send_user_message(&self.context, Message::Task(Box::new(f)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) -> WindowEventId {
|
||||||
|
let id = self.context.next_webview_event_id();
|
||||||
|
let _ = self.context.proxy.send_event(Message::Webview(
|
||||||
|
self.window_id,
|
||||||
|
self.webview_id,
|
||||||
|
WebviewMessage::AddEventListener(id, Box::new(f)),
|
||||||
|
));
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()> {
|
fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()> {
|
||||||
send_user_message(
|
send_user_message(
|
||||||
&self.context,
|
&self.context,
|
||||||
@ -1853,9 +1823,11 @@ impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WebviewWrapper {
|
pub struct WebviewWrapper {
|
||||||
|
label: String,
|
||||||
id: WebviewId,
|
id: WebviewId,
|
||||||
inner: Rc<WebView>,
|
inner: Rc<WebView>,
|
||||||
context_store: WebContextStore,
|
context_store: WebContextStore,
|
||||||
|
webview_event_listeners: WebviewEventListeners,
|
||||||
// the key of the WebContext if it's not shared
|
// the key of the WebContext if it's not shared
|
||||||
context_key: Option<PathBuf>,
|
context_key: Option<PathBuf>,
|
||||||
bounds: Option<Arc<Mutex<WebviewBounds>>>,
|
bounds: Option<Arc<Mutex<WebviewBounds>>>,
|
||||||
@ -1977,7 +1949,7 @@ impl<T: UserEvent> WryHandle<T> {
|
|||||||
pub fn window_id(&self, window_id: TaoWindowId) -> WindowId {
|
pub fn window_id(&self, window_id: TaoWindowId) -> WindowId {
|
||||||
*self
|
*self
|
||||||
.context
|
.context
|
||||||
.webview_id_map
|
.window_id_map
|
||||||
.0
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -2132,10 +2104,10 @@ impl<T: UserEvent> Wry<T> {
|
|||||||
let web_context = WebContextStore::default();
|
let web_context = WebContextStore::default();
|
||||||
|
|
||||||
let windows = Rc::new(RefCell::new(HashMap::default()));
|
let windows = Rc::new(RefCell::new(HashMap::default()));
|
||||||
let webview_id_map = WindowIdStore::default();
|
let window_id_map = WindowIdStore::default();
|
||||||
|
|
||||||
let context = Context {
|
let context = Context {
|
||||||
webview_id_map,
|
window_id_map,
|
||||||
main_thread_id,
|
main_thread_id,
|
||||||
proxy: event_loop.create_proxy(),
|
proxy: event_loop.create_proxy(),
|
||||||
main_thread: DispatcherMainThreadContext {
|
main_thread: DispatcherMainThreadContext {
|
||||||
@ -2149,6 +2121,7 @@ impl<T: UserEvent> Wry<T> {
|
|||||||
next_window_id: Default::default(),
|
next_window_id: Default::default(),
|
||||||
next_webview_id: Default::default(),
|
next_webview_id: Default::default(),
|
||||||
next_window_event_id: Default::default(),
|
next_window_event_id: Default::default(),
|
||||||
|
next_webview_event_id: Default::default(),
|
||||||
next_webcontext_id: Default::default(),
|
next_webcontext_id: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2347,7 +2320,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
|
|||||||
fn run_iteration<F: FnMut(RunEvent<T>)>(&mut self, mut callback: F) {
|
fn run_iteration<F: FnMut(RunEvent<T>)>(&mut self, mut callback: F) {
|
||||||
use tao::platform::run_return::EventLoopExtRunReturn;
|
use tao::platform::run_return::EventLoopExtRunReturn;
|
||||||
let windows = self.context.main_thread.windows.clone();
|
let windows = self.context.main_thread.windows.clone();
|
||||||
let webview_id_map = self.context.webview_id_map.clone();
|
let window_id_map = self.context.window_id_map.clone();
|
||||||
let web_context = &self.context.main_thread.web_context;
|
let web_context = &self.context.main_thread.web_context;
|
||||||
let plugins = self.context.plugins.clone();
|
let plugins = self.context.plugins.clone();
|
||||||
|
|
||||||
@ -2372,7 +2345,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
|
|||||||
control_flow,
|
control_flow,
|
||||||
EventLoopIterationContext {
|
EventLoopIterationContext {
|
||||||
callback: &mut callback,
|
callback: &mut callback,
|
||||||
webview_id_map: webview_id_map.clone(),
|
window_id_map: window_id_map.clone(),
|
||||||
windows: windows.clone(),
|
windows: windows.clone(),
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
active_tracing_spans: active_tracing_spans.clone(),
|
active_tracing_spans: active_tracing_spans.clone(),
|
||||||
@ -2391,7 +2364,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
|
|||||||
EventLoopIterationContext {
|
EventLoopIterationContext {
|
||||||
callback: &mut callback,
|
callback: &mut callback,
|
||||||
windows: windows.clone(),
|
windows: windows.clone(),
|
||||||
webview_id_map: webview_id_map.clone(),
|
window_id_map: window_id_map.clone(),
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
active_tracing_spans: active_tracing_spans.clone(),
|
active_tracing_spans: active_tracing_spans.clone(),
|
||||||
},
|
},
|
||||||
@ -2401,7 +2374,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
|
|||||||
|
|
||||||
fn run<F: FnMut(RunEvent<T>) + 'static>(self, mut callback: F) {
|
fn run<F: FnMut(RunEvent<T>) + 'static>(self, mut callback: F) {
|
||||||
let windows = self.context.main_thread.windows.clone();
|
let windows = self.context.main_thread.windows.clone();
|
||||||
let webview_id_map = self.context.webview_id_map.clone();
|
let window_id_map = self.context.window_id_map.clone();
|
||||||
let web_context = self.context.main_thread.web_context;
|
let web_context = self.context.main_thread.web_context;
|
||||||
let plugins = self.context.plugins.clone();
|
let plugins = self.context.plugins.clone();
|
||||||
|
|
||||||
@ -2418,7 +2391,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
|
|||||||
control_flow,
|
control_flow,
|
||||||
EventLoopIterationContext {
|
EventLoopIterationContext {
|
||||||
callback: &mut callback,
|
callback: &mut callback,
|
||||||
webview_id_map: webview_id_map.clone(),
|
window_id_map: window_id_map.clone(),
|
||||||
windows: windows.clone(),
|
windows: windows.clone(),
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
active_tracing_spans: active_tracing_spans.clone(),
|
active_tracing_spans: active_tracing_spans.clone(),
|
||||||
@ -2435,7 +2408,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
|
|||||||
control_flow,
|
control_flow,
|
||||||
EventLoopIterationContext {
|
EventLoopIterationContext {
|
||||||
callback: &mut callback,
|
callback: &mut callback,
|
||||||
webview_id_map: webview_id_map.clone(),
|
window_id_map: window_id_map.clone(),
|
||||||
windows: windows.clone(),
|
windows: windows.clone(),
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
active_tracing_spans: active_tracing_spans.clone(),
|
active_tracing_spans: active_tracing_spans.clone(),
|
||||||
@ -2447,7 +2420,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
|
|||||||
|
|
||||||
pub struct EventLoopIterationContext<'a, T: UserEvent> {
|
pub struct EventLoopIterationContext<'a, T: UserEvent> {
|
||||||
pub callback: &'a mut (dyn FnMut(RunEvent<T>)),
|
pub callback: &'a mut (dyn FnMut(RunEvent<T>)),
|
||||||
pub webview_id_map: WindowIdStore,
|
pub window_id_map: WindowIdStore,
|
||||||
pub windows: Rc<RefCell<HashMap<WindowId, WindowWrapper>>>,
|
pub windows: Rc<RefCell<HashMap<WindowId, WindowWrapper>>>,
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
pub active_tracing_spans: ActiveTraceSpanStore,
|
pub active_tracing_spans: ActiveTraceSpanStore,
|
||||||
@ -2455,7 +2428,7 @@ pub struct EventLoopIterationContext<'a, T: UserEvent> {
|
|||||||
|
|
||||||
struct UserMessageContext {
|
struct UserMessageContext {
|
||||||
windows: Rc<RefCell<HashMap<WindowId, WindowWrapper>>>,
|
windows: Rc<RefCell<HashMap<WindowId, WindowWrapper>>>,
|
||||||
webview_id_map: WindowIdStore,
|
window_id_map: WindowIdStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_user_message<T: UserEvent>(
|
fn handle_user_message<T: UserEvent>(
|
||||||
@ -2464,7 +2437,7 @@ fn handle_user_message<T: UserEvent>(
|
|||||||
context: UserMessageContext,
|
context: UserMessageContext,
|
||||||
) {
|
) {
|
||||||
let UserMessageContext {
|
let UserMessageContext {
|
||||||
webview_id_map,
|
window_id_map,
|
||||||
windows,
|
windows,
|
||||||
} = context;
|
} = context;
|
||||||
match message {
|
match message {
|
||||||
@ -2684,6 +2657,17 @@ fn handle_user_message<T: UserEvent>(
|
|||||||
});
|
});
|
||||||
if let Some((Some(window), Some(webview))) = webview_handle {
|
if let Some((Some(window), Some(webview))) = webview_handle {
|
||||||
match webview_message {
|
match webview_message {
|
||||||
|
WebviewMessage::WebviewEvent(_) => { /* already handled */ }
|
||||||
|
WebviewMessage::SynthesizedWindowEvent(_) => { /* already handled */ }
|
||||||
|
|
||||||
|
WebviewMessage::AddEventListener(id, listener) => {
|
||||||
|
webview
|
||||||
|
.webview_event_listeners
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(id, listener);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "tracing", not(target_os = "android")))]
|
#[cfg(all(feature = "tracing", not(target_os = "android")))]
|
||||||
WebviewMessage::EvaluateScript(script, tx, span) => {
|
WebviewMessage::EvaluateScript(script, tx, span) => {
|
||||||
let _span = span.entered();
|
let _span = span.entered();
|
||||||
@ -2743,7 +2727,6 @@ fn handle_user_message<T: UserEvent>(
|
|||||||
WebviewMessage::SetFocus => {
|
WebviewMessage::SetFocus => {
|
||||||
webview.focus();
|
webview.focus();
|
||||||
}
|
}
|
||||||
WebviewMessage::WebviewEvent(_event) => { /* already handled */ }
|
|
||||||
WebviewMessage::WithWebview(f) => {
|
WebviewMessage::WithWebview(f) => {
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "linux",
|
target_os = "linux",
|
||||||
@ -2850,7 +2833,7 @@ fn handle_user_message<T: UserEvent>(
|
|||||||
let (label, builder) = handler();
|
let (label, builder) = handler();
|
||||||
let is_window_transparent = builder.window.transparent;
|
let is_window_transparent = builder.window.transparent;
|
||||||
if let Ok(window) = builder.build(event_loop) {
|
if let Ok(window) = builder.build(event_loop) {
|
||||||
webview_id_map.insert(window.id(), window_id);
|
window_id_map.insert(window.id(), window_id);
|
||||||
|
|
||||||
let window = Arc::new(window);
|
let window = Arc::new(window);
|
||||||
|
|
||||||
@ -2901,7 +2884,7 @@ fn handle_event_loop<T: UserEvent>(
|
|||||||
) {
|
) {
|
||||||
let EventLoopIterationContext {
|
let EventLoopIterationContext {
|
||||||
callback,
|
callback,
|
||||||
webview_id_map,
|
window_id_map,
|
||||||
windows,
|
windows,
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
active_tracing_spans,
|
active_tracing_spans,
|
||||||
@ -2930,7 +2913,7 @@ fn handle_event_loop<T: UserEvent>(
|
|||||||
#[cfg(any(feature = "tracing", windows))]
|
#[cfg(any(feature = "tracing", windows))]
|
||||||
Event::RedrawRequested(id) => {
|
Event::RedrawRequested(id) => {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
if let Some(window_id) = webview_id_map.get(&id) {
|
if let Some(window_id) = window_id_map.get(&id) {
|
||||||
let mut windows_ref = windows.borrow_mut();
|
let mut windows_ref = windows.borrow_mut();
|
||||||
if let Some(window) = windows_ref.get_mut(&window_id) {
|
if let Some(window) = windows_ref.get_mut(&window_id) {
|
||||||
if window.is_window_transparent {
|
if window.is_window_transparent {
|
||||||
@ -2949,19 +2932,50 @@ fn handle_event_loop<T: UserEvent>(
|
|||||||
|
|
||||||
Event::UserEvent(Message::Webview(
|
Event::UserEvent(Message::Webview(
|
||||||
window_id,
|
window_id,
|
||||||
_webview_id,
|
webview_id,
|
||||||
WebviewMessage::WebviewEvent(event),
|
WebviewMessage::WebviewEvent(event),
|
||||||
|
)) => {
|
||||||
|
let windows_ref = windows.borrow();
|
||||||
|
if let Some(window) = windows_ref.get(&window_id) {
|
||||||
|
if let Some(webview) = window.webviews.iter().find(|w| w.id == webview_id) {
|
||||||
|
let label = webview.label.clone();
|
||||||
|
let webview_event_listeners = webview.webview_event_listeners.clone();
|
||||||
|
|
||||||
|
drop(windows_ref);
|
||||||
|
|
||||||
|
callback(RunEvent::WebviewEvent {
|
||||||
|
label,
|
||||||
|
event: event.clone(),
|
||||||
|
});
|
||||||
|
let listeners = webview_event_listeners.lock().unwrap();
|
||||||
|
let handlers = listeners.values();
|
||||||
|
for handler in handlers {
|
||||||
|
handler(&event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::UserEvent(Message::Webview(
|
||||||
|
window_id,
|
||||||
|
_webview_id,
|
||||||
|
WebviewMessage::SynthesizedWindowEvent(event),
|
||||||
)) => {
|
)) => {
|
||||||
if let Some(event) = WindowEventWrapper::from(event).0 {
|
if let Some(event) = WindowEventWrapper::from(event).0 {
|
||||||
let windows = windows.borrow();
|
let windows_ref = windows.borrow();
|
||||||
let window = windows.get(&window_id);
|
let window = windows_ref.get(&window_id);
|
||||||
if let Some(window) = window {
|
if let Some(window) = window {
|
||||||
|
let label = window.label.clone();
|
||||||
|
let window_event_listeners = window.window_event_listeners.clone();
|
||||||
|
|
||||||
|
drop(windows_ref);
|
||||||
|
|
||||||
callback(RunEvent::WindowEvent {
|
callback(RunEvent::WindowEvent {
|
||||||
label: window.label.clone(),
|
label,
|
||||||
event: event.clone(),
|
event: event.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let listeners = window.window_event_listeners.lock().unwrap();
|
let listeners = window_event_listeners.lock().unwrap();
|
||||||
let handlers = listeners.values();
|
let handlers = listeners.values();
|
||||||
for handler in handlers {
|
for handler in handlers {
|
||||||
handler(&event);
|
handler(&event);
|
||||||
@ -2973,7 +2987,7 @@ fn handle_event_loop<T: UserEvent>(
|
|||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event, window_id, ..
|
event, window_id, ..
|
||||||
} => {
|
} => {
|
||||||
if let Some(window_id) = webview_id_map.get(&window_id) {
|
if let Some(window_id) = window_id_map.get(&window_id) {
|
||||||
{
|
{
|
||||||
let windows_ref = windows.borrow();
|
let windows_ref = windows.borrow();
|
||||||
if let Some(window) = windows_ref.get(&window_id) {
|
if let Some(window) = windows_ref.get(&window_id) {
|
||||||
@ -3076,7 +3090,7 @@ fn handle_event_loop<T: UserEvent>(
|
|||||||
event_loop,
|
event_loop,
|
||||||
message,
|
message,
|
||||||
UserMessageContext {
|
UserMessageContext {
|
||||||
webview_id_map,
|
window_id_map,
|
||||||
windows,
|
windows,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -3216,7 +3230,7 @@ fn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
context.webview_id_map.insert(window.id(), window_id);
|
context.window_id_map.insert(window.id(), window_id);
|
||||||
|
|
||||||
if window_builder.center {
|
if window_builder.center {
|
||||||
let _ = center_window(&window, window.inner_size());
|
let _ = center_window(&window, window.inner_size());
|
||||||
@ -3290,7 +3304,8 @@ fn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// the kind of the webview
|
/// the kind of the webview
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||||
enum WebviewKind {
|
enum WebviewKind {
|
||||||
// webview is the entire window content
|
// webview is the entire window content
|
||||||
WindowContent,
|
WindowContent,
|
||||||
@ -3375,12 +3390,32 @@ fn create_webview<T: UserEvent>(
|
|||||||
if webview_attributes.file_drop_handler_enabled {
|
if webview_attributes.file_drop_handler_enabled {
|
||||||
let proxy = context.proxy.clone();
|
let proxy = context.proxy.clone();
|
||||||
webview_builder = webview_builder.with_file_drop_handler(move |event| {
|
webview_builder = webview_builder.with_file_drop_handler(move |event| {
|
||||||
let event: FileDropEvent = FileDropEventWrapper(event).into();
|
let event = match event {
|
||||||
let _ = proxy.send_event(Message::Webview(
|
WryFileDropEvent::Hovered {
|
||||||
window_id,
|
paths,
|
||||||
id,
|
position: (x, y),
|
||||||
WebviewMessage::WebviewEvent(WebviewEvent::FileDrop(event)),
|
} => FileDropEvent::Hovered {
|
||||||
));
|
paths,
|
||||||
|
position: PhysicalPosition::new(x as _, y as _),
|
||||||
|
},
|
||||||
|
WryFileDropEvent::Dropped {
|
||||||
|
paths,
|
||||||
|
position: (x, y),
|
||||||
|
} => FileDropEvent::Dropped {
|
||||||
|
paths,
|
||||||
|
position: PhysicalPosition::new(x as _, y as _),
|
||||||
|
},
|
||||||
|
WryFileDropEvent::Cancelled => FileDropEvent::Cancelled,
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = if kind == WebviewKind::WindowContent {
|
||||||
|
WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::FileDrop(event))
|
||||||
|
} else {
|
||||||
|
WebviewMessage::WebviewEvent(WebviewEvent::FileDrop(event))
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = proxy.send_event(Message::Webview(window_id, id, message));
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -3566,7 +3601,7 @@ fn create_webview<T: UserEvent>(
|
|||||||
.map_err(|e| Error::CreateWebview(Box::new(e)))?;
|
.map_err(|e| Error::CreateWebview(Box::new(e)))?;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
if kind == WebviewKind::WindowContent {
|
||||||
let controller = webview.controller();
|
let controller = webview.controller();
|
||||||
let proxy = context.proxy.clone();
|
let proxy = context.proxy.clone();
|
||||||
let proxy_ = proxy.clone();
|
let proxy_ = proxy.clone();
|
||||||
@ -3574,10 +3609,10 @@ fn create_webview<T: UserEvent>(
|
|||||||
unsafe {
|
unsafe {
|
||||||
controller.add_GotFocus(
|
controller.add_GotFocus(
|
||||||
&FocusChangedEventHandler::create(Box::new(move |_, _| {
|
&FocusChangedEventHandler::create(Box::new(move |_, _| {
|
||||||
let _ = proxy.send_event(Message::Webview(
|
let _ = proxy_.send_event(Message::Webview(
|
||||||
window_id,
|
window_id,
|
||||||
id,
|
id,
|
||||||
WebviewMessage::WebviewEvent(WebviewEvent::Focused(true)),
|
WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(true)),
|
||||||
));
|
));
|
||||||
Ok(())
|
Ok(())
|
||||||
})),
|
})),
|
||||||
@ -3588,10 +3623,10 @@ fn create_webview<T: UserEvent>(
|
|||||||
unsafe {
|
unsafe {
|
||||||
controller.add_LostFocus(
|
controller.add_LostFocus(
|
||||||
&FocusChangedEventHandler::create(Box::new(move |_, _| {
|
&FocusChangedEventHandler::create(Box::new(move |_, _| {
|
||||||
let _ = proxy_.send_event(Message::Webview(
|
let _ = proxy.send_event(Message::Webview(
|
||||||
window_id,
|
window_id,
|
||||||
id,
|
id,
|
||||||
WebviewMessage::WebviewEvent(WebviewEvent::Focused(false)),
|
WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(false)),
|
||||||
));
|
));
|
||||||
Ok(())
|
Ok(())
|
||||||
})),
|
})),
|
||||||
@ -3602,9 +3637,11 @@ fn create_webview<T: UserEvent>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(WebviewWrapper {
|
Ok(WebviewWrapper {
|
||||||
|
label,
|
||||||
id,
|
id,
|
||||||
inner: Rc::new(webview),
|
inner: Rc::new(webview),
|
||||||
context_store: context.main_thread.web_context.clone(),
|
context_store: context.main_thread.web_context.clone(),
|
||||||
|
webview_event_listeners: Default::default(),
|
||||||
context_key: if automation_enabled {
|
context_key: if automation_enabled {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -27,7 +27,7 @@ pub mod window;
|
|||||||
use monitor::Monitor;
|
use monitor::Monitor;
|
||||||
use window::{
|
use window::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||||
CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent,
|
CursorIcon, DetachedWindow, PendingWindow, RawWindow, WebviewEvent, WindowEvent,
|
||||||
};
|
};
|
||||||
use window::{WindowBuilder, WindowId};
|
use window::{WindowBuilder, WindowId};
|
||||||
|
|
||||||
@ -38,6 +38,7 @@ use http::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub type WindowEventId = u32;
|
pub type WindowEventId = u32;
|
||||||
|
pub type WebviewEventId = u32;
|
||||||
|
|
||||||
/// Type of user attention requested on a window.
|
/// Type of user attention requested on a window.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
|
||||||
@ -166,6 +167,13 @@ pub enum RunEvent<T: UserEvent> {
|
|||||||
/// The detailed event.
|
/// The detailed event.
|
||||||
event: WindowEvent,
|
event: WindowEvent,
|
||||||
},
|
},
|
||||||
|
/// An event associated with a webview.
|
||||||
|
WebviewEvent {
|
||||||
|
/// The webview label.
|
||||||
|
label: String,
|
||||||
|
/// The detailed event.
|
||||||
|
event: WebviewEvent,
|
||||||
|
},
|
||||||
/// Application ready.
|
/// Application ready.
|
||||||
Ready,
|
Ready,
|
||||||
/// Sent if the event loop is being resumed.
|
/// Sent if the event loop is being resumed.
|
||||||
@ -362,6 +370,9 @@ pub trait WebviewDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + '
|
|||||||
/// Run a task on the main thread.
|
/// Run a task on the main thread.
|
||||||
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;
|
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;
|
||||||
|
|
||||||
|
/// Registers a webview event handler.
|
||||||
|
fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) -> WebviewEventId;
|
||||||
|
|
||||||
/// Runs a closure with the platform webview object as argument.
|
/// Runs a closure with the platform webview object as argument.
|
||||||
fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()>;
|
fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()>;
|
||||||
|
|
||||||
|
@ -65,6 +65,13 @@ pub enum WindowEvent {
|
|||||||
ThemeChanged(Theme),
|
ThemeChanged(Theme),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An event from a window.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum WebviewEvent {
|
||||||
|
/// An event associated with the file drop action.
|
||||||
|
FileDrop(FileDropEvent),
|
||||||
|
}
|
||||||
|
|
||||||
/// The file drop event payload.
|
/// The file drop event payload.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
File diff suppressed because one or more lines are too long
@ -13,8 +13,8 @@ use crate::{
|
|||||||
},
|
},
|
||||||
plugin::{Plugin, PluginStore},
|
plugin::{Plugin, PluginStore},
|
||||||
runtime::{
|
runtime::{
|
||||||
window::WindowEvent as RuntimeWindowEvent, ExitRequestedEventAction,
|
window::{WebviewEvent as RuntimeWebviewEvent, WindowEvent as RuntimeWindowEvent},
|
||||||
RunEvent as RuntimeRunEvent,
|
ExitRequestedEventAction, RunEvent as RuntimeRunEvent,
|
||||||
},
|
},
|
||||||
sealed::{ManagerBase, RuntimeOrDispatch},
|
sealed::{ManagerBase, RuntimeOrDispatch},
|
||||||
utils::config::Config,
|
utils::config::Config,
|
||||||
@ -62,6 +62,8 @@ pub(crate) type GlobalMenuEventListener<T> = Box<dyn Fn(&T, crate::menu::MenuEve
|
|||||||
pub(crate) type GlobalTrayIconEventListener<T> =
|
pub(crate) type GlobalTrayIconEventListener<T> =
|
||||||
Box<dyn Fn(&T, crate::tray::TrayIconEvent) + Send + Sync>;
|
Box<dyn Fn(&T, crate::tray::TrayIconEvent) + Send + Sync>;
|
||||||
pub(crate) type GlobalWindowEventListener<R> = Box<dyn Fn(&Window<R>, &WindowEvent) + Send + Sync>;
|
pub(crate) type GlobalWindowEventListener<R> = Box<dyn Fn(&Window<R>, &WindowEvent) + Send + Sync>;
|
||||||
|
pub(crate) type GlobalWebviewEventListener<R> =
|
||||||
|
Box<dyn Fn(&Webview<R>, &WebviewEvent) + Send + Sync>;
|
||||||
/// A closure that is run when the Tauri application is setting up.
|
/// A closure that is run when the Tauri application is setting up.
|
||||||
pub type SetupHook<R> =
|
pub type SetupHook<R> =
|
||||||
Box<dyn FnOnce(&mut App<R>) -> Result<(), Box<dyn std::error::Error>> + Send>;
|
Box<dyn FnOnce(&mut App<R>) -> Result<(), Box<dyn std::error::Error>> + Send>;
|
||||||
@ -164,6 +166,22 @@ impl From<RuntimeWindowEvent> for WindowEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An event from a window.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum WebviewEvent {
|
||||||
|
/// An event associated with the file drop action.
|
||||||
|
FileDrop(FileDropEvent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RuntimeWebviewEvent> for WebviewEvent {
|
||||||
|
fn from(event: RuntimeWebviewEvent) -> Self {
|
||||||
|
match event {
|
||||||
|
RuntimeWebviewEvent::FileDrop(e) => Self::FileDrop(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An application event, triggered from the event loop.
|
/// An application event, triggered from the event loop.
|
||||||
///
|
///
|
||||||
/// See [`App::run`](crate::App#method.run) for usage examples.
|
/// See [`App::run`](crate::App#method.run) for usage examples.
|
||||||
@ -190,6 +208,14 @@ pub enum RunEvent {
|
|||||||
/// The detailed event.
|
/// The detailed event.
|
||||||
event: WindowEvent,
|
event: WindowEvent,
|
||||||
},
|
},
|
||||||
|
/// An event associated with a webview.
|
||||||
|
#[non_exhaustive]
|
||||||
|
WebviewEvent {
|
||||||
|
/// The window label.
|
||||||
|
label: String,
|
||||||
|
/// The detailed event.
|
||||||
|
event: WebviewEvent,
|
||||||
|
},
|
||||||
/// Application ready.
|
/// Application ready.
|
||||||
Ready,
|
Ready,
|
||||||
/// Sent if the event loop is being resumed.
|
/// Sent if the event loop is being resumed.
|
||||||
@ -1043,6 +1069,9 @@ pub struct Builder<R: Runtime> {
|
|||||||
/// Window event handlers that listens to all windows.
|
/// Window event handlers that listens to all windows.
|
||||||
window_event_listeners: Vec<GlobalWindowEventListener<R>>,
|
window_event_listeners: Vec<GlobalWindowEventListener<R>>,
|
||||||
|
|
||||||
|
/// Webview event handlers that listens to all webviews.
|
||||||
|
webview_event_listeners: Vec<GlobalWebviewEventListener<R>>,
|
||||||
|
|
||||||
/// The device event filter.
|
/// The device event filter.
|
||||||
device_event_filter: DeviceEventFilter,
|
device_event_filter: DeviceEventFilter,
|
||||||
}
|
}
|
||||||
@ -1101,6 +1130,7 @@ impl<R: Runtime> Builder<R> {
|
|||||||
menu: None,
|
menu: None,
|
||||||
enable_macos_default_menu: true,
|
enable_macos_default_menu: true,
|
||||||
window_event_listeners: Vec::new(),
|
window_event_listeners: Vec::new(),
|
||||||
|
webview_event_listeners: Vec::new(),
|
||||||
device_event_filter: Default::default(),
|
device_event_filter: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1400,6 +1430,27 @@ tauri::Builder::default()
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers a webview event handler for all webviews.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// tauri::Builder::default()
|
||||||
|
/// .on_webview_event(|window, event| match event {
|
||||||
|
/// tauri::WebviewEvent::FileDrop(event) => {
|
||||||
|
/// println!("{:?}", event);
|
||||||
|
/// }
|
||||||
|
/// _ => {}
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn on_webview_event<F: Fn(&Webview<R>, &WebviewEvent) + Send + Sync + 'static>(
|
||||||
|
mut self,
|
||||||
|
handler: F,
|
||||||
|
) -> Self {
|
||||||
|
self.webview_event_listeners.push(Box::new(handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Registers a URI scheme protocol available to all webviews.
|
/// Registers a URI scheme protocol available to all webviews.
|
||||||
/// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
|
/// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
|
||||||
/// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
|
/// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
|
||||||
@ -1544,6 +1595,7 @@ tauri::Builder::default()
|
|||||||
self.uri_scheme_protocols,
|
self.uri_scheme_protocols,
|
||||||
self.state,
|
self.state,
|
||||||
self.window_event_listeners,
|
self.window_event_listeners,
|
||||||
|
self.webview_event_listeners,
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
HashMap::new(),
|
HashMap::new(),
|
||||||
(self.invoke_responder, self.invoke_initialization_script),
|
(self.invoke_responder, self.invoke_initialization_script),
|
||||||
@ -1800,6 +1852,10 @@ fn on_event_loop_event<R: Runtime>(
|
|||||||
label,
|
label,
|
||||||
event: event.into(),
|
event: event.into(),
|
||||||
},
|
},
|
||||||
|
RuntimeRunEvent::WebviewEvent { label, event } => RunEvent::WebviewEvent {
|
||||||
|
label,
|
||||||
|
event: event.into(),
|
||||||
|
},
|
||||||
RuntimeRunEvent::Ready => {
|
RuntimeRunEvent::Ready => {
|
||||||
// set the app icon in development
|
// set the app icon in development
|
||||||
#[cfg(all(dev, target_os = "macos"))]
|
#[cfg(all(dev, target_os = "macos"))]
|
||||||
|
@ -285,28 +285,49 @@ impl Listeners {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_for_each_js<'a, R, I, F>(
|
pub(crate) fn emit_js_filter<'a, R, I, F>(
|
||||||
&self,
|
&self,
|
||||||
event: &str,
|
|
||||||
mut webviews: I,
|
mut webviews: I,
|
||||||
callback: F,
|
event: &str,
|
||||||
|
emit_args: &EmitArgs,
|
||||||
|
filter: Option<&F>,
|
||||||
) -> crate::Result<()>
|
) -> crate::Result<()>
|
||||||
where
|
where
|
||||||
R: Runtime,
|
R: Runtime,
|
||||||
I: Iterator<Item = &'a Webview<R>>,
|
I: Iterator<Item = &'a Webview<R>>,
|
||||||
F: Fn(&Webview<R>, &EventTarget) -> crate::Result<()>,
|
F: Fn(&EventTarget) -> bool,
|
||||||
{
|
{
|
||||||
let listeners = self.inner.js_event_listeners.lock().unwrap();
|
let listeners = self.inner.js_event_listeners.lock().unwrap();
|
||||||
webviews.try_for_each(|webview| {
|
webviews.try_for_each(|webview| {
|
||||||
if let Some(handlers) = listeners.get(webview.label()).and_then(|s| s.get(event)) {
|
if let Some(handlers) = listeners.get(webview.label()).and_then(|s| s.get(event)) {
|
||||||
for JsHandler { target, .. } in handlers {
|
for JsHandler { target, .. } in handlers {
|
||||||
callback(webview, target)?;
|
if *target == EventTarget::Any || filter.as_ref().map(|f| f(target)).unwrap_or(false) {
|
||||||
|
webview.emit_js(emit_args, target)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn emit_js<'a, R, I>(
|
||||||
|
&self,
|
||||||
|
webviews: I,
|
||||||
|
event: &str,
|
||||||
|
emit_args: &EmitArgs,
|
||||||
|
) -> crate::Result<()>
|
||||||
|
where
|
||||||
|
R: Runtime,
|
||||||
|
I: Iterator<Item = &'a Webview<R>>,
|
||||||
|
{
|
||||||
|
self.emit_js_filter(
|
||||||
|
webviews,
|
||||||
|
event,
|
||||||
|
emit_args,
|
||||||
|
None::<&&dyn Fn(&EventTarget) -> bool>,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -238,7 +238,7 @@ pub fn event_initialization_script(function: &str, listeners: &str) -> String {
|
|||||||
const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
|
const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
|
||||||
for (let i = listeners.length - 1; i >= 0; i--) {{
|
for (let i = listeners.length - 1; i >= 0; i--) {{
|
||||||
const listener = listeners[i]
|
const listener = listeners[i]
|
||||||
if ((listener.target.kind === 'Global' && target.kind === 'Global') || (listener.target.kind === target.kind && listener.target.label === target.label)) {{
|
if (listener.target.kind === 'Any' || (listener.target.kind === target.kind && listener.target.label === target.label)) {{
|
||||||
eventData.id = listener.id
|
eventData.id = listener.id
|
||||||
listener.handler(eventData)
|
listener.handler(eventData)
|
||||||
}}
|
}}
|
||||||
|
@ -206,7 +206,9 @@ pub use self::utils::TitleBarStyle;
|
|||||||
|
|
||||||
pub use self::event::{Event, EventId, EventTarget};
|
pub use self::event::{Event, EventId, EventTarget};
|
||||||
pub use {
|
pub use {
|
||||||
self::app::{App, AppHandle, AssetResolver, Builder, CloseRequestApi, RunEvent, WindowEvent},
|
self::app::{
|
||||||
|
App, AppHandle, AssetResolver, Builder, CloseRequestApi, RunEvent, WebviewEvent, WindowEvent,
|
||||||
|
},
|
||||||
self::manager::Asset,
|
self::manager::Asset,
|
||||||
self::runtime::{
|
self::runtime::{
|
||||||
webview::WebviewAttributes,
|
webview::WebviewAttributes,
|
||||||
@ -800,7 +802,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
|
|||||||
/// Fetch a single webview window from the manager.
|
/// Fetch a single webview window from the manager.
|
||||||
fn get_webview_window(&self, label: &str) -> Option<WebviewWindow<R>> {
|
fn get_webview_window(&self, label: &str) -> Option<WebviewWindow<R>> {
|
||||||
self.manager().get_webview(label).and_then(|webview| {
|
self.manager().get_webview(label).and_then(|webview| {
|
||||||
if webview.window().webview_window {
|
if webview.window().is_webview_window {
|
||||||
Some(WebviewWindow { webview })
|
Some(WebviewWindow { webview })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -815,7 +817,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
|
|||||||
.webviews()
|
.webviews()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(label, webview)| {
|
.filter_map(|(label, webview)| {
|
||||||
if webview.window().webview_window {
|
if webview.window().is_webview_window {
|
||||||
Some((label, WebviewWindow { webview }))
|
Some((label, WebviewWindow { webview }))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -21,7 +21,7 @@ use tauri_utils::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{AppHandle, GlobalWindowEventListener, OnPageLoad},
|
app::{AppHandle, GlobalWebviewEventListener, GlobalWindowEventListener, OnPageLoad},
|
||||||
event::{assert_event_name_is_valid, Event, EventId, EventTarget, Listeners},
|
event::{assert_event_name_is_valid, Event, EventId, EventTarget, Listeners},
|
||||||
ipc::{Invoke, InvokeHandler, InvokeResponder, RuntimeAuthority},
|
ipc::{Invoke, InvokeHandler, InvokeResponder, RuntimeAuthority},
|
||||||
plugin::PluginStore,
|
plugin::PluginStore,
|
||||||
@ -231,6 +231,7 @@ impl<R: Runtime> AppManager<R> {
|
|||||||
uri_scheme_protocols: HashMap<String, Arc<webview::UriSchemeProtocol<R>>>,
|
uri_scheme_protocols: HashMap<String, Arc<webview::UriSchemeProtocol<R>>>,
|
||||||
state: StateManager,
|
state: StateManager,
|
||||||
window_event_listeners: Vec<GlobalWindowEventListener<R>>,
|
window_event_listeners: Vec<GlobalWindowEventListener<R>>,
|
||||||
|
webiew_event_listeners: Vec<GlobalWebviewEventListener<R>>,
|
||||||
#[cfg(desktop)] window_menu_event_listeners: HashMap<
|
#[cfg(desktop)] window_menu_event_listeners: HashMap<
|
||||||
String,
|
String,
|
||||||
crate::app::GlobalMenuEventListener<Window<R>>,
|
crate::app::GlobalMenuEventListener<Window<R>>,
|
||||||
@ -255,6 +256,7 @@ impl<R: Runtime> AppManager<R> {
|
|||||||
invoke_handler,
|
invoke_handler,
|
||||||
on_page_load,
|
on_page_load,
|
||||||
uri_scheme_protocols: Mutex::new(uri_scheme_protocols),
|
uri_scheme_protocols: Mutex::new(uri_scheme_protocols),
|
||||||
|
event_listeners: Arc::new(webiew_event_listeners),
|
||||||
invoke_responder,
|
invoke_responder,
|
||||||
invoke_initialization_script,
|
invoke_initialization_script,
|
||||||
},
|
},
|
||||||
@ -485,16 +487,11 @@ impl<R: Runtime> AppManager<R> {
|
|||||||
|
|
||||||
let listeners = self.listeners();
|
let listeners = self.listeners();
|
||||||
|
|
||||||
listeners.try_for_each_js(
|
listeners.emit_js_filter(
|
||||||
event,
|
|
||||||
self.webview.webviews_lock().values(),
|
self.webview.webviews_lock().values(),
|
||||||
|webview, target| {
|
event,
|
||||||
if filter(target) {
|
&emit_args,
|
||||||
webview.emit_js(&emit_args, target)
|
Some(&filter),
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
listeners.emit_filter(emit_args, Some(filter))?;
|
listeners.emit_filter(emit_args, Some(filter))?;
|
||||||
@ -511,12 +508,7 @@ impl<R: Runtime> AppManager<R> {
|
|||||||
|
|
||||||
let listeners = self.listeners();
|
let listeners = self.listeners();
|
||||||
|
|
||||||
listeners.try_for_each_js(
|
listeners.emit_js(self.webview.webviews_lock().values(), event, &emit_args)?;
|
||||||
event,
|
|
||||||
self.webview.webviews_lock().values(),
|
|
||||||
|webview, target| webview.emit_js(&emit_args, target),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
listeners.emit(emit_args)?;
|
listeners.emit(emit_args)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -647,6 +639,7 @@ mod test {
|
|||||||
StateManager::new(),
|
StateManager::new(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
(None, "".into()),
|
(None, "".into()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -12,20 +12,26 @@ use std::{
|
|||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
|
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
|
||||||
use tauri_runtime::webview::{DetachedWebview, PendingWebview};
|
use tauri_runtime::{
|
||||||
|
webview::{DetachedWebview, PendingWebview},
|
||||||
|
window::FileDropEvent,
|
||||||
|
};
|
||||||
use tauri_utils::config::WebviewUrl;
|
use tauri_utils::config::WebviewUrl;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{OnPageLoad, UriSchemeResponder},
|
app::{GlobalWebviewEventListener, OnPageLoad, UriSchemeResponder, WebviewEvent},
|
||||||
ipc::{InvokeHandler, InvokeResponder},
|
ipc::{InvokeHandler, InvokeResponder},
|
||||||
pattern::PatternJavascript,
|
pattern::PatternJavascript,
|
||||||
sealed::ManagerBase,
|
sealed::ManagerBase,
|
||||||
webview::PageLoadPayload,
|
webview::PageLoadPayload,
|
||||||
AppHandle, EventLoopMessage, Manager, Runtime, Webview, Window,
|
AppHandle, EventLoopMessage, EventTarget, Manager, Runtime, Scopes, Webview, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::AppManager;
|
use super::{
|
||||||
|
window::{FileDropPayload, DROP_CANCELLED_EVENT, DROP_EVENT, DROP_HOVER_EVENT},
|
||||||
|
AppManager,
|
||||||
|
};
|
||||||
|
|
||||||
// we need to proxy the dev server on mobile because we can't use `localhost`, so we use the local IP address
|
// we need to proxy the dev server on mobile because we can't use `localhost`, so we use the local IP address
|
||||||
// and we do not get a secure context without the custom protocol that proxies to the dev server
|
// and we do not get a secure context without the custom protocol that proxies to the dev server
|
||||||
@ -73,6 +79,8 @@ pub struct WebviewManager<R: Runtime> {
|
|||||||
pub on_page_load: Option<Arc<OnPageLoad<R>>>,
|
pub on_page_load: Option<Arc<OnPageLoad<R>>>,
|
||||||
/// The webview protocols available to all webviews.
|
/// The webview protocols available to all webviews.
|
||||||
pub uri_scheme_protocols: Mutex<HashMap<String, Arc<UriSchemeProtocol<R>>>>,
|
pub uri_scheme_protocols: Mutex<HashMap<String, Arc<UriSchemeProtocol<R>>>>,
|
||||||
|
/// Webview event listeners to all webviews.
|
||||||
|
pub event_listeners: Arc<Vec<GlobalWebviewEventListener<R>>>,
|
||||||
|
|
||||||
/// Responder for invoke calls.
|
/// Responder for invoke calls.
|
||||||
pub invoke_responder: Option<Arc<InvokeResponder<R>>>,
|
pub invoke_responder: Option<Arc<InvokeResponder<R>>>,
|
||||||
@ -557,6 +565,15 @@ impl<R: Runtime> WebviewManager<R> {
|
|||||||
) -> Webview<R> {
|
) -> Webview<R> {
|
||||||
let webview = Webview::new(window, webview);
|
let webview = Webview::new(window, webview);
|
||||||
|
|
||||||
|
let webview_event_listeners = self.event_listeners.clone();
|
||||||
|
let webview_ = webview.clone();
|
||||||
|
webview.on_webview_event(move |event| {
|
||||||
|
let _ = on_webview_event(&webview_, event);
|
||||||
|
for handler in webview_event_listeners.iter() {
|
||||||
|
handler(&webview_, event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// insert the webview into our manager
|
// insert the webview into our manager
|
||||||
{
|
{
|
||||||
self
|
self
|
||||||
@ -600,3 +617,43 @@ impl<R: Runtime> WebviewManager<R> {
|
|||||||
self.webviews_lock().keys().cloned().collect()
|
self.webviews_lock().keys().cloned().collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<R: Runtime> Webview<R> {
|
||||||
|
/// Emits event to [`EventTarget::Window`] and [`EventTarget::WebviewWindow`]
|
||||||
|
fn emit_to_webview<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
|
||||||
|
let window_label = self.label();
|
||||||
|
self.emit_filter(event, payload, |target| match target {
|
||||||
|
EventTarget::Webview { label } | EventTarget::WebviewWindow { label } => {
|
||||||
|
label == window_label
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_webview_event<R: Runtime>(webview: &Webview<R>, event: &WebviewEvent) -> crate::Result<()> {
|
||||||
|
match event {
|
||||||
|
WebviewEvent::FileDrop(event) => match event {
|
||||||
|
FileDropEvent::Hovered { paths, position } => {
|
||||||
|
let payload = FileDropPayload { paths, position };
|
||||||
|
webview.emit_to_webview(DROP_HOVER_EVENT, payload)?
|
||||||
|
}
|
||||||
|
FileDropEvent::Dropped { paths, position } => {
|
||||||
|
let scopes = webview.state::<Scopes>();
|
||||||
|
for path in paths {
|
||||||
|
if path.is_file() {
|
||||||
|
let _ = scopes.allow_file(path);
|
||||||
|
} else {
|
||||||
|
let _ = scopes.allow_directory(path, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let payload = FileDropPayload { paths, position };
|
||||||
|
webview.emit_to_webview(DROP_EVENT, payload)?
|
||||||
|
}
|
||||||
|
FileDropEvent::Cancelled => webview.emit_to_webview(DROP_CANCELLED_EVENT, ())?,
|
||||||
|
_ => unimplemented!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -23,8 +23,6 @@ use crate::{
|
|||||||
Icon, Manager, Runtime, Scopes, Window, WindowEvent,
|
Icon, Manager, Runtime, Scopes, Window, WindowEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::AppManager;
|
|
||||||
|
|
||||||
const WINDOW_RESIZED_EVENT: &str = "tauri://resize";
|
const WINDOW_RESIZED_EVENT: &str = "tauri://resize";
|
||||||
const WINDOW_MOVED_EVENT: &str = "tauri://move";
|
const WINDOW_MOVED_EVENT: &str = "tauri://move";
|
||||||
const WINDOW_CLOSE_REQUESTED_EVENT: &str = "tauri://close-requested";
|
const WINDOW_CLOSE_REQUESTED_EVENT: &str = "tauri://close-requested";
|
||||||
@ -33,9 +31,9 @@ const WINDOW_FOCUS_EVENT: &str = "tauri://focus";
|
|||||||
const WINDOW_BLUR_EVENT: &str = "tauri://blur";
|
const WINDOW_BLUR_EVENT: &str = "tauri://blur";
|
||||||
const WINDOW_SCALE_FACTOR_CHANGED_EVENT: &str = "tauri://scale-change";
|
const WINDOW_SCALE_FACTOR_CHANGED_EVENT: &str = "tauri://scale-change";
|
||||||
const WINDOW_THEME_CHANGED: &str = "tauri://theme-changed";
|
const WINDOW_THEME_CHANGED: &str = "tauri://theme-changed";
|
||||||
const WINDOW_FILE_DROP_EVENT: &str = "tauri://file-drop";
|
pub const DROP_EVENT: &str = "tauri://file-drop";
|
||||||
const WINDOW_FILE_DROP_HOVER_EVENT: &str = "tauri://file-drop-hover";
|
pub const DROP_HOVER_EVENT: &str = "tauri://file-drop-hover";
|
||||||
const WINDOW_FILE_DROP_CANCELLED_EVENT: &str = "tauri://file-drop-cancelled";
|
pub const DROP_CANCELLED_EVENT: &str = "tauri://file-drop-cancelled";
|
||||||
|
|
||||||
pub struct WindowManager<R: Runtime> {
|
pub struct WindowManager<R: Runtime> {
|
||||||
pub windows: Mutex<HashMap<String, Window<R>>>,
|
pub windows: Mutex<HashMap<String, Window<R>>>,
|
||||||
@ -95,9 +93,8 @@ impl<R: Runtime> WindowManager<R> {
|
|||||||
|
|
||||||
let window_ = window.clone();
|
let window_ = window.clone();
|
||||||
let window_event_listeners = self.event_listeners.clone();
|
let window_event_listeners = self.event_listeners.clone();
|
||||||
let manager = window.manager.clone();
|
|
||||||
window.on_window_event(move |event| {
|
window.on_window_event(move |event| {
|
||||||
let _ = on_window_event(&window_, &manager, event);
|
let _ = on_window_event(&window_, event);
|
||||||
for handler in window_event_listeners.iter() {
|
for handler in window_event_listeners.iter() {
|
||||||
handler(&window_, event);
|
handler(&window_, event);
|
||||||
}
|
}
|
||||||
@ -152,16 +149,12 @@ impl<R: Runtime> Window<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
struct FileDropPayload<'a> {
|
pub struct FileDropPayload<'a> {
|
||||||
paths: &'a Vec<PathBuf>,
|
pub paths: &'a Vec<PathBuf>,
|
||||||
position: &'a PhysicalPosition<f64>,
|
pub position: &'a PhysicalPosition<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_window_event<R: Runtime>(
|
fn on_window_event<R: Runtime>(window: &Window<R>, event: &WindowEvent) -> crate::Result<()> {
|
||||||
window: &Window<R>,
|
|
||||||
manager: &AppManager<R>,
|
|
||||||
event: &WindowEvent,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
match event {
|
match event {
|
||||||
WindowEvent::Resized(size) => window.emit_to_window(WINDOW_RESIZED_EVENT, size)?,
|
WindowEvent::Resized(size) => window.emit_to_window(WINDOW_RESIZED_EVENT, size)?,
|
||||||
WindowEvent::Moved(position) => window.emit_to_window(WINDOW_MOVED_EVENT, position)?,
|
WindowEvent::Moved(position) => window.emit_to_window(WINDOW_MOVED_EVENT, position)?,
|
||||||
@ -174,7 +167,7 @@ fn on_window_event<R: Runtime>(
|
|||||||
WindowEvent::Destroyed => {
|
WindowEvent::Destroyed => {
|
||||||
window.emit_to_window(WINDOW_DESTROYED_EVENT, ())?;
|
window.emit_to_window(WINDOW_DESTROYED_EVENT, ())?;
|
||||||
let label = window.label();
|
let label = window.label();
|
||||||
let webviews_map = manager.webview.webviews_lock();
|
let webviews_map = window.manager().webview.webviews_lock();
|
||||||
let webviews = webviews_map.values();
|
let webviews = webviews_map.values();
|
||||||
for webview in webviews {
|
for webview in webviews {
|
||||||
webview.eval(&format!(
|
webview.eval(&format!(
|
||||||
@ -204,7 +197,15 @@ fn on_window_event<R: Runtime>(
|
|||||||
WindowEvent::FileDrop(event) => match event {
|
WindowEvent::FileDrop(event) => match event {
|
||||||
FileDropEvent::Hovered { paths, position } => {
|
FileDropEvent::Hovered { paths, position } => {
|
||||||
let payload = FileDropPayload { paths, position };
|
let payload = FileDropPayload { paths, position };
|
||||||
window.emit_to_window(WINDOW_FILE_DROP_HOVER_EVENT, payload)?
|
if window.is_webview_window {
|
||||||
|
window.emit_to(
|
||||||
|
EventTarget::labeled(window.label()),
|
||||||
|
DROP_HOVER_EVENT,
|
||||||
|
payload,
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
window.emit_to_window(DROP_HOVER_EVENT, payload)?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
FileDropEvent::Dropped { paths, position } => {
|
FileDropEvent::Dropped { paths, position } => {
|
||||||
let scopes = window.state::<Scopes>();
|
let scopes = window.state::<Scopes>();
|
||||||
@ -216,9 +217,24 @@ fn on_window_event<R: Runtime>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let payload = FileDropPayload { paths, position };
|
let payload = FileDropPayload { paths, position };
|
||||||
window.emit_to_window(WINDOW_FILE_DROP_EVENT, payload)?
|
|
||||||
|
if window.is_webview_window {
|
||||||
|
window.emit_to(EventTarget::labeled(window.label()), DROP_EVENT, payload)?
|
||||||
|
} else {
|
||||||
|
window.emit_to_window(DROP_EVENT, payload)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FileDropEvent::Cancelled => {
|
||||||
|
if window.is_webview_window {
|
||||||
|
window.emit_to(
|
||||||
|
EventTarget::labeled(window.label()),
|
||||||
|
DROP_CANCELLED_EVENT,
|
||||||
|
(),
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
window.emit_to_window(DROP_CANCELLED_EVENT, ())?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
FileDropEvent::Cancelled => window.emit_to_window(WINDOW_FILE_DROP_CANCELLED_EVENT, ())?,
|
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
},
|
},
|
||||||
WindowEvent::ThemeChanged(theme) => {
|
WindowEvent::ThemeChanged(theme) => {
|
||||||
|
@ -61,6 +61,7 @@ pub struct RuntimeContext {
|
|||||||
next_window_id: Arc<AtomicU32>,
|
next_window_id: Arc<AtomicU32>,
|
||||||
next_webview_id: Arc<AtomicU32>,
|
next_webview_id: Arc<AtomicU32>,
|
||||||
next_window_event_id: Arc<AtomicU32>,
|
next_window_event_id: Arc<AtomicU32>,
|
||||||
|
next_webview_event_id: Arc<AtomicU32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: we ensure this type is only used on the main thread.
|
// SAFETY: we ensure this type is only used on the main thread.
|
||||||
@ -100,6 +101,10 @@ impl RuntimeContext {
|
|||||||
fn next_window_event_id(&self) -> WindowEventId {
|
fn next_window_event_id(&self) -> WindowEventId {
|
||||||
self.next_window_event_id.fetch_add(1, Ordering::Relaxed)
|
self.next_window_event_id.fetch_add(1, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_webview_event_id(&self) -> WindowEventId {
|
||||||
|
self.next_webview_event_id.fetch_add(1, Ordering::Relaxed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for RuntimeContext {
|
impl fmt::Debug for RuntimeContext {
|
||||||
@ -460,6 +465,13 @@ impl<T: UserEvent> WebviewDispatch<T> for MockWebviewDispatcher {
|
|||||||
self.context.send_message(Message::Task(Box::new(f)))
|
self.context.send_message(Message::Task(Box::new(f)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_webview_event<F: Fn(&tauri_runtime::window::WebviewEvent) + Send + 'static>(
|
||||||
|
&self,
|
||||||
|
f: F,
|
||||||
|
) -> tauri_runtime::WebviewEventId {
|
||||||
|
self.context.next_window_event_id()
|
||||||
|
}
|
||||||
|
|
||||||
fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()> {
|
fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -922,6 +934,7 @@ impl MockRuntime {
|
|||||||
next_window_id: Default::default(),
|
next_window_id: Default::default(),
|
||||||
next_webview_id: Default::default(),
|
next_webview_id: Default::default(),
|
||||||
next_window_event_id: Default::default(),
|
next_window_event_id: Default::default(),
|
||||||
|
next_webview_event_id: Default::default(),
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
is_running,
|
is_running,
|
||||||
|
@ -26,7 +26,7 @@ use tauri_utils::config::{WebviewUrl, WindowConfig};
|
|||||||
pub use url::Url;
|
pub use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::UriSchemeResponder,
|
app::{UriSchemeResponder, WebviewEvent},
|
||||||
event::{EmitArgs, EventTarget},
|
event::{EmitArgs, EventTarget},
|
||||||
ipc::{
|
ipc::{
|
||||||
CallbackFn, CommandArg, CommandItem, Invoke, InvokeBody, InvokeError, InvokeMessage,
|
CallbackFn, CommandArg, CommandItem, Invoke, InvokeBody, InvokeError, InvokeMessage,
|
||||||
@ -861,6 +861,14 @@ impl<R: Runtime> Webview<R> {
|
|||||||
pub fn label(&self) -> &str {
|
pub fn label(&self) -> &str {
|
||||||
&self.webview.label
|
&self.webview.label
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers a window event listener.
|
||||||
|
pub fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) {
|
||||||
|
self
|
||||||
|
.webview
|
||||||
|
.dispatcher
|
||||||
|
.on_webview_event(move |event| f(&event.clone().into()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Desktop webview setters and actions.
|
/// Desktop webview setters and actions.
|
||||||
@ -875,7 +883,7 @@ impl<R: Runtime> Webview<R> {
|
|||||||
|
|
||||||
/// Closes this webview.
|
/// Closes this webview.
|
||||||
pub fn close(&self) -> crate::Result<()> {
|
pub fn close(&self) -> crate::Result<()> {
|
||||||
if self.window.webview_window {
|
if self.window.is_webview_window {
|
||||||
self.window.close()
|
self.window.close()
|
||||||
} else {
|
} else {
|
||||||
self.webview.dispatcher.close()?;
|
self.webview.dispatcher.close()?;
|
||||||
@ -886,7 +894,7 @@ impl<R: Runtime> Webview<R> {
|
|||||||
|
|
||||||
/// Resizes this webview.
|
/// Resizes this webview.
|
||||||
pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {
|
pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {
|
||||||
if self.window.webview_window {
|
if self.window.is_webview_window {
|
||||||
self.window.set_size(size.into())
|
self.window.set_size(size.into())
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
@ -899,7 +907,7 @@ impl<R: Runtime> Webview<R> {
|
|||||||
|
|
||||||
/// Sets this webviews's position.
|
/// Sets this webviews's position.
|
||||||
pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
|
pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
|
||||||
if self.window.webview_window {
|
if self.window.is_webview_window {
|
||||||
self.window.set_position(position.into())
|
self.window.set_position(position.into())
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
@ -920,7 +928,7 @@ impl<R: Runtime> Webview<R> {
|
|||||||
/// - For child webviews, returns the position of the top-left hand corner of the webviews's client area relative to the top-left hand corner of the parent window.
|
/// - For child webviews, returns the position of the top-left hand corner of the webviews's client area relative to the top-left hand corner of the parent window.
|
||||||
/// - For webview window, returns the inner position of the window.
|
/// - For webview window, returns the inner position of the window.
|
||||||
pub fn position(&self) -> crate::Result<PhysicalPosition<i32>> {
|
pub fn position(&self) -> crate::Result<PhysicalPosition<i32>> {
|
||||||
if self.window.webview_window {
|
if self.window.is_webview_window {
|
||||||
self.window.inner_position()
|
self.window.inner_position()
|
||||||
} else {
|
} else {
|
||||||
self.webview.dispatcher.position().map_err(Into::into)
|
self.webview.dispatcher.position().map_err(Into::into)
|
||||||
@ -929,7 +937,7 @@ impl<R: Runtime> Webview<R> {
|
|||||||
|
|
||||||
/// Returns the physical size of the webviews's client area.
|
/// Returns the physical size of the webviews's client area.
|
||||||
pub fn size(&self) -> crate::Result<PhysicalSize<u32>> {
|
pub fn size(&self) -> crate::Result<PhysicalSize<u32>> {
|
||||||
if self.window.webview_window {
|
if self.window.is_webview_window {
|
||||||
self.window.inner_size()
|
self.window.inner_size()
|
||||||
} else {
|
} else {
|
||||||
self.webview.dispatcher.size().map_err(Into::into)
|
self.webview.dispatcher.size().map_err(Into::into)
|
||||||
|
@ -876,7 +876,7 @@ impl<'de, R: Runtime> CommandArg<'de, R> for WebviewWindow<R> {
|
|||||||
/// Grabs the [`Window`] from the [`CommandItem`]. This will never fail.
|
/// Grabs the [`Window`] from the [`CommandItem`]. This will never fail.
|
||||||
fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
|
fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
|
||||||
let webview = command.message.webview();
|
let webview = command.message.webview();
|
||||||
if webview.window().webview_window {
|
if webview.window().is_webview_window {
|
||||||
Ok(Self { webview })
|
Ok(Self { webview })
|
||||||
} else {
|
} else {
|
||||||
Err(InvokeError::from_anyhow(anyhow::anyhow!(
|
Err(InvokeError::from_anyhow(anyhow::anyhow!(
|
||||||
|
@ -863,7 +863,7 @@ pub struct Window<R: Runtime> {
|
|||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
pub(crate) menu: Arc<std::sync::Mutex<Option<WindowMenu<R>>>>,
|
pub(crate) menu: Arc<std::sync::Mutex<Option<WindowMenu<R>>>>,
|
||||||
/// Whether this window is a Webview window (hosts only a single webview) or a container for multiple webviews
|
/// Whether this window is a Webview window (hosts only a single webview) or a container for multiple webviews
|
||||||
pub(crate) webview_window: bool,
|
pub(crate) is_webview_window: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Runtime> std::fmt::Debug for Window<R> {
|
impl<R: Runtime> std::fmt::Debug for Window<R> {
|
||||||
@ -872,7 +872,7 @@ impl<R: Runtime> std::fmt::Debug for Window<R> {
|
|||||||
.field("window", &self.window)
|
.field("window", &self.window)
|
||||||
.field("manager", &self.manager)
|
.field("manager", &self.manager)
|
||||||
.field("app_handle", &self.app_handle)
|
.field("app_handle", &self.app_handle)
|
||||||
.field("webview_window", &self.webview_window)
|
.field("is_webview_window", &self.is_webview_window)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -893,7 +893,7 @@ impl<R: Runtime> Clone for Window<R> {
|
|||||||
app_handle: self.app_handle.clone(),
|
app_handle: self.app_handle.clone(),
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
menu: self.menu.clone(),
|
menu: self.menu.clone(),
|
||||||
webview_window: self.webview_window,
|
is_webview_window: self.is_webview_window,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -948,7 +948,7 @@ impl<R: Runtime> Window<R> {
|
|||||||
window: DetachedWindow<EventLoopMessage, R>,
|
window: DetachedWindow<EventLoopMessage, R>,
|
||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
#[cfg(desktop)] menu: Option<WindowMenu<R>>,
|
#[cfg(desktop)] menu: Option<WindowMenu<R>>,
|
||||||
webview_window: bool,
|
is_webview_window: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
window,
|
window,
|
||||||
@ -956,7 +956,7 @@ impl<R: Runtime> Window<R> {
|
|||||||
app_handle,
|
app_handle,
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
menu: Arc::new(std::sync::Mutex::new(menu)),
|
menu: Arc::new(std::sync::Mutex::new(menu)),
|
||||||
webview_window,
|
is_webview_window,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
EffectState,
|
EffectState,
|
||||||
ProgressBarStatus
|
ProgressBarStatus
|
||||||
} from '@tauri-apps/api/window'
|
} from '@tauri-apps/api/window'
|
||||||
import { WebviewWindow } from '@tauri-apps/api/webview'
|
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||||
|
|
||||||
const webview = WebviewWindow.getCurrent()
|
const webview = WebviewWindow.getCurrent()
|
||||||
|
|
||||||
|
@ -56,9 +56,9 @@ enum TauriEvent {
|
|||||||
WINDOW_SCALE_FACTOR_CHANGED = 'tauri://scale-change',
|
WINDOW_SCALE_FACTOR_CHANGED = 'tauri://scale-change',
|
||||||
WINDOW_THEME_CHANGED = 'tauri://theme-changed',
|
WINDOW_THEME_CHANGED = 'tauri://theme-changed',
|
||||||
WEBVIEW_CREATED = 'tauri://webview-created',
|
WEBVIEW_CREATED = 'tauri://webview-created',
|
||||||
WEBVIEW_FILE_DROP = 'tauri://file-drop',
|
FILE_DROP = 'tauri://file-drop',
|
||||||
WEBVIEW_FILE_DROP_HOVER = 'tauri://file-drop-hover',
|
FILE_DROP_HOVER = 'tauri://file-drop-hover',
|
||||||
WEBVIEW_FILE_DROP_CANCELLED = 'tauri://file-drop-cancelled'
|
FILE_DROP_CANCELLED = 'tauri://file-drop-cancelled'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -183,8 +183,8 @@ async function emit(event: string, payload?: unknown): Promise<void> {
|
|||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* import { emit } from '@tauri-apps/api/event';
|
* import { emitTo } from '@tauri-apps/api/event';
|
||||||
* await emit('frontend-loaded', { loggedIn: true, token: 'authToken' });
|
* await emitTo('main', 'frontend-loaded', { loggedIn: true, token: 'authToken' });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
|
* @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
|
||||||
|
@ -18,9 +18,21 @@ import * as event from './event'
|
|||||||
import * as core from './core'
|
import * as core from './core'
|
||||||
import * as window from './window'
|
import * as window from './window'
|
||||||
import * as webview from './webview'
|
import * as webview from './webview'
|
||||||
|
import * as webviewWindow from './webviewWindow'
|
||||||
import * as path from './path'
|
import * as path from './path'
|
||||||
import * as dpi from './dpi'
|
import * as dpi from './dpi'
|
||||||
import * as tray from './tray'
|
import * as tray from './tray'
|
||||||
import * as menu from './menu'
|
import * as menu from './menu'
|
||||||
|
|
||||||
export { app, dpi, event, path, core, window, webview, tray, menu }
|
export {
|
||||||
|
app,
|
||||||
|
dpi,
|
||||||
|
event,
|
||||||
|
path,
|
||||||
|
core,
|
||||||
|
window,
|
||||||
|
webview,
|
||||||
|
webviewWindow,
|
||||||
|
tray,
|
||||||
|
menu
|
||||||
|
}
|
||||||
|
@ -31,7 +31,6 @@ import {
|
|||||||
} from './event'
|
} from './event'
|
||||||
import { invoke } from './core'
|
import { invoke } from './core'
|
||||||
import { Window, getCurrent as getCurrentWindow } from './window'
|
import { Window, getCurrent as getCurrentWindow } from './window'
|
||||||
import type { WindowOptions } from './window'
|
|
||||||
|
|
||||||
interface FileDropPayload {
|
interface FileDropPayload {
|
||||||
paths: string[]
|
paths: string[]
|
||||||
@ -47,7 +46,7 @@ type FileDropEvent =
|
|||||||
/**
|
/**
|
||||||
* Get an instance of `Webview` for the current webview.
|
* Get an instance of `Webview` for the current webview.
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
function getCurrent(): Webview {
|
function getCurrent(): Webview {
|
||||||
return new Webview(
|
return new Webview(
|
||||||
@ -63,7 +62,7 @@ function getCurrent(): Webview {
|
|||||||
/**
|
/**
|
||||||
* Gets a list of instances of `Webview` for all available webviews.
|
* Gets a list of instances of `Webview` for all available webviews.
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
function getAll(): Webview[] {
|
function getAll(): Webview[] {
|
||||||
return window.__TAURI_INTERNALS__.metadata.webviews.map(
|
return window.__TAURI_INTERNALS__.metadata.webviews.map(
|
||||||
@ -298,7 +297,7 @@ class Webview {
|
|||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* import { getCurrent } from '@tauri-apps/api/webview';
|
* import { getCurrent } from '@tauri-apps/api/webview';
|
||||||
* await getCurrent().emit('webview-loaded', { loggedIn: true, token: 'authToken' });
|
* await getCurrent().emitTo('main', 'webview-loaded', { loggedIn: true, token: 'authToken' });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
|
* @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
|
||||||
@ -506,7 +505,7 @@ class Webview {
|
|||||||
handler: EventCallback<FileDropEvent>
|
handler: EventCallback<FileDropEvent>
|
||||||
): Promise<UnlistenFn> {
|
): Promise<UnlistenFn> {
|
||||||
const unlistenFileDrop = await this.listen<FileDropPayload>(
|
const unlistenFileDrop = await this.listen<FileDropPayload>(
|
||||||
TauriEvent.WEBVIEW_FILE_DROP,
|
TauriEvent.FILE_DROP,
|
||||||
(event) => {
|
(event) => {
|
||||||
handler({
|
handler({
|
||||||
...event,
|
...event,
|
||||||
@ -520,7 +519,7 @@ class Webview {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const unlistenFileHover = await this.listen<FileDropPayload>(
|
const unlistenFileHover = await this.listen<FileDropPayload>(
|
||||||
TauriEvent.WEBVIEW_FILE_DROP_HOVER,
|
TauriEvent.FILE_DROP_HOVER,
|
||||||
(event) => {
|
(event) => {
|
||||||
handler({
|
handler({
|
||||||
...event,
|
...event,
|
||||||
@ -534,7 +533,7 @@ class Webview {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const unlistenCancel = await this.listen<null>(
|
const unlistenCancel = await this.listen<null>(
|
||||||
TauriEvent.WEBVIEW_FILE_DROP_CANCELLED,
|
TauriEvent.FILE_DROP_CANCELLED,
|
||||||
(event) => {
|
(event) => {
|
||||||
handler({ ...event, payload: { type: 'cancel' } })
|
handler({ ...event, payload: { type: 'cancel' } })
|
||||||
}
|
}
|
||||||
@ -552,199 +551,10 @@ function mapPhysicalPosition(m: PhysicalPosition): PhysicalPosition {
|
|||||||
return new PhysicalPosition(m.x, m.y)
|
return new PhysicalPosition(m.x, m.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
||||||
interface WebviewWindow extends Webview, Window {}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
||||||
class WebviewWindow {
|
|
||||||
label: string
|
|
||||||
/** Local event listeners. */
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
listeners: Record<string, Array<EventCallback<any>>>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new {@link Window} hosting a {@link Webview}.
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* import { WebviewWindow } from '@tauri-apps/api/webview'
|
|
||||||
* const webview = new WebviewWindow('my-label', {
|
|
||||||
* url: 'https://github.com/tauri-apps/tauri'
|
|
||||||
* });
|
|
||||||
* webview.once('tauri://created', function () {
|
|
||||||
* // webview successfully created
|
|
||||||
* });
|
|
||||||
* webview.once('tauri://error', function (e) {
|
|
||||||
* // an error happened creating the webview
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param label The unique webview label. Must be alphanumeric: `a-zA-Z-/:_`.
|
|
||||||
* @returns The {@link WebviewWindow} instance to communicate with the window and webview.
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
label: WebviewLabel,
|
|
||||||
options: Omit<WebviewOptions, 'x' | 'y' | 'width' | 'height'> &
|
|
||||||
WindowOptions = {}
|
|
||||||
) {
|
|
||||||
this.label = label
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
||||||
this.listeners = Object.create(null)
|
|
||||||
|
|
||||||
// @ts-expect-error `skip` is not a public API so it is not defined in WebviewOptions
|
|
||||||
if (!options?.skip) {
|
|
||||||
invoke('plugin:webview|create_webview_window', {
|
|
||||||
options: {
|
|
||||||
...options,
|
|
||||||
parent:
|
|
||||||
typeof options.parent === 'string'
|
|
||||||
? options.parent
|
|
||||||
: options.parent?.label,
|
|
||||||
label
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(async () => this.emit('tauri://created'))
|
|
||||||
.catch(async (e: string) => this.emit('tauri://error', e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the Webview for the webview associated with the given label.
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* import { Webview } from '@tauri-apps/api/webview';
|
|
||||||
* const mainWebview = Webview.getByLabel('main');
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param label The webview label.
|
|
||||||
* @returns The Webview instance to communicate with the webview or null if the webview doesn't exist.
|
|
||||||
*/
|
|
||||||
static getByLabel(label: string): WebviewWindow | null {
|
|
||||||
const webview = getAll().find((w) => w.label === label) ?? null
|
|
||||||
if (webview) {
|
|
||||||
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
|
||||||
return new WebviewWindow(webview.label, { skip: true })
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an instance of `Webview` for the current webview.
|
|
||||||
*/
|
|
||||||
static getCurrent(): WebviewWindow {
|
|
||||||
const webview = getCurrent()
|
|
||||||
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
|
||||||
return new WebviewWindow(webview.label, { skip: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of instances of `Webview` for all available webviews.
|
|
||||||
*/
|
|
||||||
static getAll(): WebviewWindow[] {
|
|
||||||
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
|
||||||
return getAll().map((w) => new WebviewWindow(w.label, { skip: true }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to an emitted event on this webivew window.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* import { WebviewWindow } from '@tauri-apps/api/webview';
|
|
||||||
* const unlisten = await WebviewWindow.getCurrent().listen<string>('state-changed', (event) => {
|
|
||||||
* console.log(`Got error: ${payload}`);
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
|
||||||
* unlisten();
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
|
||||||
* @param handler Event handler.
|
|
||||||
* @returns A promise resolving to a function to unlisten to the event.
|
|
||||||
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
|
||||||
*/
|
|
||||||
async listen<T>(
|
|
||||||
event: EventName,
|
|
||||||
handler: EventCallback<T>
|
|
||||||
): Promise<UnlistenFn> {
|
|
||||||
if (this._handleTauriEvent(event, handler)) {
|
|
||||||
return Promise.resolve(() => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, security/detect-object-injection
|
|
||||||
const listeners = this.listeners[event]
|
|
||||||
listeners.splice(listeners.indexOf(handler), 1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return listen(event, handler, {
|
|
||||||
target: { kind: 'WebviewWindow', label: this.label }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to an emitted event on this webivew window only once.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* import { WebviewWindow } from '@tauri-apps/api/webview';
|
|
||||||
* const unlisten = await WebviewWindow.getCurrent().once<null>('initialized', (event) => {
|
|
||||||
* console.log(`Webview initialized!`);
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
|
||||||
* unlisten();
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
|
||||||
* @param handler Event handler.
|
|
||||||
* @returns A promise resolving to a function to unlisten to the event.
|
|
||||||
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
|
||||||
*/
|
|
||||||
async once<T>(event: string, handler: EventCallback<T>): Promise<UnlistenFn> {
|
|
||||||
if (this._handleTauriEvent(event, handler)) {
|
|
||||||
return Promise.resolve(() => {
|
|
||||||
// eslint-disable-next-line security/detect-object-injection
|
|
||||||
const listeners = this.listeners[event]
|
|
||||||
listeners.splice(listeners.indexOf(handler), 1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return once(event, handler, {
|
|
||||||
target: { kind: 'WebviewWindow', label: this.label }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order matters, we use window APIs by default
|
|
||||||
applyMixins(WebviewWindow, [Window, Webview])
|
|
||||||
|
|
||||||
/** Extends a base class by other specifed classes, wihtout overriding existing properties */
|
|
||||||
function applyMixins(
|
|
||||||
baseClass: { prototype: unknown },
|
|
||||||
extendedClasses: unknown
|
|
||||||
): void {
|
|
||||||
;(Array.isArray(extendedClasses)
|
|
||||||
? extendedClasses
|
|
||||||
: [extendedClasses]
|
|
||||||
).forEach((extendedClass: { prototype: unknown }) => {
|
|
||||||
Object.getOwnPropertyNames(extendedClass.prototype).forEach((name) => {
|
|
||||||
if (
|
|
||||||
typeof baseClass.prototype === 'object' &&
|
|
||||||
baseClass.prototype &&
|
|
||||||
name in baseClass.prototype
|
|
||||||
)
|
|
||||||
return
|
|
||||||
Object.defineProperty(
|
|
||||||
baseClass.prototype,
|
|
||||||
name,
|
|
||||||
// eslint-disable-next-line
|
|
||||||
Object.getOwnPropertyDescriptor(extendedClass.prototype, name) ??
|
|
||||||
Object.create(null)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Configuration for the webview to create.
|
* Configuration for the webview to create.
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
interface WebviewOptions {
|
interface WebviewOptions {
|
||||||
/**
|
/**
|
||||||
@ -803,6 +613,6 @@ interface WebviewOptions {
|
|||||||
proxyUrl?: string
|
proxyUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Webview, WebviewWindow, getCurrent, getAll }
|
export { Webview, getCurrent, getAll }
|
||||||
|
|
||||||
export type { FileDropEvent, WebviewOptions }
|
export type { FileDropEvent, FileDropPayload, WebviewOptions }
|
||||||
|
234
tooling/api/src/webviewWindow.ts
Normal file
234
tooling/api/src/webviewWindow.ts
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import {
|
||||||
|
getCurrent as getCurrentWebview,
|
||||||
|
Webview,
|
||||||
|
WebviewLabel,
|
||||||
|
WebviewOptions
|
||||||
|
} from './webview'
|
||||||
|
import type { WindowOptions } from './window'
|
||||||
|
import { Window } from './window'
|
||||||
|
import { listen, once } from './event'
|
||||||
|
import type { EventName, EventCallback, UnlistenFn } from './event'
|
||||||
|
import { invoke } from './core'
|
||||||
|
import type { FileDropEvent, FileDropPayload } from './webview'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of `Webview` for the current webview window.
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
function getCurrent(): WebviewWindow {
|
||||||
|
const webview = getCurrentWebview()
|
||||||
|
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||||
|
return new WebviewWindow(webview.label, { skip: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of instances of `Webview` for all available webview windows.
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
function getAll(): WebviewWindow[] {
|
||||||
|
return window.__TAURI_INTERNALS__.metadata.webviews.map(
|
||||||
|
(w) =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
new WebviewWindow(w.label, {
|
||||||
|
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||||
|
skip: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
||||||
|
interface WebviewWindow extends Webview, Window {}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
||||||
|
class WebviewWindow {
|
||||||
|
label: string
|
||||||
|
/** Local event listeners. */
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
listeners: Record<string, Array<EventCallback<any>>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Window} hosting a {@link Webview}.
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||||
|
* const webview = new WebviewWindow('my-label', {
|
||||||
|
* url: 'https://github.com/tauri-apps/tauri'
|
||||||
|
* });
|
||||||
|
* webview.once('tauri://created', function () {
|
||||||
|
* // webview successfully created
|
||||||
|
* });
|
||||||
|
* webview.once('tauri://error', function (e) {
|
||||||
|
* // an error happened creating the webview
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param label The unique webview label. Must be alphanumeric: `a-zA-Z-/:_`.
|
||||||
|
* @returns The {@link WebviewWindow} instance to communicate with the window and webview.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
label: WebviewLabel,
|
||||||
|
options: Omit<WebviewOptions, 'x' | 'y' | 'width' | 'height'> &
|
||||||
|
WindowOptions = {}
|
||||||
|
) {
|
||||||
|
this.label = label
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
this.listeners = Object.create(null)
|
||||||
|
|
||||||
|
// @ts-expect-error `skip` is not a public API so it is not defined in WebviewOptions
|
||||||
|
if (!options?.skip) {
|
||||||
|
invoke('plugin:webview|create_webview_window', {
|
||||||
|
options: {
|
||||||
|
...options,
|
||||||
|
parent:
|
||||||
|
typeof options.parent === 'string'
|
||||||
|
? options.parent
|
||||||
|
: options.parent?.label,
|
||||||
|
label
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(async () => this.emit('tauri://created'))
|
||||||
|
.catch(async (e: string) => this.emit('tauri://error', e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Webview for the webview associated with the given label.
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { Webview } from '@tauri-apps/api/webviewWindow';
|
||||||
|
* const mainWebview = Webview.getByLabel('main');
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param label The webview label.
|
||||||
|
* @returns The Webview instance to communicate with the webview or null if the webview doesn't exist.
|
||||||
|
*/
|
||||||
|
static getByLabel(label: string): WebviewWindow | null {
|
||||||
|
const webview = getAll().find((w) => w.label === label) ?? null
|
||||||
|
if (webview) {
|
||||||
|
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||||
|
return new WebviewWindow(webview.label, { skip: true })
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of `Webview` for the current webview.
|
||||||
|
*/
|
||||||
|
static getCurrent(): WebviewWindow {
|
||||||
|
return getCurrent()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of instances of `Webview` for all available webviews.
|
||||||
|
*/
|
||||||
|
static getAll(): WebviewWindow[] {
|
||||||
|
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||||
|
return getAll().map((w) => new WebviewWindow(w.label, { skip: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to an emitted event on this webivew window.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
* const unlisten = await WebviewWindow.getCurrent().listen<string>('state-changed', (event) => {
|
||||||
|
* console.log(`Got error: ${payload}`);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||||
|
* unlisten();
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||||
|
* @param handler Event handler.
|
||||||
|
* @returns A promise resolving to a function to unlisten to the event.
|
||||||
|
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||||
|
*/
|
||||||
|
async listen<T>(
|
||||||
|
event: EventName,
|
||||||
|
handler: EventCallback<T>
|
||||||
|
): Promise<UnlistenFn> {
|
||||||
|
if (this._handleTauriEvent(event, handler)) {
|
||||||
|
return Promise.resolve(() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, security/detect-object-injection
|
||||||
|
const listeners = this.listeners[event]
|
||||||
|
listeners.splice(listeners.indexOf(handler), 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return listen(event, handler, {
|
||||||
|
target: { kind: 'WebviewWindow', label: this.label }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to an emitted event on this webivew window only once.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
* const unlisten = await WebviewWindow.getCurrent().once<null>('initialized', (event) => {
|
||||||
|
* console.log(`Webview initialized!`);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||||
|
* unlisten();
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||||
|
* @param handler Event handler.
|
||||||
|
* @returns A promise resolving to a function to unlisten to the event.
|
||||||
|
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||||
|
*/
|
||||||
|
async once<T>(event: string, handler: EventCallback<T>): Promise<UnlistenFn> {
|
||||||
|
if (this._handleTauriEvent(event, handler)) {
|
||||||
|
return Promise.resolve(() => {
|
||||||
|
// eslint-disable-next-line security/detect-object-injection
|
||||||
|
const listeners = this.listeners[event]
|
||||||
|
listeners.splice(listeners.indexOf(handler), 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return once(event, handler, {
|
||||||
|
target: { kind: 'WebviewWindow', label: this.label }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order matters, we use window APIs by default
|
||||||
|
applyMixins(WebviewWindow, [Window, Webview])
|
||||||
|
|
||||||
|
/** Extends a base class by other specifed classes, wihtout overriding existing properties */
|
||||||
|
function applyMixins(
|
||||||
|
baseClass: { prototype: unknown },
|
||||||
|
extendedClasses: unknown
|
||||||
|
): void {
|
||||||
|
;(Array.isArray(extendedClasses)
|
||||||
|
? extendedClasses
|
||||||
|
: [extendedClasses]
|
||||||
|
).forEach((extendedClass: { prototype: unknown }) => {
|
||||||
|
Object.getOwnPropertyNames(extendedClass.prototype).forEach((name) => {
|
||||||
|
if (
|
||||||
|
typeof baseClass.prototype === 'object' &&
|
||||||
|
baseClass.prototype &&
|
||||||
|
name in baseClass.prototype
|
||||||
|
)
|
||||||
|
return
|
||||||
|
Object.defineProperty(
|
||||||
|
baseClass.prototype,
|
||||||
|
name,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
Object.getOwnPropertyDescriptor(extendedClass.prototype, name) ??
|
||||||
|
Object.create(null)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export { WebviewWindow, getCurrent, getAll }
|
||||||
|
export type { FileDropEvent, FileDropPayload }
|
@ -34,7 +34,8 @@ import {
|
|||||||
once
|
once
|
||||||
} from './event'
|
} from './event'
|
||||||
import { invoke } from './core'
|
import { invoke } from './core'
|
||||||
import { WebviewWindow } from './webview'
|
import { WebviewWindow } from './webviewWindow'
|
||||||
|
import type { FileDropEvent, FileDropPayload } from './webview'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows you to retrieve information about a given monitor.
|
* Allows you to retrieve information about a given monitor.
|
||||||
@ -458,7 +459,7 @@ class Window {
|
|||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* import { getCurrent } from '@tauri-apps/api/window';
|
* import { getCurrent } from '@tauri-apps/api/window';
|
||||||
* await getCurrent().emit('window-loaded', { loggedIn: true, token: 'authToken' });
|
* await getCurrent().emit('main', 'window-loaded', { loggedIn: true, token: 'authToken' });
|
||||||
* ```
|
* ```
|
||||||
* @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
|
* @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
|
||||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||||
@ -1716,6 +1717,76 @@ class Window {
|
|||||||
}
|
}
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to a file drop event.
|
||||||
|
* The listener is triggered when the user hovers the selected files on the webview,
|
||||||
|
* drops the files or cancels the operation.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { getCurrent } from "@tauri-apps/api/webview";
|
||||||
|
* const unlisten = await getCurrent().onFileDropEvent((event) => {
|
||||||
|
* if (event.payload.type === 'hover') {
|
||||||
|
* console.log('User hovering', event.payload.paths);
|
||||||
|
* } else if (event.payload.type === 'drop') {
|
||||||
|
* console.log('User dropped', event.payload.paths);
|
||||||
|
* } else {
|
||||||
|
* console.log('File drop cancelled');
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||||
|
* unlisten();
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @returns A promise resolving to a function to unlisten to the event.
|
||||||
|
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||||
|
*/
|
||||||
|
async onFileDropEvent(
|
||||||
|
handler: EventCallback<FileDropEvent>
|
||||||
|
): Promise<UnlistenFn> {
|
||||||
|
const unlistenFileDrop = await this.listen<FileDropPayload>(
|
||||||
|
TauriEvent.FILE_DROP,
|
||||||
|
(event) => {
|
||||||
|
handler({
|
||||||
|
...event,
|
||||||
|
payload: {
|
||||||
|
type: 'drop',
|
||||||
|
paths: event.payload.paths,
|
||||||
|
position: mapPhysicalPosition(event.payload.position)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const unlistenFileHover = await this.listen<FileDropPayload>(
|
||||||
|
TauriEvent.FILE_DROP_HOVER,
|
||||||
|
(event) => {
|
||||||
|
handler({
|
||||||
|
...event,
|
||||||
|
payload: {
|
||||||
|
type: 'hover',
|
||||||
|
paths: event.payload.paths,
|
||||||
|
position: mapPhysicalPosition(event.payload.position)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const unlistenCancel = await this.listen<null>(
|
||||||
|
TauriEvent.FILE_DROP_CANCELLED,
|
||||||
|
(event) => {
|
||||||
|
handler({ ...event, payload: { type: 'cancel' } })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlistenFileDrop()
|
||||||
|
unlistenFileHover()
|
||||||
|
unlistenCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen to window focus change.
|
* Listen to window focus change.
|
||||||
*
|
*
|
||||||
@ -2200,5 +2271,7 @@ export type {
|
|||||||
TitleBarStyle,
|
TitleBarStyle,
|
||||||
ScaleFactorChanged,
|
ScaleFactorChanged,
|
||||||
WindowOptions,
|
WindowOptions,
|
||||||
Color
|
Color,
|
||||||
|
FileDropEvent,
|
||||||
|
FileDropPayload
|
||||||
}
|
}
|
||||||
|
@ -302,7 +302,7 @@ fn build_ignore_matcher(dir: &Path) -> IgnoreMatcher {
|
|||||||
|
|
||||||
for line in crate::dev::TAURI_CLI_BUILTIN_WATCHER_IGNORE_FILE
|
for line in crate::dev::TAURI_CLI_BUILTIN_WATCHER_IGNORE_FILE
|
||||||
.lines()
|
.lines()
|
||||||
.flatten()
|
.map_while(Result::ok)
|
||||||
{
|
{
|
||||||
let _ = ignore_builder.add_line(None, &line);
|
let _ = ignore_builder.add_line(None, &line);
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,10 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> {
|
|||||||
let new = "@tauri-apps/api/core".to_string();
|
let new = "@tauri-apps/api/core".to_string();
|
||||||
log::info!("Replacing `{original}` with `{new}` on {}", path.display());
|
log::info!("Replacing `{original}` with `{new}` on {}", path.display());
|
||||||
new
|
new
|
||||||
|
} else if module == "window" {
|
||||||
|
let new = "@tauri-apps/api/webviewWindow".to_string();
|
||||||
|
log::info!("Replacing `{original}` with `{new}` on {}", path.display());
|
||||||
|
new
|
||||||
} else if CORE_API_MODULES.contains(&module) {
|
} else if CORE_API_MODULES.contains(&module) {
|
||||||
original.to_string()
|
original.to_string()
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user