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