Skip to main content

cadmus_core/view/
frontlight.rs

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}