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