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}