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 {
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}