Skip to main content

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