diff --git a/Cargo.toml b/Cargo.toml index 9da2b6a..c9ae3c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,11 @@ wasm-bindgen-test = "0.3.42" all-features = true [features] -all = ["core", "event"] +all = ["core", "dpi", "event", "window"] core = [] +dpi = [] event = ["dep:futures"] +window = ["dpi", "event"] [workspace] members = ["examples/test", "examples/test/src-tauri"] diff --git a/README.md b/README.md index c2a276b..7d196bd 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ All modules are gated by accordingly named Cargo features. It is recommended you - **all**: Enables all modules. - **core**: Enables the `core` module. (Only `invoke` and `convertFileSrc` currently implemented.) - **event**: Enables the `event` module. +- **window**: Enables the `windows` module. (~20% implemented) ## Are we Tauri yet? @@ -60,7 +61,7 @@ These API bindings are not completely on-par with `@tauri-apps/api` yet, but her - [ ] `app` - [x] `core` (partial implementation) -- [ ] `dpi` +- [x] `dpi` - [x] `event` - [ ] `image` - [ ] `menu` @@ -69,7 +70,7 @@ These API bindings are not completely on-par with `@tauri-apps/api` yet, but her - [ ] `tray` - [ ] `webview` - [ ] `webviewWindow` -- [ ] `window` +- [x] `window` (partial implementation) The current API also very closely mirrors the JS API even though that might not be the most ergonomic choice, ideas for improving the API with quality-of-life features beyond the regular JS API interface are very welcome. diff --git a/src/core.rs b/src/core.rs index a5b598c..b2d8555 100644 --- a/src/core.rs +++ b/src/core.rs @@ -43,7 +43,6 @@ mod inner { #[wasm_bindgen(module = "/src/core.js")] extern "C" { - #[wasm_bindgen] pub async fn invoke(cmd: &str, args: JsValue) -> JsValue; #[wasm_bindgen(js_name = "invoke", catch)] pub async fn invoke_result(cmd: &str, args: JsValue) -> Result; diff --git a/src/dpi.rs b/src/dpi.rs new file mode 100644 index 0000000..3697ed2 --- /dev/null +++ b/src/dpi.rs @@ -0,0 +1,110 @@ +use serde::Deserialize; + +pub type ScaleFactor = f64; +pub type PixelCount = isize; + +#[derive(Debug)] +pub enum Kind { + Logical, + Physical, +} + +/// A size represented in logical pixels. +#[derive(Deserialize, Clone, Debug)] +pub struct LogicalSize { + width: PixelCount, + height: PixelCount, +} + +impl LogicalSize { + pub fn kind() -> Kind { + Kind::Logical + } + + pub fn width(&self) -> PixelCount { + self.width + } + + pub fn height(&self) -> PixelCount { + self.height + } +} + +/// A size represented in physical pixels. +#[derive(Deserialize, Clone, Debug)] +pub struct PhysicalSize { + width: PixelCount, + height: PixelCount, +} + +impl PhysicalSize { + pub fn kind() -> Kind { + Kind::Physical + } + + pub fn width(&self) -> PixelCount { + self.width + } + + pub fn height(&self) -> PixelCount { + self.height + } + + /// Converts the physical size to a logical one. + pub fn as_logical(&self, scale_factor: ScaleFactor) -> LogicalSize { + LogicalSize { + width: (self.width as f64 / scale_factor) as PixelCount, + height: (self.height as f64 / scale_factor) as PixelCount, + } + } +} + +/// A position represented in logical pixels. +#[derive(Deserialize, Clone, Debug)] +pub struct LogicalPosition { + x: PixelCount, + y: PixelCount, +} + +impl LogicalPosition { + pub fn kind() -> Kind { + Kind::Logical + } + + pub fn x(&self) -> PixelCount { + self.x + } + + pub fn y(&self) -> PixelCount { + self.y + } +} + +/// A position represented in physical pixels. +#[derive(Deserialize, Clone, Debug)] +pub struct PhysicalPosition { + x: PixelCount, + y: PixelCount, +} + +impl PhysicalPosition { + pub fn kind() -> Kind { + Kind::Physical + } + + pub fn x(&self) -> PixelCount { + self.x + } + + pub fn y(&self) -> PixelCount { + self.y + } + + /// Converts the physical position to a logical one. + pub fn as_logical(&self, scale_factor: ScaleFactor) -> LogicalPosition { + LogicalPosition { + x: (self.x as f64 / scale_factor) as PixelCount, + y: (self.y as f64 / scale_factor) as PixelCount, + } + } +} diff --git a/src/event.js b/src/event.js index ec0dae9..d7d89ee 100644 --- a/src/event.js +++ b/src/event.js @@ -55,28 +55,9 @@ async function once(event, handler, options) { ) } -// tauri/tooling/api/src/event.ts -var TauriEvent = /* @__PURE__ */ ((TauriEvent2) => { - TauriEvent2["WINDOW_RESIZED"] = 'tauri://resize'; - TauriEvent2["WINDOW_MOVED"] = 'tauri://move'; - TauriEvent2["WINDOW_CLOSE_REQUESTED"] = 'tauri://close-requested'; - TauriEvent2["WINDOW_DESTROYED"] = 'tauri://destroyed'; - TauriEvent2["WINDOW_FOCUS"] = 'tauri://focus'; - TauriEvent2["WINDOW_BLUR"] = 'tauri://blur'; - TauriEvent2["WINDOW_SCALE_FACTOR_CHANGED"] = 'tauri://scale-change'; - TauriEvent2["WINDOW_THEME_CHANGED"] = 'tauri://theme-changed'; - TauriEvent2["WINDOW_CREATED"] = 'tauri://window-created'; - TauriEvent2["WEBVIEW_CREATED"] = 'tauri://webview-created'; - TauriEvent2["DRAG"] = 'tauri://drag'; - TauriEvent2["DROP"] = 'tauri://drop'; - TauriEvent2["DROP_OVER"] = 'tauri://drop-over'; - TauriEvent2["DROP_CANCELLED"] = 'tauri://drag-cancelled'; - return TauriEvent2; -})(TauriEvent || {}); export { - TauriEvent, emit, emitTo, listen, - once + once, }; diff --git a/src/event.rs b/src/event.rs index 0a16aa4..9884ad2 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,5 +1,4 @@ //! The event system allows you to emit events to the backend and listen to events from it. - use futures::{ channel::{mpsc, oneshot}, Future, FutureExt, Stream, StreamExt, @@ -8,13 +7,28 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; use wasm_bindgen::{prelude::Closure, JsValue}; +pub const WINDOW_RESIZED: &str = "tauri://resize"; +pub const WINDOW_MOVED: &str = "tauri://move"; +pub const WINDOW_CLOSE_REQUESTED: &str = "tauri://close-requested"; +pub const WINDOW_DESTROYED: &str = "tauri://destroyed"; +pub const WINDOW_FOCUS: &str = "tauri://focus"; +pub const WINDOW_BLUR: &str = "tauri://blur"; +pub const WINDOW_SCALE_FACTOR_CHANGED: &str = "tauri://scale-change"; +pub const WINDOW_THEME_CHANGED: &str = "tauri://theme-changed"; +pub const WINDOW_CREATED: &str = "tauri://window-created"; +pub const WEBVIEW_CREATED: &str = "tauri://webview-created"; +pub const DRAG: &str = "tauri://drag"; +pub const DROP: &str = "tauri://drop"; +pub const DROP_OVER: &str = "tauri://drop-over"; +pub const DROP_CANCELLED: &str = "tauri://drag-cancelled"; + #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Event { /// Event name pub event: String, /// Event identifier used to unlisten - pub id: f32, + pub id: isize, /// Event payload pub payload: T, } @@ -31,8 +45,8 @@ pub enum EventTarget { } #[derive(Debug, Clone, PartialEq, Serialize)] -struct Options { - target: EventTarget, +pub(crate) struct Options { + pub target: EventTarget, } /// Emits an event to the backend. @@ -334,7 +348,7 @@ impl Future for Once { } } -mod inner { +pub(crate) mod inner { use wasm_bindgen::{ prelude::{wasm_bindgen, Closure}, JsValue, diff --git a/src/lib.rs b/src/lib.rs index 0aca1c5..13f36a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,6 +92,12 @@ pub mod event; #[cfg(feature = "core")] pub mod core; +#[cfg(feature = "dpi")] +pub mod dpi; + +#[cfg(feature = "window")] +pub mod window; + pub use error::Error; pub(crate) type Result = std::result::Result; diff --git a/src/window.js b/src/window.js new file mode 100644 index 0000000..8f68839 --- /dev/null +++ b/src/window.js @@ -0,0 +1,38 @@ +// tauri/tooling/api/src/core.ts +async function invoke(cmd, args = {}) { + // NB: `options` ignored as not used here. + return window.__TAURI_INTERNALS__.invoke(cmd, args) +} + +// tauri/tooling/api/src/window.ts +function getCurrent() { + return window.__TAURI_INTERNALS__.metadata.currentWindow +} +function getAll() { + return window.__TAURI_INTERNALS__.metadata.windows +} +async function currentMonitor() { + return invoke('plugin:window|current_monitor') +} +async function primaryMonitor() { + return invoke('plugin:window|primary_monitor') +} +async function monitorFromPoint(x, y) { + return invoke('plugin:window|monitor_from_point', { x, y }) +} +async function availableMonitors() { + return invoke('plugin:window|available_monitors') +} +async function cursorPosition() { + return invoke('plugin:window|cursor_position') +} + +export { + getCurrent, + getAll, + currentMonitor, + primaryMonitor, + monitorFromPoint, + availableMonitors, + cursorPosition, +} diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..2f09f1b --- /dev/null +++ b/src/window.rs @@ -0,0 +1,751 @@ +//! 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 SenderVec for Vec> +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 { + pub rx: mpsc::UnboundedReceiver, +} + +impl Stream for Listen { + type Item = T; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.rx.poll_next_unpin(cx) + } +} + +pub(crate) struct DragDropListen { + pub rx: mpsc::UnboundedReceiver>, + pub unlisten_drag: js_sys::Function, + pub unlisten_drop: js_sys::Function, + pub unlisten_drag_over: js_sys::Function, + pub unlisten_cancel: js_sys::Function, +} + +impl Drop for DragDropListen { + fn drop(&mut self) { + log::debug!("Calling unlisten for listen callback"); + self.unlisten_drag + .call0(&wasm_bindgen::JsValue::NULL) + .unwrap(); + self.unlisten_drop + .call0(&wasm_bindgen::JsValue::NULL) + .unwrap(); + self.unlisten_drag_over + .call0(&wasm_bindgen::JsValue::NULL) + .unwrap(); + self.unlisten_cancel + .call0(&wasm_bindgen::JsValue::NULL) + .unwrap(); + } +} + +impl Stream for DragDropListen { + type Item = Event; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.rx.poll_next_unpin(cx) + } +} + +#[derive(Deserialize)] +struct WindowLabel { + label: String, +} + +#[derive(Deserialize)] +pub enum DragDropEvent { + Dragged(DragDropPayload), + DragOver(DragOverPayload), + Dropped(DragDropPayload), + Cancelled, +} + +#[derive(Deserialize)] +pub struct DragDropPayload { + paths: Vec, + position: dpi::PhysicalPosition, +} + +impl DragDropPayload { + pub fn paths(&self) -> &Vec { + &self.paths + } + + pub fn position(&self) -> &dpi::PhysicalPosition { + &self.position + } +} + +#[derive(Deserialize)] +pub struct DragOverPayload { + position: dpi::PhysicalPosition, +} + +impl DragOverPayload { + pub fn position(&self) -> &dpi::PhysicalPosition { + &self.position + } +} + +pub struct Window { + label: String, + listeners: HashMap>, +} + +impl Window { + /// Create a new Window. + /// + /// # Arguments + /// + `label`: Unique window label. Must be alphanumberic: `a-zA-Z-/:_`. + pub fn new(label: impl Into) -> Self { + Self { + label: label.into(), + listeners: HashMap::new(), + } + } + + /// Gets the Window associated with the given label. + pub fn get_by_label(label: impl AsRef) -> Option { + 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 { + get_all() + } +} + +impl Window { + pub fn label(&self) -> &String { + &self.label + } + + fn handle_tauri_event(&mut self, event: String) -> Option>> + where + T: DeserializeOwned + 'static, + { + if LOCAL_TAURI_EVENTS.contains(&event.as_str()) { + let (tx, rx) = mpsc::unbounded::>(); + let entry = self + .listeners + .entry(event) + .or_insert(Box::new(Vec::>>::new())); + + let senders = entry + .as_any_mut() + .downcast_mut::>>>() + .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( + &mut self, + event: impl Into, + ) -> crate::Result>> + 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, handler: Closure) { + 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( + &self, + event: impl Into, + 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::>>>() + .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( + &self, + target: &event::EventTarget, + event: impl Into, + 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::>>>() + .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. + /// + /// You need to call unlisten if your handler goes out of scope e.g. the component is unmounted + /// unlisten(); + /// + /// # 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( + &mut self, + ) -> crate::Result>> { + let (tx, rx) = mpsc::unbounded::>(); + + let closure = { + let tx = tx.clone(); + Closure::::new(move |raw| { + let Event { event, id, payload } = + serde_wasm_bindgen::from_value::>(raw).unwrap(); + let _ = tx.unbounded_send(Event { + event, + id, + payload: DragDropEvent::Dragged(payload), + }); + }) + }; + let unlisten = event::inner::listen( + event::DRAG, + &closure, + serde_wasm_bindgen::to_value(&event::Options { + target: event::EventTarget::Window(self.label.clone()), + })?, + ) + .await?; + closure.forget(); + + let unlisten_drag = js_sys::Function::from(unlisten); + + let closure = { + let tx = tx.clone(); + Closure::::new(move |raw| { + let Event { event, id, payload } = + serde_wasm_bindgen::from_value::>(raw).unwrap(); + let _ = tx.unbounded_send(Event { + event, + id, + payload: DragDropEvent::Dropped(payload), + }); + }) + }; + let unlisten = event::inner::listen( + event::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::::new(move |raw| { + let Event { event, id, payload } = + serde_wasm_bindgen::from_value::>(raw).unwrap(); + let _ = tx.unbounded_send(Event { + event, + id, + payload: DragDropEvent::DragOver(payload), + }); + }) + }; + let unlisten = event::inner::listen( + event::DROP_OVER, + &closure, + serde_wasm_bindgen::to_value(&event::Options { + target: event::EventTarget::Window(self.label.clone()), + })?, + ) + .await?; + closure.forget(); + + let unlisten_drag_over = js_sys::Function::from(unlisten); + + let closure = { + let tx = tx.clone(); + Closure::::new(move |raw| { + let Event { event, id, .. } = + serde_wasm_bindgen::from_value::>(raw).unwrap(); + let _ = tx.unbounded_send(Event { + event, + id, + payload: DragDropEvent::Cancelled, + }); + }) + }; + let unlisten = event::inner::listen( + event::DROP_CANCELLED, + &closure, + serde_wasm_bindgen::to_value(&event::Options { + target: event::EventTarget::Window(self.label.clone()), + })?, + ) + .await?; + closure.forget(); + + let unlisten_cancel = js_sys::Function::from(unlisten); + + Ok(DragDropListen { + rx, + unlisten_drag, + unlisten_drop, + unlisten_drag_over, + unlisten_cancel, + }) + } +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Monitor { + /// Human-readable name of the monitor. + name: Option, + + /// 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 { + &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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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!(); + } +} +*/