use std::{
collections::{HashMap, HashSet},
fmt,
path::PathBuf,
sync::{Arc, Mutex, MutexGuard},
};
use serde::Serialize;
use tauri_runtime::{
dpi::{PhysicalPosition, PhysicalSize},
window::WindowBuilder,
window::{DetachedWindow, DragDropEvent, PendingWindow},
};
use crate::{
app::GlobalWindowEventListener, event::EventName, image::Image, sealed::ManagerBase, AppHandle,
EventLoopMessage, EventTarget, Manager, Runtime, Scopes, Window, WindowEvent,
};
use super::EmitPayload;
const WINDOW_RESIZED_EVENT: EventName<&str> = EventName::from_str("tauri://resize");
const WINDOW_MOVED_EVENT: EventName<&str> = EventName::from_str("tauri://move");
const WINDOW_CLOSE_REQUESTED_EVENT: EventName<&str> =
EventName::from_str("tauri://close-requested");
const WINDOW_DESTROYED_EVENT: EventName<&str> = EventName::from_str("tauri://destroyed");
const WINDOW_FOCUS_EVENT: EventName<&str> = EventName::from_str("tauri://focus");
const WINDOW_BLUR_EVENT: EventName<&str> = EventName::from_str("tauri://blur");
const WINDOW_SCALE_FACTOR_CHANGED_EVENT: EventName<&str> =
EventName::from_str("tauri://scale-change");
const WINDOW_THEME_CHANGED: EventName<&str> = EventName::from_str("tauri://theme-changed");
pub(crate) const DRAG_ENTER_EVENT: EventName<&str> = EventName::from_str("tauri://drag-enter");
pub(crate) const DRAG_OVER_EVENT: EventName<&str> = EventName::from_str("tauri://drag-over");
pub(crate) const DRAG_DROP_EVENT: EventName<&str> = EventName::from_str("tauri://drag-drop");
pub(crate) const DRAG_LEAVE_EVENT: EventName<&str> = EventName::from_str("tauri://drag-leave");
pub struct WindowManager<R: Runtime> {
pub windows: Mutex<HashMap<String, Window<R>>>,
pub default_icon: Option<Image<'static>>,
pub event_listeners: Arc<Vec<GlobalWindowEventListener<R>>>,
}
impl<R: Runtime> fmt::Debug for WindowManager<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WindowManager")
.field("default_window_icon", &self.default_icon)
.finish()
}
}
impl<R: Runtime> WindowManager<R> {
pub(crate) fn windows_lock(&self) -> MutexGuard<'_, HashMap<String, Window<R>>> {
self.windows.lock().expect("poisoned window manager")
}
pub fn prepare_window(
&self,
mut pending: PendingWindow<EventLoopMessage, R>,
) -> crate::Result<PendingWindow<EventLoopMessage, R>> {
if self.windows_lock().contains_key(&pending.label) {
return Err(crate::Error::WindowLabelAlreadyExists(pending.label));
}
if !pending.window_builder.has_icon() {
if let Some(default_window_icon) = self.default_icon.clone() {
pending.window_builder = pending.window_builder.icon(default_window_icon.into())?;
}
}
Ok(pending)
}
pub(crate) fn attach_window(
&self,
app_handle: AppHandle<R>,
window: DetachedWindow<EventLoopMessage, R>,
#[cfg(desktop)] menu: Option<crate::window::WindowMenu<R>>,
) -> Window<R> {
let window = Window::new(
app_handle.manager.clone(),
window,
app_handle,
#[cfg(desktop)]
menu,
);
let window_ = window.clone();
let window_event_listeners = self.event_listeners.clone();
window.on_window_event(move |event| {
let _ = on_window_event(&window_, event);
for handler in window_event_listeners.iter() {
handler(&window_, event);
}
});
{
self
.windows_lock()
.insert(window.label().to_string(), window.clone());
}
let manager = window.manager.clone();
let window_ = window.clone();
let _ = window.run_on_main_thread(move || {
manager
.plugins
.lock()
.expect("poisoned plugin store")
.window_created(window_);
});
window
}
pub fn labels(&self) -> HashSet<String> {
self.windows_lock().keys().cloned().collect()
}
}
impl<R: Runtime> Window<R> {
fn emit_to_window<S: Serialize>(&self, event: EventName<&str>, payload: &S) -> crate::Result<()> {
let window_label = self.label();
let payload = EmitPayload::Serialize(payload);
self
.manager()
.emit_filter(event, payload, |target| match target {
EventTarget::Window { label } | EventTarget::WebviewWindow { label } => {
label == window_label
}
_ => false,
})
}
fn has_js_listener(&self, event: EventName<&str>) -> bool {
let window_label = self.label();
let listeners = self.manager().listeners();
listeners.has_js_listener(event, |target| match target {
EventTarget::Window { label } | EventTarget::WebviewWindow { label } => label == window_label,
_ => false,
})
}
}
#[derive(Serialize, Clone)]
pub(crate) struct DragDropPayload<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub paths: Option<&'a Vec<PathBuf>>,
pub position: &'a PhysicalPosition<f64>,
}
fn on_window_event<R: Runtime>(window: &Window<R>, event: &WindowEvent) -> crate::Result<()> {
match event {
WindowEvent::Resized(size) => window.emit_to_window(WINDOW_RESIZED_EVENT, size)?,
WindowEvent::Moved(position) => window.emit_to_window(WINDOW_MOVED_EVENT, position)?,
WindowEvent::CloseRequested { api } => {
if window.has_js_listener(WINDOW_CLOSE_REQUESTED_EVENT) {
api.prevent_close();
}
window.emit_to_window(WINDOW_CLOSE_REQUESTED_EVENT, &())?;
}
WindowEvent::Destroyed => {
window.emit_to_window(WINDOW_DESTROYED_EVENT, &())?;
}
WindowEvent::Focused(focused) => window.emit_to_window(
if *focused {
WINDOW_FOCUS_EVENT
} else {
WINDOW_BLUR_EVENT
},
&(),
)?,
WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
..
} => window.emit_to_window(
WINDOW_SCALE_FACTOR_CHANGED_EVENT,
&ScaleFactorChanged {
scale_factor: *scale_factor,
size: *new_inner_size,
},
)?,
WindowEvent::DragDrop(event) => match event {
DragDropEvent::Enter { paths, position } => {
let payload = DragDropPayload {
paths: Some(paths),
position,
};
if window.is_webview_window() {
window.manager().emit_to(
EventTarget::labeled(window.label()),
DRAG_ENTER_EVENT,
EmitPayload::Serialize(&payload),
)?
} else {
window.emit_to_window(DRAG_ENTER_EVENT, &payload)?
}
}
DragDropEvent::Over { position } => {
let payload = DragDropPayload {
position,
paths: None,
};
if window.is_webview_window() {
window.manager().emit_to(
EventTarget::labeled(window.label()),
DRAG_OVER_EVENT,
EmitPayload::Serialize(&payload),
)?
} else {
window.emit_to_window(DRAG_OVER_EVENT, &payload)?
}
}
DragDropEvent::Drop { paths, position } => {
let scopes = window.state::<Scopes>();
for path in paths {
if path.is_file() {
let _ = scopes.allow_file(path);
} else {
let _ = scopes.allow_directory(path, true);
}
}
let payload = DragDropPayload {
paths: Some(paths),
position,
};
if window.is_webview_window() {
window.manager().emit_to(
EventTarget::labeled(window.label()),
DRAG_DROP_EVENT,
EmitPayload::Serialize(&payload),
)?
} else {
window.emit_to_window(DRAG_DROP_EVENT, &payload)?
}
}
DragDropEvent::Leave => {
if window.is_webview_window() {
window.manager().emit_to(
EventTarget::labeled(window.label()),
DRAG_LEAVE_EVENT,
EmitPayload::Serialize(&()),
)?
} else {
window.emit_to_window(DRAG_LEAVE_EVENT, &())?
}
}
_ => unimplemented!(),
},
WindowEvent::ThemeChanged(theme) => window.emit_to_window(WINDOW_THEME_CHANGED, &theme)?,
}
Ok(())
}
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct ScaleFactorChanged {
scale_factor: f64,
size: PhysicalSize<u32>,
}