1use crate::geometry::{Line, Point, ResizeHandle, Shape};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Measure {
14 pub line: Line,
15 pub label: String,
16 pub monitor: usize,
19}
20
21impl Measure {
22 #[must_use]
23 pub const fn new(line: Line, monitor: usize) -> Self {
24 Self {
25 line,
26 label: String::new(),
27 monitor,
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum MeasureGrab {
35 Endpoint(bool),
37 Move,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum GrabKind {
44 Move,
46 Resize(ResizeHandle),
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct Selection {
52 pub shape: Shape,
53 pub label: String,
54 pub monitor: usize,
56 pub rot_deg: i32,
58}
59
60impl Selection {
61 pub const fn new(shape: Shape, monitor: usize) -> Self {
62 Self {
63 shape,
64 label: String::new(),
65 monitor,
66 rot_deg: 0,
67 }
68 }
69}
70
71#[derive(Debug, Clone)]
74enum UndoOp {
75 RemoveLast,
76 Restack { from: usize, to: usize },
77 RemoveAt { index: usize },
78 Reinsert { index: usize, selection: Selection },
79 RestoreShape { index: usize, shape: Shape },
80 RestoreLabel { index: usize, label: String },
81 RestoreRotation { index: usize, deg: i32 },
82 RemoveLastMeasure,
87 ReinsertMeasure { index: usize, measure: Measure },
88 RemoveMeasureAt { index: usize },
89 RestoreMeasureLine { index: usize, line: Line },
90 RestoreMeasureLabel { index: usize, label: String },
91}
92
93#[derive(Debug, Default)]
94pub struct SelectionSet {
95 items: Vec<Selection>,
96 measures: Vec<Measure>,
97 undo: Vec<UndoOp>,
98 redo: Vec<UndoOp>,
99}
100
101impl SelectionSet {
102 pub fn new() -> Self {
103 Self::default()
104 }
105
106 pub fn seed(items: Vec<Selection>, measures: Vec<Measure>) -> Self {
111 Self {
112 items,
113 measures,
114 undo: Vec::new(),
115 redo: Vec::new(),
116 }
117 }
118
119 pub fn items(&self) -> &[Selection] {
120 &self.items
121 }
122
123 pub fn len(&self) -> usize {
124 self.items.len()
125 }
126
127 pub fn is_empty(&self) -> bool {
128 self.items.is_empty()
129 }
130
131 pub fn get(&self, index: usize) -> Option<&Selection> {
132 self.items.get(index)
133 }
134
135 pub fn hit_topmost(&self, monitor: usize, p: Point) -> Option<usize> {
138 self.items
139 .iter()
140 .enumerate()
141 .rev()
142 .find(|(_, s)| s.monitor == monitor && s.shape.hit_test_rotated(s.rot_deg, p))
143 .map(|(i, _)| i)
144 }
145
146 pub fn grab_topmost(
150 &self,
151 monitor: usize,
152 p: Point,
153 tolerance: i32,
154 ) -> Option<(usize, GrabKind)> {
155 self.items
156 .iter()
157 .enumerate()
158 .rev()
159 .filter(|(_, s)| s.monitor == monitor)
160 .find_map(|(i, s)| {
161 if let Some(handle) = s.shape.resize_grab_rotated(s.rot_deg, p, tolerance) {
162 return Some((i, GrabKind::Resize(handle)));
163 }
164 if s.shape.hit_test_rotated(s.rot_deg, p) {
165 return Some((i, GrabKind::Move));
166 }
167 None
168 })
169 }
170
171 pub fn rotate(&mut self, index: usize, delta: i32) -> Option<i32> {
175 let s = self.items.get_mut(index)?;
176 if matches!(s.shape, Shape::Circle { .. }) || delta == 0 {
177 return None;
178 }
179 let previous = s.rot_deg;
180 s.rot_deg = crate::geometry::normalize_deg(s.rot_deg + delta);
181 self.undo.push(UndoOp::RestoreRotation {
182 index,
183 deg: previous,
184 });
185 self.redo.clear();
186 Some(self.items[index].rot_deg)
187 }
188
189 pub fn measures(&self) -> &[Measure] {
190 &self.measures
191 }
192
193 pub fn add_measure(&mut self, measure: Measure) {
194 self.measures.push(measure);
195 self.undo.push(UndoOp::RemoveLastMeasure);
196 self.redo.clear();
197 }
198
199 pub fn delete_measure(&mut self, index: usize) -> bool {
200 if index >= self.measures.len() {
201 return false;
202 }
203 let measure = self.measures.remove(index);
204 self.undo.push(UndoOp::ReinsertMeasure { index, measure });
205 self.redo.clear();
206 true
207 }
208
209 pub fn grab_measure(
213 &self,
214 monitor: usize,
215 p: Point,
216 tolerance: i32,
217 ) -> Option<(usize, MeasureGrab)> {
218 self.measures
219 .iter()
220 .enumerate()
221 .rev()
222 .filter(|(_, m)| m.monitor == monitor)
223 .find_map(|(i, m)| {
224 if let Some(is_a) = m.line.endpoint_grab(p, tolerance) {
225 return Some((i, MeasureGrab::Endpoint(is_a)));
226 }
227 m.line
228 .hit_test(p, tolerance)
229 .then_some((i, MeasureGrab::Move))
230 })
231 }
232
233 pub fn set_measure_line_live(&mut self, index: usize, line: Line) {
236 if let Some(m) = self.measures.get_mut(index) {
237 m.line = line;
238 }
239 }
240
241 pub fn commit_measure(&mut self, index: usize, original: Line) -> bool {
245 let Some(m) = self.measures.get(index) else {
246 return false;
247 };
248 if m.line == original {
249 return false;
250 }
251 self.undo.push(UndoOp::RestoreMeasureLine {
252 index,
253 line: original,
254 });
255 self.redo.clear();
256 true
257 }
258
259 pub fn label_measure(&mut self, index: usize, label: String) -> bool {
262 let Some(m) = self.measures.get_mut(index) else {
263 return false;
264 };
265 if m.label == label {
266 return false;
267 }
268 let previous = std::mem::replace(&mut m.label, label);
269 self.undo.push(UndoOp::RestoreMeasureLabel {
270 index,
271 label: previous,
272 });
273 self.redo.clear();
274 true
275 }
276
277 pub fn add(&mut self, selection: Selection) {
278 self.items.push(selection);
279 self.undo.push(UndoOp::RemoveLast);
280 self.redo.clear();
281 }
282
283 pub fn delete(&mut self, index: usize) -> bool {
284 if index >= self.items.len() {
285 return false;
286 }
287 let selection = self.items.remove(index);
288 self.undo.push(UndoOp::Reinsert { index, selection });
289 self.redo.clear();
290 true
291 }
292
293 pub fn set_shape_live(&mut self, index: usize, shape: Shape) {
296 if let Some(s) = self.items.get_mut(index) {
297 s.shape = shape;
298 }
299 }
300
301 pub fn commit_move(&mut self, index: usize, original: Shape) -> bool {
306 let Some(s) = self.items.get(index) else {
307 return false;
308 };
309 if s.shape == original {
310 return false;
311 }
312 self.undo.push(UndoOp::RestoreShape {
313 index,
314 shape: original,
315 });
316 self.redo.clear();
317 true
318 }
319
320 pub fn set_label(&mut self, index: usize, label: String) -> bool {
323 let Some(s) = self.items.get_mut(index) else {
324 return false;
325 };
326 if s.label == label {
327 return false;
328 }
329 let previous = std::mem::replace(&mut s.label, label);
330 self.undo.push(UndoOp::RestoreLabel {
331 index,
332 label: previous,
333 });
334 self.redo.clear();
335 true
336 }
337
338 pub fn cycle_at(&mut self, monitor: usize, p: Point) -> bool {
343 let hits: Vec<usize> = self
344 .items
345 .iter()
346 .enumerate()
347 .filter(|(_, s)| s.monitor == monitor && s.shape.hit_test_rotated(s.rot_deg, p))
348 .map(|(i, _)| i)
349 .collect();
350 let (Some(&bottom), Some(&top)) = (hits.first(), hits.last()) else {
351 return false;
352 };
353 if bottom == top {
354 return false;
355 }
356 let selection = self.items.remove(top);
357 self.items.insert(bottom, selection);
358 self.undo.push(UndoOp::Restack {
359 from: bottom,
360 to: top,
361 });
362 self.redo.clear();
363 true
364 }
365
366 pub fn undo(&mut self) -> bool {
369 let Some(op) = self.undo.pop() else {
370 return false;
371 };
372 if let Some(inverse) = self.apply(op) {
373 self.redo.push(inverse);
374 }
375 true
376 }
377
378 pub fn redo(&mut self) -> bool {
381 let Some(op) = self.redo.pop() else {
382 return false;
383 };
384 if let Some(inverse) = self.apply(op) {
385 self.undo.push(inverse);
386 }
387 true
388 }
389
390 fn apply(&mut self, op: UndoOp) -> Option<UndoOp> {
393 match op {
394 UndoOp::Restack { from, to } => {
395 if from >= self.items.len() {
396 return None;
397 }
398 let selection = self.items.remove(from);
399 let to = to.min(self.items.len());
400 self.items.insert(to, selection);
401 Some(UndoOp::Restack { from: to, to: from })
402 }
403 UndoOp::RemoveLast => {
404 let selection = self.items.pop()?;
405 Some(UndoOp::Reinsert {
406 index: self.items.len(),
407 selection,
408 })
409 }
410 UndoOp::RemoveAt { index } => {
411 if index >= self.items.len() {
412 return None;
413 }
414 let selection = self.items.remove(index);
415 Some(UndoOp::Reinsert { index, selection })
416 }
417 UndoOp::Reinsert { index, selection } => {
418 let index = index.min(self.items.len());
419 self.items.insert(index, selection);
420 Some(UndoOp::RemoveAt { index })
421 }
422 UndoOp::RestoreShape { index, shape } => {
423 let s = self.items.get_mut(index)?;
424 let previous = std::mem::replace(&mut s.shape, shape);
425 Some(UndoOp::RestoreShape {
426 index,
427 shape: previous,
428 })
429 }
430 UndoOp::RestoreLabel { index, label } => {
431 let s = self.items.get_mut(index)?;
432 let previous = std::mem::replace(&mut s.label, label);
433 Some(UndoOp::RestoreLabel {
434 index,
435 label: previous,
436 })
437 }
438 UndoOp::RestoreRotation { index, deg } => {
439 let s = self.items.get_mut(index)?;
440 let previous = std::mem::replace(&mut s.rot_deg, deg);
441 Some(UndoOp::RestoreRotation {
442 index,
443 deg: previous,
444 })
445 }
446 UndoOp::RemoveLastMeasure => {
447 let measure = self.measures.pop()?;
448 Some(UndoOp::ReinsertMeasure {
449 index: self.measures.len(),
450 measure,
451 })
452 }
453 UndoOp::ReinsertMeasure { index, measure } => {
454 let index = index.min(self.measures.len());
455 self.measures.insert(index, measure);
456 Some(UndoOp::RemoveMeasureAt { index })
457 }
458 UndoOp::RemoveMeasureAt { index } => {
459 if index >= self.measures.len() {
460 return None;
461 }
462 let measure = self.measures.remove(index);
463 Some(UndoOp::ReinsertMeasure { index, measure })
464 }
465 UndoOp::RestoreMeasureLine { index, line } => {
466 let m = self.measures.get_mut(index)?;
467 let previous = m.line;
468 m.line = line;
469 Some(UndoOp::RestoreMeasureLine {
470 index,
471 line: previous,
472 })
473 }
474 UndoOp::RestoreMeasureLabel { index, label } => {
475 let m = self.measures.get_mut(index)?;
476 let previous = std::mem::replace(&mut m.label, label);
477 Some(UndoOp::RestoreMeasureLabel {
478 index,
479 label: previous,
480 })
481 }
482 }
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 #[test]
491 fn measures_and_selections_share_one_undo_stack() {
492 let mut set = SelectionSet::new();
496 set.add(Selection::new(Shape::Rect(Rect::new(0, 0, 10, 10)), 0));
497 set.add_measure(Measure::new(
498 Line::new(Point::new(0, 0), Point::new(50, 0)),
499 0,
500 ));
501 assert_eq!((set.len(), set.measures().len()), (1, 1));
502
503 assert!(set.undo(), "undoes the measure, the most recent edit");
504 assert_eq!((set.len(), set.measures().len()), (1, 0));
505 assert!(set.undo(), "then the selection");
506 assert_eq!((set.len(), set.measures().len()), (0, 0));
507
508 assert!(set.redo());
509 assert!(set.redo());
510 assert_eq!((set.len(), set.measures().len()), (1, 1));
511 }
512
513 #[test]
514 fn a_deleted_measure_comes_back_where_it_was() {
515 let mut set = SelectionSet::new();
516 for x in [10, 20, 30] {
517 set.add_measure(Measure::new(
518 Line::new(Point::new(x, 0), Point::new(x, 40)),
519 0,
520 ));
521 }
522 assert!(set.delete_measure(1));
523 assert_eq!(set.measures().len(), 2);
524 assert!(set.undo());
525 assert_eq!(set.measures()[1].line.a.x, 20, "restored in position");
526 }
527
528 #[test]
529 fn a_measure_drag_that_ends_where_it_began_records_nothing() {
530 let mut set = SelectionSet::new();
531 let line = Line::new(Point::new(0, 0), Point::new(60, 20));
532 set.add_measure(Measure::new(line, 0));
533 set.set_measure_line_live(0, Line::new(Point::new(0, 0), Point::new(99, 99)));
534 set.set_measure_line_live(0, line);
535 assert!(
536 !set.commit_measure(0, line),
537 "a no-op drag must not dirty the session"
538 );
539 assert!(set.undo());
541 assert!(set.measures().is_empty());
542 assert!(!set.undo());
543 }
544
545 #[test]
546 fn relabeling_a_measure_is_undoable_and_a_no_op_label_is_not() {
547 let mut set = SelectionSet::new();
548 set.add_measure(Measure::new(
549 Line::new(Point::new(0, 0), Point::new(10, 0)),
550 0,
551 ));
552 assert!(set.label_measure(0, "gap".into()));
553 assert!(!set.label_measure(0, "gap".into()), "unchanged label");
554 assert!(set.undo());
555 assert_eq!(set.measures()[0].label, "");
556 }
557
558 #[test]
559 fn grabbing_prefers_an_endpoint_over_the_line_and_respects_the_monitor() {
560 let mut set = SelectionSet::new();
561 set.add_measure(Measure::new(
562 Line::new(Point::new(0, 0), Point::new(100, 0)),
563 0,
564 ));
565 assert_eq!(
566 set.grab_measure(0, Point::new(1, 1), 6),
567 Some((0, MeasureGrab::Endpoint(true)))
568 );
569 assert_eq!(
570 set.grab_measure(0, Point::new(50, 2), 6),
571 Some((0, MeasureGrab::Move))
572 );
573 assert_eq!(set.grab_measure(0, Point::new(50, 40), 6), None);
574 assert_eq!(
575 set.grab_measure(1, Point::new(1, 1), 6),
576 None,
577 "a measure belongs to one monitor's frame"
578 );
579 }
580 use crate::geometry::Rect;
581
582 fn rect_at(x: i32) -> Shape {
583 Shape::Rect(Rect::new(x, 0, 100, 100))
584 }
585
586 #[test]
587 fn hit_topmost_prefers_most_recent() {
588 let mut set = SelectionSet::new();
589 set.add(Selection::new(rect_at(0), 0));
590 set.add(Selection::new(rect_at(50), 0));
591 assert_eq!(set.hit_topmost(0, Point::new(60, 10)), Some(1));
593 assert_eq!(set.hit_topmost(0, Point::new(10, 10)), Some(0));
594 assert_eq!(set.hit_topmost(0, Point::new(500, 500)), None);
595 }
596
597 #[test]
598 fn hit_topmost_filters_by_monitor() {
599 let mut set = SelectionSet::new();
600 set.add(Selection::new(rect_at(0), 1));
601 assert_eq!(set.hit_topmost(0, Point::new(10, 10)), None);
602 assert_eq!(set.hit_topmost(1, Point::new(10, 10)), Some(0));
603 }
604
605 #[test]
606 fn undo_add_removes_it() {
607 let mut set = SelectionSet::new();
608 set.add(Selection::new(rect_at(0), 0));
609 assert!(set.undo());
610 assert!(set.is_empty());
611 }
612
613 #[test]
614 fn undo_delete_reinserts_at_original_position() {
615 let mut set = SelectionSet::new();
616 set.add(Selection::new(rect_at(0), 0));
617 set.add(Selection::new(rect_at(200), 0));
618 set.add(Selection::new(rect_at(400), 0));
619 assert!(set.delete(1));
620 assert_eq!(set.len(), 2);
621 assert!(set.undo());
622 assert_eq!(set.items()[1].shape, rect_at(200));
623 }
624
625 #[test]
626 fn undo_move_restores_original_shape() {
627 let mut set = SelectionSet::new();
628 set.add(Selection::new(rect_at(0), 0));
629 let original = set.items()[0].shape.clone();
630 set.set_shape_live(0, rect_at(300));
631 set.commit_move(0, original.clone());
632 assert!(set.undo());
633 assert_eq!(set.items()[0].shape, original);
634 }
635
636 #[test]
637 fn commit_move_without_change_records_nothing() {
638 let mut set = SelectionSet::new();
639 set.add(Selection::new(rect_at(0), 0));
640 let original = set.items()[0].shape.clone();
641 set.commit_move(0, original.clone());
642 assert!(set.undo());
644 assert!(set.is_empty());
645 assert!(!set.undo());
646 }
647
648 #[test]
649 fn noop_label_and_move_record_nothing() {
650 let mut set = SelectionSet::new();
651 set.add(Selection::new(rect_at(0), 0));
652 assert!(!set.set_label(0, String::new()));
654 assert!(set.set_label(0, "named".into()));
655 assert!(!set.set_label(0, "named".into()));
656 let original = set.items()[0].shape.clone();
658 assert!(!set.commit_move(0, original));
659 assert!(set.undo());
661 assert_eq!(set.items()[0].label, "");
662 assert!(set.undo());
663 assert!(!set.undo());
664 }
665
666 #[test]
667 fn undo_label_restores_previous_text() {
668 let mut set = SelectionSet::new();
669 set.add(Selection::new(rect_at(0), 0));
670 set.set_label(0, "first".into());
671 set.set_label(0, "second".into());
672 assert!(set.undo());
673 assert_eq!(set.items()[0].label, "first");
674 assert!(set.undo());
675 assert_eq!(set.items()[0].label, "");
676 }
677
678 #[test]
679 fn cycling_overlap_reaches_the_shape_beneath_and_undoes() {
680 let mut set = SelectionSet::new();
681 let mut below = Selection::new(Shape::Rect(Rect::new(0, 0, 100, 100)), 0);
682 below.label = "below".into();
683 let mut above = Selection::new(Shape::Rect(Rect::new(10, 10, 50, 50)), 0);
684 above.label = "above".into();
685 set.add(below);
686 set.add(above);
687 let p = Point::new(20, 20);
688 assert_eq!(set.items()[set.hit_topmost(0, p).unwrap()].label, "above");
689
690 assert!(set.cycle_at(0, p));
691 assert_eq!(
692 set.items()[set.hit_topmost(0, p).unwrap()].label,
693 "below",
694 "the shape beneath is now grabbable"
695 );
696 assert!(set.cycle_at(0, p));
698 assert_eq!(set.items()[set.hit_topmost(0, p).unwrap()].label, "above");
699 assert!(set.undo());
701 assert!(set.undo());
702 assert_eq!(set.items()[set.hit_topmost(0, p).unwrap()].label, "above");
703 assert_eq!(set.items()[0].label, "below", "original order restored");
704 }
705
706 #[test]
707 fn get_returns_the_selection_or_nothing() {
708 let mut set = SelectionSet::new();
709 set.add(Selection::new(Shape::Rect(Rect::new(1, 1, 2, 2)), 0));
710 assert!(set.get(0).is_some());
711 assert!(set.get(1).is_none());
712 }
713
714 #[test]
715 fn cycling_needs_at_least_two_shapes_under_the_point() {
716 let mut set = SelectionSet::new();
717 set.add(Selection::new(Shape::Rect(Rect::new(0, 0, 10, 10)), 0));
718 assert!(
719 !set.cycle_at(0, Point::new(5, 5)),
720 "one shape: nothing to cycle"
721 );
722 assert!(
723 !set.cycle_at(0, Point::new(50, 50)),
724 "no shape: nothing to cycle"
725 );
726 }
727
728 #[test]
729 fn seeding_restores_items_without_undo_history() {
730 let mut sel = Selection::new(Shape::Rect(Rect::new(1, 2, 3, 4)), 0);
731 sel.label = "kept".into();
732 sel.rot_deg = 30;
733 let mut set = SelectionSet::seed(vec![sel], Vec::new());
734 assert_eq!(set.len(), 1);
735 assert_eq!(set.items()[0].label, "kept");
736 assert!(!set.undo(), "the resume point is the floor of history");
737 set.delete(0);
739 assert!(set.undo());
740 assert_eq!(set.len(), 1);
741 }
742
743 #[test]
744 fn redo_reapplies_undone_edits_across_every_op_kind() {
745 let mut set = SelectionSet::new();
746 set.add(Selection::new(Shape::Rect(Rect::new(0, 0, 10, 10)), 0));
747 set.set_label(0, "a".into());
748 set.rotate(0, 45);
749 set.set_shape_live(0, Shape::Rect(Rect::new(5, 5, 10, 10)));
750 set.commit_move(0, Shape::Rect(Rect::new(0, 0, 10, 10)));
751 set.delete(0);
752 while set.undo() {}
753 assert!(set.is_empty(), "everything unwinds");
754 while set.redo() {}
755 assert!(set.is_empty(), "the final delete replays too");
756 assert!(set.undo());
758 let s = &set.items()[0];
759 assert_eq!(s.label, "a");
760 assert_eq!(s.rot_deg, 45);
761 assert_eq!(s.shape, Shape::Rect(Rect::new(5, 5, 10, 10)));
762 }
763
764 #[test]
765 fn a_new_edit_empties_the_redo_branch() {
766 let mut set = SelectionSet::new();
767 set.add(Selection::new(Shape::Rect(Rect::new(0, 0, 10, 10)), 0));
768 assert!(set.undo());
769 set.add(Selection::new(Shape::Rect(Rect::new(9, 9, 5, 5)), 0));
770 assert!(!set.redo(), "a new edit forked history; redo is gone");
771 }
772
773 #[test]
774 fn redo_with_nothing_undone_is_false() {
775 let mut set = SelectionSet::new();
776 assert!(!set.redo());
777 set.add(Selection::new(Shape::Rect(Rect::new(0, 0, 4, 4)), 0));
778 assert!(!set.redo(), "un-undone edits leave nothing to redo");
779 }
780
781 #[test]
782 fn undo_is_lifo_across_mixed_ops() {
783 let mut set = SelectionSet::new();
784 set.add(Selection::new(rect_at(0), 0));
785 set.set_label(0, "a".into());
786 set.delete(0);
787 assert!(set.undo()); assert_eq!(set.items()[0].label, "a");
789 assert!(set.undo()); assert_eq!(set.items()[0].label, "");
791 assert!(set.undo()); assert!(set.is_empty());
793 assert!(!set.undo());
794 }
795
796 #[test]
797 fn delete_out_of_range_is_false() {
798 let mut set = SelectionSet::new();
799 assert!(!set.delete(0));
800 }
801
802 #[test]
803 fn rotate_records_undo_and_skips_circles() {
804 let mut set = SelectionSet::new();
805 set.add(Selection::new(rect_at(0), 0));
806 set.add(Selection::new(Shape::Circle { cx: 0, cy: 0, r: 9 }, 0));
807
808 assert_eq!(set.rotate(0, 15), Some(15));
809 assert_eq!(set.rotate(0, -30), Some(345));
810 assert_eq!(set.rotate(1, 15), None, "circles don't rotate");
811 assert_eq!(set.rotate(0, 0), None, "zero delta is a no-op");
812
813 assert!(set.undo());
814 assert_eq!(set.items()[0].rot_deg, 15);
815 assert!(set.undo());
816 assert_eq!(set.items()[0].rot_deg, 0);
817 }
818
819 #[test]
820 fn rotated_selection_hit_follows_rotation() {
821 let mut set = SelectionSet::new();
822 set.add(Selection::new(Shape::Rect(Rect::new(100, 100, 200, 20)), 0));
825 set.rotate(0, 90);
826 assert_eq!(set.hit_topmost(0, Point::new(200, 30)), Some(0));
827 assert_eq!(set.hit_topmost(0, Point::new(290, 110)), None);
828 }
829
830 #[test]
831 fn grab_prefers_border_resize_over_interior_move() {
832 let mut set = SelectionSet::new();
833 set.add(Selection::new(rect_at(0), 0)); let Some((0, GrabKind::Resize(_))) = set.grab_topmost(0, Point::new(1, 50), 5) else {
836 panic!("expected a resize grab on the edge");
837 };
838 assert_eq!(
840 set.grab_topmost(0, Point::new(50, 50), 5),
841 Some((0, GrabKind::Move))
842 );
843 assert_eq!(set.grab_topmost(0, Point::new(500, 500), 5), None);
845 }
846
847 #[test]
848 fn grab_topmost_shape_shadows_lower_border() {
849 let mut set = SelectionSet::new();
850 set.add(Selection::new(rect_at(0), 0)); set.add(Selection::new(Shape::Rect(Rect::new(50, 0, 200, 100)), 0)); assert_eq!(
855 set.grab_topmost(0, Point::new(100, 50), 5),
856 Some((1, GrabKind::Move))
857 );
858 }
859}