use crate::{
http::{Request as HttpRequest, Response as HttpResponse},
menu::{Menu, MenuEntry, MenuHash, MenuId},
webview::{WebviewAttributes, WebviewIpcHandler},
Dispatch, Runtime, UserEvent, WindowBuilder,
};
use serde::{Deserialize, Deserializer, Serialize};
use tauri_utils::{config::WindowConfig, Theme};
use url::Url;
use std::{
collections::{HashMap, HashSet},
hash::{Hash, Hasher},
path::PathBuf,
sync::{mpsc::Sender, Arc, Mutex},
};
type UriSchemeProtocol =
dyn Fn(&HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> + Send + Sync + 'static;
type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync;
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(Vec<PathBuf>),
Dropped(Vec<PathBuf>),
Cancelled,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MenuEvent {
pub menu_item_id: u16,
}
fn get_menu_ids(map: &mut HashMap<MenuHash, MenuId>, menu: &Menu) {
for item in &menu.items {
match item {
MenuEntry::CustomItem(c) => {
map.insert(c.id, c.id_str.clone());
}
MenuEntry::Submenu(s) => get_menu_ids(map, &s.inner),
_ => {}
}
}
}
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum CursorIcon {
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,
})
}
}
impl Default for CursorIcon {
fn default() -> Self {
CursorIcon::Default
}
}
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 http_scheme: bool,
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u32>>>>,
pub navigation_handler: Option<Box<dyn Fn(Url) -> bool + Send>>,
pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
pub url: String,
}
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 mut menu_ids = HashMap::new();
if let Some(menu) = window_builder.get_menu() {
get_menu_ids(&mut menu_ids, menu);
}
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,
menu_ids: Arc::new(Mutex::new(menu_ids)),
js_event_listeners: Default::default(),
navigation_handler: Default::default(),
web_resource_request_handler: Default::default(),
url: "tauri://localhost".to_string(),
http_scheme: false,
})
}
}
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 mut menu_ids = HashMap::new();
if let Some(menu) = window_builder.get_menu() {
get_menu_ids(&mut menu_ids, menu);
}
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,
menu_ids: Arc::new(Mutex::new(menu_ids)),
js_event_listeners: Default::default(),
navigation_handler: Default::default(),
web_resource_request_handler: Default::default(),
url: "tauri://localhost".to_string(),
http_scheme: false,
})
}
}
#[must_use]
pub fn set_menu(mut self, menu: Menu) -> Self {
let mut menu_ids = HashMap::new();
get_menu_ids(&mut menu_ids, &menu);
*self.menu_ids.lock().unwrap() = menu_ids;
self.window_builder = self.window_builder.menu(menu);
self
}
pub fn register_uri_scheme_protocol<
N: Into<String>,
H: Fn(&HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> + 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(move |data| (protocol)(data)));
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct JsEventListenerKey {
pub window_label: Option<String>,
pub event: String,
}
#[derive(Debug)]
pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
pub label: String,
pub dispatcher: R::Dispatcher,
pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u32>>>>,
}
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
fn clone(&self) -> Self {
Self {
label: self.label.clone(),
dispatcher: self.dispatcher.clone(),
menu_ids: self.menu_ids.clone(),
js_event_listeners: self.js_event_listeners.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)
}
}