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}