cadmus_core/view/
presets_list.rs

1use super::preset::{Preset, PresetKind};
2use super::{Bus, Event, Hub, Id, RenderData, RenderQueue, View, ID_FEEDER};
3use crate::color::WHITE;
4use crate::context::Context;
5use crate::device::CURRENT_DEVICE;
6use crate::font::{font_from_style, Fonts, NORMAL_STYLE};
7use crate::framebuffer::{Framebuffer, UpdateMode};
8use crate::geom::{CycleDir, Dir, Rectangle};
9use crate::gesture::GestureEvent;
10use crate::settings::LightPreset;
11
12pub struct PresetsList {
13    id: Id,
14    rect: Rectangle,
15    pages: Vec<Vec<Box<dyn View>>>,
16    current_page: usize,
17}
18
19impl PresetsList {
20    pub fn new(rect: Rectangle) -> PresetsList {
21        PresetsList {
22            id: ID_FEEDER.next(),
23            rect,
24            pages: Vec::new(),
25            current_page: 0,
26        }
27    }
28
29    pub fn update(&mut self, presets: &[LightPreset], rq: &mut RenderQueue, fonts: &mut Fonts) {
30        let dpi = CURRENT_DEVICE.dpi;
31        let font = font_from_style(fonts, &NORMAL_STYLE, dpi);
32        let x_height = font.x_heights.0 as i32;
33        let preset_height = 4 * x_height;
34        let padding = font.em() as i32;
35        let preset_width = font.plan(&presets[0].name(), None, None).width + padding;
36        let max_per_line = (self.rect.width() as i32 + padding) / (preset_width + padding);
37
38        self.pages.clear();
39        let mut children = Vec::new();
40
41        let presets_count = presets.len() as i32;
42        let first_line_count = max_per_line.min(presets_count);
43        let mut item_index = 0;
44        let mut index = 0;
45
46        let dx = (self.rect.width() as i32
47            - (first_line_count * preset_width + (first_line_count - 1) * padding))
48            / 2;
49
50        while index < presets_count {
51            let position = item_index % max_per_line;
52            let x = self.rect.min.x + dx + position * (preset_width + padding);
53            let preset_rect = rect![
54                x,
55                self.rect.max.y - preset_height,
56                x + preset_width,
57                self.rect.max.y
58            ];
59            let kind = if (position == 0 && index > 0)
60                || (position == max_per_line - 1 && index < presets_count - 1)
61            {
62                let dir = if position == 0 {
63                    CycleDir::Previous
64                } else {
65                    CycleDir::Next
66                };
67                PresetKind::Page(dir)
68            } else {
69                let name = presets[index as usize].name();
70                let kind = PresetKind::Normal(name, index as usize);
71                index += 1;
72                kind
73            };
74
75            let preset = Preset::new(preset_rect, kind);
76            children.push(Box::new(preset) as Box<dyn View>);
77            item_index += 1;
78
79            if item_index % max_per_line == 0 || index == presets_count {
80                self.pages.push(children);
81                children = Vec::new();
82            }
83        }
84
85        self.current_page = self.current_page.min(self.pages.len().saturating_sub(1));
86
87        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
88    }
89
90    pub fn set_current_page(&mut self, dir: CycleDir) {
91        match dir {
92            CycleDir::Next if self.current_page < self.pages.len() - 1 => {
93                self.current_page += 1;
94            }
95            CycleDir::Previous if self.current_page > 0 => {
96                self.current_page -= 1;
97            }
98            _ => (),
99        }
100    }
101}
102
103impl View for PresetsList {
104    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, _bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
105    fn handle_event(
106        &mut self,
107        evt: &Event,
108        _hub: &Hub,
109        _bus: &mut Bus,
110        rq: &mut RenderQueue,
111        _context: &mut Context,
112    ) -> bool {
113        match *evt {
114            Event::Gesture(GestureEvent::Swipe { dir, start, .. }) if self.rect.includes(start) => {
115                match dir {
116                    Dir::West => {
117                        self.set_current_page(CycleDir::Next);
118                        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
119                        true
120                    }
121                    Dir::East => {
122                        self.set_current_page(CycleDir::Previous);
123                        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
124                        true
125                    }
126                    _ => false,
127                }
128            }
129            Event::Page(dir) => {
130                self.set_current_page(dir);
131                rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
132                true
133            }
134            _ => false,
135        }
136    }
137
138    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, _fonts, _rect), fields(rect = ?_rect)))]
139    fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {
140        fb.draw_rectangle(&self.rect, WHITE);
141    }
142
143    fn is_background(&self) -> bool {
144        true
145    }
146
147    fn rect(&self) -> &Rectangle {
148        &self.rect
149    }
150
151    fn rect_mut(&mut self) -> &mut Rectangle {
152        &mut self.rect
153    }
154
155    fn children(&self) -> &Vec<Box<dyn View>> {
156        &self.pages[self.current_page]
157    }
158
159    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
160        &mut self.pages[self.current_page]
161    }
162
163    fn id(&self) -> Id {
164        self.id
165    }
166}