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