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