use crate::{
webview::{WebviewAttributes, WebviewIpcHandler},
Dispatch, Runtime, UserEvent, WindowBuilder,
};
use serde::{Deserialize, Deserializer};
use tauri_utils::{config::WindowConfig, Theme};
use url::Url;
use std::{
borrow::Cow,
collections::HashMap,
hash::{Hash, Hasher},
marker::PhantomData,
path::PathBuf,
sync::mpsc::Sender,
};
use self::dpi::PhysicalPosition;
type UriSchemeProtocol = dyn Fn(http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
+ Send
+ Sync
+ 'static;
type WebResourceRequestHandler =
dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
type NavigationHandler = dyn Fn(&Url) -> bool + Send;
type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PageLoadEvent {
Started,
Finished,
}
pub mod dpi;
#[derive(Debug, Clone)]
pub enum WindowEvent {
Resized(dpi::PhysicalSize<u32>),
Moved(dpi::PhysicalPosition<i32>),
CloseRequested {
signal_tx: Sender<bool>,
},
Destroyed,
Focused(bool),
ScaleFactorChanged {
scale_factor: f64,
new_inner_size: dpi::PhysicalSize<u32>,
},
FileDrop(FileDropEvent),
ThemeChanged(Theme),
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum FileDropEvent {
Hovered {
paths: Vec<PathBuf>,
position: PhysicalPosition<f64>,
},
Dropped {
paths: Vec<PathBuf>,
position: PhysicalPosition<f64>,
},
Cancelled,
}
#[non_exhaustive]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
pub enum CursorIcon {
#[default]
Default,
Crosshair,
Hand,
Arrow,
Move,
Text,
Wait,
Help,
Progress,
NotAllowed,
ContextMenu,
Cell,
VerticalText,
Alias,
Copy,
NoDrop,
Grab,
Grabbing,
AllScroll,
ZoomIn,
ZoomOut,
EResize,
NResize,
NeResize,
NwResize,
SResize,
SeResize,
SwResize,
WResize,
EwResize,
NsResize,
NeswResize,
NwseResize,
ColResize,
RowResize,
}
impl<'de> Deserialize<'de> for CursorIcon {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(match s.to_lowercase().as_str() {
"default" => CursorIcon::Default,
"crosshair" => CursorIcon::Crosshair,
"hand" => CursorIcon::Hand,
"arrow" => CursorIcon::Arrow,
"move" => CursorIcon::Move,
"text" => CursorIcon::Text,
"wait" => CursorIcon::Wait,
"help" => CursorIcon::Help,
"progress" => CursorIcon::Progress,
"notallowed" => CursorIcon::NotAllowed,
"contextmenu" => CursorIcon::ContextMenu,
"cell" => CursorIcon::Cell,
"verticaltext" => CursorIcon::VerticalText,
"alias" => CursorIcon::Alias,
"copy" => CursorIcon::Copy,
"nodrop" => CursorIcon::NoDrop,
"grab" => CursorIcon::Grab,
"grabbing" => CursorIcon::Grabbing,
"allscroll" => CursorIcon::AllScroll,
"zoomin" => CursorIcon::ZoomIn,
"zoomout" => CursorIcon::ZoomOut,
"eresize" => CursorIcon::EResize,
"nresize" => CursorIcon::NResize,
"neresize" => CursorIcon::NeResize,
"nwresize" => CursorIcon::NwResize,
"sresize" => CursorIcon::SResize,
"seresize" => CursorIcon::SeResize,
"swresize" => CursorIcon::SwResize,
"wresize" => CursorIcon::WResize,
"ewresize" => CursorIcon::EwResize,
"nsresize" => CursorIcon::NsResize,
"neswresize" => CursorIcon::NeswResize,
"nwseresize" => CursorIcon::NwseResize,
"colresize" => CursorIcon::ColResize,
"rowresize" => CursorIcon::RowResize,
_ => CursorIcon::Default,
})
}
}
#[cfg(target_os = "android")]
pub struct CreationContext<'a, 'b> {
pub env: &'a mut jni::JNIEnv<'b>,
pub activity: &'a jni::objects::JObject<'b>,
pub webview: &'a jni::objects::JObject<'b>,
}
pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
pub label: String,
pub window_builder: <R::Dispatcher as Dispatch<T>>::WindowBuilder,
pub webview_attributes: WebviewAttributes,
pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
pub navigation_handler: Option<Box<NavigationHandler>>,
pub url: String,
#[cfg(target_os = "android")]
#[allow(clippy::type_complexity)]
pub on_webview_created:
Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
}
pub fn is_label_valid(label: &str) -> bool {
label
.chars()
.all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_')
}
pub fn assert_label_is_valid(label: &str) {
assert!(
is_label_valid(label),
"Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`."
);
}
impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
pub fn new(
window_builder: <R::Dispatcher as Dispatch<T>>::WindowBuilder,
webview_attributes: WebviewAttributes,
label: impl Into<String>,
) -> crate::Result<Self> {
let label = label.into();
if !is_label_valid(&label) {
Err(crate::Error::InvalidWindowLabel)
} else {
Ok(Self {
window_builder,
webview_attributes,
uri_scheme_protocols: Default::default(),
label,
ipc_handler: None,
navigation_handler: None,
url: "tauri://localhost".to_string(),
#[cfg(target_os = "android")]
on_webview_created: None,
web_resource_request_handler: None,
on_page_load_handler: None,
})
}
}
pub fn with_config(
window_config: WindowConfig,
webview_attributes: WebviewAttributes,
label: impl Into<String>,
) -> crate::Result<Self> {
let window_builder =
<<R::Dispatcher as Dispatch<T>>::WindowBuilder>::with_config(window_config);
let label = label.into();
if !is_label_valid(&label) {
Err(crate::Error::InvalidWindowLabel)
} else {
Ok(Self {
window_builder,
webview_attributes,
uri_scheme_protocols: Default::default(),
label,
ipc_handler: None,
navigation_handler: None,
url: "tauri://localhost".to_string(),
#[cfg(target_os = "android")]
on_webview_created: None,
web_resource_request_handler: None,
on_page_load_handler: None,
})
}
}
pub fn register_uri_scheme_protocol<
N: Into<String>,
H: Fn(http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
+ Send
+ Sync
+ 'static,
>(
&mut self,
uri_scheme: N,
protocol: H,
) {
let uri_scheme = uri_scheme.into();
self
.uri_scheme_protocols
.insert(uri_scheme, Box::new(protocol));
}
#[cfg(target_os = "android")]
pub fn on_webview_created<
F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
>(
mut self,
f: F,
) -> Self {
self.on_webview_created.replace(Box::new(f));
self
}
}
#[derive(Debug)]
pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
pub label: String,
pub dispatcher: R::Dispatcher,
}
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
fn clone(&self) -> Self {
Self {
label: self.label.clone(),
dispatcher: self.dispatcher.clone(),
}
}
}
impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWindow<T, R> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.label.hash(state)
}
}
impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWindow<T, R> {}
impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWindow<T, R> {
fn eq(&self, other: &Self) -> bool {
self.label.eq(&other.label)
}
}
pub struct RawWindow<'a> {
#[cfg(windows)]
pub hwnd: isize,
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
pub gtk_window: &'a gtk::ApplicationWindow,
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
pub default_vbox: Option<&'a gtk::Box>,
pub _marker: &'a PhantomData<()>,
}