cadmus_core/view/
intermission.rs

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