Added initial window module functionality. (#57)
This commit is contained in:
parent
125136ae79
commit
9c3cd94275
9 changed files with 931 additions and 29 deletions
|
@ -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"]
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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<JsValue, JsValue>;
|
||||
|
|
110
src/dpi.rs
Normal file
110
src/dpi.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
21
src/event.js
21
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,
|
||||
};
|
||||
|
|
24
src/event.rs
24
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<T> {
|
||||
/// 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<T> Future for Once<T> {
|
|||
}
|
||||
}
|
||||
|
||||
mod inner {
|
||||
pub(crate) mod inner {
|
||||
use wasm_bindgen::{
|
||||
prelude::{wasm_bindgen, Closure},
|
||||
JsValue,
|
||||
|
|
|
@ -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<T> = std::result::Result<T, Error>;
|
||||
|
||||
|
|
38
src/window.js
Normal file
38
src/window.js
Normal file
|
@ -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,
|
||||
}
|
751
src/window.rs
Normal file
751
src/window.rs
Normal file
|
@ -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<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_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<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)]
|
||||
pub enum DragDropEvent {
|
||||
Dragged(DragDropPayload),
|
||||
DragOver(DragOverPayload),
|
||||
Dropped(DragDropPayload),
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
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)]
|
||||
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.
|
||||
///
|
||||
/// 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<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::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::<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::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::<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::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::<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::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<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!();
|
||||
}
|
||||
}
|
||||
*/
|
Loading…
Add table
Reference in a new issue