tray_icon/lib.rs
1// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5#![allow(clippy::uninlined_format_args)]
6
7//! tray-icon lets you create tray icons for desktop applications.
8//!
9//! # Platforms supported:
10//!
11//! - Windows
12//! - macOS
13//! - Linux (gtk Only)
14//!
15//! # Platform-specific notes:
16//!
17//! - On Windows and Linux, an event loop must be running on the thread, on Windows, a win32 event loop and on Linux, a gtk event loop. It doesn't need to be the main thread but you have to create the tray icon on the same thread as the event loop.
18//! - On macOS, an event loop must be running on the main thread so you also need to create the tray icon on the main thread. You must make sure that the event loop is already running and not just created before creating a TrayIcon to prevent issues with fullscreen apps. In Winit for example the earliest you can create icons is on [`StartCause::Init`](https://docs.rs/winit/latest/winit/event/enum.StartCause.html#variant.Init).
19//!
20//! # Dependencies (Linux Only)
21//!
22//! On Linux, `gtk`, `libxdo` is used to make the predfined `Copy`, `Cut`, `Paste` and `SelectAll` menu items work and `libappindicator` or `libayatnat-appindicator` are used to create the tray icon, so make sure to install them on your system.
23//!
24//! #### Arch Linux / Manjaro:
25//!
26//! ```sh
27//! pacman -S gtk3 xdotool libappindicator-gtk3 #or libayatana-appindicator
28//! ```
29//!
30//! #### Debian / Ubuntu:
31//!
32//! ```sh
33//! sudo apt install libgtk-3-dev libxdo-dev libappindicator3-dev #or libayatana-appindicator3-dev
34//! ```
35//!
36//! # Examples
37//!
38//! #### Create a tray icon without a menu.
39//!
40//! ```no_run
41//! use tray_icon::{TrayIconBuilder, Icon};
42//!
43//! # let icon = Icon::from_rgba(Vec::new(), 0, 0).unwrap();
44//! let tray_icon = TrayIconBuilder::new()
45//! .with_tooltip("system-tray - tray icon library!")
46//! .with_icon(icon)
47//! .build()
48//! .unwrap();
49//! ```
50//!
51//! #### Create a tray icon with a menu.
52//!
53//! ```no_run
54//! use tray_icon::{TrayIconBuilder, menu::Menu,Icon};
55//!
56//! # let icon = Icon::from_rgba(Vec::new(), 0, 0).unwrap();
57//! let tray_menu = Menu::new();
58//! let tray_icon = TrayIconBuilder::new()
59//! .with_menu(Box::new(tray_menu))
60//! .with_tooltip("system-tray - tray icon library!")
61//! .with_icon(icon)
62//! .build()
63//! .unwrap();
64//! ```
65//!
66//! # Processing tray events
67//!
68//! You can use [`TrayIconEvent::receiver`] to get a reference to the [`TrayIconEventReceiver`]
69//! which you can use to listen to events when a click happens on the tray icon
70//! ```no_run
71//! use tray_icon::TrayIconEvent;
72//!
73//! if let Ok(event) = TrayIconEvent::receiver().try_recv() {
74//! println!("{:?}", event);
75//! }
76//! ```
77//!
78//! You can also listen for the menu events using [`MenuEvent::receiver`](crate::menu::MenuEvent::receiver) to get events for the tray context menu.
79//!
80//! ```no_run
81//! use tray_icon::{TrayIconEvent, menu::MenuEvent};
82//!
83//! if let Ok(event) = TrayIconEvent::receiver().try_recv() {
84//! println!("tray event: {:?}", event);
85//! }
86//!
87//! if let Ok(event) = MenuEvent::receiver().try_recv() {
88//! println!("menu event: {:?}", event);
89//! }
90//! ```
91//!
92//! ### Note for [winit] or [tao] users:
93//!
94//! You should use [`TrayIconEvent::set_event_handler`] and forward
95//! the tray icon events to the event loop by using [`EventLoopProxy`]
96//! so that the event loop is awakened on each tray icon event.
97//! Same can be done for menu events using [`MenuEvent::set_event_handler`].
98//!
99//! ```no_run
100//! # use winit::event_loop::EventLoop;
101//! enum UserEvent {
102//! TrayIconEvent(tray_icon::TrayIconEvent),
103//! MenuEvent(tray_icon::menu::MenuEvent)
104//! }
105//!
106//! let event_loop = EventLoop::<UserEvent>::with_user_event().build().unwrap();
107//!
108//! let proxy = event_loop.create_proxy();
109//! tray_icon::TrayIconEvent::set_event_handler(Some(move |event| {
110//! proxy.send_event(UserEvent::TrayIconEvent(event));
111//! }));
112//!
113//! let proxy = event_loop.create_proxy();
114//! tray_icon::menu::MenuEvent::set_event_handler(Some(move |event| {
115//! proxy.send_event(UserEvent::MenuEvent(event));
116//! }));
117//! ```
118//!
119//! [`EventLoopProxy`]: https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopProxy.html
120//! [winit]: https://docs.rs/winit
121//! [tao]: https://docs.rs/tao
122
123use std::{
124 cell::RefCell,
125 path::{Path, PathBuf},
126 rc::Rc,
127};
128
129use counter::Counter;
130use crossbeam_channel::{unbounded, Receiver, Sender};
131use once_cell::sync::{Lazy, OnceCell};
132
133mod counter;
134mod error;
135mod icon;
136mod platform_impl;
137mod tray_icon_id;
138
139pub use self::error::*;
140pub use self::icon::{BadIcon, Icon};
141pub use self::tray_icon_id::TrayIconId;
142
143/// Re-export of [muda](::muda) crate and used for tray context menu.
144pub mod menu {
145 pub use muda::*;
146}
147pub use muda::dpi;
148
149static COUNTER: Counter = Counter::new();
150
151/// Attributes to use when creating a tray icon.
152pub struct TrayIconAttributes {
153 /// Tray icon tooltip
154 ///
155 /// ## Platform-specific:
156 ///
157 /// - **Linux:** Unsupported.
158 pub tooltip: Option<String>,
159
160 /// Tray menu
161 ///
162 /// ## Platform-specific:
163 ///
164 /// - **Linux**: once a menu is set, it cannot be removed.
165 pub menu: Option<Box<dyn menu::ContextMenu>>,
166
167 /// Tray icon
168 ///
169 /// ## Platform-specific:
170 ///
171 /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
172 /// Setting an empty [`Menu`](crate::menu::Menu) is enough.
173 pub icon: Option<Icon>,
174
175 /// Tray icon temp dir path. **Linux only**.
176 pub temp_dir_path: Option<PathBuf>,
177
178 /// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
179 pub icon_is_template: bool,
180
181 /// Whether to show the tray menu on left click or not, default is `true`.
182 ///
183 /// ## Platform-specific:
184 ///
185 /// - **Linux:** Unsupported.
186 pub menu_on_left_click: bool,
187
188 /// Tray icon title.
189 ///
190 /// ## Platform-specific
191 ///
192 /// - **Linux:** The title will not be shown unless there is an icon
193 /// as well. The title is useful for numerical and other frequently
194 /// updated information. In general, it shouldn't be shown unless a
195 /// user requests it as it can take up a significant amount of space
196 /// on the user's panel. This may not be shown in all visualizations.
197 /// - **Windows:** Unsupported.
198 pub title: Option<String>,
199}
200
201impl Default for TrayIconAttributes {
202 fn default() -> Self {
203 Self {
204 tooltip: None,
205 menu: None,
206 icon: None,
207 temp_dir_path: None,
208 icon_is_template: false,
209 menu_on_left_click: true,
210 title: None,
211 }
212 }
213}
214
215/// [`TrayIcon`] builder struct and associated methods.
216#[derive(Default)]
217pub struct TrayIconBuilder {
218 id: TrayIconId,
219 attrs: TrayIconAttributes,
220}
221
222impl TrayIconBuilder {
223 /// Creates a new [`TrayIconBuilder`] with default [`TrayIconAttributes`].
224 ///
225 /// See [`TrayIcon::new`] for more info.
226 pub fn new() -> Self {
227 Self {
228 id: TrayIconId(COUNTER.next().to_string()),
229 attrs: TrayIconAttributes::default(),
230 }
231 }
232
233 /// Sets the unique id to build the tray icon with.
234 pub fn with_id<I: Into<TrayIconId>>(mut self, id: I) -> Self {
235 self.id = id.into();
236 self
237 }
238
239 /// Set the a menu for this tray icon.
240 ///
241 /// ## Platform-specific:
242 ///
243 /// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content.
244 pub fn with_menu(mut self, menu: Box<dyn menu::ContextMenu>) -> Self {
245 self.attrs.menu = Some(menu);
246 self
247 }
248
249 /// Set an icon for this tray icon.
250 ///
251 /// ## Platform-specific:
252 ///
253 /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
254 /// Setting an empty [`Menu`](crate::menu::Menu) is enough.
255 pub fn with_icon(mut self, icon: Icon) -> Self {
256 self.attrs.icon = Some(icon);
257 self
258 }
259
260 /// Set a tooltip for this tray icon.
261 ///
262 /// ## Platform-specific:
263 ///
264 /// - **Linux:** Unsupported.
265 pub fn with_tooltip<S: AsRef<str>>(mut self, s: S) -> Self {
266 self.attrs.tooltip = Some(s.as_ref().to_string());
267 self
268 }
269
270 /// Set the tray icon title.
271 ///
272 /// ## Platform-specific
273 ///
274 /// - **Linux:** The title will not be shown unless there is an icon
275 /// as well. The title is useful for numerical and other frequently
276 /// updated information. In general, it shouldn't be shown unless a
277 /// user requests it as it can take up a significant amount of space
278 /// on the user's panel. This may not be shown in all visualizations.
279 /// - **Windows:** Unsupported.
280 pub fn with_title<S: AsRef<str>>(mut self, title: S) -> Self {
281 self.attrs.title.replace(title.as_ref().to_string());
282 self
283 }
284
285 /// Set tray icon temp dir path. **Linux only**.
286 ///
287 /// On Linux, we need to write the icon to the disk and usually it will
288 /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
289 pub fn with_temp_dir_path<P: AsRef<Path>>(mut self, s: P) -> Self {
290 self.attrs.temp_dir_path = Some(s.as_ref().to_path_buf());
291 self
292 }
293
294 /// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
295 pub fn with_icon_as_template(mut self, is_template: bool) -> Self {
296 self.attrs.icon_is_template = is_template;
297 self
298 }
299
300 /// Whether to show the tray menu on left click or not, default is `true`.
301 ///
302 /// ## Platform-specific:
303 ///
304 /// - **Linux:** Unsupported.
305 pub fn with_menu_on_left_click(mut self, enable: bool) -> Self {
306 self.attrs.menu_on_left_click = enable;
307 self
308 }
309
310 /// Access the unique id that will be assigned to the tray icon
311 /// this builder will create.
312 pub fn id(&self) -> &TrayIconId {
313 &self.id
314 }
315
316 /// Builds and adds a new [`TrayIcon`] to the system tray.
317 pub fn build(self) -> Result<TrayIcon> {
318 TrayIcon::with_id(self.id, self.attrs)
319 }
320}
321
322/// Tray icon struct and associated methods.
323///
324/// This type is reference-counted and the icon is removed when the last instance is dropped.
325#[derive(Clone)]
326pub struct TrayIcon {
327 id: TrayIconId,
328 tray: Rc<RefCell<platform_impl::TrayIcon>>,
329}
330
331impl TrayIcon {
332 /// Builds and adds a new tray icon to the system tray.
333 ///
334 /// ## Platform-specific:
335 ///
336 /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
337 /// Setting an empty [`Menu`](crate::menu::Menu) is enough.
338 pub fn new(attrs: TrayIconAttributes) -> Result<Self> {
339 let id = TrayIconId(COUNTER.next().to_string());
340 Ok(Self {
341 tray: Rc::new(RefCell::new(platform_impl::TrayIcon::new(
342 id.clone(),
343 attrs,
344 )?)),
345 id,
346 })
347 }
348
349 /// Builds and adds a new tray icon to the system tray with the specified Id.
350 ///
351 /// See [`TrayIcon::new`] for more info.
352 pub fn with_id<I: Into<TrayIconId>>(id: I, attrs: TrayIconAttributes) -> Result<Self> {
353 let id = id.into();
354 Ok(Self {
355 tray: Rc::new(RefCell::new(platform_impl::TrayIcon::new(
356 id.clone(),
357 attrs,
358 )?)),
359 id,
360 })
361 }
362
363 /// Returns the id associated with this tray icon.
364 pub fn id(&self) -> &TrayIconId {
365 &self.id
366 }
367
368 /// Set new tray icon. If `None` is provided, it will remove the icon.
369 pub fn set_icon(&self, icon: Option<Icon>) -> Result<()> {
370 self.tray.borrow_mut().set_icon(icon)
371 }
372
373 /// Set new tray menu.
374 ///
375 /// ## Platform-specific:
376 ///
377 /// - **Linux**: once a menu is set it cannot be removed so `None` has no effect
378 pub fn set_menu(&self, menu: Option<Box<dyn menu::ContextMenu>>) {
379 self.tray.borrow_mut().set_menu(menu)
380 }
381
382 /// Sets the tooltip for this tray icon.
383 ///
384 /// ## Platform-specific:
385 ///
386 /// - **Linux:** Unsupported
387 pub fn set_tooltip<S: AsRef<str>>(&self, tooltip: Option<S>) -> Result<()> {
388 self.tray.borrow_mut().set_tooltip(tooltip)
389 }
390
391 /// Sets the tooltip for this tray icon.
392 ///
393 /// ## Platform-specific:
394 ///
395 /// - **Linux:** The title will not be shown unless there is an icon
396 /// as well. The title is useful for numerical and other frequently
397 /// updated information. In general, it shouldn't be shown unless a
398 /// user requests it as it can take up a significant amount of space
399 /// on the user's panel. This may not be shown in all visualizations.
400 /// - **Windows:** Unsupported
401 pub fn set_title<S: AsRef<str>>(&self, title: Option<S>) {
402 self.tray.borrow_mut().set_title(title)
403 }
404
405 /// Show or hide this tray icon
406 pub fn set_visible(&self, visible: bool) -> Result<()> {
407 self.tray.borrow_mut().set_visible(visible)
408 }
409
410 /// Sets the tray icon temp dir path. **Linux only**.
411 ///
412 /// On Linux, we need to write the icon to the disk and usually it will
413 /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
414 pub fn set_temp_dir_path<P: AsRef<Path>>(&self, path: Option<P>) {
415 #[cfg(target_os = "linux")]
416 self.tray.borrow_mut().set_temp_dir_path(path);
417 #[cfg(not(target_os = "linux"))]
418 let _ = path;
419 }
420
421 /// Set the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
422 pub fn set_icon_as_template(&self, is_template: bool) {
423 #[cfg(target_os = "macos")]
424 self.tray.borrow_mut().set_icon_as_template(is_template);
425 #[cfg(not(target_os = "macos"))]
426 let _ = is_template;
427 }
428
429 pub fn set_icon_with_as_template(&self, icon: Option<Icon>, is_template: bool) -> Result<()> {
430 #[cfg(target_os = "macos")]
431 return self
432 .tray
433 .borrow_mut()
434 .set_icon_with_as_template(icon, is_template);
435 #[cfg(not(target_os = "macos"))]
436 {
437 let _ = icon;
438 let _ = is_template;
439 Ok(())
440 }
441 }
442
443 /// Disable or enable showing the tray menu on left click.
444 ///
445 /// ## Platform-specific:
446 ///
447 /// - **Linux:** Unsupported.
448 pub fn set_show_menu_on_left_click(&self, enable: bool) {
449 #[cfg(any(target_os = "macos", target_os = "windows"))]
450 self.tray.borrow_mut().set_show_menu_on_left_click(enable);
451 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
452 let _ = enable;
453 }
454
455 /// Get tray icon rect.
456 ///
457 /// ## Platform-specific:
458 ///
459 /// - **Linux**: Unsupported.
460 pub fn rect(&self) -> Option<Rect> {
461 self.tray.borrow().rect()
462 }
463
464 /// Get the tray icon's underlying [window handle](windows_sys::Win32::Foundation::HWND) **Windows only**.
465 ///
466 /// This window handle is valid as long as the tray icon.
467 #[cfg(windows)]
468 pub fn window_handle(&self) -> windows_sys::Win32::Foundation::HWND {
469 self.tray.borrow().hwnd()
470 }
471
472 /// Get the tray icon's underlying [NSStatusItem](objc2_app_kit::NSStatusItem) **macOS only**.
473 ///
474 /// Returns `None` if the status item is not available.
475 #[cfg(target_os = "macos")]
476 pub fn ns_status_item(&self) -> Option<objc2::rc::Retained<objc2_app_kit::NSStatusItem>> {
477 self.tray.borrow().ns_status_item().cloned()
478 }
479
480 /// Get the tray icon's underlying [AppIndicator](libappindicator::AppIndicator) **Linux only**.
481 ///
482 /// # Safety
483 ///
484 /// The returned pointer is valid as long as the `TrayIcon` is.
485 #[cfg(all(unix, not(target_os = "macos")))]
486 pub unsafe fn app_indicator(&self) -> *const libappindicator::AppIndicator {
487 self.tray.borrow().app_indicator() as *const _
488 }
489}
490
491/// Describes a tray icon event.
492///
493/// ## Platform-specific:
494///
495/// - **Linux**: Unsupported. The event is not emmited even though the icon is shown
496/// and will still show a context menu on right click.
497#[derive(Debug, Clone)]
498#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
499#[cfg_attr(feature = "serde", serde(tag = "type"))]
500#[non_exhaustive]
501pub enum TrayIconEvent {
502 /// A click happened on the tray icon.
503 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
504 Click {
505 /// Id of the tray icon which triggered this event.
506 id: TrayIconId,
507 /// Physical Position of this event.
508 position: dpi::PhysicalPosition<f64>,
509 /// Position and size of the tray icon.
510 rect: Rect,
511 /// Mouse button that triggered this event.
512 button: MouseButton,
513 /// Mouse button state when this event was triggered.
514 button_state: MouseButtonState,
515 },
516 /// A double click happened on the tray icon. **Windows Only**
517 DoubleClick {
518 /// Id of the tray icon which triggered this event.
519 id: TrayIconId,
520 /// Physical Position of this event.
521 position: dpi::PhysicalPosition<f64>,
522 /// Position and size of the tray icon.
523 rect: Rect,
524 /// Mouse button that triggered this event.
525 button: MouseButton,
526 },
527 /// The mouse entered the tray icon region.
528 Enter {
529 /// Id of the tray icon which triggered this event.
530 id: TrayIconId,
531 /// Physical Position of this event.
532 position: dpi::PhysicalPosition<f64>,
533 /// Position and size of the tray icon.
534 rect: Rect,
535 },
536 /// The mouse moved over the tray icon region.
537 Move {
538 /// Id of the tray icon which triggered this event.
539 id: TrayIconId,
540 /// Physical Position of this event.
541 position: dpi::PhysicalPosition<f64>,
542 /// Position and size of the tray icon.
543 rect: Rect,
544 },
545 /// The mouse left the tray icon region.
546 Leave {
547 /// Id of the tray icon which triggered this event.
548 id: TrayIconId,
549 /// Physical Position of this event.
550 position: dpi::PhysicalPosition<f64>,
551 /// Position and size of the tray icon.
552 rect: Rect,
553 },
554}
555
556/// Describes the mouse button state.
557#[derive(Clone, Copy, PartialEq, Eq, Debug)]
558#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
559pub enum MouseButtonState {
560 Up,
561 Down,
562}
563
564impl Default for MouseButtonState {
565 fn default() -> Self {
566 Self::Up
567 }
568}
569
570/// Describes which mouse button triggered the event..
571#[derive(Clone, Copy, PartialEq, Eq, Debug)]
572#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
573pub enum MouseButton {
574 Left,
575 Right,
576 Middle,
577}
578
579impl Default for MouseButton {
580 fn default() -> Self {
581 Self::Left
582 }
583}
584
585/// Describes a rectangle including position (x - y axis) and size.
586#[derive(Debug, PartialEq, Clone, Copy)]
587#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
588pub struct Rect {
589 pub size: dpi::PhysicalSize<u32>,
590 pub position: dpi::PhysicalPosition<f64>,
591}
592
593impl Default for Rect {
594 fn default() -> Self {
595 Self {
596 size: dpi::PhysicalSize::new(0, 0),
597 position: dpi::PhysicalPosition::new(0., 0.),
598 }
599 }
600}
601
602/// A reciever that could be used to listen to tray events.
603pub type TrayIconEventReceiver = Receiver<TrayIconEvent>;
604type TrayIconEventHandler = Box<dyn Fn(TrayIconEvent) + Send + Sync + 'static>;
605
606static TRAY_CHANNEL: Lazy<(Sender<TrayIconEvent>, TrayIconEventReceiver)> = Lazy::new(unbounded);
607static TRAY_EVENT_HANDLER: OnceCell<Option<TrayIconEventHandler>> = OnceCell::new();
608
609impl TrayIconEvent {
610 /// Returns the id of the tray icon which triggered this event.
611 pub fn id(&self) -> &TrayIconId {
612 match self {
613 TrayIconEvent::Click { id, .. } => id,
614 TrayIconEvent::DoubleClick { id, .. } => id,
615 TrayIconEvent::Enter { id, .. } => id,
616 TrayIconEvent::Move { id, .. } => id,
617 TrayIconEvent::Leave { id, .. } => id,
618 }
619 }
620
621 /// Gets a reference to the event channel's [`TrayIconEventReceiver`]
622 /// which can be used to listen for tray events.
623 ///
624 /// ## Note
625 ///
626 /// This will not receive any events if [`TrayIconEvent::set_event_handler`] has been called with a `Some` value.
627 pub fn receiver<'a>() -> &'a TrayIconEventReceiver {
628 &TRAY_CHANNEL.1
629 }
630
631 /// Set a handler to be called for new events. Useful for implementing custom event sender.
632 ///
633 /// ## Note
634 ///
635 /// Calling this function with a `Some` value,
636 /// will not send new events to the channel associated with [`TrayIconEvent::receiver`]
637 pub fn set_event_handler<F: Fn(TrayIconEvent) + Send + Sync + 'static>(f: Option<F>) {
638 if let Some(f) = f {
639 let _ = TRAY_EVENT_HANDLER.set(Some(Box::new(f)));
640 } else {
641 let _ = TRAY_EVENT_HANDLER.set(None);
642 }
643 }
644
645 #[allow(unused)]
646 pub(crate) fn send(event: TrayIconEvent) {
647 if let Some(handler) = TRAY_EVENT_HANDLER.get_or_init(|| None) {
648 handler(event);
649 } else {
650 let _ = TRAY_CHANNEL.0.send(event);
651 }
652 }
653}
654
655#[cfg(test)]
656mod tests {
657
658 #[cfg(feature = "serde")]
659 #[test]
660 fn it_serializes() {
661 use super::*;
662 let event = TrayIconEvent::Click {
663 button: MouseButton::Left,
664 button_state: MouseButtonState::Down,
665 id: TrayIconId::new("id"),
666 position: dpi::PhysicalPosition::default(),
667 rect: Rect::default(),
668 };
669
670 let value = serde_json::to_value(&event).unwrap();
671 assert_eq!(
672 value,
673 serde_json::json!({
674 "type": "Click",
675 "button": "Left",
676 "buttonState": "Down",
677 "id": "id",
678 "position": {
679 "x": 0.0,
680 "y": 0.0,
681 },
682 "rect": {
683 "size": {
684 "width": 0,
685 "height": 0,
686 },
687 "position": {
688 "x": 0.0,
689 "y": 0.0,
690 },
691 }
692 })
693 )
694 }
695}