tauri-sys/src/window.rs
2024-07-23 13:43:37 +02:00

748 lines
22 KiB
Rust

//! Provides APIs to create windows, communicate with other windows and manipulate the current window.
//!
//! ## Window events
//! Events can be listened to using [`Window::listen`].
use crate::{
dpi,
event::{self, Event},
};
use futures::{
channel::{
mpsc::{self, UnboundedSender},
oneshot,
},
Future, FutureExt, Stream, StreamExt,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{any::Any, collections::HashMap};
use wasm_bindgen::{prelude::Closure, JsValue};
/// Events that are emitted right here instead of by the created window.
const LOCAL_TAURI_EVENTS: &'static [&'static str; 2] = &["tauri://created", "tauri://error"];
trait SenderVec: Any {
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
}
impl<T> SenderVec for Vec<mpsc::UnboundedSender<T>>
where
T: DeserializeOwned + 'static,
{
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self as &mut dyn Any
}
}
pub(crate) struct Listen<T> {
pub rx: mpsc::UnboundedReceiver<T>,
}
impl<T> Stream for Listen<T> {
type Item = T;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.rx.poll_next_unpin(cx)
}
}
pub(crate) struct DragDropListen {
pub rx: mpsc::UnboundedReceiver<Event<DragDropEvent>>,
pub unlisten_enter: js_sys::Function,
pub unlisten_drop: js_sys::Function,
pub unlisten_over: js_sys::Function,
pub unlisten_leave: js_sys::Function,
}
impl Drop for DragDropListen {
fn drop(&mut self) {
log::debug!("Calling unlisten for listen callback");
self.unlisten_enter
.call0(&wasm_bindgen::JsValue::NULL)
.unwrap();
self.unlisten_drop
.call0(&wasm_bindgen::JsValue::NULL)
.unwrap();
self.unlisten_over
.call0(&wasm_bindgen::JsValue::NULL)
.unwrap();
self.unlisten_leave
.call0(&wasm_bindgen::JsValue::NULL)
.unwrap();
}
}
impl Stream for DragDropListen {
type Item = Event<DragDropEvent>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.rx.poll_next_unpin(cx)
}
}
#[derive(Deserialize)]
struct WindowLabel {
label: String,
}
#[derive(Deserialize, Debug)]
pub enum DragDropEvent {
Enter(DragDropPayload),
Over(DragOverPayload),
Drop(DragDropPayload),
Leave,
}
#[derive(Deserialize, Debug)]
pub struct DragDropPayload {
paths: Vec<String>,
position: dpi::PhysicalPosition,
}
impl DragDropPayload {
pub fn paths(&self) -> &Vec<String> {
&self.paths
}
pub fn position(&self) -> &dpi::PhysicalPosition {
&self.position
}
}
#[derive(Deserialize, Debug)]
pub struct DragOverPayload {
position: dpi::PhysicalPosition,
}
impl DragOverPayload {
pub fn position(&self) -> &dpi::PhysicalPosition {
&self.position
}
}
pub struct Window {
label: String,
listeners: HashMap<String, Box<dyn SenderVec>>,
}
impl Window {
/// Create a new Window.
///
/// # Arguments
/// + `label`: Unique window label. Must be alphanumberic: `a-zA-Z-/:_`.
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
listeners: HashMap::new(),
}
}
/// Gets the Window associated with the given label.
pub fn get_by_label(label: impl AsRef<str>) -> Option<Self> {
js_sys::try_iter(&inner::get_all())
.unwrap()
.unwrap()
.into_iter()
.find_map(|value| {
let window_label = value.unwrap().as_string().unwrap();
if window_label == label.as_ref() {
Some(Window::new(window_label))
} else {
None
}
})
}
/// Get an instance of `Window` for the current window.
pub fn get_current() -> Self {
get_current()
}
/// Gets a list of instances of `Window` for all available windows.
pub fn get_all() -> Vec<Self> {
get_all()
}
}
impl Window {
pub fn label(&self) -> &String {
&self.label
}
fn handle_tauri_event<T>(&mut self, event: String) -> Option<impl Stream<Item = Event<T>>>
where
T: DeserializeOwned + 'static,
{
if LOCAL_TAURI_EVENTS.contains(&event.as_str()) {
let (tx, rx) = mpsc::unbounded::<Event<T>>();
let entry = self
.listeners
.entry(event)
.or_insert(Box::new(Vec::<mpsc::UnboundedSender<Event<T>>>::new()));
let senders = entry
.as_any_mut()
.downcast_mut::<Vec<mpsc::UnboundedSender<Event<T>>>>()
.unwrap();
senders.push(tx);
Some(Listen { rx })
} else {
None
}
}
}
impl Window {
/// Listen to an emitted event on this window.
///
/// # Arguments
/// + `event`: Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
/// + `handler`: Event handler.
///
/// # Returns
/// 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.
pub async fn listen<T>(
&mut self,
event: impl Into<String>,
) -> crate::Result<impl Stream<Item = Event<T>>>
where
T: DeserializeOwned + 'static,
{
use futures::future::Either;
let event = event.into();
if let Some(listener) = self.handle_tauri_event(event.clone()) {
Ok(Either::Left(listener))
} else {
let listener =
event::listen_to(&event, event::EventTarget::Window(self.label.clone())).await?;
Ok(Either::Right(listener))
}
}
/// Listen to an emitted event on this window only once.
///
/// # Arguments
/// + `event`: Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
/// + `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.
pub async fn once(&self, event: impl Into<String>, handler: Closure<dyn FnMut(JsValue)>) {
todo!();
}
/// Emits an event to all {@link EventTarget|targets}.
///
/// # Arguments
/// + `event`: Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
/// + `payload`: Event payload.
pub async fn emit<T: Serialize + Clone + 'static>(
&self,
event: impl Into<String>,
payload: T,
) -> crate::Result<()> {
let event: String = event.into();
if LOCAL_TAURI_EVENTS.contains(&event.as_str()) {
if let Some(listeners) = self.listeners.get(&event) {
let listeners = listeners
.as_any()
.downcast_ref::<Vec<UnboundedSender<Event<T>>>>()
.unwrap();
for listener in listeners {
listener
.unbounded_send(event::Event {
event: event.clone(),
id: -1,
payload: payload.clone(),
})
.unwrap();
}
}
Ok(())
} else {
event::emit(event.as_str(), &payload).await
}
}
/// Emits an event to all {@link EventTarget|targets} matching the given target.
///
/// # Arguments
/// + `target`: Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
/// + `event`: Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
/// + `payload`: Event payload.
pub async fn emit_to<T: Serialize + Clone + 'static>(
&self,
target: &event::EventTarget,
event: impl Into<String>,
payload: T,
) -> crate::Result<()> {
let event: String = event.into();
if LOCAL_TAURI_EVENTS.contains(&event.as_str()) {
if let Some(listeners) = self.listeners.get(&event) {
let listeners = listeners
.as_any()
.downcast_ref::<Vec<UnboundedSender<Event<T>>>>()
.unwrap();
for listener in listeners {
listener
.unbounded_send(event::Event {
event: event.clone(),
id: -1,
payload: payload.clone(),
})
.unwrap();
}
}
Ok(())
} else {
event::emit_to(target, event.as_str(), &payload).await
}
}
}
impl Window {
/// 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.
///
/// # Returns
/// 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.
pub async fn on_drag_drop_event(
&self,
) -> crate::Result<impl Stream<Item = Event<DragDropEvent>>> {
let (tx, rx) = mpsc::unbounded::<Event<DragDropEvent>>();
let closure = {
let tx = tx.clone();
Closure::<dyn FnMut(JsValue)>::new(move |raw| {
let Event { event, id, payload } =
serde_wasm_bindgen::from_value::<Event<DragDropPayload>>(raw).unwrap();
let _ = tx.unbounded_send(Event {
event,
id,
payload: DragDropEvent::Enter(payload),
});
})
};
let unlisten = event::inner::listen(
event::DRAG_ENTER,
&closure,
serde_wasm_bindgen::to_value(&event::Options {
target: event::EventTarget::Window(self.label.clone()),
})?,
)
.await?;
closure.forget();
let unlisten_enter = js_sys::Function::from(unlisten);
let closure = {
let tx = tx.clone();
Closure::<dyn FnMut(JsValue)>::new(move |raw| {
let Event { event, id, payload } =
serde_wasm_bindgen::from_value::<Event<DragDropPayload>>(raw).unwrap();
let _ = tx.unbounded_send(Event {
event,
id,
payload: DragDropEvent::Drop(payload),
});
})
};
let unlisten = event::inner::listen(
event::DRAG_DROP,
&closure,
serde_wasm_bindgen::to_value(&event::Options {
target: event::EventTarget::Window(self.label.clone()),
})?,
)
.await?;
closure.forget();
let unlisten_drop = js_sys::Function::from(unlisten);
let closure = {
let tx = tx.clone();
Closure::<dyn FnMut(JsValue)>::new(move |raw| {
let Event { event, id, payload } =
serde_wasm_bindgen::from_value::<Event<DragOverPayload>>(raw).unwrap();
let _ = tx.unbounded_send(Event {
event,
id,
payload: DragDropEvent::Over(payload),
});
})
};
let unlisten = event::inner::listen(
event::DRAG_OVER,
&closure,
serde_wasm_bindgen::to_value(&event::Options {
target: event::EventTarget::Window(self.label.clone()),
})?,
)
.await?;
closure.forget();
let unlisten_over = js_sys::Function::from(unlisten);
let closure = {
let tx = tx.clone();
Closure::<dyn FnMut(JsValue)>::new(move |raw| {
let Event { event, id, .. } =
serde_wasm_bindgen::from_value::<Event<()>>(raw).unwrap();
let _ = tx.unbounded_send(Event {
event,
id,
payload: DragDropEvent::Leave,
});
})
};
let unlisten = event::inner::listen(
event::DRAG_LEAVE,
&closure,
serde_wasm_bindgen::to_value(&event::Options {
target: event::EventTarget::Window(self.label.clone()),
})?,
)
.await?;
closure.forget();
let unlisten_leave = js_sys::Function::from(unlisten);
Ok(DragDropListen {
rx,
unlisten_enter,
unlisten_drop,
unlisten_over,
unlisten_leave,
})
}
}
#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Monitor {
/// Human-readable name of the monitor.
name: Option<String>,
/// The monitor's resolution.
size: dpi::PhysicalSize,
/// the Top-left corner position of the monitor relative to the larger full screen area.
position: dpi::PhysicalPosition,
/// The scale factor that can be used to map physical pixels to logical pixels.
scale_factor: f64,
}
impl Monitor {
pub fn name(&self) -> &Option<String> {
&self.name
}
pub fn size(&self) -> &dpi::PhysicalSize {
&self.size
}
pub fn position(&self) -> &dpi::PhysicalPosition {
&self.position
}
pub fn scale_factor(&self) -> f64 {
self.scale_factor
}
}
pub fn get_current() -> Window {
let WindowLabel { label } = serde_wasm_bindgen::from_value(inner::get_current()).unwrap();
Window::new(label)
}
pub fn get_all() -> Vec<Window> {
js_sys::try_iter(&inner::get_all())
.unwrap()
.unwrap()
.into_iter()
.map(|value| {
let WindowLabel { label } = serde_wasm_bindgen::from_value(value.unwrap()).unwrap();
Window::new(label)
})
.collect()
}
/// # Returns
/// Monitor on which the window currently resides.
pub async fn current_monitor() -> Option<Monitor> {
let value = inner::current_monitor().await;
if value.is_null() {
None
} else {
Some(serde_wasm_bindgen::from_value(value).unwrap())
}
}
/// # Returns
/// Primary monitor of the system.
pub async fn primary_monitor() -> Option<Monitor> {
let value = inner::primary_monitor().await;
if value.is_null() {
None
} else {
Some(serde_wasm_bindgen::from_value(value).unwrap())
}
}
/// # Returns
/// Monitor that contains the given point.
pub async fn monitor_from_point(x: isize, y: isize) -> Option<Monitor> {
let value = inner::monitor_from_point(x, y).await;
if value.is_null() {
None
} else {
Some(serde_wasm_bindgen::from_value(value).unwrap())
}
}
/// # Returns
/// All the monitors available on the system.
pub async fn available_monitors() -> Vec<Monitor> {
let value = inner::available_monitors().await;
js_sys::try_iter(&value)
.unwrap()
.unwrap()
.into_iter()
.map(|value| serde_wasm_bindgen::from_value(value.unwrap()).unwrap())
.collect()
}
// TODO: Issue with cursorPosition in Tauri.
// See: https://github.com/tauri-apps/tauri/issues/10340
///// Get the cursor position relative to the top-left hand corner of the desktop.
/////
///// Note that the top-left hand corner of the desktop is not necessarily the same as the screen.
///// If the user uses a desktop with multiple monitors,
///// the top-left hand corner of the desktop is the top-left hand corner of the main monitor on Windows and macOS
///// or the top-left of the leftmost monitor on X11.
/////
///// The coordinates can be negative if the top-left hand corner of the window is outside of the visible screen region.
//pub async fn cursor_position() -> PhysicalPosition {
// serde_wasm_bindgen::from_value(inner::cursor_position().await).unwrap()
//}
mod inner {
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
#[wasm_bindgen(module = "/src/window.js")]
extern "C" {
#[wasm_bindgen(js_name = "getCurrent")]
pub fn get_current() -> JsValue;
#[wasm_bindgen(js_name = "getAll")]
pub fn get_all() -> JsValue;
#[wasm_bindgen(js_name = "currentMonitor")]
pub async fn current_monitor() -> JsValue;
#[wasm_bindgen(js_name = "primaryMonitor")]
pub async fn primary_monitor() -> JsValue;
#[wasm_bindgen(js_name = "monitorFromPoint")]
pub async fn monitor_from_point(x: isize, y: isize) -> JsValue;
#[wasm_bindgen(js_name = "availableMonitors")]
pub async fn available_monitors() -> JsValue;
#[wasm_bindgen(js_name = "cursorPosition")]
pub async fn cursor_position() -> JsValue;
}
}
// partial mocks
/*
pub enum Theme {
Light,
Dark,
}
/// Attention type to request on a window.
pub enum UserAttentionType {
/// # Platform-specific
/// - **macOS:** Bounces the dock icon until the application is in focus.
/// - **Windows:** Flashes both the window and the taskbar button until the application is in focus.
Critical = 1,
/// # Platform-specific
/// - **macOS:** Bounces the dock icon once.
/// - **Windows:** Flashes the taskbar button until the application is in focus.
Informational,
}
impl Window {
/// The scale factor that can be used to map physical pixels to logical pixels.
pub async fn scale_factor(&self) -> dpi::ScaleFactor {
todo!();
}
/// The position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop.
pub async fn inner_position(&self) -> dpi::PhysicalPosition {
todo!();
}
/// The position of the top-left hand corner of the window relative to the top-left hand corner of the desktop.
pub async fn outer_position(&self) -> dpi::PhysicalPosition {
todo!();
}
/// The physical size of the window's client area.
/// The client area is the content of the window, excluding the title bar and borders.
pub async fn inner_size(&self) -> dpi::PhysicalSize {
todo!();
}
/// The physical size of the entire window.
/// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead.
pub async fn outer_size(&self) -> dpi::PhysicalSize {
todo!();
}
/// Gets the window's current fullscreen state.
pub async fn is_fullscreen(&self) -> bool {
todo!();
}
/// Gets the window's current minimized state.
pub async fn is_minimized(&self) -> bool {
todo!();
}
/// Gets the window's current maximized state.
pub async fn is_maximized(&self) -> bool {
todo!();
}
/// Gets the window's current focused state.
pub async fn is_focused(&self) -> bool {
todo!();
}
/// Gets the window's current decorated state.
pub async fn is_decorated(&self) -> bool {
todo!();
}
/// Gets the window's current resizable state.
pub async fn is_resizable(&self) -> bool {
todo!();
}
/// Gets the window's current visible state.
pub async fn is_visible(&self) -> bool {
todo!();
}
/// Gets the window's current title.
pub async fn title(&self) -> String {
todo!();
}
/// Gets the window's current theme.
pub async fn theme(&self) -> Option<Theme> {
todo!();
}
}
/// # Platform-specific
/// - **Linux / iOS / Android:** Unsupported.
impl Window {
/// Gets the window's native maximize button state.
///
/// # Platform-specific
/// - **Linux / iOS / Android:** Unsupported.
pub async fn is_maximizable(&self) -> bool {
todo!();
}
/// Gets the window's native minimize button state.
///
/// # Platform-specific
/// - **Linux / iOS / Android:** Unsupported.
pub async fn is_minimizable(&self) -> bool {
todo!();
}
/// Gets the window's native close button state.
///
/// # Platform-specific
/// - **Linux / iOS / Android:** Unsupported.
pub async fn is_closable(&self) -> bool {
todo!();
}
}
impl Window {
/// Centers the window.
///
/// # Returns
/// The success or failure of the operation.
pub async fn center(&self) -> Result<(), ()> {
todo!();
}
/// Requests user attention to the window, this has no effect if the application
/// is already focused. How requesting for user attention manifests is platform dependent,
/// see `UserAttentionType` for details.
///
/// Providing `null` will unset the request for user attention. Unsetting the request for
/// user attention might not be done automatically by the WM when the window receives input.
///
/// # Platform-specific
/// - **macOS:** `null` has no effect.
/// - **Linux:** Urgency levels have the same effect.
///
/// # Returns
/// The success or failure of the operation.
pub async fn request_user_attention() -> Result<(), ()> {
todo!();
}
/// Requests user attention to the window, this has no effect if the application
/// is already focused. How requesting for user attention manifests is platform dependent,
/// see `UserAttentionType` for details.
///
/// Providing `null` will unset the request for user attention. Unsetting the request for
/// user attention might not be done automatically by the WM when the window receives input.
///
/// # Platform-specific
/// - **macOS:** `null` has no effect.
/// - **Linux:** Urgency levels have the same effect.
///
/// # Returns
/// The success or failure of the operation.
pub async fn request_user_attention_with_type(
request_type: UserAttentionType,
) -> Result<(), ()> {
todo!();
}
/// Updates the window resizable flag.
///
/// # Returns
/// The success or failure of the operation.
pub async fn set_resizable(&self, resizable: bool) -> Result<(), ()> {
todo!();
}
}
*/