cadmus_core/view/
icon.rs

1use super::{Align, Bus, Event, Hub, Id, RenderData, RenderQueue, View, ViewId, ID_FEEDER};
2use crate::color::{Color, TEXT_INVERTED_HARD, TEXT_NORMAL};
3use crate::context::Context;
4use crate::device::CURRENT_DEVICE;
5use crate::document::pdf::PdfOpener;
6use crate::font::Fonts;
7use crate::framebuffer::{Framebuffer, Pixmap, UpdateMode};
8use crate::geom::{CornerSpec, Rectangle};
9use crate::gesture::GestureEvent;
10use crate::input::{DeviceEvent, FingerStatus};
11use crate::unit::scale_by_dpi_raw;
12use fxhash::FxHashMap;
13use lazy_static::lazy_static;
14use std::path::Path;
15
16const ICON_SCALE: f32 = 1.0 / 32.0;
17
18lazy_static! {
19    pub static ref ICONS_PIXMAPS: FxHashMap<&'static str, Pixmap> = {
20        let mut m = FxHashMap::default();
21        let scale = scale_by_dpi_raw(ICON_SCALE, CURRENT_DEVICE.dpi);
22        #[cfg(test)]
23        let dir = Path::new(
24            &std::env::var("TEST_ROOT_DIR").expect("TEST_ROOT_DIR must be set for tests."),
25        )
26        .join("icons");
27        #[cfg(not(test))]
28        let dir = Path::new("icons").to_path_buf();
29        for name in [
30            "home",
31            "search",
32            "back",
33            "frontlight",
34            "frontlight-disabled",
35            "menu",
36            "angle-left",
37            "angle-right",
38            "angle-left-small",
39            "angle-right-small",
40            "return",
41            "shift",
42            "combine",
43            "alternate",
44            "delete-backward",
45            "delete-forward",
46            "move-backward",
47            "move-backward-short",
48            "move-forward",
49            "move-forward-short",
50            "close",
51            "check_mark-small",
52            "check_mark",
53            "check_mark-large",
54            "bullet",
55            "arrow-left",
56            "arrow-right",
57            "angle-down",
58            "angle-up",
59            "crop",
60            "toc",
61            "font_family",
62            "font_size",
63            "line_height",
64            "align-justify",
65            "align-left",
66            "align-right",
67            "align-center",
68            "margin",
69            "plug",
70            "cover",
71            "enclosed_menu",
72            "contrast",
73            "gray",
74            "plus",
75        ]
76        .iter()
77        .cloned()
78        {
79            let path = dir.join(format!("{}.svg", name));
80            let doc = PdfOpener::new().and_then(|o| o.open(&path).ok()).unwrap();
81            let pixmap = doc.page(0).and_then(|p| p.pixmap(scale, 1)).unwrap();
82            m.insert(name, pixmap);
83        }
84        m
85    };
86}
87
88pub struct Icon {
89    id: Id,
90    pub rect: Rectangle,
91    children: Vec<Box<dyn View>>,
92    pub name: String,
93    background: Color,
94    align: Align,
95    corners: Option<CornerSpec>,
96    event: Event,
97    pub active: bool,
98}
99
100impl Icon {
101    pub fn new(name: &str, rect: Rectangle, event: Event) -> Icon {
102        Icon {
103            id: ID_FEEDER.next(),
104            rect,
105            children: Vec::new(),
106            name: name.to_string(),
107            background: TEXT_NORMAL[0],
108            align: Align::Center,
109            corners: None,
110            event,
111            active: false,
112        }
113    }
114
115    pub fn background(mut self, background: Color) -> Icon {
116        self.background = background;
117        self
118    }
119
120    pub fn align(mut self, align: Align) -> Icon {
121        self.align = align;
122        self
123    }
124
125    pub fn corners(mut self, corners: Option<CornerSpec>) -> Icon {
126        self.corners = corners;
127        self
128    }
129}
130
131impl View for Icon {
132    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, hub, bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
133    fn handle_event(
134        &mut self,
135        evt: &Event,
136        hub: &Hub,
137        bus: &mut Bus,
138        rq: &mut RenderQueue,
139        _context: &mut Context,
140    ) -> bool {
141        match *evt {
142            Event::Device(DeviceEvent::Finger {
143                status, position, ..
144            }) => match status {
145                FingerStatus::Down if self.rect.includes(position) => {
146                    self.active = true;
147                    rq.add(RenderData::new(self.id, self.rect, UpdateMode::Fast));
148                    true
149                }
150                FingerStatus::Up if self.active => {
151                    self.active = false;
152                    rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
153                    true
154                }
155                _ => false,
156            },
157            Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
158                bus.push_back(self.event.clone());
159                true
160            }
161            Event::Gesture(GestureEvent::HoldFingerShort(center, ..))
162                if self.rect.includes(center) =>
163            {
164                match self.event {
165                    Event::Page(dir) => bus.push_back(Event::Chapter(dir)),
166                    Event::Show(ViewId::Frontlight) => {
167                        hub.send(Event::ToggleFrontlight).ok();
168                    }
169                    Event::Show(ViewId::MarginCropper) => {
170                        bus.push_back(Event::ToggleNear(ViewId::MarginCropperMenu, self.rect));
171                    }
172                    Event::History(dir, false) => {
173                        bus.push_back(Event::History(dir, true));
174                    }
175                    _ => (),
176                }
177                true
178            }
179            _ => false,
180        }
181    }
182
183    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, _fonts, _rect), fields(rect = ?_rect)))]
184    fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {
185        let scheme = if self.active {
186            TEXT_INVERTED_HARD
187        } else {
188            TEXT_NORMAL
189        };
190
191        let pixmap = ICONS_PIXMAPS.get(&self.name[..]).unwrap();
192        let dx = self
193            .align
194            .offset(pixmap.width as i32, self.rect.width() as i32);
195        let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2;
196        let pt = self.rect.min + pt!(dx, dy);
197
198        let background = if self.active {
199            scheme[0]
200        } else {
201            self.background
202        };
203
204        if let Some(ref cs) = self.corners {
205            fb.draw_rounded_rectangle(&self.rect, cs, background);
206        } else {
207            fb.draw_rectangle(&self.rect, background);
208        }
209
210        fb.draw_blended_pixmap(pixmap, pt, scheme[1]);
211    }
212
213    fn resize(
214        &mut self,
215        rect: Rectangle,
216        _hub: &Hub,
217        _rq: &mut RenderQueue,
218        _context: &mut Context,
219    ) {
220        if let Event::ToggleNear(_, ref mut event_rect) = self.event {
221            *event_rect = rect;
222        }
223        self.rect = rect;
224    }
225
226    fn rect(&self) -> &Rectangle {
227        &self.rect
228    }
229
230    fn rect_mut(&mut self) -> &mut Rectangle {
231        &mut self.rect
232    }
233
234    fn children(&self) -> &Vec<Box<dyn View>> {
235        &self.children
236    }
237
238    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
239        &mut self.children
240    }
241
242    fn id(&self) -> Id {
243        self.id
244    }
245}