cadmus_core/view/reader/
margin_cropper.rs

1use crate::color::{BLACK, GRAY12, WHITE};
2use crate::context::Context;
3use crate::device::CURRENT_DEVICE;
4use crate::font::Fonts;
5use crate::framebuffer::{Framebuffer, Pixmap, UpdateMode};
6use crate::geom::{BorderSpec, CornerSpec, Point, Rectangle};
7use crate::gesture::GestureEvent;
8use crate::metadata::Margin;
9use crate::unit::scale_by_dpi;
10use crate::view::rounded_button::RoundedButton;
11use crate::view::{Bus, Event, Hub, Id, RenderData, RenderQueue, View, ViewId, ID_FEEDER};
12use crate::view::{SMALL_BAR_HEIGHT, THICKNESS_MEDIUM};
13
14pub const BUTTON_DIAMETER: f32 = 30.0;
15
16pub struct MarginCropper {
17    id: Id,
18    rect: Rectangle,
19    children: Vec<Box<dyn View>>,
20    pixmap: Pixmap,
21    frame: Rectangle,
22}
23
24impl MarginCropper {
25    pub fn new(
26        rect: Rectangle,
27        pixmap: Pixmap,
28        margin: &Margin,
29        _context: &mut Context,
30    ) -> MarginCropper {
31        let id = ID_FEEDER.next();
32        let mut children = Vec::new();
33
34        let pt = pt!(
35            (rect.width() as i32 - pixmap.width as i32) / 2,
36            (rect.height() as i32 - pixmap.height as i32) / 2
37        );
38        let x_min = pt.x + (margin.left * pixmap.width as f32).round() as i32;
39        let y_min = pt.y + (margin.top * pixmap.height as f32).round() as i32;
40        let x_max =
41            pt.x + pixmap.width as i32 - (margin.right * pixmap.width as f32).round() as i32;
42        let y_max =
43            pt.y + pixmap.height as i32 - (margin.bottom * pixmap.height as f32).round() as i32;
44        let frame = rect![x_min, y_min, x_max, y_max];
45
46        let dpi = CURRENT_DEVICE.dpi;
47        let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
48        let big_button_diameter = small_height;
49        let padding = big_button_diameter / 2;
50
51        let cancel_button = RoundedButton::new(
52            "close",
53            rect![
54                rect.min.x + padding,
55                rect.max.y - padding - big_button_diameter,
56                rect.min.x + padding + big_button_diameter,
57                rect.max.y - padding
58            ],
59            Event::Cancel,
60        );
61        children.push(Box::new(cancel_button) as Box<dyn View>);
62
63        let validate_button = RoundedButton::new(
64            "check_mark-large",
65            rect![
66                rect.max.x - padding - big_button_diameter,
67                rect.max.y - padding - big_button_diameter,
68                rect.max.x - padding,
69                rect.max.y - padding
70            ],
71            Event::Validate,
72        );
73        children.push(Box::new(validate_button) as Box<dyn View>);
74
75        MarginCropper {
76            id,
77            rect,
78            children,
79            pixmap,
80            frame,
81        }
82    }
83
84    fn update(&mut self, start: Point, end: Point) {
85        let mut nearest = None;
86        let mut dmin = u32::MAX;
87
88        for i in 0..3i32 {
89            for j in 0..3i32 {
90                if i == 1 && j == 1 {
91                    continue;
92                }
93                let x = self.frame.min.x + i * self.frame.width() as i32 / 2;
94                let y = self.frame.min.y + j * self.frame.height() as i32 / 2;
95                let pt = pt!(x, y);
96                let d = pt.dist2(start);
97                if d < dmin {
98                    nearest = Some((i, j));
99                    dmin = d;
100                }
101            }
102        }
103
104        if let Some((i, j)) = nearest {
105            match (i, j) {
106                (0, 0) => self.frame.min = end,
107                (1, 0) => self.frame.min.y = end.y,
108                (1, 2) => self.frame.max.y = end.y,
109                (0, 1) => self.frame.min.x = end.x,
110                (2, 1) => self.frame.max.x = end.x,
111                (0, 2) => {
112                    self.frame.min.x = end.x;
113                    self.frame.max.y = end.y
114                }
115                (2, 0) => {
116                    self.frame.max.x = end.x;
117                    self.frame.min.y = end.y
118                }
119                (2, 2) => self.frame.max = end,
120                _ => (),
121            }
122        }
123
124        let dpi = CURRENT_DEVICE.dpi;
125        let button_radius = scale_by_dpi(BUTTON_DIAMETER / 2.0, dpi) as i32;
126
127        self.frame.min.x = self.frame.min.x.max(self.rect.min.x + button_radius);
128        self.frame.min.y = self.frame.min.y.max(self.rect.min.y + button_radius);
129        self.frame.max.x = self.frame.max.x.min(self.rect.max.x - button_radius);
130        self.frame.max.y = self.frame.max.y.min(self.rect.max.y - button_radius);
131    }
132
133    fn margin(&self) -> Margin {
134        let x_min = (self.rect.width() as i32 - self.pixmap.width as i32) / 2;
135        let y_min = (self.rect.height() as i32 - self.pixmap.height as i32) / 2;
136        let x_max = x_min + self.pixmap.width as i32;
137        let y_max = y_min + self.pixmap.height as i32;
138
139        let top = (self.frame.min.y - y_min).max(0) as f32 / self.pixmap.height as f32;
140        let right = (x_max - self.frame.max.x).max(0) as f32 / self.pixmap.width as f32;
141        let bottom = (y_max - self.frame.max.y).max(0) as f32 / self.pixmap.height as f32;
142        let left = (self.frame.min.x - x_min).max(0) as f32 / self.pixmap.width as f32;
143
144        Margin::new(top, right, bottom, left)
145    }
146}
147
148impl View for MarginCropper {
149    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
150    fn handle_event(
151        &mut self,
152        evt: &Event,
153        _hub: &Hub,
154        bus: &mut Bus,
155        rq: &mut RenderQueue,
156        _context: &mut Context,
157    ) -> bool {
158        match *evt {
159            Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
160                self.update(center, center);
161                rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
162                true
163            }
164            Event::Gesture(GestureEvent::Swipe { start, end, .. }) if self.rect.includes(start) => {
165                self.update(start, end);
166                rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
167                true
168            }
169            Event::Gesture(GestureEvent::HoldFingerShort(center, ..))
170                if self.rect.includes(center) =>
171            {
172                true
173            }
174            Event::Validate => {
175                bus.push_back(Event::CropMargins(Box::new(self.margin())));
176                bus.push_back(Event::Close(ViewId::MarginCropper));
177                true
178            }
179            Event::Cancel => {
180                bus.push_back(Event::Close(ViewId::MarginCropper));
181                true
182            }
183            _ => false,
184        }
185    }
186    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, _fonts, _rect), fields(rect = ?_rect)))]
187    fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {
188        let dpi = CURRENT_DEVICE.dpi;
189        let dx = (self.rect.width() as i32 - self.pixmap.width as i32) / 2;
190        let dy = (self.rect.height() as i32 - self.pixmap.height as i32) / 2;
191
192        fb.draw_rectangle(&self.rect, WHITE);
193        fb.draw_pixmap(&self.pixmap, pt!(dx, dy));
194
195        let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as u16;
196
197        fb.draw_blended_rectangle(
198            &rect![
199                self.rect.min.x,
200                self.rect.min.y,
201                self.frame.min.x,
202                self.frame.max.y
203            ],
204            GRAY12,
205            0.4,
206        );
207        fb.draw_blended_rectangle(
208            &rect![
209                self.rect.min.x,
210                self.frame.max.y,
211                self.frame.max.x,
212                self.rect.max.y
213            ],
214            GRAY12,
215            0.4,
216        );
217        fb.draw_blended_rectangle(
218            &rect![
219                self.frame.max.x,
220                self.frame.min.y,
221                self.rect.max.x,
222                self.rect.max.y
223            ],
224            GRAY12,
225            0.4,
226        );
227        fb.draw_blended_rectangle(
228            &rect![
229                self.frame.min.x,
230                self.rect.min.y,
231                self.rect.max.x,
232                self.frame.min.y
233            ],
234            GRAY12,
235            0.4,
236        );
237
238        fb.draw_rectangle_outline(
239            &self.frame,
240            &BorderSpec {
241                thickness: thickness as u16,
242                color: BLACK,
243            },
244        );
245
246        let button_radius = scale_by_dpi(BUTTON_DIAMETER / 2.0, dpi) as i32;
247
248        for i in 0..3i32 {
249            for j in 0..3i32 {
250                if i == 1 && j == 1 {
251                    continue;
252                }
253
254                let x = self.frame.min.x + i * self.frame.width() as i32 / 2;
255                let y = self.frame.min.y + j * self.frame.height() as i32 / 2;
256                let button_rect = rect![
257                    x - button_radius,
258                    y - button_radius,
259                    x + button_radius,
260                    y + button_radius
261                ];
262
263                fb.draw_rounded_rectangle_with_border(
264                    &button_rect,
265                    &CornerSpec::Uniform(button_radius),
266                    &BorderSpec {
267                        thickness: thickness as u16,
268                        color: BLACK,
269                    },
270                    &WHITE,
271                );
272            }
273        }
274    }
275
276    fn is_background(&self) -> bool {
277        true
278    }
279
280    fn view_id(&self) -> Option<ViewId> {
281        Some(ViewId::MarginCropper)
282    }
283
284    fn rect(&self) -> &Rectangle {
285        &self.rect
286    }
287
288    fn rect_mut(&mut self) -> &mut Rectangle {
289        &mut self.rect
290    }
291
292    fn children(&self) -> &Vec<Box<dyn View>> {
293        &self.children
294    }
295
296    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
297        &mut self.children
298    }
299
300    fn id(&self) -> Id {
301        self.id
302    }
303}