Skip to main content

ui/
taskbar.rs

1// taskbar -- the app's taskbar / dock button (plat::taskbar): progress, a badge, jump list tasks
2//
3//   taskbar_progress(TaskbarProgress) -> bool   TaskbarProgress::Normal(0.4), Paused(..),
4//                                    Error(..), Indeterminate, None
5//   taskbar_badge(n) -> bool         n > 0: a red dot with the number on the button (Windows:
6//                                    the overlay icon drawn here at the window's dpi, "99+"
7//                                    above 99; Linux: the dock's own counter); 0 removes it
8//   taskbar_tasks(&[JumpTask]) -> bool   the jump list's tasks (Windows): each starts the
9//                                    program with its arguments (AF_SINGLE_INSTANCE: they
10//                                    arrive as UIC_OPEN); empty removes them
11// Headless runs (scripts, --shot) only print what they would show (plat::tray's dry run).
12// Call from the UI thread (frame / command), as everything of Ui.
13
14use alloc::format;
15use alloc::string::String;
16
17use gfx::{Surface, FMT_PARGB};
18use plat::taskbar::{self, JumpTask, TaskbarProgress};
19use plat::tray::{self, TrayIcon};
20
21use super::cmd::Gfx;
22use super::consts::FONT_BOLD;
23use super::Ui;
24
25/// the badge's colours: a red dot, white digits
26const BADGE_BG: u32 = 0xFFC4_2B1C;
27const BADGE_FG: u32 = 0xFFFF_FFFF;
28
29impl Ui {
30    /// show progress on the app's taskbar button -> false: not shown here
31    pub fn taskbar_progress(&mut self, p: TaskbarProgress) -> bool {
32        tray::set_dry_run(self.headless);
33        taskbar::taskbar_progress(p)
34    }
35
36    /// a badge with n on the app's taskbar button (0: none) -> false: not shown here
37    pub fn taskbar_badge(&mut self, n: u32) -> bool {
38        tray::set_dry_run(self.headless);
39        if n != 0 && taskbar::taskbar_badge_shown(n) {
40            return true; // drawn and shown already (calls every frame stay cheap)
41        }
42        let icon = if n == 0 { None } else { self.badge_icon(n) };
43        taskbar::taskbar_badge(n, icon.as_ref())
44    }
45
46    /// the tasks of the button's jump list (empty: none) -> false: no jump list here
47    pub fn taskbar_tasks(&mut self, tasks: &[JumpTask]) -> bool {
48        tray::set_dry_run(self.headless);
49        taskbar::taskbar_tasks(tasks)
50    }
51
52    /// the badge for n: a red circle with the number, 16 x 16 at 100 % (at most 64 px)
53    pub(crate) fn badge_icon(&mut self, n: u32) -> Option<TrayIcon> {
54        let side = ((16 * self.wp.rt.dpi.max(96) + 48) / 96).clamp(16, 64);
55        let label = if n > 99 { String::from("99+") } else { format!("{n}") };
56        let mut sf = Surface::new(side, side, FMT_PARGB);
57        if sf.px.is_empty() {
58            return None;
59        }
60        let saved = self.text.get_font();
61        self.dc.begin(&sf);
62        {
63            let mut g = Gfx { dc: &mut self.dc, sf: &mut sf, text: &mut self.text };
64            g.dc.set_color(BADGE_BG);
65            g.shapes().fill_circle_fx(side << 7, side << 7, side << 7);
66            // the largest bold size whose text fits inside the circle
67            let mut px = side * 3 / 4;
68            loop {
69                g.text.set_font(FONT_BOLD, px << 8);
70                if px <= 6 || g.text.width(&label) <= side - side / 4 {
71                    break;
72                }
73                px -= 1;
74            }
75            let w = g.text.width(&label);
76            let (asc, desc) = (g.text.ascent(), g.text.descent());
77            let base = (side + asc - desc + 1) / 2;
78            g.dc.set_color(BADGE_FG);
79            g.text.draw(g.dc, g.sf, (side - w + 1) / 2, base, label.as_bytes());
80        }
81        self.dc.end();
82        self.text.set_font(saved.0, saved.1);
83        super::tray::icon_of(&sf)
84    }
85}