rvlib/tools/
zoom.rs

1use std::fmt::Debug;
2
3use crate::{
4    GeoFig,
5    drawme::{Annotation, BboxAnnotation, Stroke},
6    events::{Events, KeyCode},
7    history::History,
8    make_tool_transform,
9    tools::core::Manipulate,
10    tools_data::InstanceLabelDisplay,
11    types::ViewImage,
12    world::World,
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            && let (Some(mps), Some(m)) = (self.mouse_pressed_start_pos, events.mouse_pos_on_orig)
141        {
142            // animation
143            let bb = BbF::from_points(mps, m);
144            let white = [255, 255, 255];
145            let anno = BboxAnnotation {
146                geofig: GeoFig::BB(bb),
147                fill_color: None,
148                fill_alpha: 0,
149                outline: Stroke::from_color(white),
150                outline_alpha: 255,
151                label: None,
152                is_selected: None,
153                highlight_circles: vec![],
154                instance_label_display: InstanceLabelDisplay::None,
155            };
156            world.request_redraw_tmp_anno(Annotation::Bbox(anno));
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, types::ExtraIms},
200    image::DynamicImage,
201    rvimage_domain::RvResult,
202    std::path::Path,
203};
204#[cfg(test)]
205fn mk_z(x: TPtF, y: TPtF, w: TPtF, h: TPtF) -> Option<BbF> {
206    Some(BbF { x, y, w, h })
207}
208#[test]
209fn test_make_zoom() -> RvResult<()> {
210    fn test(mps: (TPtF, TPtF), mpr: (TPtF, TPtF), expected: Option<BbF>) {
211        assert_eq!(make_zoom_on_release(mps, mpr), expected);
212    }
213
214    test((0.0, 0.0), (10.0, 10.0), mk_z(0.0, 0.0, 10.0, 10.0));
215    test((0.0, 0.0), (100.0, 10.0), mk_z(0.0, 0.0, 100.0, 10.0));
216    test((13.0, 7.0), (33.0, 17.0), mk_z(13.0, 7.0, 20.0, 10.0));
217    test((5.0, 9.0), (6.0, 9.0), None);
218    test((5.0, 9.0), (17.0, 19.0), mk_z(5.0, 9.0, 12.0, 10.0));
219
220    Ok(())
221}
222#[test]
223fn test_move_zoom() -> RvResult<()> {
224    fn test(
225        mpp: (usize, usize),
226        mph: (usize, usize),
227        zoom_box: Option<BbF>,
228        expected: Option<BbF>,
229    ) {
230        let mpp = (mpp.0 as TPtF, mpp.1 as TPtF).into();
231        let mph = (mph.0 as TPtF, mph.1 as TPtF).into();
232        let shape_orig = ShapeI { w: 80, h: 80 };
233        assert_eq!(follow_zoom_box(mpp, mph, shape_orig, zoom_box), expected);
234    }
235    test(
236        (4, 4),
237        (12, 8),
238        mk_z(12.0, 16.0, 40.0, 40.0),
239        mk_z(4.0, 12.0, 40.0, 40.0),
240    );
241    Ok(())
242}
243#[test]
244fn test_on_mouse_pressed() -> RvResult<()> {
245    let mouse_pos = Some((30.0, 45.0).into());
246    let im_orig = DynamicImage::ImageRgb8(ViewImage::new(250, 500));
247    let mut z = Zoom::new();
248    let prj_path = Path::new("");
249    let world = World::from_real_im(
250        im_orig,
251        ExtraIms::default(),
252        ToolsDataMap::new(),
253        None,
254        None,
255        prj_path,
256        None,
257    );
258    let history = History::default();
259    let im_orig_old = world.data.clone();
260    let event = Events::default().mousepos_orig(mouse_pos);
261    let (res, _) = z.mouse_pressed(&event, world, history);
262    assert_eq!(res.data, im_orig_old);
263    assert_eq!(z.mouse_pressed_start_pos, mouse_pos);
264    Ok(())
265}
266
267#[test]
268fn test_on_mouse_released() -> RvResult<()> {
269    let im_orig = DynamicImage::ImageRgb8(ViewImage::new(250, 500));
270    let mut z = Zoom::new();
271    let prj_path = Path::new("");
272    let world = World::from_real_im(
273        im_orig,
274        ExtraIms::default(),
275        ToolsDataMap::new(),
276        None,
277        None,
278        prj_path,
279        None,
280    );
281
282    z.set_mouse_start_zoom((30.0, 70.0).into());
283
284    let world = z.mouse_released_left_btn(world, Some((40.0, 80.0).into()));
285    assert_eq!(
286        *world.zoom_box(),
287        Some(BbF {
288            x: 30.0,
289            y: 70.0,
290            w: 10.0,
291            h: 10.0
292        })
293    );
294    assert_eq!(z.mouse_pressed_start_pos, None);
295    Ok(())
296}
297
298#[test]
299fn test_on_mouse_held() {}