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