Skip to main content

cadmus_core/view/intermission/
mod.rs

1mod calendar;
2
3use super::{Bus, Event, Hub, Id, RenderQueue, View, ID_FEEDER};
4use crate::color::{Color, BLACK, TEXT_INVERTED_HARD, TEXT_NORMAL, WHITE};
5use crate::context::Context;
6use crate::device::CURRENT_DEVICE;
7use crate::document::{open, Location};
8use crate::fl;
9use crate::font::{font_from_style, Fonts, DISPLAY_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                match open("icons/dodecahedron.svg") {
136                    None => warn!("failed to open icons/dodecahedron.svg"),
137                    Some(mut doc) => match doc.dims(0) {
138                        None => warn!("failed to read dimensions from dodecahedron.svg"),
139                        Some((width, height)) => {
140                            let scale = (plan.width as f32 / width.max(height)) / 4.0;
141                            match doc.pixmap(Location::Exact(0), scale, 1) {
142                                None => warn!("failed to render pixmap from dodecahedron.svg"),
143                                Some((pixmap, _)) => {
144                                    let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2;
145                                    let dy = dy + 2 * x_height;
146                                    let pt = self.rect.min + pt!(dx, dy);
147                                    fb.draw_blended_pixmap(&pixmap, pt, scheme[1]);
148                                }
149                            }
150                        }
151                    },
152                }
153            }
154            Message::Image(ref path) => {
155                if let Some(mut doc) = open(path) {
156                    if let Some((width, height)) = doc.dims(0) {
157                        let w_ratio = self.rect.width() as f32 / width;
158                        let h_ratio = self.rect.height() as f32 / height;
159                        let scale = w_ratio.min(h_ratio);
160                        if let Some((pixmap, _)) =
161                            doc.pixmap(Location::Exact(0), scale, CURRENT_DEVICE.color_samples())
162                        {
163                            let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2;
164                            let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2;
165                            let pt = self.rect.min + pt!(dx, dy);
166                            fb.draw_pixmap(&pixmap, pt);
167                            if fb.inverted() {
168                                let rect = pixmap.rect() + pt;
169                                fb.invert_region(&rect);
170                            }
171                        }
172                    }
173                }
174            }
175            Message::Cover(ref path) => {
176                if let Some(mut doc) = open(path) {
177                    if let Some(pixmap) = doc.preview_pixmap(
178                        self.rect.width() as f32,
179                        self.rect.height() as f32,
180                        CURRENT_DEVICE.color_samples(),
181                    ) {
182                        let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2;
183                        let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2;
184                        let pt = self.rect.min + pt!(dx, dy);
185                        fb.draw_pixmap(&pixmap, pt);
186                        if fb.inverted() {
187                            let rect = pixmap.rect() + pt;
188                            fb.invert_region(&rect);
189                        }
190                    }
191                }
192            }
193            Message::Fill(color) => {
194                fb.draw_rectangle(&self.rect, color);
195            }
196            // CalendarView child handles its own rendering; this arm is never
197            // reached because Intermission has children in the Calendar case.
198            Message::Calendar => {}
199        }
200    }
201
202    fn might_rotate(&self) -> bool {
203        false
204    }
205
206    fn rect(&self) -> &Rectangle {
207        &self.rect
208    }
209
210    fn rect_mut(&mut self) -> &mut Rectangle {
211        &mut self.rect
212    }
213
214    fn children(&self) -> &Vec<Box<dyn View>> {
215        &self.children
216    }
217
218    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
219        &mut self.children
220    }
221
222    fn id(&self) -> Id {
223        self.id
224    }
225}