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