1use crate::{WindowIcon, WindowId};
9use std::fmt;
10
11macro_rules! generational_id {
12 ($name:ident, $label:literal) => {
13 #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
14 pub struct $name {
15 index: u32,
16 generation: u32,
17 }
18
19 impl $name {
20 #[must_use]
21 pub const fn from_parts(index: u32, generation: u32) -> Self {
22 Self { index, generation }
23 }
24
25 #[must_use]
26 pub const fn index(self) -> u32 {
27 self.index
28 }
29
30 #[must_use]
31 pub const fn generation(self) -> u32 {
32 self.generation
33 }
34 }
35
36 impl fmt::Debug for $name {
37 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(
39 formatter,
40 concat!($label, "Id({}, {})"),
41 self.index, self.generation
42 )
43 }
44 }
45 };
46}
47
48generational_id!(TrayItemId, "TrayItem");
49generational_id!(NotificationId, "Notification");
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
53pub enum ApplicationShellFeature {
54 TrayOrStatusItem,
55 Notifications,
56 NotificationActions,
57 NotificationUpdate,
58 NotificationDismiss,
59 TaskbarProgress,
60 ApplicationBadge,
61 TaskbarOverlayIcon,
62}
63
64#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
66pub enum ApplicationShellError {
67 #[error("{0:?} is not supported")]
68 Unsupported(ApplicationShellFeature),
69 #[error("application-shell permission denied")]
70 PermissionDenied,
71 #[error("platform configuration is required: {0}")]
72 PlatformConfigurationRequired(String),
73 #[error("application-shell resource is stale")]
74 StaleResource,
75 #[error("application has stopped")]
76 ApplicationStopped,
77 #[error("native shell operation failed: {0}")]
78 NativeFailure(String),
79}
80
81#[derive(Clone, Debug, PartialEq, Eq)]
83pub struct TrayItemPresentation {
84 pub icon: Option<WindowIcon>,
85 pub tooltip: Option<String>,
86 pub title: Option<String>,
87 pub visible: bool,
88}
89
90impl TrayItemPresentation {
91 #[must_use]
92 pub fn new() -> Self {
93 Self::default()
94 }
95
96 #[must_use]
97 pub fn icon(mut self, icon: WindowIcon) -> Self {
98 self.icon = Some(icon);
99 self
100 }
101
102 #[must_use]
103 pub fn tooltip(mut self, tooltip: impl Into<String>) -> Self {
104 self.tooltip = Some(tooltip.into());
105 self
106 }
107
108 #[must_use]
109 pub fn title(mut self, title: impl Into<String>) -> Self {
110 self.title = Some(title.into());
111 self
112 }
113
114 #[must_use]
115 pub fn visible(mut self, visible: bool) -> Self {
116 self.visible = visible;
117 self
118 }
119}
120
121impl Default for TrayItemPresentation {
122 fn default() -> Self {
123 Self {
124 icon: None,
125 tooltip: None,
126 title: None,
127 visible: true,
128 }
129 }
130}
131
132#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
134pub struct NotificationActionId(String);
135
136impl NotificationActionId {
137 #[must_use]
138 pub fn new(id: impl Into<String>) -> Self {
139 Self(id.into())
140 }
141
142 #[must_use]
143 pub fn as_str(&self) -> &str {
144 &self.0
145 }
146}
147
148impl From<&str> for NotificationActionId {
149 fn from(value: &str) -> Self {
150 Self::new(value)
151 }
152}
153
154impl From<String> for NotificationActionId {
155 fn from(value: String) -> Self {
156 Self::new(value)
157 }
158}
159
160#[derive(Clone, Debug, PartialEq, Eq)]
162pub struct NotificationAction {
163 pub id: NotificationActionId,
164 pub label: String,
165}
166
167impl NotificationAction {
168 #[must_use]
169 pub fn new(id: impl Into<NotificationActionId>, label: impl Into<String>) -> Self {
170 Self {
171 id: id.into(),
172 label: label.into(),
173 }
174 }
175}
176
177#[derive(Clone, Debug, PartialEq, Eq)]
179pub struct NotificationPresentation {
180 pub title: String,
181 pub body: String,
182 pub icon: Option<WindowIcon>,
183 pub actions: Vec<NotificationAction>,
184}
185
186impl NotificationPresentation {
187 #[must_use]
188 pub fn new(title: impl Into<String>, body: impl Into<String>) -> Self {
189 Self {
190 title: title.into(),
191 body: body.into(),
192 icon: None,
193 actions: Vec::new(),
194 }
195 }
196
197 #[must_use]
198 pub fn icon(mut self, icon: WindowIcon) -> Self {
199 self.icon = Some(icon);
200 self
201 }
202
203 #[must_use]
204 pub fn action(mut self, action: NotificationAction) -> Self {
205 self.actions.push(action);
206 self
207 }
208}
209
210#[derive(Clone, Copy, Debug, Default, PartialEq)]
212pub enum TaskbarProgress {
213 #[default]
214 None,
215 Indeterminate,
216 Normal(f64),
217 Paused(f64),
218 Error(f64),
219}
220
221impl TaskbarProgress {
222 pub fn normal(value: f64) -> Result<Self, TaskbarProgressError> {
223 validate_progress(value).map(Self::Normal)
224 }
225
226 pub fn paused(value: f64) -> Result<Self, TaskbarProgressError> {
227 validate_progress(value).map(Self::Paused)
228 }
229
230 pub fn error(value: f64) -> Result<Self, TaskbarProgressError> {
231 validate_progress(value).map(Self::Error)
232 }
233
234 #[must_use]
235 pub const fn fraction(self) -> Option<f64> {
236 match self {
237 Self::Normal(value) | Self::Paused(value) | Self::Error(value) => Some(value),
238 Self::None | Self::Indeterminate => None,
239 }
240 }
241}
242
243fn validate_progress(value: f64) -> Result<f64, TaskbarProgressError> {
244 (value.is_finite() && (0.0..=1.0).contains(&value))
245 .then_some(value)
246 .ok_or(TaskbarProgressError)
247}
248
249#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
250#[error("taskbar progress must be a finite fraction between 0 and 1")]
251pub struct TaskbarProgressError;
252
253#[derive(Clone, Debug, Default, PartialEq, Eq)]
256pub enum ApplicationBadge {
257 #[default]
258 None,
259 Count(u64),
260 Text(String),
261}
262
263#[derive(Clone, Debug, Default, PartialEq)]
267pub struct TaskbarDockState {
268 pub window_id: Option<WindowId>,
269 pub progress: TaskbarProgress,
270 pub badge: ApplicationBadge,
271 pub overlay_icon: Option<WindowIcon>,
272}
273
274impl TaskbarDockState {
275 #[must_use]
276 pub fn for_window(window_id: WindowId) -> Self {
277 Self {
278 window_id: Some(window_id),
279 ..Self::default()
280 }
281 }
282
283 #[must_use]
284 pub fn progress(mut self, progress: TaskbarProgress) -> Self {
285 self.progress = progress;
286 self
287 }
288
289 #[must_use]
290 pub fn badge(mut self, badge: ApplicationBadge) -> Self {
291 self.badge = badge;
292 self
293 }
294
295 #[must_use]
296 pub fn overlay_icon(mut self, icon: WindowIcon) -> Self {
297 self.overlay_icon = Some(icon);
298 self
299 }
300
301 #[must_use]
303 pub fn required_feature(&self) -> ApplicationShellFeature {
304 if self.overlay_icon.is_some() {
305 ApplicationShellFeature::TaskbarOverlayIcon
306 } else if self.badge != ApplicationBadge::None {
307 ApplicationShellFeature::ApplicationBadge
308 } else {
309 ApplicationShellFeature::TaskbarProgress
310 }
311 }
312}