use crate::{WindowIcon, WindowId};
use std::fmt;
macro_rules! generational_id {
($name:ident, $label:literal) => {
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct $name {
index: u32,
generation: u32,
}
impl $name {
#[must_use]
pub const fn from_parts(index: u32, generation: u32) -> Self {
Self { index, generation }
}
#[must_use]
pub const fn index(self) -> u32 {
self.index
}
#[must_use]
pub const fn generation(self) -> u32 {
self.generation
}
}
impl fmt::Debug for $name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
concat!($label, "Id({}, {})"),
self.index, self.generation
)
}
}
};
}
generational_id!(TrayItemId, "TrayItem");
generational_id!(NotificationId, "Notification");
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ApplicationShellFeature {
TrayOrStatusItem,
Notifications,
NotificationActions,
NotificationUpdate,
NotificationDismiss,
TaskbarProgress,
ApplicationBadge,
TaskbarOverlayIcon,
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum ApplicationShellError {
#[error("{0:?} is not supported")]
Unsupported(ApplicationShellFeature),
#[error("application-shell permission denied")]
PermissionDenied,
#[error("platform configuration is required: {0}")]
PlatformConfigurationRequired(String),
#[error("application-shell resource is stale")]
StaleResource,
#[error("application has stopped")]
ApplicationStopped,
#[error("native shell operation failed: {0}")]
NativeFailure(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrayItemPresentation {
pub icon: Option<WindowIcon>,
pub tooltip: Option<String>,
pub title: Option<String>,
pub visible: bool,
}
impl TrayItemPresentation {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn icon(mut self, icon: WindowIcon) -> Self {
self.icon = Some(icon);
self
}
#[must_use]
pub fn tooltip(mut self, tooltip: impl Into<String>) -> Self {
self.tooltip = Some(tooltip.into());
self
}
#[must_use]
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
#[must_use]
pub fn visible(mut self, visible: bool) -> Self {
self.visible = visible;
self
}
}
impl Default for TrayItemPresentation {
fn default() -> Self {
Self {
icon: None,
tooltip: None,
title: None,
visible: true,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NotificationActionId(String);
impl NotificationActionId {
#[must_use]
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for NotificationActionId {
fn from(value: &str) -> Self {
Self::new(value)
}
}
impl From<String> for NotificationActionId {
fn from(value: String) -> Self {
Self::new(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NotificationAction {
pub id: NotificationActionId,
pub label: String,
}
impl NotificationAction {
#[must_use]
pub fn new(id: impl Into<NotificationActionId>, label: impl Into<String>) -> Self {
Self {
id: id.into(),
label: label.into(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NotificationPresentation {
pub title: String,
pub body: String,
pub icon: Option<WindowIcon>,
pub actions: Vec<NotificationAction>,
}
impl NotificationPresentation {
#[must_use]
pub fn new(title: impl Into<String>, body: impl Into<String>) -> Self {
Self {
title: title.into(),
body: body.into(),
icon: None,
actions: Vec::new(),
}
}
#[must_use]
pub fn icon(mut self, icon: WindowIcon) -> Self {
self.icon = Some(icon);
self
}
#[must_use]
pub fn action(mut self, action: NotificationAction) -> Self {
self.actions.push(action);
self
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum TaskbarProgress {
#[default]
None,
Indeterminate,
Normal(f64),
Paused(f64),
Error(f64),
}
impl TaskbarProgress {
pub fn normal(value: f64) -> Result<Self, TaskbarProgressError> {
validate_progress(value).map(Self::Normal)
}
pub fn paused(value: f64) -> Result<Self, TaskbarProgressError> {
validate_progress(value).map(Self::Paused)
}
pub fn error(value: f64) -> Result<Self, TaskbarProgressError> {
validate_progress(value).map(Self::Error)
}
#[must_use]
pub const fn fraction(self) -> Option<f64> {
match self {
Self::Normal(value) | Self::Paused(value) | Self::Error(value) => Some(value),
Self::None | Self::Indeterminate => None,
}
}
}
fn validate_progress(value: f64) -> Result<f64, TaskbarProgressError> {
(value.is_finite() && (0.0..=1.0).contains(&value))
.then_some(value)
.ok_or(TaskbarProgressError)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[error("taskbar progress must be a finite fraction between 0 and 1")]
pub struct TaskbarProgressError;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum ApplicationBadge {
#[default]
None,
Count(u64),
Text(String),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct TaskbarDockState {
pub window_id: Option<WindowId>,
pub progress: TaskbarProgress,
pub badge: ApplicationBadge,
pub overlay_icon: Option<WindowIcon>,
}
impl TaskbarDockState {
#[must_use]
pub fn for_window(window_id: WindowId) -> Self {
Self {
window_id: Some(window_id),
..Self::default()
}
}
#[must_use]
pub fn progress(mut self, progress: TaskbarProgress) -> Self {
self.progress = progress;
self
}
#[must_use]
pub fn badge(mut self, badge: ApplicationBadge) -> Self {
self.badge = badge;
self
}
#[must_use]
pub fn overlay_icon(mut self, icon: WindowIcon) -> Self {
self.overlay_icon = Some(icon);
self
}
#[must_use]
pub fn required_feature(&self) -> ApplicationShellFeature {
if self.overlay_icon.is_some() {
ApplicationShellFeature::TaskbarOverlayIcon
} else if self.badge != ApplicationBadge::None {
ApplicationShellFeature::ApplicationBadge
} else {
ApplicationShellFeature::TaskbarProgress
}
}
}