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 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 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 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 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, }
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}