rvlib/tools/
brush.rs

1use brush_data::BrushToolData;
2use std::{cmp::Ordering, mem, sync::mpsc::Receiver, thread};
3
4use super::{
5    BRUSH_NAME, Manipulate,
6    core::{
7        HeldKey, Mover, ReleasedKey, change_annos, check_autopaste, check_erase_mode,
8        check_instance_label_display_change, deselect_all, instance_label_display_sort,
9        label_change_key, map_held_key, map_released_key, on_selection_keys,
10    },
11    instance_anno_shared::get_rot90_data,
12};
13use crate::{
14    Annotation, BrushAnnotation, Line, ShapeI, annotations_accessor_mut,
15    cfg::ExportPath,
16    events::{Events, KeyCode},
17    history::{History, Record},
18    instance_annotations_accessor, make_tool_transform,
19    meta_data::MetaData,
20    result::trace_ok_err,
21    tools::{
22        core::{check_recolorboxes, check_trigger_history_update, check_trigger_redraw},
23        instance_anno_shared::{check_cocoimport, predictive_labeling},
24    },
25    tools_data::{
26        self, ExportAsCoco, InstanceAnnotate, LabelInfo, Rot90ToolData,
27        annotations::{BrushAnnotations, InstanceAnnotations},
28        brush_data::{self, MAX_INTENSITY, MAX_THICKNESS, MIN_INTENSITY, MIN_THICKNESS},
29        coco_io::to_per_file_crowd,
30        vis_from_lfoption,
31    },
32    tools_data_accessors, tools_data_accessors_objects,
33    util::Visibility,
34    world::World,
35    world_annotations_accessor,
36};
37use rvimage_domain::{BrushLine, Canvas, PtF, TPtF};
38
39pub const ACTOR_NAME: &str = "Brush";
40const MISSING_ANNO_MSG: &str = "brush annotations have not yet been initialized";
41const MISSING_DATA_MSG: &str = "brush data not available";
42annotations_accessor_mut!(ACTOR_NAME, brush_mut, MISSING_ANNO_MSG, BrushAnnotations);
43world_annotations_accessor!(ACTOR_NAME, brush, MISSING_ANNO_MSG, BrushAnnotations);
44instance_annotations_accessor!(Canvas);
45tools_data_accessors!(
46    ACTOR_NAME,
47    MISSING_DATA_MSG,
48    brush_data,
49    BrushToolData,
50    brush,
51    brush_mut
52);
53tools_data_accessors_objects!(
54    ACTOR_NAME,
55    MISSING_DATA_MSG,
56    brush_data,
57    BrushToolData,
58    brush,
59    brush_mut
60);
61pub(super) fn change_annos_brush(world: &mut World, change: impl FnOnce(&mut BrushAnnotations)) {
62    change_annos::<_, DataAccessors, InstanceAnnoAccessors>(world, change);
63}
64
65fn import_coco(
66    meta_data: &MetaData,
67    coco_file: &ExportPath,
68    rot90_data: Option<&Rot90ToolData>,
69) -> Option<BrushToolData> {
70    trace_ok_err(tools_data::coco_io::read_coco(meta_data, coco_file, rot90_data).map(|(_, d)| d))
71}
72
73fn max_select_dist(shape: ShapeI) -> TPtF {
74    (TPtF::from(shape.w.pow(2) + shape.h.pow(2)).sqrt() / 100.0).max(50.0)
75}
76
77fn draw_erase_circle(mut world: World, mp: PtF) -> World {
78    let show_only_current = get_specific(&world).map(|d| d.label_info.show_only_current);
79    let options = get_options(&world).copied();
80    let idx_current = get_specific(&world).map(|d| d.label_info.cat_idx_current);
81    if let Some(options) = options {
82        let erase = |annos: &mut BrushAnnotations| {
83            let to_be_removed_line_idx = find_closest_canvas(annos, mp, |idx| {
84                annos.is_of_current_label(idx, idx_current, show_only_current)
85            });
86            if let Some((idx, _)) = to_be_removed_line_idx {
87                let canvas = annos.edit(idx);
88                trace_ok_err(canvas.draw_circle(mp, options.thickness, 0));
89            }
90        };
91        change_annos_brush(&mut world, erase);
92        set_visible(&mut world);
93    }
94    world
95}
96fn mouse_released(events: &Events, mut world: World, mut history: History) -> (World, History) {
97    if events.held_ctrl() {
98        let shape_orig = world.shape_orig();
99        let show_only_current = get_specific(&world).map(|d| d.label_info.show_only_current);
100        let idx_current = get_specific(&world).map(|d| d.label_info.cat_idx_current);
101        if let (Some(mp), Some(annos)) = (events.mouse_pos_on_orig, get_annos_mut(&mut world)) {
102            let to_be_selected_line_idx = find_closest_canvas(annos, mp, |idx| {
103                annos.is_of_current_label(idx, idx_current, show_only_current)
104            });
105            if let Some((idx, dist)) = to_be_selected_line_idx {
106                if dist < max_select_dist(shape_orig) {
107                    if annos.selected_mask()[idx] {
108                        annos.deselect(idx);
109                    } else {
110                        annos.select(idx);
111                    }
112                } else {
113                    world =
114                        deselect_all::<_, DataAccessors, InstanceAnnoAccessors>(world, BRUSH_NAME);
115                }
116            }
117        }
118        set_visible(&mut world);
119    } else if !(events.held_alt() || events.held_shift()) {
120        // neither shift nor alt nor ctrl were held => a brushline has been finished
121        // or a brush line has been deleted.
122        let erase = get_options(&world).map(|o| o.core.erase);
123        let cat_idx = get_specific(&world).map(|o| o.label_info.cat_idx_current);
124        if erase != Some(true) {
125            let shape_orig = world.shape_orig();
126            let line = get_specific_mut(&mut world).and_then(|d| mem::take(&mut d.tmp_line));
127            let line = if let Some((line, _)) = line {
128                Some(line)
129            } else if let (Some(mp), Some(options)) =
130                (events.mouse_pos_on_orig, get_options(&world))
131            {
132                Some(BrushLine {
133                    line: Line::from(mp),
134                    intensity: options.intensity,
135                    thickness: options.thickness,
136                })
137            } else {
138                None
139            };
140            let ild = get_instance_label_display(&world);
141
142            let change_annos = |annos: &mut BrushAnnotations| {
143                if let (Some(line), Some(cat_idx)) = (line, cat_idx) {
144                    let canvas = Canvas::new(&line, shape_orig, None);
145                    if let Ok(canvas) = canvas {
146                        annos.add_elt(canvas, cat_idx, ild);
147                    }
148                }
149            };
150            change_annos_brush(&mut world, change_annos);
151            set_visible(&mut world);
152        } else if let Some(mp) = events.mouse_pos_on_orig {
153            world = draw_erase_circle(world, mp);
154        }
155        history.push(Record::new(world.clone(), ACTOR_NAME));
156    }
157    (world, history)
158}
159fn mouse_pressed_left(events: &Events, mut world: World) -> World {
160    if !(events.held_alt() || events.held_ctrl() || events.held_shift()) {
161        world = deselect_all::<_, DataAccessors, InstanceAnnoAccessors>(world, BRUSH_NAME);
162    }
163    if !events.held_ctrl() {
164        let options = get_options(&world).copied();
165        let idx_current = get_specific(&world).map(|d| d.label_info.cat_idx_current);
166        if let (Some(mp), Some(options)) = (events.mouse_pos_on_orig, options) {
167            let erase = options.core.erase;
168            if !erase && let (Some(d), Some(cat_idx)) = (get_specific_mut(&mut world), idx_current)
169            {
170                let line = Line::from(mp);
171                d.tmp_line = Some((
172                    BrushLine {
173                        line,
174                        intensity: options.intensity,
175                        thickness: options.thickness,
176                    },
177                    cat_idx,
178                ));
179            }
180        }
181        set_visible(&mut world);
182    }
183    world
184}
185fn key_released(events: &Events, mut world: World, mut history: History) -> (World, History) {
186    let released_key = map_released_key(events);
187    (world, history) = on_selection_keys::<_, DataAccessors, InstanceAnnoAccessors>(
188        world,
189        history,
190        released_key,
191        events.held_ctrl(),
192        BRUSH_NAME,
193    );
194    let mut trigger_redraw = false;
195    if let Some(label_info) = get_specific_mut(&mut world).map(|s| &mut s.label_info) {
196        (*label_info, trigger_redraw) = label_change_key(released_key, mem::take(label_info));
197    }
198    if trigger_redraw {
199        let visible = get_options(&world).map(|o| o.core.visible) == Some(true);
200        let vis = vis_from_lfoption(get_label_info(&world), visible);
201        world.request_redraw_annotations(BRUSH_NAME, vis);
202    }
203    match released_key {
204        ReleasedKey::H if events.held_ctrl() => {
205            // Hide all boxes (selected or not)
206            if let Some(options_mut) = get_options_mut(&mut world) {
207                options_mut.core.visible = !options_mut.core.visible;
208            }
209            let vis = get_visible(&world);
210            world.request_redraw_annotations(BRUSH_NAME, vis);
211        }
212        _ => (),
213    }
214    world = check_instance_label_display_change::<_, DataAccessors, InstanceAnnoAccessors>(
215        world,
216        released_key,
217        ACTOR_NAME,
218    );
219    world = check_erase_mode::<DataAccessors>(released_key, set_visible, world);
220    (world, history)
221}
222fn find_closest_canvas(
223    annos: &BrushAnnotations,
224    p: PtF,
225    predicate: impl Fn(usize) -> bool,
226) -> Option<(usize, f64)> {
227    annos
228        .elts()
229        .iter()
230        .enumerate()
231        .map(|(i, cvs)| {
232            (
233                i,
234                cvs.dist_to_boundary(p) * if cvs.contains(p) { 0.0 } else { 1.0 },
235            )
236        })
237        .filter(|(i, _)| predicate(*i))
238        .min_by(|(_, x), (_, y)| match x.partial_cmp(y) {
239            Some(o) => o,
240            None => Ordering::Greater,
241        })
242}
243
244fn check_selected_intensity_thickness(mut world: World) -> World {
245    let options = get_options(&world).copied();
246    let annos = get_annos_mut(&mut world);
247    let mut any_selected = false;
248    if let (Some(annos), Some(options)) = (annos, options)
249        && options.is_selection_change_needed
250    {
251        for brushline in annos.selected_elts_iter_mut() {
252            brushline.intensity = options.intensity;
253            any_selected = true;
254        }
255    }
256    let options_mut = get_options_mut(&mut world);
257    if let Some(options_mut) = options_mut {
258        options_mut.is_selection_change_needed = false;
259        if any_selected {
260            options_mut.core.is_redraw_annos_triggered = true;
261        }
262    }
263    world
264}
265
266fn check_export(mut world: World) -> World {
267    let options = get_options(&world);
268    let specifics = get_specific(&world);
269
270    if options.map(|o| o.core.import_export_trigger.export_triggered()) == Some(true) {
271        let rot90_data = get_rot90_data(&world).cloned();
272        if let Some(data) = specifics {
273            let meta_data = world.data.meta_data.clone();
274            let mut data = data.clone();
275            let per_file_crowd = options.map(|o| o.per_file_crowd) == Some(true);
276            let double_check_shape =
277                options.map(|o| o.core.doublecheck_cocoexport_shape) == Some(true);
278            let f_export = move || {
279                let start = std::time::Instant::now();
280                if per_file_crowd {
281                    to_per_file_crowd(&mut data.annotations_map);
282                }
283                let coco_file_conn = data.cocofile_conn();
284                match tools_data::write_coco(
285                    &meta_data,
286                    data,
287                    rot90_data.as_ref(),
288                    &coco_file_conn,
289                    double_check_shape,
290                ) {
291                    Ok((p, _)) => tracing::info!("export to {p:?} successfully triggered"),
292                    Err(e) => tracing::error!("trigger export failed due to {e:?}"),
293                };
294                tracing::info!("export took {} seconds", start.elapsed().as_secs_f32());
295            };
296            thread::spawn(f_export);
297        }
298        if let Some(options_mut) = get_options_mut(&mut world) {
299            options_mut.core.import_export_trigger.untrigger_export();
300        }
301    }
302    world
303}
304
305pub(super) fn on_mouse_held_right(
306    mouse_pos: Option<PtF>,
307    mover: &mut Mover,
308    mut world: World,
309    history: History,
310) -> (World, History) {
311    if get_options(&world).map(|o| o.core.erase) != Some(true) {
312        let orig_shape = world.data.shape();
313        let move_boxes = |mpo_from, mpo_to| {
314            let annos = get_annos_mut(&mut world);
315            if let Some(annos) = annos {
316                let (mut elts, cat_idxs, selected_mask) = mem::take(annos).separate_data();
317                for (i, anno) in elts.iter_mut().enumerate() {
318                    if selected_mask[i] {
319                        anno.follow_movement(mpo_from, mpo_to, orig_shape);
320                    }
321                }
322                *annos = InstanceAnnotations::new(elts, cat_idxs, selected_mask).unwrap();
323            }
324            Some(())
325        };
326        mover.move_mouse_held(move_boxes, mouse_pos);
327        let vis = get_visible(&world);
328        world.request_redraw_annotations(ACTOR_NAME, vis);
329    }
330    (world, history)
331}
332#[derive(Debug)]
333pub struct Brush {
334    mover: Mover,
335    prediction_receiver: Option<Receiver<(World, History)>>,
336}
337
338impl Brush {
339    fn mouse_pressed(
340        &mut self,
341        events: &Events,
342        mut world: World,
343        history: History,
344    ) -> (World, History) {
345        if events.pressed(KeyCode::MouseRight) {
346            self.mover.move_mouse_pressed(events.mouse_pos_on_orig);
347        } else {
348            world = mouse_pressed_left(events, world);
349        }
350        (world, history)
351    }
352    fn mouse_held(
353        &mut self,
354        events: &Events,
355        mut world: World,
356        history: History,
357    ) -> (World, History) {
358        if events.held(KeyCode::MouseRight) {
359            on_mouse_held_right(events.mouse_pos_on_orig, &mut self.mover, world, history)
360        } else {
361            if !events.held_ctrl() {
362                let options = get_options(&world).copied();
363                if let (Some(mp), Some(options)) = (events.mouse_pos_on_orig, options) {
364                    if options.core.erase {
365                        world = draw_erase_circle(world, mp);
366                    } else {
367                        let line = if let Some((line, _)) =
368                            get_specific_mut(&mut world).and_then(|d| d.tmp_line.as_mut())
369                        {
370                            let last_point = line.line.last_point();
371                            let dist = if let Some(last_point) = last_point {
372                                last_point.dist_square(&mp)
373                            } else {
374                                100.0
375                            };
376                            if dist >= 3.0 {
377                                line.line.push(mp);
378                            }
379                            Some(line.clone())
380                        } else {
381                            None
382                        };
383                        if let (Some(line), Some(color)) = (
384                            line,
385                            get_specific(&world)
386                                .map(|d| d.label_info.colors()[d.label_info.cat_idx_current]),
387                        ) {
388                            let orig_shape = world.shape_orig();
389                            let canvas_with_new_buffer = || {
390                                let lower_buffer_bound = 100;
391                                let extension_factor = if line.line.points.len() < 10 {
392                                    4.0
393                                } else if line.line.points.len() < 50 {
394                                    3.0
395                                } else {
396                                    2.0
397                                };
398                                Canvas::from_line_extended(
399                                    &line,
400                                    orig_shape,
401                                    extension_factor,
402                                    lower_buffer_bound,
403                                )
404                            };
405                            let canvas = if let Some(buffer) =
406                                mem::take(&mut world.update_view.tmp_anno_buffer)
407                            {
408                                match buffer {
409                                    Annotation::Brush(brush_anno) => {
410                                        tracing::debug!("found buffer for tmp anno");
411                                        Canvas::new(&line, orig_shape, Some(brush_anno.canvas.mask))
412                                    }
413                                    _ => canvas_with_new_buffer(),
414                                }
415                            } else {
416                                canvas_with_new_buffer()
417                            };
418
419                            let canvas = trace_ok_err(canvas);
420                            if let Some(canvas) = canvas {
421                                world.request_redraw_tmp_anno(Annotation::Brush(BrushAnnotation {
422                                    canvas,
423                                    color,
424                                    label: None,
425                                    is_selected: None,
426                                    fill_alpha: options.fill_alpha,
427                                    instance_display_label: options.core.instance_label_display,
428                                }));
429                            }
430                        }
431                    }
432                }
433            }
434
435            (world, history)
436        }
437    }
438
439    fn mouse_released(
440        &mut self,
441        events: &Events,
442        world: World,
443        history: History,
444    ) -> (World, History) {
445        mouse_released(events, world, history)
446    }
447
448    #[allow(clippy::unused_self)]
449    fn key_released(
450        &mut self,
451        events: &Events,
452        world: World,
453        history: History,
454    ) -> (World, History) {
455        key_released(events, world, history)
456    }
457    fn key_held(
458        &mut self,
459        events: &Events,
460        mut world: World,
461        history: History,
462    ) -> (World, History) {
463        const INTENSITY_STEP: f64 = MAX_INTENSITY / 20.0;
464        const THICKNESS_STEP: f64 = MAX_THICKNESS / 20.0;
465        let held_key = map_held_key(events);
466        let snap_to_step = |x: TPtF, step: TPtF| {
467            if x < 2.0 * step {
468                (x.div_euclid(step)) * step
469            } else {
470                x
471            }
472        };
473        match held_key {
474            HeldKey::I if events.held_alt() => {
475                if let Some(o) = get_options_mut(&mut world) {
476                    o.intensity = MIN_INTENSITY
477                        .max(snap_to_step(o.intensity - INTENSITY_STEP, INTENSITY_STEP));
478                    o.is_selection_change_needed = true;
479                }
480            }
481            HeldKey::I => {
482                if let Some(o) = get_options_mut(&mut world) {
483                    o.intensity = MAX_INTENSITY
484                        .min(snap_to_step(o.intensity + INTENSITY_STEP, INTENSITY_STEP));
485                    o.is_selection_change_needed = true;
486                }
487            }
488            HeldKey::T if events.held_alt() => {
489                if let Some(o) = get_options_mut(&mut world) {
490                    o.thickness = MIN_THICKNESS
491                        .max(snap_to_step(o.thickness - THICKNESS_STEP, THICKNESS_STEP));
492                    o.is_selection_change_needed = true;
493                }
494            }
495            HeldKey::T => {
496                if let Some(o) = get_options_mut(&mut world) {
497                    o.thickness = MAX_THICKNESS
498                        .min(snap_to_step(o.thickness + THICKNESS_STEP, THICKNESS_STEP));
499                    o.is_selection_change_needed = true;
500                }
501            }
502            HeldKey::None => (),
503        }
504        (world, history)
505    }
506}
507
508impl Clone for Brush {
509    fn clone(&self) -> Self {
510        Self {
511            mover: self.mover,
512            prediction_receiver: None, // JoinHandle cannot be cloned
513        }
514    }
515}
516impl Manipulate for Brush {
517    fn new() -> Self {
518        Self {
519            mover: Mover::new(),
520            prediction_receiver: None,
521        }
522    }
523
524    fn on_filechange(&mut self, mut world: World, mut history: History) -> (World, History) {
525        use_currentimageshape_for_annos(&mut world);
526
527        let brush_data = get_specific_mut(&mut world);
528        if let Some(brush_data) = brush_data {
529            for (_, (anno, _)) in brush_data.anno_iter_mut() {
530                anno.deselect_all();
531            }
532            let ild = get_instance_label_display(&world);
533            world = instance_label_display_sort::<_, DataAccessors, InstanceAnnoAccessors>(
534                world, ild, ACTOR_NAME,
535            );
536        }
537        (world, history) =
538            check_autopaste::<_, DataAccessors, InstanceAnnoAccessors>(world, history, ACTOR_NAME);
539        set_visible(&mut world);
540        (world, history)
541    }
542    fn on_activate(&mut self, mut world: World) -> World {
543        if let Some(data) = trace_ok_err(get_data_mut(&mut world)) {
544            data.menu_active = true;
545        }
546        set_visible(&mut world);
547        world
548    }
549    fn on_deactivate(&mut self, mut world: World) -> World {
550        if let Some(data) = trace_ok_err(get_data_mut(&mut world)) {
551            data.menu_active = false;
552        }
553        world.request_redraw_annotations(BRUSH_NAME, Visibility::None);
554        world
555    }
556    fn on_always_active_zoom(&mut self, mut world: World, history: History) -> (World, History) {
557        let visible = get_options(&world).map(|o| o.core.visible) == Some(true);
558        let vis = vis_from_lfoption(get_label_info(&world), visible);
559        world.request_redraw_annotations(BRUSH_NAME, vis);
560        (world, history)
561    }
562    fn events_tf(
563        &mut self,
564        mut world: World,
565        mut history: History,
566        events: &Events,
567    ) -> (World, History) {
568        world = check_trigger_redraw::<DataAccessors>(world, BRUSH_NAME);
569        (world, history) =
570            check_trigger_history_update::<DataAccessors>(world, history, BRUSH_NAME);
571        let imported;
572        (world, imported) = check_cocoimport::<_, _, DataAccessors>(
573            world,
574            get_specific,
575            get_specific_mut,
576            import_coco,
577        );
578        if imported {
579            set_visible(&mut world);
580        }
581        predictive_labeling::<DataAccessors>(
582            &mut world,
583            &mut history,
584            ACTOR_NAME,
585            &mut self.prediction_receiver,
586        );
587        world = check_recolorboxes::<DataAccessors>(world, BRUSH_NAME);
588        world = check_selected_intensity_thickness(world);
589        world = check_export(world);
590        make_tool_transform!(
591            self,
592            world,
593            history,
594            events,
595            [
596                (pressed, KeyCode::MouseLeft, mouse_pressed),
597                (pressed, KeyCode::MouseRight, mouse_pressed),
598                (held, KeyCode::MouseLeft, mouse_held),
599                (held, KeyCode::MouseRight, mouse_held),
600                (released, KeyCode::MouseLeft, mouse_released),
601                (released, KeyCode::Back, key_released),
602                (released, KeyCode::Delete, key_released),
603                (released, KeyCode::A, key_released),
604                (released, KeyCode::C, key_released),
605                (released, KeyCode::D, key_released),
606                (released, KeyCode::E, key_released),
607                (released, KeyCode::H, key_released),
608                (held, KeyCode::I, key_held),
609                (released, KeyCode::L, key_released),
610                (held, KeyCode::T, key_held),
611                (released, KeyCode::V, key_released),
612                (released, KeyCode::Key1, key_released),
613                (released, KeyCode::Key2, key_released),
614                (released, KeyCode::Key3, key_released),
615                (released, KeyCode::Key4, key_released),
616                (released, KeyCode::Key5, key_released),
617                (released, KeyCode::Key6, key_released),
618                (released, KeyCode::Key7, key_released),
619                (released, KeyCode::Key8, key_released),
620                (released, KeyCode::Key9, key_released)
621            ]
622        )
623    }
624}
625
626#[cfg(test)]
627use {
628    crate::{
629        tracing_setup::init_tracing_for_tests,
630        types::{ExtraIms, ViewImage},
631    },
632    image::DynamicImage,
633};
634
635#[cfg(test)]
636pub fn test_data() -> (Option<PtF>, World, History) {
637    use std::path::Path;
638
639    use crate::ToolsDataMap;
640    let im_test = DynamicImage::ImageRgb8(ViewImage::new(64, 64));
641    let mut world = World::from_real_im(
642        im_test,
643        ExtraIms::default(),
644        ToolsDataMap::new(),
645        None,
646        Some("superimage.png".to_string()),
647        Path::new("superimage.png"),
648        Some(0),
649    );
650    world.data.meta_data.flags.is_loading_screen_active = Some(false);
651    get_specific_mut(&mut world)
652        .unwrap()
653        .label_info
654        .push("label".to_string(), None, None)
655        .unwrap();
656    let history = History::default();
657    let mouse_pos = Some((32.0, 32.0).into());
658    (mouse_pos, world, history)
659}
660
661#[test]
662fn test_mouse_released() {
663    init_tracing_for_tests();
664    let (mp, mut world, history) = test_data();
665    let options = get_options_mut(&mut world).unwrap();
666    options.thickness = 1.0;
667    let mut events = Events::default();
668    events.mouse_pos_on_orig = mp;
669    let (world, history) = mouse_released(&events, world, history);
670    let annos = get_annos(&world).unwrap();
671    assert_eq!(annos.len(), 1);
672    assert_eq!(annos.elts()[0].bb.x, 32);
673    assert_eq!(annos.elts()[0].bb.y, 32);
674    events.mouse_pos_on_orig = Some((40, 40).into());
675    let world = mouse_pressed_left(&events, world);
676    let (world, history) = mouse_released(&events, world, history);
677    let annos = get_annos(&world).unwrap();
678    assert_eq!(annos.len(), 2);
679    assert_eq!(annos.elts()[0].bb.x, 32);
680    assert_eq!(annos.elts()[0].bb.y, 32);
681    assert_eq!(annos.elts()[1].bb.x, 40);
682    assert_eq!(annos.elts()[1].bb.y, 40);
683    events.mouse_pos_on_orig = Some((10, 10).into());
684    let (world, _) = mouse_released(&events, world, history);
685    let annos = get_annos(&world).unwrap();
686    assert_eq!(annos.len(), 3);
687    assert_eq!(annos.elts()[0].bb.x, 32);
688    assert_eq!(annos.elts()[0].bb.y, 32);
689    assert_eq!(annos.elts()[1].bb.x, 40);
690    assert_eq!(annos.elts()[1].bb.y, 40);
691    assert_eq!(annos.elts()[2].bb.x, 10);
692    assert_eq!(annos.elts()[2].bb.y, 10);
693}