rvlib/tools/
zoom.rs

1use std::fmt::Debug;
2
3use crate::{
4    drawme::{Annotation, BboxAnnotation, Stroke},
5    events::{Events, KeyCode},
6    history::History,
7    make_tool_transform,
8    tools::core::Manipulate,
9    types::ViewImage,
10    world::World,
11    GeoFig,
12};
13use rvimage_domain::{BbF, OutOfBoundsMode, PtF, ShapeI, TPtF};
14
15use super::core::Mover;
16const MIN_ZOOM: TPtF = 2.0;
17
18pub fn move_zoom_box(mut mover: Mover, mut world: World, mouse_pos: Option<PtF>) -> (Mover, World) {
19    let shape_orig = world.data.shape();
20    let zoom_box = *world.zoom_box();
21    let f_move = |mp_from, mp_to| follow_zoom_box(mp_from, mp_to, shape_orig, zoom_box);
22    let opt_opt_zoom_box = mover.move_mouse_held(f_move, mouse_pos);
23    if let Some(zoom_box) = opt_opt_zoom_box {
24        world.set_zoom_box(zoom_box);
25    }
26    (mover, world)
27}
28
29fn make_zoom_on_release<P>(mp_start: P, mp_release: P) -> Option<BbF>
30where
31    P: Into<PtF>,
32{
33    let mp_start = mp_start.into();
34    let mp_release = mp_release.into();
35    let x_min = mp_start.x.min(mp_release.x);
36    let y_min = mp_start.y.min(mp_release.y);
37    let x_max = mp_start.x.max(mp_release.x);
38    let y_max = mp_start.y.max(mp_release.y);
39
40    let w = x_max - x_min;
41    let h = y_max - y_min;
42    if w >= MIN_ZOOM && h >= MIN_ZOOM {
43        Some(BbF {
44            x: x_min,
45            y: y_min,
46            w,
47            h,
48        })
49    } else {
50        None
51    }
52}
53
54fn follow_zoom_box(
55    mp_from: PtF,
56    mp_to: PtF,
57    shape_orig: ShapeI,
58    zoom_box: Option<BbF>,
59) -> Option<BbF> {
60    match zoom_box {
61        // we move from mp_to to mp_from since we want the image to follow the mouse
62        // instead for the zoom-box to follow the mouse
63        Some(zb) => match zb.follow_movement(mp_to, mp_from, shape_orig, OutOfBoundsMode::Deny) {
64            Some(zb) => Some(zb),
65            None => Some(zb),
66        },
67        _ => zoom_box,
68    }
69}
70
71#[derive(Clone, Debug)]
72pub struct Zoom {
73    mouse_pressed_start_pos: Option<PtF>,
74    mover: Mover,
75    initial_view: Option<ViewImage>,
76}
77impl Zoom {
78    fn set_mouse_start_zoom(&mut self, mp: PtF) {
79        self.mouse_pressed_start_pos = Some(mp);
80    }
81
82    fn unset_mouse_start_zoom(&mut self) {
83        self.mouse_pressed_start_pos = None;
84        self.initial_view = None;
85    }
86
87    fn mouse_pressed(
88        &mut self,
89        events: &Events,
90        world: World,
91        history: History,
92    ) -> (World, History) {
93        if events.pressed(KeyCode::MouseRight) {
94            self.mover.move_mouse_pressed(events.mouse_pos_on_view);
95        } else if let Some(mp) = events.mouse_pos_on_orig {
96            self.set_mouse_start_zoom(mp);
97        }
98        (world, history)
99    }
100
101    fn mouse_released_left_btn(&mut self, mut world: World, mouse_pos: Option<PtF>) -> World {
102        let bx = if let (Some(mps), Some(mr)) = (self.mouse_pressed_start_pos, mouse_pos) {
103            make_zoom_on_release(mps, mr).or(*world.zoom_box())
104        } else {
105            *world.zoom_box()
106        };
107        world.set_zoom_box(bx);
108        world.stop_tmp_anno();
109        self.unset_mouse_start_zoom();
110        world
111    }
112
113    fn mouse_released(
114        &mut self,
115        events: &Events,
116        mut world: World,
117        history: History,
118    ) -> (World, History) {
119        if events.released(KeyCode::MouseRight) || events.held_ctrl() {
120            self.unset_mouse_start_zoom();
121            (world, history)
122        } else if events.released(KeyCode::MouseLeft) {
123            world = self.mouse_released_left_btn(world, events.mouse_pos_on_orig);
124            (world, history)
125        } else {
126            (world, history)
127        }
128    }
129
130    fn mouse_held(
131        &mut self,
132        events: &Events,
133        mut world: World,
134        history: History,
135    ) -> (World, History) {
136        if events.held(KeyCode::MouseRight) || events.held_ctrl() {
137            (self.mover, world) = move_zoom_box(self.mover, world, events.mouse_pos_on_view);
138        } else if events.held(KeyCode::MouseLeft) {
139            if let (Some(mps), Some(m)) = (self.mouse_pressed_start_pos, events.mouse_pos_on_orig) {
140                // animation
141                let bb = BbF::from_points(mps, m);
142                let white = [255, 255, 255];
143                let anno = BboxAnnotation {
144                    geofig: GeoFig::BB(bb),
145                    fill_color: None,
146                    fill_alpha: 0,
147                    outline: Stroke::from_color(white),
148                    outline_alpha: 255,
149                    label: None,
150                    is_selected: None,
151                    highlight_circles: vec![],
152                };
153                world.request_redraw_tmp_anno(Annotation::Bbox(anno));
154            }
155        }
156        (world, history)
157    }
158
159    fn key_pressed(
160        &mut self,
161        _event: &Events,
162        mut world: World,
163        history: History,
164    ) -> (World, History) {
165        world.set_zoom_box(None);
166        (world, history)
167    }
168}
169impl Manipulate for Zoom {
170    fn new() -> Zoom {
171        Zoom {
172            mouse_pressed_start_pos: None,
173            initial_view: None,
174            mover: Mover::new(),
175        }
176    }
177    fn events_tf(&mut self, world: World, history: History, events: &Events) -> (World, History) {
178        make_tool_transform!(
179            self,
180            world,
181            history,
182            events,
183            [
184                (pressed, KeyCode::MouseLeft, mouse_pressed),
185                (pressed, KeyCode::MouseRight, mouse_pressed),
186                (released, KeyCode::MouseLeft, mouse_released),
187                (held, KeyCode::MouseLeft, mouse_held),
188                (held, KeyCode::MouseRight, mouse_held),
189                (pressed, KeyCode::Back, key_pressed)
190            ]
191        )
192    }
193}
194
195#[cfg(test)]
196use {image::DynamicImage, rvimage_domain::RvResult, std::collections::HashMap, std::path::Path};
197#[cfg(test)]
198fn mk_z(x: TPtF, y: TPtF, w: TPtF, h: TPtF) -> Option<BbF> {
199    Some(BbF { x, y, w, h })
200}
201#[test]
202fn test_make_zoom() -> RvResult<()> {
203    fn test(mps: (TPtF, TPtF), mpr: (TPtF, TPtF), expected: Option<BbF>) {
204        assert_eq!(make_zoom_on_release(mps, mpr), expected);
205    }
206
207    test((0.0, 0.0), (10.0, 10.0), mk_z(0.0, 0.0, 10.0, 10.0));
208    test((0.0, 0.0), (100.0, 10.0), mk_z(0.0, 0.0, 100.0, 10.0));
209    test((13.0, 7.0), (33.0, 17.0), mk_z(13.0, 7.0, 20.0, 10.0));
210    test((5.0, 9.0), (6.0, 9.0), None);
211    test((5.0, 9.0), (17.0, 19.0), mk_z(5.0, 9.0, 12.0, 10.0));
212
213    Ok(())
214}
215#[test]
216fn test_move_zoom() -> RvResult<()> {
217    fn test(
218        mpp: (usize, usize),
219        mph: (usize, usize),
220        zoom_box: Option<BbF>,
221        expected: Option<BbF>,
222    ) {
223        let mpp = (mpp.0 as TPtF, mpp.1 as TPtF).into();
224        let mph = (mph.0 as TPtF, mph.1 as TPtF).into();
225        let shape_orig = ShapeI { w: 80, h: 80 };
226        assert_eq!(follow_zoom_box(mpp, mph, shape_orig, zoom_box), expected);
227    }
228    test(
229        (4, 4),
230        (12, 8),
231        mk_z(12.0, 16.0, 40.0, 40.0),
232        mk_z(4.0, 12.0, 40.0, 40.0),
233    );
234    Ok(())
235}
236#[test]
237fn test_on_mouse_pressed() -> RvResult<()> {
238    let mouse_pos = Some((30.0, 45.0).into());
239    let im_orig = DynamicImage::ImageRgb8(ViewImage::new(250, 500));
240    let mut z = Zoom::new();
241    let prj_path = Path::new("");
242    let world = World::from_real_im(im_orig, HashMap::new(), None, None, prj_path, None);
243    let history = History::default();
244    let im_orig_old = world.data.clone();
245    let event = Events::default().mousepos_orig(mouse_pos);
246    let (res, _) = z.mouse_pressed(&event, world, history);
247    assert_eq!(res.data, im_orig_old);
248    assert_eq!(z.mouse_pressed_start_pos, mouse_pos);
249    Ok(())
250}
251
252#[test]
253fn test_on_mouse_released() -> RvResult<()> {
254    let im_orig = DynamicImage::ImageRgb8(ViewImage::new(250, 500));
255    let mut z = Zoom::new();
256    let prj_path = Path::new("");
257    let world = World::from_real_im(im_orig, HashMap::new(), None, None, prj_path, None);
258
259    z.set_mouse_start_zoom((30.0, 70.0).into());
260
261    let world = z.mouse_released_left_btn(world, Some((40.0, 80.0).into()));
262    assert_eq!(
263        *world.zoom_box(),
264        Some(BbF {
265            x: 30.0,
266            y: 70.0,
267            w: 10.0,
268            h: 10.0
269        })
270    );
271    assert_eq!(z.mouse_pressed_start_pos, None);
272    Ok(())
273}
274
275#[test]
276fn test_on_mouse_held() {}