Skip to main content

cadmus_core/view/intermission/
mod.rs

1mod calendar;
2
3use super::{Bus, Event, Hub, ID_FEEDER, Id, RenderQueue, View};
4use crate::color::{BLACK, Color, TEXT_INVERTED_HARD, TEXT_NORMAL, WHITE};
5use crate::context::Context;
6use crate::device::CURRENT_DEVICE;
7use crate::document::{Location, open};
8use crate::fl;
9use crate::font::{DISPLAY_STYLE, Fonts, font_from_style};
10use crate::framebuffer::Framebuffer;
11use crate::geom::Rectangle;
12use crate::i18n::I18nDisplay;
13use crate::rtc::AlarmType;
14use crate::settings::{IntermKind, IntermissionDisplay};
15use calendar::CalendarView;
16use std::path::PathBuf;
17use tracing::warn;
18
19pub struct Intermission {
20    id: Id,
21    rect: Rectangle,
22    children: Vec<Box<dyn View>>,
23    message: Message,
24    halt: bool,
25}
26
27enum Message {
28    Text(String),
29    Image(PathBuf),
30    Cover(PathBuf),
31    /// Calendar rendering is delegated entirely to the CalendarView child.
32    Calendar,
33    Fill(Color),
34}
35
36impl Intermission {
37    pub fn new(rect: Rectangle, kind: IntermKind, context: &Context) -> Intermission {
38        let halt = kind == IntermKind::PowerOff;
39
40        let (message, children): (Message, Vec<Box<dyn View>>) =
41            match &context.settings.intermissions[kind] {
42                IntermissionDisplay::Logo => (Message::Text(kind.text().to_string()), Vec::new()),
43                IntermissionDisplay::Cover => {
44                    let msg =
45                        if let Some(info) = context.library.most_recently_opened_reading_book() {
46                            Message::Cover(context.library.home.join(&info.file.path))
47                        } else {
48                            Message::Text(kind.text().to_string())
49                        };
50                    (msg, Vec::new())
51                }
52                IntermissionDisplay::Blank => (Message::Fill(WHITE), Vec::new()),
53                IntermissionDisplay::BlankInverted => (Message::Fill(BLACK), Vec::new()),
54                IntermissionDisplay::Image(path) => (Message::Image(path.clone()), Vec::new()),
55                IntermissionDisplay::Calendar => {
56                    let minutes_until_poweroff = context
57                        .alarm_manager
58                        .as_ref()
59                        .and_then(|am| am.time_until_alarm(AlarmType::AutoPowerOff))
60                        .map(|secs| secs / 60);
61                    let child = CalendarView::new(rect, minutes_until_poweroff, halt);
62                    (Message::Calendar, vec![Box::new(child) as Box<dyn View>])
63                }
64            };
65
66        Intermission {
67            id: ID_FEEDER.next(),
68            rect,
69            children,
70            message,
71            halt,
72        }
73    }
74}
75
76impl I18nDisplay for IntermissionDisplay {
77    fn to_i18n_string(&self) -> String {
78        match self {
79            IntermissionDisplay::Logo => fl!("settings-intermission-logo"),
80            IntermissionDisplay::Blank => fl!("settings-intermission-blank"),
81            IntermissionDisplay::BlankInverted => fl!("settings-intermission-blank-inverted"),
82            IntermissionDisplay::Cover => fl!("settings-intermission-cover"),
83            IntermissionDisplay::Calendar => fl!("settings-intermission-calendar"),
84            IntermissionDisplay::Image(_) => fl!("settings-intermission-custom"),
85        }
86    }
87}
88
89impl View for Intermission {
90    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _evt, _hub, _bus, _rq, _context), fields(event = ?_evt), ret(level=tracing::Level::TRACE)))]
91    fn handle_event(
92        &mut self,
93        _evt: &Event,
94        _hub: &Hub,
95        _bus: &mut Bus,
96        _rq: &mut RenderQueue,
97        _context: &mut Context,
98    ) -> bool {
99        true
100    }
101
102    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, fb, fonts, _rect), fields(rect = ?_rect)))]
103    fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, fonts: &mut Fonts) {
104        let scheme = if self.halt {
105            TEXT_INVERTED_HARD
106        } else {
107            TEXT_NORMAL
108        };
109
110        fb.draw_rectangle(&self.rect, scheme[0]);
111
112        match self.message {
113            Message::Text(ref text) => {
114                let dpi = CURRENT_DEVICE.dpi;
115
116                let font = font_from_style(fonts, &DISPLAY_STYLE, dpi);
117                let padding = font.em() as i32;
118                let max_width = self.rect.width() as i32 - 3 * padding;
119                let mut plan = font.plan(text, None, None);
120
121                if plan.width > max_width {
122                    let scale = max_width as f32 / plan.width as f32;
123                    let size = (scale * DISPLAY_STYLE.size as f32) as u32;
124                    font.set_size(size, dpi);
125                    plan = font.plan(text, None, None);
126                }
127
128                let x_height = font.x_heights.0 as i32;
129
130                let dx = (self.rect.width() as i32 - plan.width) / 2;
131                let dy = (self.rect.height() as i32) / 3;
132
133                font.render(fb, scheme[1], &plan, pt!(dx, dy));
134
135                let logo_path = CURRENT_DEVICE.install_path("icons/dodecahedron.svg");
136                match open(&logo_path) {
137                    None => warn!(path = %logo_path.display(), "failed to open logo icon"),
138                    Some(mut doc) => match doc.dims(0) {
139                        None => warn!("failed to read dimensions from dodecahedron.svg"),
140                        Some((width, height)) => {
141                            let scale = (plan.width as f32 / width.max(height)) / 4.0;
142                            match doc.pixmap(Location::Exact(0), scale, 1) {
143                                None => warn!("failed to render pixmap from dodecahedron.svg"),
144                                Some((pixmap, _)) => {
145                                    let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2;
146                                    let dy = dy + 2 * x_height;
147                                    let pt = self.rect.min + pt!(dx, dy);
148                                    fb.draw_blended_pixmap(&pixmap, pt, scheme[1]);
149                                }
150                            }
151                        }
152                    },
153                }
154            }
155            Message::Image(ref path) => {
156                if let Some(mut doc) = open(path) {
157                    if let Some((width, height)) = doc.dims(0) {
158                        let w_ratio = self.rect.width() as f32 / width;
159                        let h_ratio = self.rect.height() as f32 / height;
160                        let scale = w_ratio.min(h_ratio);
161                        if let Some((pixmap, _)) =
162                            doc.pixmap(Location::Exact(0), scale, CURRENT_DEVICE.color_samples())
163                        {
164                            let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2;
165                            let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2;
166                            let pt = self.rect.min + pt!(dx, dy);
167                            fb.draw_pixmap(&pixmap, pt);
168                            if fb.inverted() {
169                                let rect = pixmap.rect() + pt;
170                                fb.invert_region(&rect);
171                            }
172                        }
173                    }
174                }
175            }
176            Message::Cover(ref path) => {
177                if let Some(mut doc) = open(path) {
178                    if let Some(pixmap) = doc.preview_pixmap(
179                        self.rect.width() as f32,
180                        self.rect.height() as f32,
181                        CURRENT_DEVICE.color_samples(),
182                    ) {
183                        let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2;
184                        let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2;
185                        let pt = self.rect.min + pt!(dx, dy);
186                        fb.draw_pixmap(&pixmap, pt);
187                        if fb.inverted() {
188                            let rect = pixmap.rect() + pt;
189                            fb.invert_region(&rect);
190                        }
191                    }
192                }
193            }
194            Message::Fill(color) => {
195                fb.draw_rectangle(&self.rect, color);
196            }
197            // CalendarView child handles its own rendering; this arm is never
198            // reached because Intermission has children in the Calendar case.
199            Message::Calendar => {}
200        }
201    }
202
203    fn might_rotate(&self) -> bool {
204        false
205    }
206
207    fn rect(&self) -> &Rectangle {
208        &self.rect
209    }
210
211    fn rect_mut(&mut self) -> &mut Rectangle {
212        &mut self.rect
213    }
214
215    fn children(&self) -> &Vec<Box<dyn View>> {
216        &self.children
217    }
218
219    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
220        &mut self.children
221    }
222
223    fn id(&self) -> Id {
224        self.id
225    }
226}