cadmus_core/view/intermission/
mod.rs1mod 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,
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 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}