759 lines
22 KiB
Rust
759 lines
22 KiB
Rust
//! Production canvas event regressions for exact multi-selection and group gestures.
|
|
use super::{DiagramCanvas, State};
|
|
use crate::app::{
|
|
Message,
|
|
multiselect::{Choice, Event as SelectionEvent, context},
|
|
};
|
|
use crate::diagram_selection::Selection;
|
|
use crate::diagram_usage::Target;
|
|
use iced::widget::canvas::{self, Program};
|
|
use iced::{Point, Rectangle, Size, Vector, event::Status, keyboard, mouse};
|
|
use oa_core::*;
|
|
|
|
fn position(x: f32, y: f32) -> Position {
|
|
Position { x, y }
|
|
}
|
|
fn bounds() -> Rectangle {
|
|
Rectangle::new(Point::new(260., 100.), Size::new(2400., 1800.))
|
|
}
|
|
|
|
struct Fixture {
|
|
project: Project,
|
|
diagram: Id,
|
|
a: Id,
|
|
b: Id,
|
|
child: Id,
|
|
internal: Id,
|
|
context: crate::route_edit::Context,
|
|
}
|
|
|
|
impl Fixture {
|
|
fn new() -> Self {
|
|
let mut project = Project::empty();
|
|
let a = project
|
|
.add_element(Kind::Activity, Layer::Oa, "A", None)
|
|
.unwrap();
|
|
let b = project
|
|
.add_element(Kind::Activity, Layer::Oa, "B", None)
|
|
.unwrap();
|
|
let c = project
|
|
.add_element(Kind::Activity, Layer::Oa, "C", None)
|
|
.unwrap();
|
|
let child = project
|
|
.add_element(Kind::Activity, Layer::Oa, "Child", Some(a))
|
|
.unwrap();
|
|
let internal = project.relate(RelationKind::Interaction, "Internal", a, b);
|
|
let external = project.relate(RelationKind::Interaction, "External", b, c);
|
|
let diagram = project.ensure_diagram(Layer::Oa);
|
|
project.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Overview;
|
|
let a = project.place(diagram, a, position(100., 200.));
|
|
let b = project.place(diagram, b, position(500., 200.));
|
|
let child = project.place(diagram, child, position(120., 280.));
|
|
project.place(diagram, c, position(900., 200.));
|
|
let view = project.diagrams.get_mut(&diagram).unwrap();
|
|
view.nodes.get_mut(&a).unwrap().collapsed = true;
|
|
view.routes = [
|
|
(internal, position(400., 320.)),
|
|
(external, position(800., 340.)),
|
|
]
|
|
.into_iter()
|
|
.map(|(edge, point)| EdgeRoute {
|
|
edge,
|
|
source_attachment: None,
|
|
target_attachment: None,
|
|
waypoints: vec![point],
|
|
label: Some(EdgeLabel {
|
|
fraction: 0.4,
|
|
offset: position(3., -9.),
|
|
}),
|
|
})
|
|
.collect();
|
|
validate_candidate(&project).unwrap();
|
|
Self {
|
|
project,
|
|
diagram,
|
|
a,
|
|
b,
|
|
child,
|
|
internal,
|
|
context: crate::route_edit::Context {
|
|
document: Id::new_v4(),
|
|
diagram,
|
|
revision: 7,
|
|
epoch: 11,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn selection(&self, targets: impl IntoIterator<Item = Target>) -> Selection {
|
|
let mut selection = Selection::new(context(self.context).scope);
|
|
for target in targets {
|
|
assert!(selection.add(target, |_| true));
|
|
}
|
|
selection
|
|
}
|
|
|
|
fn pair(&self) -> Selection {
|
|
self.selection([Target::Placement(self.a), Target::Placement(self.b)])
|
|
}
|
|
|
|
fn canvas<'a>(&'a self, selection: &'a Selection, zoom: f32) -> DiagramCanvas<'a> {
|
|
let selected = match selection.primary() {
|
|
Some(Target::Placement(id)) => self.project.diagrams[&self.diagram].nodes[id].element,
|
|
Some(Target::Edge(id)) => *id,
|
|
Some(Target::Port { port, .. }) => *port,
|
|
_ => self.diagram,
|
|
};
|
|
DiagramCanvas {
|
|
selection: Some(selection),
|
|
project: &self.project,
|
|
view_index: None,
|
|
diagram: self.diagram,
|
|
selected,
|
|
navigation_focus: None,
|
|
zoom,
|
|
pan: Vector::new(20., 30.),
|
|
context: self.context,
|
|
selected_bend: None,
|
|
selected_attachment: None,
|
|
editable: true,
|
|
placing: false,
|
|
connection: None,
|
|
allocation: None,
|
|
}
|
|
}
|
|
|
|
fn screen(&self, placement: Id, zoom: f32) -> Point {
|
|
let p = self.project.diagrams[&self.diagram].nodes[&placement].position;
|
|
Point::new(
|
|
bounds().x + (p.x + 30.) * zoom + 20.,
|
|
bounds().y + (p.y + 25.) * zoom + 30.,
|
|
)
|
|
}
|
|
|
|
fn expected_move(&self) -> Diagram {
|
|
// Independent literal expectations: the folded child moves with A,
|
|
// only the internal edge's authored world pin translates.
|
|
let mut expected = self.project.diagrams[&self.diagram].clone();
|
|
expected.nodes.get_mut(&self.a).unwrap().position = position(140., 260.);
|
|
expected.nodes.get_mut(&self.b).unwrap().position = position(540., 260.);
|
|
expected.nodes.get_mut(&self.child).unwrap().position = position(160., 340.);
|
|
expected.routes[0].waypoints = vec![position(440., 380.)];
|
|
expected
|
|
}
|
|
}
|
|
|
|
fn input(
|
|
c: &DiagramCanvas<'_>,
|
|
state: &mut State,
|
|
event: canvas::Event,
|
|
viewport: Rectangle,
|
|
final_cursor: Point,
|
|
) -> (Option<Message>, Status) {
|
|
c.update(
|
|
state,
|
|
&event,
|
|
viewport,
|
|
mouse::Cursor::Available(final_cursor),
|
|
)
|
|
.map_or((None, Status::Ignored), |action| {
|
|
let (message, _, status) = action.into_inner();
|
|
(message, status)
|
|
})
|
|
}
|
|
|
|
fn pointer(
|
|
c: &DiagramCanvas<'_>,
|
|
state: &mut State,
|
|
event: mouse::Event,
|
|
final_cursor: Point,
|
|
) -> (Option<Message>, Status) {
|
|
input(
|
|
c,
|
|
state,
|
|
canvas::Event::Mouse(event),
|
|
bounds(),
|
|
final_cursor,
|
|
)
|
|
}
|
|
|
|
fn start(c: &DiagramCanvas<'_>, state: &mut State, press: Point, final_cursor: Point) {
|
|
assert!(
|
|
pointer(
|
|
c,
|
|
state,
|
|
mouse::Event::CursorMoved { position: press },
|
|
final_cursor
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
let (message, status) = pointer(
|
|
c,
|
|
state,
|
|
mouse::Event::ButtonPressed(mouse::Button::Left),
|
|
final_cursor,
|
|
);
|
|
assert!(message.is_none());
|
|
assert_eq!(status, Status::Captured);
|
|
assert!(state.group.is_some());
|
|
assert!(state.drag.is_none());
|
|
}
|
|
|
|
fn key(named: keyboard::key::Named) -> canvas::Event {
|
|
canvas::Event::Keyboard(keyboard::Event::KeyPressed {
|
|
key: keyboard::Key::Named(named),
|
|
modified_key: keyboard::Key::Named(named),
|
|
physical_key: keyboard::key::Physical::Code(if named == keyboard::key::Named::Escape {
|
|
keyboard::key::Code::Escape
|
|
} else {
|
|
keyboard::key::Code::Delete
|
|
}),
|
|
location: keyboard::Location::Standard,
|
|
modifiers: keyboard::Modifiers::empty(),
|
|
text: None,
|
|
repeat: false,
|
|
})
|
|
}
|
|
|
|
fn expect_move(
|
|
message: Option<Message>,
|
|
selection: &Selection,
|
|
current: crate::route_edit::Context,
|
|
expected: Position,
|
|
) -> crate::diagram_selection::Snapshot {
|
|
let Some(Message::DiagramSelection(SelectionEvent::Move(snapshot, delta))) = message else {
|
|
panic!("expected one captured group Move");
|
|
};
|
|
assert_eq!(delta, expected);
|
|
assert!(selection.matches(&snapshot, context(current)));
|
|
snapshot
|
|
}
|
|
|
|
#[test]
|
|
fn shift_press_uses_ordered_hit_not_native_batch_final_cursor() {
|
|
let f = Fixture::new();
|
|
let selection = f.selection([Target::Placement(f.b)]);
|
|
let before = selection.snapshot(context(f.context)).unwrap();
|
|
let c = f.canvas(&selection, 1.);
|
|
let mut state = State::default();
|
|
let at_a = f.screen(f.a, 1.);
|
|
let final_cursor = f.screen(f.b, 1.);
|
|
assert!(
|
|
input(
|
|
&c,
|
|
&mut state,
|
|
canvas::Event::Keyboard(keyboard::Event::ModifiersChanged(
|
|
keyboard::Modifiers::SHIFT
|
|
)),
|
|
bounds(),
|
|
final_cursor
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: at_a },
|
|
final_cursor
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
let (message, status) = pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonPressed(mouse::Button::Left),
|
|
final_cursor,
|
|
);
|
|
assert_eq!(status, Status::Captured);
|
|
assert!(
|
|
matches!(message, Some(Message::DiagramSelection(SelectionEvent::Choose(captured, Target::Placement(id), Choice::Toggle))) if captured == context(f.context) && id == f.a)
|
|
);
|
|
assert!(
|
|
state.group.is_none()
|
|
&& state.drag.is_none()
|
|
&& state.route_drag.is_none()
|
|
&& state.port_drag.is_none()
|
|
);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
final_cursor
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert!(
|
|
selection.matches(&before, context(f.context)),
|
|
"canvas emits intent without mutating selection"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn group_canvas_preview_and_release_match_independent_expected_geometry() {
|
|
for zoom in [0.5, 1., 2.] {
|
|
let f = Fixture::new();
|
|
let selection = f.pair();
|
|
let captured = selection.snapshot(context(f.context)).unwrap();
|
|
let c = f.canvas(&selection, zoom);
|
|
let mut state = State::default();
|
|
let press = f.screen(f.a, zoom);
|
|
let end = press + Vector::new(40. * zoom, 60. * zoom);
|
|
start(&c, &mut state, press, end);
|
|
assert_eq!(c.preview_diagram(&state), f.project.diagrams[&f.diagram]);
|
|
let (message, status) = pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: end },
|
|
end,
|
|
);
|
|
assert!(message.is_none());
|
|
assert_eq!(status, Status::Captured);
|
|
assert_eq!(c.preview_diagram(&state), f.expected_move());
|
|
let (message, status) = pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
end,
|
|
);
|
|
assert_eq!(status, Status::Captured);
|
|
let snapshot = expect_move(message, &selection, f.context, position(40., 60.));
|
|
assert_eq!(snapshot, captured);
|
|
assert!(state.group.is_none());
|
|
let ids = crate::app::multiselect::placements(&snapshot).unwrap();
|
|
let command =
|
|
diagram_edit::translate_placements(&f.project, f.diagram, &ids, position(40., 60.))
|
|
.unwrap();
|
|
let actual = prepare_command(&f.project, &command).unwrap().project;
|
|
let mut expected = f.project.clone();
|
|
expected.diagrams.insert(f.diagram, f.expected_move());
|
|
assert_eq!(actual, expected);
|
|
assert!(selection.matches(&captured, context(f.context)));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn stationary_group_click_does_not_use_motion_after_release_in_same_batch() {
|
|
let f = Fixture::new();
|
|
let selection = f.pair();
|
|
let c = f.canvas(&selection, 1.);
|
|
let mut state = State::default();
|
|
let press = f.screen(f.a, 1.);
|
|
let end = press + Vector::new(80., 70.);
|
|
start(&c, &mut state, press, end);
|
|
let (message, _) = pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
end,
|
|
);
|
|
assert!(
|
|
matches!(message, Some(Message::DiagramSelection(SelectionEvent::Collapse(captured, Target::Placement(id)))) if selection.matches(&captured, context(f.context)) && id == f.a)
|
|
);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: end },
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert_eq!(c.preview_diagram(&state), f.project.diagrams[&f.diagram]);
|
|
}
|
|
|
|
#[test]
|
|
fn mixed_set_keeps_click_lifecycle_and_refuses_only_actual_drag() {
|
|
for moved in [false, true] {
|
|
let f = Fixture::new();
|
|
let selection = f.selection([Target::Placement(f.a), Target::Edge(f.internal)]);
|
|
let captured = selection.snapshot(context(f.context)).unwrap();
|
|
let c = f.canvas(&selection, 1.);
|
|
let mut state = State::default();
|
|
let press = f.screen(f.a, 1.);
|
|
let end = if moved {
|
|
press + Vector::new(40., 60.)
|
|
} else {
|
|
press + Vector::new(2., 1.)
|
|
};
|
|
start(&c, &mut state, press, end);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: end },
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert_eq!(c.preview_diagram(&state), f.project.diagrams[&f.diagram]);
|
|
let (message, status) = pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
end,
|
|
);
|
|
assert_eq!(status, Status::Captured);
|
|
if moved {
|
|
assert!(
|
|
matches!(message, Some(Message::DiagramSelection(SelectionEvent::Refuse(snapshot, reason))) if snapshot == captured && reason.contains("only placed elements"))
|
|
);
|
|
} else {
|
|
assert!(
|
|
matches!(message, Some(Message::DiagramSelection(SelectionEvent::Collapse(current, Target::Placement(id)))) if current == captured && id == f.a)
|
|
);
|
|
}
|
|
assert!(state.group.is_none());
|
|
assert!(selection.matches(&captured, context(f.context)));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn escape_cancels_live_group_without_clearing_or_publishing_a_model_intent() {
|
|
let f = Fixture::new();
|
|
let selection = f.pair();
|
|
let captured = selection.snapshot(context(f.context)).unwrap();
|
|
let c = f.canvas(&selection, 1.);
|
|
let mut state = State::default();
|
|
let press = f.screen(f.a, 1.);
|
|
let end = press + Vector::new(40., 60.);
|
|
start(&c, &mut state, press, end);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: end },
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert_eq!(c.preview_diagram(&state), f.expected_move());
|
|
let (message, status) = input(
|
|
&c,
|
|
&mut state,
|
|
key(keyboard::key::Named::Escape),
|
|
bounds(),
|
|
end,
|
|
);
|
|
assert!(message.is_none());
|
|
assert_eq!(status, Status::Captured);
|
|
assert!(state.group.is_none());
|
|
assert_eq!(c.preview_diagram(&state), f.project.diagrams[&f.diagram]);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert!(selection.matches(&captured, context(f.context)));
|
|
// A subsequent Escape, after the gesture is idle, may clear selection.
|
|
assert!(matches!(
|
|
input(
|
|
&c,
|
|
&mut state,
|
|
key(keyboard::key::Named::Escape),
|
|
bounds(),
|
|
end
|
|
)
|
|
.0,
|
|
Some(Message::DiagramSelection(SelectionEvent::Clear(_)))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn delete_is_captured_without_hiding_during_live_group_then_release_moves_once() {
|
|
let f = Fixture::new();
|
|
let selection = f.pair();
|
|
let c = f.canvas(&selection, 1.);
|
|
let mut state = State::default();
|
|
let press = f.screen(f.a, 1.);
|
|
let end = press + Vector::new(40., 60.);
|
|
start(&c, &mut state, press, end);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: end },
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
let (message, status) = input(
|
|
&c,
|
|
&mut state,
|
|
key(keyboard::key::Named::Delete),
|
|
bounds(),
|
|
end,
|
|
);
|
|
assert!(message.is_none());
|
|
assert_eq!(status, Status::Captured);
|
|
assert!(state.group.is_some());
|
|
assert_eq!(c.preview_diagram(&state), f.expected_move());
|
|
expect_move(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
end,
|
|
)
|
|
.0,
|
|
&selection,
|
|
f.context,
|
|
position(40., 60.),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn middle_pan_excludes_group_start_and_delete_even_when_left_is_pressed() {
|
|
let f = Fixture::new();
|
|
let selection = f.pair();
|
|
let captured = selection.snapshot(context(f.context)).unwrap();
|
|
let c = f.canvas(&selection, 1.);
|
|
let mut state = State::default();
|
|
let press = f.screen(f.a, 1.);
|
|
let end = press + Vector::new(40., 60.);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: press },
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonPressed(mouse::Button::Middle),
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
let (message, status) = pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonPressed(mouse::Button::Left),
|
|
end,
|
|
);
|
|
assert!(message.is_none());
|
|
assert_eq!(status, Status::Captured);
|
|
assert!(state.group.is_none() && state.drag.is_none());
|
|
assert!(
|
|
input(
|
|
&c,
|
|
&mut state,
|
|
key(keyboard::key::Named::Delete),
|
|
bounds(),
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert!(
|
|
matches!(pointer(&c, &mut state, mouse::Event::CursorMoved { position: end }, end).0, Some(Message::Pan(delta)) if delta == Vector::new(40., 60.))
|
|
);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Middle),
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert_eq!(c.preview_diagram(&state), f.project.diagrams[&f.diagram]);
|
|
assert!(selection.matches(&captured, context(f.context)));
|
|
}
|
|
|
|
#[test]
|
|
fn changed_viewport_or_context_retires_group_without_a_release_intent() {
|
|
for change in 0..7 {
|
|
let f = Fixture::new();
|
|
let selection = f.pair();
|
|
let c = f.canvas(&selection, 1.);
|
|
let mut state = State::default();
|
|
let press = f.screen(f.a, 1.);
|
|
let end = press + Vector::new(40., 60.);
|
|
start(&c, &mut state, press, end);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: end },
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
let mut changed = c;
|
|
let mut viewport = bounds();
|
|
match change {
|
|
0 => viewport.x += 1.,
|
|
1 => viewport.width -= 1.,
|
|
2 => changed.pan.x += 1.,
|
|
3 => changed.zoom = 2.,
|
|
4 => changed.context.epoch += 1,
|
|
5 => changed.context.revision += 1,
|
|
_ => changed.context.document = Id::new_v4(),
|
|
}
|
|
let (message, _) = input(
|
|
&changed,
|
|
&mut state,
|
|
canvas::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)),
|
|
viewport,
|
|
end,
|
|
);
|
|
assert!(
|
|
message.is_none(),
|
|
"changed viewport/context {change} must not publish a commit or click"
|
|
);
|
|
assert!(state.group.is_none());
|
|
assert_eq!(
|
|
changed.preview_diagram(&state),
|
|
f.project.diagrams[&f.diagram]
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn changed_selection_generation_rejects_press_even_after_membership_is_restored() {
|
|
let f = Fixture::new();
|
|
let mut selection = f.pair();
|
|
let press = f.screen(f.a, 1.);
|
|
let end = press + Vector::new(40., 60.);
|
|
let mut state = State::default();
|
|
{
|
|
let c = f.canvas(&selection, 1.);
|
|
start(&c, &mut state, press, end);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: end },
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
}
|
|
assert!(selection.remove(&Target::Placement(f.b)));
|
|
assert!(selection.add(Target::Placement(f.b), |_| true));
|
|
let c = f.canvas(&selection, 1.);
|
|
let (message, status) = pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
end,
|
|
);
|
|
assert!(message.is_none());
|
|
assert_eq!(status, Status::Captured);
|
|
assert!(state.group.is_none());
|
|
assert_eq!(c.preview_diagram(&state), f.project.diagrams[&f.diagram]);
|
|
}
|
|
|
|
#[test]
|
|
fn initial_press_without_pointer_history_consumes_release_only_displacement() {
|
|
let f = Fixture::new();
|
|
let selection = f.pair();
|
|
let c = f.canvas(&selection, 1.);
|
|
let mut state = State::default();
|
|
let press = f.screen(f.a, 1.);
|
|
let end = press + Vector::new(40., 60.);
|
|
// With no earlier ordered move, the supplied release cursor is authoritative.
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonPressed(mouse::Button::Left),
|
|
press
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert!(state.group.is_some());
|
|
expect_move(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
end,
|
|
)
|
|
.0,
|
|
&selection,
|
|
f.context,
|
|
position(40., 60.),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn threshold_crossing_then_return_to_origin_emits_zero_move_not_singleton_click() {
|
|
let f = Fixture::new();
|
|
let selection = f.pair();
|
|
let c = f.canvas(&selection, 1.);
|
|
let mut state = State::default();
|
|
let press = f.screen(f.a, 1.);
|
|
let end = press + Vector::new(40., 60.);
|
|
start(&c, &mut state, press, end);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: end },
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert!(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::CursorMoved { position: press },
|
|
end
|
|
)
|
|
.0
|
|
.is_none()
|
|
);
|
|
assert_eq!(c.preview_diagram(&state), f.project.diagrams[&f.diagram]);
|
|
expect_move(
|
|
pointer(
|
|
&c,
|
|
&mut state,
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
end,
|
|
)
|
|
.0,
|
|
&selection,
|
|
f.context,
|
|
position(0., 0.),
|
|
);
|
|
assert_eq!(selection.targets().len(), 2);
|
|
}
|