1use super::button::Button;
2use super::common::shift;
3use super::icon::Icon;
4use super::label::Label;
5use super::presets_list::PresetsList;
6use super::slider::Slider;
7use super::{
8 Align, Bus, EntryId, Event, Hub, ID_FEEDER, Id, RenderData, RenderQueue, SliderId, View, ViewId,
9};
10use super::{BORDER_RADIUS_MEDIUM, SMALL_BAR_HEIGHT, THICKNESS_LARGE};
11use crate::color::{BLACK, WHITE};
12use crate::context::Context;
13use crate::device::CURRENT_DEVICE;
14use crate::font::{Fonts, NORMAL_STYLE, font_from_style};
15use crate::framebuffer::{Framebuffer, UpdateMode};
16use crate::frontlight::LightLevels;
17use crate::geom::{BorderSpec, CornerSpec, Rectangle};
18use crate::gesture::GestureEvent;
19use crate::settings::{LightPreset, guess_frontlight};
20use crate::unit::scale_by_dpi;
21
22const LABEL_SAVE: &str = "Save";
23const LABEL_GUESS: &str = "Guess";
24
25pub struct FrontlightWindow {
26 id: Id,
27 rect: Rectangle,
28 children: Vec<Box<dyn View>>,
29 frontlight_levels: LightLevels,
30}
31
32impl FrontlightWindow {
33 pub fn new(context: &mut Context) -> FrontlightWindow {
34 let id = ID_FEEDER.next();
35 let fonts = &mut context.fonts;
36 let levels = context.frontlight.levels();
37 let presets = &context.settings.frontlight_presets;
38 let mut children = Vec::new();
39 let dpi = CURRENT_DEVICE.dpi;
40 let (width, height) = context.display.dims;
41 let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
42 let thickness = scale_by_dpi(THICKNESS_LARGE, dpi) as i32;
43 let border_radius = scale_by_dpi(BORDER_RADIUS_MEDIUM, dpi) as i32;
44
45 let (x_height, padding) = {
46 let font = font_from_style(fonts, &NORMAL_STYLE, dpi);
47 (font.x_heights.0 as i32, font.em() as i32)
48 };
49
50 let window_width = width as i32 - 2 * padding;
51
52 let mut window_height = small_height * 3 + 2 * padding;
53
54 if CURRENT_DEVICE.has_natural_light() {
55 window_height += small_height;
56 }
57
58 if !presets.is_empty() {
59 window_height += small_height;
60 }
61
62 let dx = (width as i32 - window_width) / 2;
63 let dy = (height as i32 - window_height) / 3;
64
65 let rect = rect![dx, dy, dx + window_width, dy + window_height];
66
67 let corners = CornerSpec::Detailed {
68 north_west: 0,
69 north_east: border_radius - thickness,
70 south_east: 0,
71 south_west: 0,
72 };
73
74 let close_icon = Icon::new(
75 "close",
76 rect![
77 rect.max.x - small_height,
78 rect.min.y + thickness,
79 rect.max.x - thickness,
80 rect.min.y + small_height
81 ],
82 Event::Close(ViewId::Frontlight),
83 )
84 .corners(Some(corners));
85
86 children.push(Box::new(close_icon) as Box<dyn View>);
87
88 let label = Label::new(
89 rect![
90 rect.min.x + small_height,
91 rect.min.y + thickness,
92 rect.max.x - small_height,
93 rect.min.y + small_height
94 ],
95 "Frontlight".to_string(),
96 Align::Center,
97 );
98
99 children.push(Box::new(label) as Box<dyn View>);
100
101 let mut button_y = rect.min.y + 2 * small_height;
102
103 if CURRENT_DEVICE.has_natural_light() {
104 let max_label_width = {
105 let font = font_from_style(fonts, &NORMAL_STYLE, dpi);
106 ["Intensity", "Warmth"]
107 .iter()
108 .map(|t| font.plan(t, None, None).width)
109 .max()
110 .unwrap() as i32
111 };
112
113 for (index, slider_id) in [SliderId::LightIntensity, SliderId::LightWarmth]
114 .iter()
115 .enumerate()
116 {
117 let min_y = rect.min.y + (index + 1) as i32 * small_height;
118 let label = Label::new(
119 rect![
120 rect.min.x + padding,
121 min_y,
122 rect.min.x + 2 * padding + max_label_width,
123 min_y + small_height
124 ],
125 slider_id.label(),
126 Align::Right(padding / 2),
127 );
128 children.push(Box::new(label) as Box<dyn View>);
129
130 let value = if *slider_id == SliderId::LightIntensity {
131 levels.intensity
132 } else {
133 levels.warmth
134 };
135
136 let slider = Slider::new(
137 rect![
138 rect.min.x + max_label_width + 3 * padding,
139 min_y,
140 rect.max.x - padding,
141 min_y + small_height
142 ],
143 *slider_id,
144 value.into(),
145 0.0,
146 100.0,
147 );
148 children.push(Box::new(slider) as Box<dyn View>);
149 }
150
151 button_y += small_height;
152 } else {
153 let min_y = rect.min.y + small_height;
154 let slider = Slider::new(
155 rect![
156 rect.min.x + padding,
157 min_y,
158 rect.max.x - padding,
159 min_y + small_height
160 ],
161 SliderId::LightIntensity,
162 levels.intensity.into(),
163 0.0,
164 100.0,
165 );
166 children.push(Box::new(slider) as Box<dyn View>);
167 }
168
169 let max_label_width = {
170 let font = font_from_style(fonts, &NORMAL_STYLE, dpi);
171 [LABEL_SAVE, LABEL_GUESS]
172 .iter()
173 .map(|t| font.plan(t, None, None).width)
174 .max()
175 .unwrap() as i32
176 };
177
178 let button_height = 4 * x_height;
179
180 let button_save = Button::new(
181 rect![
182 rect.min.x + 3 * padding,
183 button_y + small_height - button_height,
184 rect.min.x + 5 * padding + max_label_width,
185 button_y + small_height
186 ],
187 Event::Save,
188 LABEL_SAVE.to_string(),
189 );
190 children.push(Box::new(button_save) as Box<dyn View>);
191
192 let button_guess = Button::new(
193 rect![
194 rect.max.x - 5 * padding - max_label_width,
195 button_y + small_height - button_height,
196 rect.max.x - 3 * padding,
197 button_y + small_height
198 ],
199 Event::Guess,
200 LABEL_GUESS.to_string(),
201 )
202 .disabled(presets.len() < 2);
203 children.push(Box::new(button_guess) as Box<dyn View>);
204
205 if !presets.is_empty() {
206 let presets_rect = rect![
207 rect.min.x + thickness + 4 * padding,
208 rect.max.y - small_height - 2 * padding,
209 rect.max.x - thickness - 4 * padding,
210 rect.max.y - thickness - 2 * padding
211 ];
212 let mut presets_list = PresetsList::new(presets_rect);
213 presets_list.update(presets, &mut RenderQueue::new(), fonts);
214 children.push(Box::new(presets_list) as Box<dyn View>);
215 }
216
217 FrontlightWindow {
218 id,
219 rect,
220 children,
221 frontlight_levels: levels,
222 }
223 }
224
225 fn toggle_presets(&mut self, enable: bool, rq: &mut RenderQueue, context: &mut Context) {
226 let dpi = CURRENT_DEVICE.dpi;
227 let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
228
229 if enable {
230 let thickness = scale_by_dpi(THICKNESS_LARGE, dpi) as i32;
231 let padding = {
232 let font = font_from_style(&mut context.fonts, &NORMAL_STYLE, dpi);
233 font.em() as i32
234 };
235 shift(self, pt!(0, -(small_height) / 2));
236 self.rect.max.y += small_height;
237 let presets_rect = rect![
238 self.rect.min.x + thickness + 4 * padding,
239 self.rect.max.y - small_height - 2 * padding,
240 self.rect.max.x - thickness - 4 * padding,
241 self.rect.max.y - thickness - 2 * padding
242 ];
243 let mut presets_list = PresetsList::new(presets_rect);
244 presets_list.update(
245 &context.settings.frontlight_presets,
246 &mut RenderQueue::new(),
247 &mut context.fonts,
248 );
249 self.children.push(Box::new(presets_list) as Box<dyn View>);
250 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
251 } else {
252 self.children.pop();
253 rq.add(RenderData::expose(self.rect, UpdateMode::Gui));
254 shift(self, pt!(0, small_height / 2));
255 self.rect.max.y -= small_height;
256 }
257 }
258
259 fn set_frontlight_levels(&mut self, frontlight_levels: LightLevels, rq: &mut RenderQueue) {
260 self.frontlight_levels = frontlight_levels;
261 let LightLevels { intensity, warmth } = frontlight_levels;
262 if CURRENT_DEVICE.has_natural_light() {
263 if let Some(slider_intensity) = self.child_mut(3).downcast_mut::<Slider>() {
264 slider_intensity.update(intensity.into(), rq);
265 }
266 if let Some(slider_warmth) = self.child_mut(5).downcast_mut::<Slider>() {
267 slider_warmth.update(warmth.into(), rq);
268 }
269 } else if let Some(slider_intensity) = self.child_mut(2).downcast_mut::<Slider>() {
270 slider_intensity.update(intensity.into(), rq);
271 }
272 }
273
274 fn update_presets(&mut self, rq: &mut RenderQueue, context: &mut Context) {
275 let len = self.len();
276 if let Some(presets_list) = self.child_mut(len - 1).downcast_mut::<PresetsList>() {
277 presets_list.update(&context.settings.frontlight_presets, rq, &mut context.fonts);
278 }
279 }
280}
281
282impl View for FrontlightWindow {
283 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, hub, _bus, rq, context), fields(event = ?evt
284 ), ret(level=tracing::Level::TRACE)))]
285 fn handle_event(
286 &mut self,
287 evt: &Event,
288 hub: &Hub,
289 _bus: &mut Bus,
290 rq: &mut RenderQueue,
291 context: &mut Context,
292 ) -> bool {
293 match *evt {
294 Event::Slider(SliderId::LightIntensity, value, _) => {
295 let mut levels = self.frontlight_levels;
296 levels.intensity = value.into();
297 self.frontlight_levels = levels;
298 hub.send(Event::SetFrontlightLevels(levels)).ok();
299 true
300 }
301 Event::Slider(SliderId::LightWarmth, value, _) => {
302 let mut levels = self.frontlight_levels;
303 levels.warmth = value.into();
304 self.frontlight_levels = levels;
305 hub.send(Event::SetFrontlightLevels(levels)).ok();
306 true
307 }
308 Event::Gesture(GestureEvent::Tap(center)) if !self.rect.includes(center) => {
309 hub.send(Event::Close(ViewId::Frontlight)).ok();
310 true
311 }
312 Event::Gesture(..) => true,
313 Event::Save => {
314 let lightsensor_level = if CURRENT_DEVICE.has_lightsensor() {
315 context.lightsensor.level().ok()
316 } else {
317 None
318 };
319 let light_preset = LightPreset {
320 lightsensor_level,
321 frontlight_levels: context.frontlight.levels(),
322 ..Default::default()
323 };
324 context.settings.frontlight_presets.push(light_preset);
325 context
326 .settings
327 .frontlight_presets
328 .sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
329 if context.settings.frontlight_presets.len() == 1 {
330 self.toggle_presets(true, rq, context);
331 } else {
332 if context.settings.frontlight_presets.len() == 2 {
333 let index = self.len() - 2;
334 if let Some(button_guess) = self.child_mut(index).downcast_mut::<Button>() {
335 button_guess.disabled = false;
336 rq.add(RenderData::new(
337 button_guess.id(),
338 *button_guess.rect(),
339 UpdateMode::Gui,
340 ));
341 }
342 }
343 self.update_presets(rq, context);
344 }
345 true
346 }
347 Event::Select(EntryId::RemovePreset(index)) => {
348 if index < context.settings.frontlight_presets.len() {
349 context.settings.frontlight_presets.remove(index);
350 if context.settings.frontlight_presets.is_empty() {
351 self.toggle_presets(false, rq, context);
352 } else {
353 if context.settings.frontlight_presets.len() == 1 {
354 let index = self.len() - 2;
355 if let Some(button_guess) =
356 self.child_mut(index).downcast_mut::<Button>()
357 {
358 button_guess.disabled = true;
359 rq.add(RenderData::new(
360 button_guess.id(),
361 *button_guess.rect(),
362 UpdateMode::Gui,
363 ));
364 }
365 }
366 self.update_presets(rq, context);
367 }
368 }
369 true
370 }
371 Event::LoadPreset(index) => {
372 let frontlight_levels =
373 context.settings.frontlight_presets[index].frontlight_levels;
374 self.set_frontlight_levels(frontlight_levels, rq);
375 hub.send(Event::SetFrontlightLevels(frontlight_levels)).ok();
376 true
377 }
378 Event::Guess => {
379 let lightsensor_level = if CURRENT_DEVICE.has_lightsensor() {
380 context.lightsensor.level().ok()
381 } else {
382 None
383 };
384 if let Some(ref frontlight_levels) =
385 guess_frontlight(lightsensor_level, &context.settings.frontlight_presets)
386 {
387 self.set_frontlight_levels(*frontlight_levels, rq);
388 hub.send(Event::SetFrontlightLevels(*frontlight_levels))
389 .ok();
390 }
391 true
392 }
393 _ => false,
394 }
395 }
396
397 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, fb, _fonts, _rect), fields(rect = ?_rect
398 )))]
399 fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {
400 let dpi = CURRENT_DEVICE.dpi;
401
402 let border_radius = scale_by_dpi(BORDER_RADIUS_MEDIUM, dpi) as i32;
403 let border_thickness = scale_by_dpi(THICKNESS_LARGE, dpi) as u16;
404
405 fb.draw_rounded_rectangle_with_border(
406 &self.rect,
407 &CornerSpec::Uniform(border_radius),
408 &BorderSpec {
409 thickness: border_thickness,
410 color: BLACK,
411 },
412 &WHITE,
413 );
414 }
415
416 fn resize(&mut self, _rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
417 let dpi = CURRENT_DEVICE.dpi;
418 let (width, height) = context.display.dims;
419 let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
420 let thickness = scale_by_dpi(THICKNESS_LARGE, dpi) as i32;
421
422 let (x_height, padding) = {
423 let font = font_from_style(&mut context.fonts, &NORMAL_STYLE, dpi);
424 (font.x_heights.0 as i32, font.em() as i32)
425 };
426
427 let window_width = width as i32 - 2 * padding;
428
429 let mut window_height = small_height * 3 + 2 * padding;
430
431 if CURRENT_DEVICE.has_natural_light() {
432 window_height += small_height;
433 }
434
435 if !context.settings.frontlight_presets.is_empty() {
436 window_height += small_height;
437 }
438
439 let dx = (width as i32 - window_width) / 2;
440 let dy = (height as i32 - window_height) / 3;
441
442 let rect = rect![dx, dy, dx + window_width, dy + window_height];
443
444 self.children[0].resize(
445 rect![
446 rect.max.x - small_height,
447 rect.min.y + thickness,
448 rect.max.x - thickness,
449 rect.min.y + small_height
450 ],
451 hub,
452 rq,
453 context,
454 );
455 self.children[1].resize(
456 rect![
457 rect.min.x + small_height,
458 rect.min.y + thickness,
459 rect.max.x - small_height,
460 rect.min.y + small_height
461 ],
462 hub,
463 rq,
464 context,
465 );
466
467 let mut button_y = rect.min.y + 2 * small_height;
468 let mut index = 2;
469
470 if CURRENT_DEVICE.has_natural_light() {
471 let max_label_width = {
472 let font = font_from_style(&mut context.fonts, &NORMAL_STYLE, dpi);
473 ["Intensity", "Warmth"]
474 .iter()
475 .map(|t| font.plan(t, None, None).width)
476 .max()
477 .unwrap() as i32
478 };
479 for i in 0..2usize {
480 let min_y = rect.min.y + (i + 1) as i32 * small_height;
481 self.children[index].resize(
482 rect![
483 rect.min.x + padding,
484 min_y,
485 rect.min.x + 2 * padding + max_label_width,
486 min_y + small_height
487 ],
488 hub,
489 rq,
490 context,
491 );
492 self.children[index + 1].resize(
493 rect![
494 rect.min.x + max_label_width + 3 * padding,
495 min_y,
496 rect.max.x - padding,
497 min_y + small_height
498 ],
499 hub,
500 rq,
501 context,
502 );
503 index += 2;
504 }
505 button_y += small_height;
506 } else {
507 let min_y = rect.min.y + small_height;
508 self.children[2].resize(
509 rect![
510 rect.min.x + padding,
511 min_y,
512 rect.max.x - padding,
513 min_y + small_height
514 ],
515 hub,
516 rq,
517 context,
518 );
519 index += 1;
520 }
521
522 let max_label_width = {
523 let font = font_from_style(&mut context.fonts, &NORMAL_STYLE, dpi);
524 [LABEL_SAVE, LABEL_GUESS]
525 .iter()
526 .map(|t| font.plan(t, None, None).width)
527 .max()
528 .unwrap() as i32
529 };
530
531 let button_height = 4 * x_height;
532
533 self.children[index].resize(
534 rect![
535 rect.min.x + 3 * padding,
536 button_y + small_height - button_height,
537 rect.min.x + 5 * padding + max_label_width,
538 button_y + small_height
539 ],
540 hub,
541 rq,
542 context,
543 );
544 index += 1;
545
546 self.children[index].resize(
547 rect![
548 rect.max.x - 5 * padding - max_label_width,
549 button_y + small_height - button_height,
550 rect.max.x - 3 * padding,
551 button_y + small_height
552 ],
553 hub,
554 rq,
555 context,
556 );
557 index += 1;
558
559 if !context.settings.frontlight_presets.is_empty() {
560 let presets_rect = rect![
561 rect.min.x + thickness + 4 * padding,
562 rect.max.y - small_height - 2 * padding,
563 rect.max.x - thickness - 4 * padding,
564 rect.max.y - thickness - 2 * padding
565 ];
566 self.children[index].resize(presets_rect, hub, rq, context);
567 }
568 }
569
570 fn is_background(&self) -> bool {
571 true
572 }
573
574 fn rect(&self) -> &Rectangle {
575 &self.rect
576 }
577
578 fn rect_mut(&mut self) -> &mut Rectangle {
579 &mut self.rect
580 }
581
582 fn children(&self) -> &Vec<Box<dyn View>> {
583 &self.children
584 }
585
586 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
587 &mut self.children
588 }
589
590 fn id(&self) -> Id {
591 self.id
592 }
593}