#![cfg_attr(doc_cfg, feature(doc_cfg))]
use serde::Deserialize;
use std::{fmt::Debug, path::PathBuf, sync::mpsc::Sender};
use uuid::Uuid;
#[cfg(windows)]
use winapi::shared::windef::HWND;
pub mod menu;
pub mod monitor;
pub mod webview;
pub mod window;
use monitor::Monitor;
use webview::WindowBuilder;
use window::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
DetachedWindow, PendingWindow, WindowEvent,
};
#[cfg(feature = "system-tray")]
#[non_exhaustive]
#[derive(Debug)]
pub struct SystemTray {
pub icon: Option<Icon>,
pub menu: Option<menu::SystemTrayMenu>,
#[cfg(target_os = "macos")]
pub icon_as_template: bool,
}
#[cfg(feature = "system-tray")]
impl Default for SystemTray {
fn default() -> Self {
Self {
icon: None,
menu: None,
#[cfg(target_os = "macos")]
icon_as_template: false,
}
}
}
#[cfg(feature = "system-tray")]
impl SystemTray {
pub fn new() -> Self {
Default::default()
}
pub fn menu(&self) -> Option<&menu::SystemTrayMenu> {
self.menu.as_ref()
}
pub fn with_icon(mut self, icon: Icon) -> Self {
self.icon.replace(icon);
self
}
#[cfg(target_os = "macos")]
pub fn with_icon_as_template(mut self, is_template: bool) -> Self {
self.icon_as_template = is_template;
self
}
pub fn with_menu(mut self, menu: menu::SystemTrayMenu) -> Self {
self.menu.replace(menu);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[serde(tag = "type")]
pub enum UserAttentionType {
Critical,
Informational,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("failed to create webview: {0}")]
CreateWebview(Box<dyn std::error::Error + Send>),
#[error("failed to create window")]
CreateWindow,
#[error("failed to send message to the webview")]
FailedToSendMessage,
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[cfg(feature = "system-tray")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
#[error("error encountered during tray setup: {0}")]
SystemTray(Box<dyn std::error::Error + Send>),
#[error("invalid icon: {0}")]
InvalidIcon(Box<dyn std::error::Error + Send>),
#[error("failed to get monitor")]
FailedToGetMonitor,
#[error(transparent)]
GlobalShortcut(Box<dyn std::error::Error + Send>),
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Icon {
File(PathBuf),
Raw(Vec<u8>),
}
impl Icon {
#[cfg(target_os = "linux")]
pub fn into_tray_icon(self) -> PathBuf {
match self {
Icon::File(path) => path,
Icon::Raw(_) => {
panic!("linux requires the system menu icon to be a file path, not bytes.")
}
}
}
#[cfg(not(target_os = "linux"))]
pub fn into_tray_icon(self) -> Vec<u8> {
match self {
Icon::Raw(bytes) => bytes,
Icon::File(_) => {
panic!("non-linux system menu icons must be bytes, not a file path.")
}
}
}
}
#[non_exhaustive]
pub enum RunEvent {
Exit,
ExitRequested {
window_label: String,
tx: Sender<ExitRequestedEventAction>,
},
CloseRequested {
label: String,
signal_tx: Sender<bool>,
},
WindowClose(String),
}
#[derive(Debug)]
pub enum ExitRequestedEventAction {
Prevent,
}
#[derive(Debug)]
pub enum SystemTrayEvent {
MenuItemClick(u16),
LeftClick {
position: PhysicalPosition<f64>,
size: PhysicalSize<f64>,
},
RightClick {
position: PhysicalPosition<f64>,
size: PhysicalSize<f64>,
},
DoubleClick {
position: PhysicalPosition<f64>,
size: PhysicalSize<f64>,
},
}
#[derive(Debug, Clone, Default)]
pub struct RunIteration {
pub window_count: usize,
}
pub trait RuntimeHandle: Debug + Send + Sized + Clone + 'static {
type Runtime: Runtime<Handle = Self>;
fn create_window(
&self,
pending: PendingWindow<Self::Runtime>,
) -> crate::Result<DetachedWindow<Self::Runtime>>;
#[cfg(all(windows, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(all(windows, feature = "system-tray"))))]
fn remove_system_tray(&self) -> crate::Result<()>;
}
pub trait GlobalShortcutManager: Debug {
fn is_registered(&self, accelerator: &str) -> crate::Result<bool>;
fn register<F: Fn() + Send + 'static>(
&mut self,
accelerator: &str,
handler: F,
) -> crate::Result<()>;
fn unregister_all(&mut self) -> crate::Result<()>;
fn unregister(&mut self, accelerator: &str) -> crate::Result<()>;
}
pub trait ClipboardManager: Debug {
fn write_text<T: Into<String>>(&mut self, text: T) -> Result<()>;
fn read_text(&self) -> Result<Option<String>>;
}
pub trait Runtime: Sized + 'static {
type Dispatcher: Dispatch<Runtime = Self>;
type Handle: RuntimeHandle<Runtime = Self>;
type GlobalShortcutManager: GlobalShortcutManager + Clone + Send;
type ClipboardManager: ClipboardManager + Clone + Send;
#[cfg(feature = "system-tray")]
type TrayHandler: menu::TrayHandle + Clone + Send;
fn new() -> crate::Result<Self>;
fn handle(&self) -> Self::Handle;
fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager;
fn clipboard_manager(&self) -> Self::ClipboardManager;
fn create_window(&self, pending: PendingWindow<Self>) -> crate::Result<DetachedWindow<Self>>;
#[cfg(feature = "system-tray")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
fn system_tray(&self, system_tray: SystemTray) -> crate::Result<Self::TrayHandler>;
#[cfg(feature = "system-tray")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
fn on_system_tray_event<F: Fn(&SystemTrayEvent) + Send + 'static>(&mut self, f: F) -> Uuid;
#[cfg(any(target_os = "windows", target_os = "macos"))]
fn run_iteration<F: Fn(RunEvent) + 'static>(&mut self, callback: F) -> RunIteration;
fn run<F: Fn(RunEvent) + 'static>(self, callback: F);
}
pub trait Dispatch: Debug + Clone + Send + Sized + 'static {
type Runtime: Runtime;
type WindowBuilder: WindowBuilder + Clone;
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()>;
fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> Uuid;
fn on_menu_event<F: Fn(&window::MenuEvent) + Send + 'static>(&self, f: F) -> Uuid;
fn scale_factor(&self) -> crate::Result<f64>;
fn inner_position(&self) -> crate::Result<PhysicalPosition<i32>>;
fn outer_position(&self) -> crate::Result<PhysicalPosition<i32>>;
fn inner_size(&self) -> crate::Result<PhysicalSize<u32>>;
fn outer_size(&self) -> crate::Result<PhysicalSize<u32>>;
fn is_fullscreen(&self) -> crate::Result<bool>;
fn is_maximized(&self) -> crate::Result<bool>;
fn is_decorated(&self) -> crate::Result<bool>;
fn is_resizable(&self) -> crate::Result<bool>;
fn is_visible(&self) -> crate::Result<bool>;
fn is_menu_visible(&self) -> crate::Result<bool>;
fn current_monitor(&self) -> crate::Result<Option<Monitor>>;
fn primary_monitor(&self) -> crate::Result<Option<Monitor>>;
fn available_monitors(&self) -> crate::Result<Vec<Monitor>>;
#[cfg(windows)]
fn hwnd(&self) -> crate::Result<HWND>;
#[cfg(target_os = "macos")]
fn ns_window(&self) -> crate::Result<*mut std::ffi::c_void>;
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
fn gtk_window(&self) -> crate::Result<gtk::ApplicationWindow>;
fn center(&self) -> crate::Result<()>;
fn print(&self) -> crate::Result<()>;
fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> crate::Result<()>;
fn create_window(
&mut self,
pending: PendingWindow<Self::Runtime>,
) -> crate::Result<DetachedWindow<Self::Runtime>>;
fn set_resizable(&self, resizable: bool) -> crate::Result<()>;
fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()>;
fn maximize(&self) -> crate::Result<()>;
fn unmaximize(&self) -> crate::Result<()>;
fn minimize(&self) -> crate::Result<()>;
fn unminimize(&self) -> crate::Result<()>;
fn show_menu(&self) -> crate::Result<()>;
fn hide_menu(&self) -> crate::Result<()>;
fn show(&self) -> crate::Result<()>;
fn hide(&self) -> crate::Result<()>;
fn close(&self) -> crate::Result<()>;
fn set_decorations(&self, decorations: bool) -> crate::Result<()>;
fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()>;
fn set_size(&self, size: Size) -> crate::Result<()>;
fn set_min_size(&self, size: Option<Size>) -> crate::Result<()>;
fn set_max_size(&self, size: Option<Size>) -> crate::Result<()>;
fn set_position(&self, position: Position) -> crate::Result<()>;
fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()>;
fn set_focus(&self) -> crate::Result<()>;
fn set_icon(&self, icon: Icon) -> crate::Result<()>;
fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()>;
fn start_dragging(&self) -> crate::Result<()>;
fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()>;
fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> crate::Result<()>;
}