Skip to main content

cadmus_core/view/
icon.rs

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