Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0409a481c7 | ||
|
|
916ec5c78b |
@@ -188,28 +188,93 @@ fn complete_fallback_scene_selects_but_cannot_capture_or_finish_coordinate_gestu
|
||||
);
|
||||
assert!(input.press.is_none() && input.source.is_none());
|
||||
}
|
||||
// A gesture already captured in a Ready frame is cancelled when capability is revoked.
|
||||
scene.coordinate_input = true;
|
||||
scene.interaction = Some((context, CanvasTool::Select, true));
|
||||
// Capture from an actual Ready presentation, then revoke coordinate input
|
||||
// with the complete fallback frame of the same Saved representation.
|
||||
let Representation::Saved { diagram } = rep else {
|
||||
unreachable!()
|
||||
};
|
||||
let mut ready_project = f.project.clone();
|
||||
ready_project.diagrams.get_mut(&diagram).unwrap().layout =
|
||||
Some(DiagramLayout::Scenario(ScenarioLayout::default()));
|
||||
let ready_original = ready_project.clone();
|
||||
let ready = state.display(&ready_project, f.owner, f.call).unwrap();
|
||||
assert!(matches!(
|
||||
ready.status(),
|
||||
presentation::PresentationStatus::Ready
|
||||
));
|
||||
let mut ready_scene = ready.into_scene().unwrap();
|
||||
let line = ready_scene
|
||||
.lines
|
||||
.iter()
|
||||
.find(|line| line.id == f.call)
|
||||
.unwrap();
|
||||
let ready_point = Point::new(
|
||||
(line.points[0].x + line.points.last().unwrap().x) / 2.,
|
||||
line.points[0].y,
|
||||
);
|
||||
let hit = ready_scene.hit_item(ready_point).unwrap();
|
||||
assert_eq!(hit.id, f.call);
|
||||
assert_eq!(hit.role, SceneHitRole::MessageBody);
|
||||
let stamp = spacing::Stamp {
|
||||
context,
|
||||
camera_epoch: 17,
|
||||
};
|
||||
ready_scene.coordinate_input = true;
|
||||
ready_scene.interaction = Some((context, CanvasTool::Select, true));
|
||||
ready_scene.spacing_stamp = Some(stamp);
|
||||
let mut input = SequenceInput::default();
|
||||
let _ = canvas::Program::update(
|
||||
&scene,
|
||||
let begin = canvas::Program::update(
|
||||
&ready_scene,
|
||||
&mut input,
|
||||
&canvas::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
|
||||
bounds,
|
||||
mouse::Cursor::Available(point),
|
||||
);
|
||||
assert!(input.press.is_some());
|
||||
scene.coordinate_input = false;
|
||||
mouse::Cursor::Available(ready_point),
|
||||
)
|
||||
.and_then(|action| action.into_inner().0)
|
||||
.unwrap();
|
||||
let Message::ScenarioCanvas(CanvasEvent::Spacing(spacing::Event::Begin {
|
||||
stamp: captured_stamp,
|
||||
token,
|
||||
target: spacing::Target::Message(target),
|
||||
..
|
||||
})) = begin
|
||||
else {
|
||||
panic!("Ready Saved message must begin a stamped spacing gesture");
|
||||
};
|
||||
assert_eq!(captured_stamp, stamp);
|
||||
assert_eq!(target, f.call);
|
||||
assert!(matches!(input.press, Some(SequencePress::Spacing {
|
||||
stamp: held_stamp, token: held_token
|
||||
}) if held_stamp == stamp && held_token == token));
|
||||
scene.interaction = Some((context, CanvasTool::Select, true));
|
||||
assert!(!scene.coordinate_input && scene.spacing_stamp.is_none());
|
||||
let release = canvas::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left));
|
||||
let result = canvas::Program::update(
|
||||
&scene,
|
||||
&mut input,
|
||||
&canvas::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)),
|
||||
&release,
|
||||
bounds,
|
||||
mouse::Cursor::Available(point),
|
||||
mouse::Cursor::Available(ready_point),
|
||||
)
|
||||
.and_then(|action| action.into_inner().0);
|
||||
assert!(
|
||||
matches!(result, Some(Message::ScenarioCanvas(CanvasEvent::Spacing(
|
||||
spacing::Event::Cancel { stamp: cancelled_stamp, token: cancelled_token }
|
||||
))) if cancelled_stamp == stamp && cancelled_token == token)
|
||||
);
|
||||
assert!(result.and_then(|action| action.into_inner().0).is_none());
|
||||
assert!(input.press.is_none());
|
||||
assert!(input.press.is_none() && input.source.is_none());
|
||||
assert!(
|
||||
canvas::Program::update(
|
||||
&scene,
|
||||
&mut input,
|
||||
&release,
|
||||
bounds,
|
||||
mouse::Cursor::Available(ready_point),
|
||||
)
|
||||
.and_then(|action| action.into_inner().0)
|
||||
.is_none()
|
||||
);
|
||||
assert_eq!(ready_project, ready_original);
|
||||
assert_eq!(f.project, original);
|
||||
}
|
||||
|
||||
|
||||
@@ -328,3 +328,94 @@ fn zoomed_out_message_tolerance_matches_actual_canvas_hit() {
|
||||
state.begin_spacing(doc.project(), event).unwrap();
|
||||
assert_eq!(state.spacing_token(), Some(token));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fractional_screen_packet_preserves_subpixel_motion_in_preview_and_finish() {
|
||||
let (doc, owner, diagram, _, message) = fixture();
|
||||
let original = doc.snapshot();
|
||||
assert!(doc.project().diagrams[&diagram].layout.is_none());
|
||||
let mut state = State::default();
|
||||
let document = Id::new_v4();
|
||||
state.enter_representation(document, owner, Representation::Saved { diagram });
|
||||
state.presentation.viewport = Some(Size::new(900., 600.));
|
||||
state.presentation.fit = false;
|
||||
state.presentation.zoom = Some(0.5);
|
||||
// 406 + 2^-15 puts the press just below the 512 screen-coordinate boundary.
|
||||
state.presentation.pan = Vector::new(0., f32::from_bits(0x43cb_0001));
|
||||
let camera = state.camera_boundary();
|
||||
let context = state.context(document, owner, doc.revision(), 4).unwrap();
|
||||
let baseline = resolve(doc.project(), context.representation, message).unwrap();
|
||||
let line = baseline
|
||||
.scene
|
||||
.lines
|
||||
.iter()
|
||||
.find(|line| line.id == message)
|
||||
.unwrap();
|
||||
assert_eq!(line.points[0].y, 148.);
|
||||
let begin = packet(doc.project(), &state, context, 9, Target::Message(message));
|
||||
let Event::Begin {
|
||||
press_screen,
|
||||
canvas_bounds,
|
||||
scale,
|
||||
pan,
|
||||
..
|
||||
} = begin
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
assert_eq!(canvas_bounds.y, 23.);
|
||||
assert_eq!(scale, 0.5);
|
||||
assert_eq!(pan.y.to_bits(), 0x43cb_0001);
|
||||
assert_eq!(press_screen.y.to_bits(), 0x43fb_8001); // 503 + 2^-15.
|
||||
let end = press_screen + Vector::new(0., 10.);
|
||||
assert_eq!(end.y, 513.);
|
||||
assert!(canvas_bounds.contains(press_screen) && canvas_bounds.contains(end));
|
||||
// The submitted f32 displacement is 10 - 2^-15, not exactly 10.
|
||||
assert_eq!((end.y - press_screen.y).to_bits(), 0x411f_ffe0);
|
||||
assert_eq!(((end.y - press_screen.y) / scale).to_bits(), 0x419f_ffe0);
|
||||
// Independent oracle: 148 + (10 - 2^-15) / .5 = 168 - 2^-14.
|
||||
let expected_y = f32::from_bits(0x4327_fffc);
|
||||
let mut expected = original.decoded.project.clone();
|
||||
expected.diagrams.get_mut(&diagram).unwrap().layout =
|
||||
Some(oa_core::DiagramLayout::Scenario(oa_core::ScenarioLayout {
|
||||
lanes: vec![],
|
||||
messages: vec![oa_core::ScenarioMessagePosition {
|
||||
address: oa_core::OccurrenceEndpoint {
|
||||
references: vec![],
|
||||
occurrence: message,
|
||||
},
|
||||
send_y: expected_y,
|
||||
}],
|
||||
}));
|
||||
state.begin_spacing(doc.project(), begin).unwrap();
|
||||
state.move_spacing(end);
|
||||
let preview = state.spacing.as_ref().unwrap().preview.as_ref().unwrap();
|
||||
assert_eq!(preview.project, expected);
|
||||
let line = preview
|
||||
.geometry
|
||||
.scene
|
||||
.lines
|
||||
.iter()
|
||||
.find(|line| line.id == message)
|
||||
.unwrap();
|
||||
assert_eq!(line.points[0].y.to_bits(), expected_y.to_bits());
|
||||
let Finish::Commit {
|
||||
command,
|
||||
selected,
|
||||
scale,
|
||||
pan,
|
||||
} = state.finish_spacing(end).unwrap()
|
||||
else {
|
||||
panic!("fractional motion must remain a commit")
|
||||
};
|
||||
assert_eq!(selected, message);
|
||||
assert_eq!(scale, 0.5);
|
||||
assert_eq!(pan.y.to_bits(), 0x43cb_0001);
|
||||
let committed = oa_core::execute_batch(doc.project(), &[command]).unwrap();
|
||||
assert_eq!(committed, expected);
|
||||
assert!(state.spacing_token().is_none());
|
||||
assert_eq!(doc.project(), &original.decoded.project);
|
||||
assert_eq!(doc.compiled().bundle, original.bundle);
|
||||
assert!(!doc.dirty() && !doc.can_undo() && !doc.can_redo());
|
||||
assert!(camera == state.camera_boundary());
|
||||
}
|
||||
|
||||
@@ -156,8 +156,26 @@ async fn saved_message_same_batch_pointer_events_commit_once_and_one_undo_redo_r
|
||||
let target = id(&app, "updated_report");
|
||||
let before = app.document.snapshot();
|
||||
let revision = app.document.revision();
|
||||
let start = screen(&mut mounted, &app, Point::new(400., 252.));
|
||||
let projected = screen(&mut mounted, &app, Point::new(400., 252.));
|
||||
// This fixture asks for exactly +20 world units. A fractional start near a
|
||||
// screen-coordinate exponent boundary need not retain an exact +10 delta.
|
||||
let start = Point::new(projected.x.round(), projected.y.round());
|
||||
let mut geometry = resolved(&app);
|
||||
let camera = app.behavior.scenario.presentation.camera(geometry.bounds);
|
||||
assert_eq!(camera.scale, 0.5);
|
||||
let bounds = mounted.geometry(&app).bounds("scenario-viewport");
|
||||
let world = Point::new(
|
||||
(start.x - bounds.x - camera.pan.x) / camera.scale,
|
||||
(start.y - bounds.y - camera.pan.y) / camera.scale,
|
||||
);
|
||||
geometry.scene.scale = camera.scale;
|
||||
geometry.scene.camera_pan = camera.pan;
|
||||
let hit = geometry.scene.hit_item(world).unwrap();
|
||||
assert_eq!(hit.id, target);
|
||||
assert_eq!(hit.role, crate::behavior::SceneHitRole::MessageBody);
|
||||
let end = start + Vector::new(0., 10.); // .5 display scale: +20 authored y.
|
||||
assert_eq!(end.y - start.y, 10.);
|
||||
assert!(bounds.contains(start) && bounds.contains(end));
|
||||
let messages = mounted
|
||||
.batch(
|
||||
&mut app,
|
||||
|
||||
Reference in New Issue
Block a user