Skip to main content

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    // TODO(OGKevin): This shall be refactored so that you don't pass a magic string
102    //                but rather an enum with the icon name.
103    pub fn new(name: &str, rect: Rectangle, event: Event) -> Icon {
104        Icon {
105            id: ID_FEEDER.next(),
106            rect,
107            children: Vec::new(),
108            name: name.to_string(),
109            background: TEXT_NORMAL[0],
110            align: Align::Center,
111            corners: None,
112            event,
113            active: false,
114        }
115    }
116
117    pub fn background(mut self, background: Color) -> Icon {
118        self.background = background;
119        self
120    }
121
122    pub fn align(mut self, align: Align) -> Icon {
123        self.align = align;
124        self
125    }
126
127    pub fn corners(mut self, corners: Option<CornerSpec>) -> Icon {
128        self.corners = corners;
129        self
130    }
131}
132
133impl View for Icon {
134    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, hub, bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
135    fn handle_event(
136        &mut self,
137        evt: &Event,
138        hub: &Hub,
139        bus: &mut Bus,
140        rq: &mut RenderQueue,
141        _context: &mut Context,
142    ) -> bool {
143        match *evt {
144            Event::Device(DeviceEvent::Finger {
145                status, position, ..
146            }) => match status {
147                FingerStatus::Down if self.rect.includes(position) => {
148                    self.active = true;
149                    rq.add(RenderData::new(self.id, self.rect, UpdateMode::Fast));
150                    true
151                }
152                FingerStatus::Up if self.active => {
153                    self.active = false;
154                    rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
155                    true
156                }
157                _ => false,
158            },
159            Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
160                bus.push_back(self.event.clone());
161                true
162            }
163            Event::Gesture(GestureEvent::HoldFingerShort(center, ..))
164                if self.rect.includes(center) =>
165            {
166                match self.event {
167                    Event::Page(dir) => bus.push_back(Event::Chapter(dir)),
168                    Event::Show(ViewId::Frontlight) => {
169                        hub.send(Event::ToggleFrontlight).ok();
170                    }
171                    Event::Show(ViewId::MarginCropper) => {
172                        bus.push_back(Event::ToggleNear(ViewId::MarginCropperMenu, self.rect));
173                    }
174                    Event::History(dir, false) => {
175                        bus.push_back(Event::History(dir, true));
176                    }
177                    _ => (),
178                }
179                true
180            }
181            _ => false,
182        }
183    }
184
185    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, fb, _fonts, _rect), fields(rect = ?_rect)))]
186    fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {
187        let scheme = if self.active {
188            TEXT_INVERTED_HARD
189        } else {
190            TEXT_NORMAL
191        };
192
193        let pixmap = ICONS_PIXMAPS.get(&self.name[..]).unwrap();
194        let dx = self
195            .align
196            .offset(pixmap.width as i32, self.rect.width() as i32);
197        let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2;
198        let pt = self.rect.min + pt!(dx, dy);
199
200        let background = if self.active {
201            scheme[0]
202        } else {
203            self.background
204        };
205
206        if let Some(ref cs) = self.corners {
207            fb.draw_rounded_rectangle(&self.rect, cs, background);
208        } else {
209            fb.draw_rectangle(&self.rect, background);
210        }
211
212        fb.draw_blended_pixmap(pixmap, pt, scheme[1]);
213    }
214
215    fn resize(
216        &mut self,
217        rect: Rectangle,
218        _hub: &Hub,
219        _rq: &mut RenderQueue,
220        _context: &mut Context,
221    ) {
222        if let Event::ToggleNear(_, ref mut event_rect) = self.event {
223            *event_rect = rect;
224        }
225        self.rect = rect;
226    }
227
228    fn rect(&self) -> &Rectangle {
229        &self.rect
230    }
231
232    fn rect_mut(&mut self) -> &mut Rectangle {
233        &mut self.rect
234    }
235
236    fn children(&self) -> &Vec<Box<dyn View>> {
237        &self.children
238    }
239
240    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
241        &mut self.children
242    }
243
244    fn id(&self) -> Id {
245        self.id
246    }
247}