Skip to main content

ui/
office_gallery.rs

1// Reusable document-theme thumbnails drawn with the shared UI rasterizer.
2// This is a visual chooser, independent of an application's document commands or formats.
3use crate::{Sp, Ui, consts::*};
4
5impl Ui {
6    /// Focusable document/template preview card, naturally 150×194 DIP. The supplied
7    /// drawing is inset into a white page; its document colors never follow the UI theme.
8    /// Hover and selection use the style accent. The full label is exposed to agents.
9    #[cfg_attr(jfgui_dev, track_caller)]
10    pub fn preview_card(&mut self, id: u32, label: &str, draw: Option<crate::DrawRef>, user: usize, version: u32) -> u32 {
11        let _src = self.src_scope();
12        let flags = self.take_flags();
13        let r = self.place(self.dp(150), self.dp(194));
14        let ev = self.behave(id, HB_FOCUS | CUR_HAND << HB_CURSOR_SHIFT
15            | if flags & ST_DISABLED != 0 { HB_DISABLED } else { 0 });
16        let active = flags & ST_DISABLED == 0 && (ev & EV_HOVER != 0 || flags & ST_CHECKED != 0);
17        let stroke = self.color(if active { T_ACCENT } else { T_STRONG_STROKE });
18        let inset = self.dp(1);
19        let offset = self.style.preview_card_offset();
20        let inner;
21        let clip;
22        if let Some([dx, dy]) = offset {
23            let (dx, dy) = (self.dp_fx(dx), self.dp_fx(dy));
24            let (x, y, w, h) = ((r[0] << 8) + dx, (r[1] << 8) + dy, r[2] << 8, r[3] << 8);
25            let border = self.dp_fx(if active { 512 } else { 256 });
26            // Two-DIP centerline radius plus half the normal one-DIP border.
27            self.cmd_panel_fx(x, y, w, h, self.dp_fx(2 * 256 + 128), border, 0xffffffff, stroke);
28            // Round the clip inward: thumbnail fills must not overwrite fractional
29            // border pixels. Hover changes the clip, never the preview's scale.
30            let inside = |t: i32| {
31                let (left, top) = ((x + t + 255) >> 8, (y + t + 255) >> 8);
32                [left, top, (((x + w - t) >> 8) - left).max(0), (((y + h - t) >> 8) - top).max(0)]
33            };
34            inner = inside(self.dp_fx(256));
35            clip = inside(border);
36        } else {
37            self.cmd_rrect(r[0], r[1], r[2], r[3], self.dp(2), self.dp(if active { 2 } else { 1 }), 0xffffffff, stroke, stroke);
38            inner = [r[0] + inset, r[1] + inset, (r[2] - 2 * inset).max(0), (r[3] - 2 * inset).max(0)];
39            clip = inner;
40        }
41        if clip[2] > 0 && clip[3] > 0 {
42            self.draw_clip(clip[0], clip[1], clip[2], clip[3]);
43            self.cmd_custom(inner[0], inner[1], inner[2], inner[3], draw, user, version);
44            self.draw_unclip();
45        }
46        self.focus_ring_draw(ev);
47        if flags & ST_CHECKED != 0 { self.wp.ctx.last_state |= SS_CHECKED as u32; }
48        self.sem(K_BUTTON, id, Some(label), 0);
49        ev
50    }
51
52    /// A 56×66-DIP theme chooser containing a 52×50-DIP miniature page.
53    /// `theme_color` is a document color, independent of the interface theme.
54    /// `variant` selects one of seven sample arrangements (0..=6); larger values wrap.
55    /// `next_flags(ST_DISABLED | ST_CHECKED)` controls behavior and selection, while
56    /// the document sample remains fully readable. The caller applies its own theme on EV_ACT.
57    #[cfg_attr(jfgui_dev, track_caller)]
58    pub fn ribbon_theme_tile(
59        &mut self,
60        id: u32,
61        label: &str,
62        theme_color: u32,
63        variant: u32,
64    ) -> u32 {
65        let _src = self.src_scope();
66        let flags = self.take_flags() | ST_SUBTLE;
67        let selected = flags & ST_CHECKED != 0;
68        let rect = self.place(self.dp(56), self.dp(66));
69        let ev = self.behave(
70            id,
71            HB_FOCUS
72                | if flags & ST_DISABLED != 0 {
73                    HB_DISABLED
74                } else {
75                    0
76                },
77        );
78        let mut sp = Sp {
79            flags,
80            ..Sp::default()
81        };
82        self.sp_placed(&mut sp, ev);
83        let style = self.style;
84        style.button(self, &sp);
85        let (font, size) = self.text.get_font();
86        let x = rect[0] + self.dp(2);
87        let y = rect[1] + self.dp(3);
88        let w = self.dp(52).min((rect[2] - self.dp(4)).max(0));
89        let h = self.dp(50).min((rect[3] - self.dp(6)).max(0));
90        if w > 0 && h > 0 {
91            let border = self.color(if selected { T_ACCENT } else { T_DIVIDER });
92            self.cmd_rrect(x, y, w, h, 0, self.dp(1), 0xffffffff, border, border);
93            self.draw_clip(
94                x + self.dp(2),
95                y + self.dp(2),
96                (w - self.dp(4)).max(0),
97                (h - self.dp(4)).max(0),
98            );
99            let variant = variant % 7;
100            let ink = theme_color | 0xff000000;
101            let inset = self.dp(4);
102            let title = if variant == 6 { "TITLE" } else { "Title" };
103            self.text.set_font(
104                if variant == 1 { FONT_BOLD } else { FONT_REG },
105                self.dp_fx(9 * 256),
106            );
107            let tw = self.text.width(title);
108            let tx = if variant == 5 {
109                x + (w - tw) / 2
110            } else {
111                x + inset
112            };
113            let title_y = y + self.dp(4) + self.text.ascent();
114            self.emit_text(tx, title_y, title, 0xff242424);
115            if variant == 2 {
116                self.draw_color(ink);
117                self.draw_rect(x + inset, y + self.dp(17), w - 2 * inset, self.dp(1));
118            }
119            if variant == 3 {
120                self.draw_color(ink);
121                self.draw_rect(x + self.dp(1), y + self.dp(26), w - self.dp(2), self.dp(12));
122            }
123            self.text.set_font(
124                if variant == 1 { FONT_BOLD } else { FONT_REG },
125                self.dp_fx(6 * 256 + 128),
126            );
127            let heading = "Heading 1";
128            let hw = self.text.width(heading);
129            let hx = if variant == 5 {
130                x + (w - hw) / 2
131            } else {
132                x + inset
133            };
134            self.emit_text(
135                hx,
136                y + self.dp(28) + self.text.ascent(),
137                heading,
138                if variant == 3 { 0xffffffff } else { ink },
139            );
140            if variant == 4 {
141                self.draw_color(ink);
142                self.draw_rect(x + inset, y + self.dp(37), w - 2 * inset, self.dp(1));
143            }
144            self.text.set_font(FONT_REG, self.dp_fx(4 * 256 + 128));
145            self.emit_text(
146                x + inset,
147                y + self.dp(40) + self.text.ascent(),
148                "Body text in a short",
149                0xff242424,
150            );
151            self.draw_color(0xff707070);
152            self.draw_rect(
153                x + inset,
154                y + self.dp(47),
155                (w - 3 * inset).max(0),
156                self.dp(1),
157            );
158            self.draw_unclip();
159        }
160        self.text.set_font(font, size);
161        self.focus_ring_draw(ev);
162        if selected {
163            self.wp.ctx.last_state |= SS_CHECKED as u32;
164        }
165        self.sem(K_BUTTON, id, Some(label), variant as i32);
166        self.tooltip(label);
167        ev
168    }
169}