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