Retire obsolete behavior canvas scroll and tool paths
This commit is contained in:
@@ -58,7 +58,7 @@ mod direct_scenario_tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn direct_scenario_scroll_and_real_resize_invalidate_only_pointer_context() {
|
fn direct_scenario_camera_details_and_real_resize_invalidate_only_pointer_context() {
|
||||||
let (mut app, f) = setup();
|
let (mut app, f) = setup();
|
||||||
let original = app.document.bundle().clone();
|
let original = app.document.bundle().clone();
|
||||||
let event = begin(&app, &f);
|
let event = begin(&app, &f);
|
||||||
@@ -74,18 +74,26 @@ mod direct_scenario_tests {
|
|||||||
let _ = app.update(Message::ScenarioCanvas(event));
|
let _ = app.update(Message::ScenarioCanvas(event));
|
||||||
assert!(!app.behavior.has_pending());
|
assert!(!app.behavior.has_pending());
|
||||||
let context = app.scenario_context().unwrap();
|
let context = app.scenario_context().unwrap();
|
||||||
for (area, offset) in [
|
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Camera(
|
||||||
(scenario::ScrollArea::Canvas, iced::Vector::new(30., 150.)),
|
context,
|
||||||
(scenario::ScrollArea::Page, iced::Vector::new(0., 90.)),
|
app.camera_epoch,
|
||||||
] {
|
behavior::canvas_workspace::Event::Pan(iced::Vector::new(30., 150.)),
|
||||||
|
)));
|
||||||
|
assert_ne!(app.scenario_context(), Some(context));
|
||||||
|
let context = app.scenario_context().unwrap();
|
||||||
|
for offset in [iced::Vector::new(0., 90.), iced::Vector::new(0., 150.)] {
|
||||||
let before = app.scenario_context().unwrap();
|
let before = app.scenario_context().unwrap();
|
||||||
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
||||||
context, area, offset,
|
context,
|
||||||
|
scenario::ScrollArea::Page,
|
||||||
|
offset,
|
||||||
)));
|
)));
|
||||||
assert_ne!(app.scenario_context(), Some(before));
|
assert_ne!(app.scenario_context(), Some(before));
|
||||||
let current = app.scenario_context().unwrap();
|
let current = app.scenario_context().unwrap();
|
||||||
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
||||||
context, area, offset,
|
context,
|
||||||
|
scenario::ScrollArea::Page,
|
||||||
|
offset,
|
||||||
)));
|
)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.scenario_context(),
|
app.scenario_context(),
|
||||||
@@ -114,7 +122,7 @@ mod direct_scenario_tests {
|
|||||||
}
|
}
|
||||||
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
||||||
stale,
|
stale,
|
||||||
scenario::ScrollArea::Canvas,
|
scenario::ScrollArea::Page,
|
||||||
iced::Vector::new(0., 900.),
|
iced::Vector::new(0., 900.),
|
||||||
)));
|
)));
|
||||||
assert_eq!(app.scenario_context(), Some(current));
|
assert_eq!(app.scenario_context(), Some(current));
|
||||||
@@ -1560,9 +1568,9 @@ impl App {
|
|||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
if let CanvasEvent::Scrolled(_, area, offset) = event {
|
if let CanvasEvent::Scrolled(_, area, offset) = event {
|
||||||
// Canvas and outline notifications may come from the
|
// Details may report more than once in a frame. Accept the
|
||||||
// same frame. Accept both across the first epoch bump,
|
// current owner's passive clamps across an epoch bump while
|
||||||
// while still rejecting another owner/document/revision.
|
// still rejecting another document, owner or revision.
|
||||||
if self.scenario_context().is_some_and(|current| {
|
if self.scenario_context().is_some_and(|current| {
|
||||||
current.document == context.document
|
current.document == context.document
|
||||||
&& current.scenario == context.scenario
|
&& current.scenario == context.scenario
|
||||||
@@ -1717,37 +1725,6 @@ impl App {
|
|||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
match mutation {
|
match mutation {
|
||||||
CanvasEvent::First(_, tool) => {
|
|
||||||
let empty = match tool {
|
|
||||||
scenario::Tool::Lifeline => !self
|
|
||||||
.project()
|
|
||||||
.children(context.scenario)
|
|
||||||
.any(|e| e.kind() == Kind::Lifeline),
|
|
||||||
scenario::Tool::Message | scenario::Tool::Action => {
|
|
||||||
self.project()
|
|
||||||
.children(context.scenario)
|
|
||||||
.any(|e| e.kind() == Kind::Lifeline)
|
|
||||||
&& !self
|
|
||||||
.project()
|
|
||||||
.children(context.scenario)
|
|
||||||
.any(|e| e.kind() == Kind::Occurrence)
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
if !empty {
|
|
||||||
self.status="This scenario already has content. Use its current authoring tools.".into();
|
|
||||||
return Task::none();
|
|
||||||
}
|
|
||||||
let mut state = std::mem::take(&mut self.behavior.scenario);
|
|
||||||
let result = state.update(
|
|
||||||
self.project(),
|
|
||||||
scenario::Event::Start(context.scenario, tool),
|
|
||||||
);
|
|
||||||
self.behavior.scenario = state;
|
|
||||||
if let Err(error) = result {
|
|
||||||
self.status = error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CanvasEvent::BeginOccurrence {
|
CanvasEvent::BeginOccurrence {
|
||||||
tool,
|
tool,
|
||||||
slot,
|
slot,
|
||||||
|
|||||||
@@ -569,32 +569,30 @@ mod tests {
|
|||||||
assert!(!app.behavior.has_pending());
|
assert!(!app.behavior.has_pending());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn direct_process_app_save_scroll_resize_and_passive_geometry_keep_drafts_and_current_origin() {
|
fn direct_process_app_save_details_resize_and_passive_geometry_keep_drafts_and_camera() {
|
||||||
let (mut app, f) = setup();
|
let (mut app, f) = setup();
|
||||||
let context = app.topology_context().unwrap();
|
let context = app.topology_context().unwrap();
|
||||||
|
let _ = app.update(Message::TopologyCanvas(CanvasEvent::Camera(
|
||||||
|
context,
|
||||||
|
app.camera_epoch,
|
||||||
|
behavior::canvas_workspace::Event::Pan(iced::Vector::new(25., 100.)),
|
||||||
|
)));
|
||||||
|
let context = app.topology_context().unwrap();
|
||||||
let original = app.document.snapshot();
|
let original = app.document.snapshot();
|
||||||
app.busy = true;
|
app.busy = true;
|
||||||
for (area, offset) in [
|
let _ = app.update(Message::TopologyCanvas(CanvasEvent::Scrolled(
|
||||||
(
|
context,
|
||||||
behavior::scenario::ScrollArea::Canvas,
|
|
||||||
iced::Vector::new(25., 100.),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
behavior::scenario::ScrollArea::Page,
|
behavior::scenario::ScrollArea::Page,
|
||||||
iced::Vector::new(0., 40.),
|
iced::Vector::new(0., 40.),
|
||||||
),
|
|
||||||
] {
|
|
||||||
let _ = app.update(Message::TopologyCanvas(CanvasEvent::Scrolled(
|
|
||||||
context, area, offset,
|
|
||||||
)));
|
)));
|
||||||
}
|
|
||||||
let _ = app.update(Message::TopologyCanvas(CanvasEvent::Viewport(
|
let _ = app.update(Message::TopologyCanvas(CanvasEvent::Viewport(
|
||||||
context,
|
context,
|
||||||
iced::Size::new(500., 300.),
|
iced::Size::new(500., 300.),
|
||||||
)));
|
)));
|
||||||
|
let camera = app.behavior.topology.camera_boundary();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.behavior.topology.scroll_offset(),
|
app.behavior.topology.details_offset(),
|
||||||
iced::Vector::new(25., 140.)
|
iced::Vector::new(0., 40.)
|
||||||
);
|
);
|
||||||
assert_ne!(app.topology_context(), Some(context));
|
assert_ne!(app.topology_context(), Some(context));
|
||||||
assert!(app.busy);
|
assert!(app.busy);
|
||||||
@@ -602,12 +600,12 @@ mod tests {
|
|||||||
stale.document = Id::new_v4();
|
stale.document = Id::new_v4();
|
||||||
let _ = app.update(Message::TopologyCanvas(CanvasEvent::Scrolled(
|
let _ = app.update(Message::TopologyCanvas(CanvasEvent::Scrolled(
|
||||||
stale,
|
stale,
|
||||||
behavior::scenario::ScrollArea::Canvas,
|
behavior::scenario::ScrollArea::Page,
|
||||||
iced::Vector::new(0., 900.),
|
iced::Vector::new(0., 900.),
|
||||||
)));
|
)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.behavior.topology.scroll_offset(),
|
app.behavior.topology.details_offset(),
|
||||||
iced::Vector::new(25., 140.)
|
iced::Vector::new(0., 40.)
|
||||||
);
|
);
|
||||||
let root = PathBuf::from("process-scroll-save-fixture");
|
let root = PathBuf::from("process-scroll-save-fixture");
|
||||||
let receipt = FileSnapshot {
|
let receipt = FileSnapshot {
|
||||||
@@ -627,6 +625,7 @@ mod tests {
|
|||||||
assert!(!app.busy);
|
assert!(!app.busy);
|
||||||
assert!(!app.document.can_undo());
|
assert!(!app.document.can_undo());
|
||||||
assert_eq!(app.project(), &f.p);
|
assert_eq!(app.project(), &f.p);
|
||||||
|
assert!(app.behavior.topology.camera_boundary() == camera);
|
||||||
let context = app.topology_context().unwrap();
|
let context = app.topology_context().unwrap();
|
||||||
let size = app.window_size;
|
let size = app.window_size;
|
||||||
let _ = app.update(Message::WindowResized(size));
|
let _ = app.update(Message::WindowResized(size));
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ mod scenario_scroll {
|
|||||||
std::time::Instant::now(),
|
std::time::Instant::now(),
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
for area in [ScrollArea::Page] {
|
let area = ScrollArea::Page;
|
||||||
for returned_height in [600., 250.] {
|
for returned_height in [600., 250.] {
|
||||||
let (project, [a, _, _], [use_id, _, _]) = navigation_fixture();
|
let (project, [a, _, _], [use_id, _, _]) = navigation_fixture();
|
||||||
let mut app = App::with_project(project.clone());
|
let mut app = App::with_project(project.clone());
|
||||||
@@ -485,7 +485,6 @@ mod scenario_scroll {
|
|||||||
assert!(!app.document.can_undo());
|
assert!(!app.document.can_undo());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#[test]
|
#[test]
|
||||||
fn direct_scenario_restore_cannot_scroll_another_document_owner_or_process_view() {
|
fn direct_scenario_restore_cannot_scroll_another_document_owner_or_process_view() {
|
||||||
for mismatch in 0..3 {
|
for mismatch in 0..3 {
|
||||||
@@ -565,9 +564,9 @@ mod scenario_scroll {
|
|||||||
other.scenario = Id::new_v4();
|
other.scenario = Id::new_v4();
|
||||||
let mut state = topology::State::default();
|
let mut state = topology::State::default();
|
||||||
state.enter(context.document, context.scenario);
|
state.enter(context.document, context.scenario);
|
||||||
state.scrolled(scenario::ScrollArea::Canvas, Vector::new(0., 180.));
|
state.scrolled(scenario::ScrollArea::Page, Vector::new(0., 180.));
|
||||||
state.enter(other.document, other.scenario);
|
state.enter(other.document, other.scenario);
|
||||||
assert_eq!(state.scroll_offset(), Vector::ZERO);
|
assert_eq!(state.details_offset(), Vector::ZERO);
|
||||||
view = observer(600., other);
|
view = observer(600., other);
|
||||||
view.process = true;
|
view.process = true;
|
||||||
view.diff(&mut tree);
|
view.diff(&mut tree);
|
||||||
@@ -584,7 +583,7 @@ mod scenario_scroll {
|
|||||||
{
|
{
|
||||||
use widget::operation::scrollable::{AbsoluteOffset, scroll_to};
|
use widget::operation::scrollable::{AbsoluteOffset, scroll_to};
|
||||||
for process in [false, true] {
|
for process in [false, true] {
|
||||||
for area in [scenario::ScrollArea::Canvas, scenario::ScrollArea::Page] {
|
let area = scenario::ScrollArea::Page;
|
||||||
for change in 0..4 {
|
for change in 0..4 {
|
||||||
let context = scenario::direct_tests::context(Id::new_v4());
|
let context = scenario::direct_tests::context(Id::new_v4());
|
||||||
let mut view = observer(600., context);
|
let mut view = observer(600., context);
|
||||||
@@ -672,7 +671,6 @@ mod scenario_scroll {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn direct_scroll_observer_reports_actual_initial_clamped_and_remounted_offsets_once() {
|
fn direct_scroll_observer_reports_actual_initial_clamped_and_remounted_offsets_once() {
|
||||||
@@ -1141,7 +1139,6 @@ struct Scene {
|
|||||||
size: Size,
|
size: Size,
|
||||||
selected: Id,
|
selected: Id,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
scroll_offset: iced::Vector,
|
|
||||||
camera_pan: iced::Vector,
|
camera_pan: iced::Vector,
|
||||||
interaction: Option<(scenario::Context, scenario::CanvasTool, bool)>,
|
interaction: Option<(scenario::Context, scenario::CanvasTool, bool)>,
|
||||||
pending_placement: Option<(scenario::Slot, Option<Id>)>,
|
pending_placement: Option<(scenario::Slot, Option<Id>)>,
|
||||||
@@ -1196,7 +1193,6 @@ impl Scene {
|
|||||||
fn sequence(p: &Project, owner: Id, selected: Id) -> Self {
|
fn sequence(p: &Project, owner: Id, selected: Id) -> Self {
|
||||||
let mut scene = Self {
|
let mut scene = Self {
|
||||||
scale: 1.,
|
scale: 1.,
|
||||||
scroll_offset: iced::Vector::ZERO,
|
|
||||||
camera_pan: iced::Vector::ZERO,
|
camera_pan: iced::Vector::ZERO,
|
||||||
boxes: vec![],
|
boxes: vec![],
|
||||||
lines: vec![],
|
lines: vec![],
|
||||||
@@ -1808,8 +1804,7 @@ impl canvas::Program<Message> for Scene {
|
|||||||
if !editable {
|
if !editable {
|
||||||
state.cancel();
|
state.cancel();
|
||||||
}
|
}
|
||||||
// Scrollable marks a pointer outside its visible viewport unavailable,
|
// The viewport masks input outside its visible canvas bounds.
|
||||||
// even when it lies inside the full (scrolled) canvas rectangle.
|
|
||||||
let visible = cursor.position().is_some();
|
let visible = cursor.position().is_some();
|
||||||
if !visible
|
if !visible
|
||||||
&& matches!(
|
&& matches!(
|
||||||
@@ -1832,7 +1827,7 @@ impl canvas::Program<Message> for Scene {
|
|||||||
SequencePress::Occurrence { .. } => {}
|
SequencePress::Occurrence { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mouse::Cursor::Available(*position + self.scroll_offset)
|
mouse::Cursor::Available(*position)
|
||||||
}
|
}
|
||||||
canvas::Event::Mouse(mouse::Event::WheelScrolled { .. })
|
canvas::Event::Mouse(mouse::Event::WheelScrolled { .. })
|
||||||
| canvas::Event::Window(iced::window::Event::Resized(_))
|
| canvas::Event::Window(iced::window::Event::Resized(_))
|
||||||
@@ -1844,11 +1839,9 @@ impl canvas::Program<Message> for Scene {
|
|||||||
}
|
}
|
||||||
canvas::Event::Mouse(
|
canvas::Event::Mouse(
|
||||||
mouse::Event::ButtonPressed(_) | mouse::Event::ButtonReleased(_),
|
mouse::Event::ButtonPressed(_) | mouse::Event::ButtonReleased(_),
|
||||||
) => state
|
) => state.pointer.map_or(cursor, mouse::Cursor::Available),
|
||||||
.pointer
|
|
||||||
.map_or(cursor, |p| mouse::Cursor::Available(p + self.scroll_offset)),
|
|
||||||
canvas::Event::Window(iced::window::Event::RedrawRequested(_)) => {
|
canvas::Event::Window(iced::window::Event::RedrawRequested(_)) => {
|
||||||
state.pointer = cursor.position().map(|p| p - self.scroll_offset);
|
state.pointer = cursor.position();
|
||||||
cursor
|
cursor
|
||||||
}
|
}
|
||||||
canvas::Event::Keyboard(iced::keyboard::Event::KeyPressed {
|
canvas::Event::Keyboard(iced::keyboard::Event::KeyPressed {
|
||||||
@@ -1885,7 +1878,7 @@ impl canvas::Program<Message> for Scene {
|
|||||||
state.press = Some(SequencePress::Message {
|
state.press = Some(SequencePress::Message {
|
||||||
source,
|
source,
|
||||||
slot,
|
slot,
|
||||||
start: cursor.position()? - self.scroll_offset,
|
start: cursor.position()?,
|
||||||
first,
|
first,
|
||||||
moved: false,
|
moved: false,
|
||||||
});
|
});
|
||||||
@@ -1914,7 +1907,7 @@ impl canvas::Program<Message> for Scene {
|
|||||||
state.press = Some(SequencePress::Reorder {
|
state.press = Some(SequencePress::Reorder {
|
||||||
occurrence,
|
occurrence,
|
||||||
parent: self.rows[&occurrence].0,
|
parent: self.rows[&occurrence].0,
|
||||||
start: cursor.position()? - self.scroll_offset,
|
start: cursor.position()?,
|
||||||
moved: false,
|
moved: false,
|
||||||
});
|
});
|
||||||
return Some(Action::request_redraw().and_capture());
|
return Some(Action::request_redraw().and_capture());
|
||||||
@@ -2416,25 +2409,26 @@ mod direct_canvas_tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn direct_scrolled_canvas_maps_raw_pointer_events_to_the_visible_slot() {
|
fn direct_panned_canvas_maps_ordered_pointer_events_to_the_visible_slot() {
|
||||||
let (f, mut scene) = scene();
|
let (f, mut scene) = scene();
|
||||||
scene.scroll_offset = iced::Vector::new(35., 140.);
|
scene.camera_pan = iced::Vector::new(-35., -140.);
|
||||||
let bounds = Rectangle::new(Point::new(20., 230.), scene.size);
|
let bounds = Rectangle::new(Point::new(20., 230.), scene.size);
|
||||||
let slot = scene.slots.last().unwrap().slot;
|
let slot = scene.slots.last().unwrap().slot;
|
||||||
let y = scene.slots.last().unwrap().y;
|
let y = scene.slots.last().unwrap().y;
|
||||||
let a = Point::new(bounds.x + scene.lanes[&f.sender], bounds.y + y) - scene.scroll_offset;
|
let a = Point::new(bounds.x + scene.lanes[&f.sender], bounds.y + y) + scene.camera_pan;
|
||||||
let b = Point::new(bounds.x + scene.lanes[&f.receiver], bounds.y + y) - scene.scroll_offset;
|
let b = Point::new(bounds.x + scene.lanes[&f.receiver], bounds.y + y) + scene.camera_pan;
|
||||||
|
assert!(bounds.contains(a) && bounds.contains(b));
|
||||||
let mut state = SequenceInput::default();
|
let mut state = SequenceInput::default();
|
||||||
// Iced Scrollable passes raw event coordinates and a translated cursor.
|
// Batched pointer events use their own positions with the final cursor.
|
||||||
for event in [
|
for event in [
|
||||||
mouse::Event::CursorMoved { position: a },
|
mouse::Event::CursorMoved { position: a },
|
||||||
mouse::Event::ButtonPressed(mouse::Button::Left),
|
mouse::Event::ButtonPressed(mouse::Button::Left),
|
||||||
mouse::Event::CursorMoved { position: b },
|
mouse::Event::CursorMoved { position: b },
|
||||||
] {
|
] {
|
||||||
send(&scene, &mut state, event, b + scene.scroll_offset, bounds);
|
send(&scene, &mut state, event, b, bounds);
|
||||||
}
|
}
|
||||||
assert!(
|
assert!(
|
||||||
matches!(send(&scene, &mut state, mouse::Event::ButtonReleased(mouse::Button::Left), b + scene.scroll_offset, bounds), Some(CanvasEvent::Begin { sender, receiver, slot: actual, .. }) if sender == f.sender && receiver == f.receiver && actual == slot)
|
matches!(send(&scene, &mut state, mouse::Event::ButtonReleased(mouse::Button::Left), b, bounds), Some(CanvasEvent::Begin { sender, receiver, slot: actual, .. }) if sender == f.sender && receiver == f.receiver && actual == slot)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -886,7 +886,7 @@ fn contextual_source_incomplete_recovered_kind_reports_error_without_panicking()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn contextual_source_old_empty_action_cannot_open_a_previous_owner_or_layer() {
|
fn contextual_source_empty_palette_lifeline_cannot_open_a_previous_owner_or_layer() {
|
||||||
use crate::app::{App, Message, Page};
|
use crate::app::{App, Message, Page};
|
||||||
let (mut p, a) = scenario_project(Layer::Oa, ScenarioViewpoint::OperationalActivity);
|
let (mut p, a) = scenario_project(Layer::Oa, ScenarioViewpoint::OperationalActivity);
|
||||||
let b = p
|
let b = p
|
||||||
@@ -898,30 +898,32 @@ fn contextual_source_old_empty_action_cannot_open_a_previous_owner_or_layer() {
|
|||||||
app.behavior.active_owner = Some(a);
|
app.behavior.active_owner = Some(a);
|
||||||
app.selected = a;
|
app.selected = a;
|
||||||
let context = app.scenario_context().unwrap();
|
let context = app.scenario_context().unwrap();
|
||||||
|
let source = app.document.bundle().clone();
|
||||||
|
let begin = |context| scenario::CanvasEvent::BeginOccurrence {
|
||||||
|
context,
|
||||||
|
tool: scenario::Tool::Lifeline,
|
||||||
|
slot: scenario::Slot {
|
||||||
|
parent: a,
|
||||||
|
before: None,
|
||||||
|
},
|
||||||
|
lifeline: None,
|
||||||
|
};
|
||||||
app.behavior.active_owner = Some(b);
|
app.behavior.active_owner = Some(b);
|
||||||
app.selected = b;
|
app.selected = b;
|
||||||
let _ = app.update(Message::ScenarioCanvas(scenario::CanvasEvent::First(
|
let _ = app.update(Message::ScenarioCanvas(begin(context)));
|
||||||
context,
|
|
||||||
scenario::Tool::Lifeline,
|
|
||||||
)));
|
|
||||||
assert!(!app.behavior.has_pending());
|
assert!(!app.behavior.has_pending());
|
||||||
assert_eq!(app.project(), &p);
|
assert_eq!(app.project(), &p);
|
||||||
app.behavior.active_owner = Some(a);
|
app.behavior.active_owner = Some(a);
|
||||||
app.selected = a;
|
app.selected = a;
|
||||||
app.layer = Layer::Sa;
|
app.layer = Layer::Sa;
|
||||||
let _ = app.update(Message::ScenarioCanvas(scenario::CanvasEvent::First(
|
let _ = app.update(Message::ScenarioCanvas(begin(context)));
|
||||||
context,
|
|
||||||
scenario::Tool::Lifeline,
|
|
||||||
)));
|
|
||||||
assert!(!app.behavior.has_pending());
|
assert!(!app.behavior.has_pending());
|
||||||
app.layer = Layer::Oa;
|
app.layer = Layer::Oa;
|
||||||
let current = app.scenario_context().unwrap();
|
let current = app.scenario_context().unwrap();
|
||||||
let _ = app.update(Message::ScenarioCanvas(scenario::CanvasEvent::First(
|
let _ = app.update(Message::ScenarioCanvas(begin(current)));
|
||||||
current,
|
|
||||||
scenario::Tool::Lifeline,
|
|
||||||
)));
|
|
||||||
assert_eq!(app.behavior.pending_owner(), Some(a));
|
assert_eq!(app.behavior.pending_owner(), Some(a));
|
||||||
assert_eq!(app.project(), &p);
|
assert_eq!(app.project(), &p);
|
||||||
|
assert_eq!(app.document.bundle(), &source);
|
||||||
assert!(!app.document.can_undo());
|
assert!(!app.document.can_undo());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ impl std::fmt::Display for CanvasTool {
|
|||||||
}
|
}
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum CanvasEvent {
|
pub enum CanvasEvent {
|
||||||
First(Context, Tool),
|
|
||||||
Scrolled(Context, ScrollArea, iced::Vector),
|
Scrolled(Context, ScrollArea, iced::Vector),
|
||||||
Restored(Context, Id, ScrollArea, iced::Vector),
|
Restored(Context, Id, ScrollArea, iced::Vector),
|
||||||
Viewport(Context, Size),
|
Viewport(Context, Size),
|
||||||
@@ -130,15 +129,13 @@ pub enum CanvasEvent {
|
|||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum ScrollArea {
|
pub enum ScrollArea {
|
||||||
Canvas,
|
// The independent Details/outline scroller; viewport motion uses Camera.
|
||||||
// Retained navigation channel, now the independent Selection/outline shelf.
|
|
||||||
Page,
|
Page,
|
||||||
}
|
}
|
||||||
impl CanvasEvent {
|
impl CanvasEvent {
|
||||||
pub fn context(&self) -> Context {
|
pub fn context(&self) -> Context {
|
||||||
match self {
|
match self {
|
||||||
Self::First(context, _)
|
Self::Scrolled(context, _, _)
|
||||||
| Self::Scrolled(context, _, _)
|
|
||||||
| Self::Restored(context, ..)
|
| Self::Restored(context, ..)
|
||||||
| Self::Viewport(context, _)
|
| Self::Viewport(context, _)
|
||||||
| Self::FormShown(context, _)
|
| Self::FormShown(context, _)
|
||||||
@@ -211,9 +208,9 @@ pub struct State {
|
|||||||
form_focus_pending: bool,
|
form_focus_pending: bool,
|
||||||
canvas_tool: Option<(Id, CanvasTool)>,
|
canvas_tool: Option<(Id, CanvasTool)>,
|
||||||
call_choices: RefCell<Option<(CallChoiceContext, Vec<Choice>)>>,
|
call_choices: RefCell<Option<(CallChoiceContext, Vec<Choice>)>>,
|
||||||
scroll_offsets: [iced::Vector; 2],
|
details_offset: iced::Vector,
|
||||||
return_to: Vec<Caller>,
|
return_to: Vec<Caller>,
|
||||||
restore: Option<ViewportRestore>,
|
restore: Option<DetailsRestore>,
|
||||||
presentation: workspace::Presentation,
|
presentation: workspace::Presentation,
|
||||||
}
|
}
|
||||||
const CALLER_LIMIT: usize = 32;
|
const CALLER_LIMIT: usize = 32;
|
||||||
@@ -223,15 +220,14 @@ pub(crate) struct Caller {
|
|||||||
pub occurrence: Id,
|
pub occurrence: Id,
|
||||||
target: Id,
|
target: Id,
|
||||||
tool: CanvasTool,
|
tool: CanvasTool,
|
||||||
offsets: [iced::Vector; 2],
|
details_offset: iced::Vector,
|
||||||
presentation: workspace::Presentation,
|
presentation: workspace::Presentation,
|
||||||
}
|
}
|
||||||
struct ViewportRestore {
|
struct DetailsRestore {
|
||||||
document: Id,
|
document: Id,
|
||||||
owner: Id,
|
owner: Id,
|
||||||
token: Id,
|
token: Id,
|
||||||
offsets: [iced::Vector; 2],
|
offset: iced::Vector,
|
||||||
pending: [bool; 2],
|
|
||||||
}
|
}
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(super) struct ScrollRestore {
|
pub(super) struct ScrollRestore {
|
||||||
@@ -336,7 +332,7 @@ impl State {
|
|||||||
focused: self.presentation.focused,
|
focused: self.presentation.focused,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
self.scroll_offsets = [iced::Vector::ZERO; 2];
|
self.details_offset = iced::Vector::ZERO;
|
||||||
self.restore = None;
|
self.restore = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,11 +392,11 @@ impl State {
|
|||||||
occurrence,
|
occurrence,
|
||||||
target,
|
target,
|
||||||
tool: self.canvas_tool(context.scenario),
|
tool: self.canvas_tool(context.scenario),
|
||||||
offsets: self.scroll_offsets,
|
details_offset: self.details_offset,
|
||||||
presentation: self.presentation.clone(),
|
presentation: self.presentation.clone(),
|
||||||
});
|
});
|
||||||
self.canvas_tool = Some((target, CanvasTool::Select));
|
self.canvas_tool = Some((target, CanvasTool::Select));
|
||||||
self.scroll_offsets = [iced::Vector::ZERO; 2];
|
self.details_offset = iced::Vector::ZERO;
|
||||||
self.restore = None;
|
self.restore = None;
|
||||||
Ok(target)
|
Ok(target)
|
||||||
}
|
}
|
||||||
@@ -427,34 +423,30 @@ impl State {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.canvas_tool = Some((frame.owner, frame.tool));
|
self.canvas_tool = Some((frame.owner, frame.tool));
|
||||||
self.scroll_offsets = frame.offsets;
|
self.details_offset = frame.details_offset;
|
||||||
self.presentation = frame.presentation;
|
self.presentation = frame.presentation;
|
||||||
self.presentation.identity = Some((context.document, context.scenario));
|
self.presentation.identity = Some((context.document, context.scenario));
|
||||||
self.restore = Some(ViewportRestore {
|
self.restore = Some(DetailsRestore {
|
||||||
document: context.document,
|
document: context.document,
|
||||||
owner: frame.owner,
|
owner: frame.owner,
|
||||||
token: Id::new_v4(),
|
token: Id::new_v4(),
|
||||||
offsets: frame.offsets,
|
offset: frame.details_offset,
|
||||||
pending: [false, true],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
pub(super) fn scroll_restore(
|
pub(super) fn scroll_restore(
|
||||||
&self,
|
&self,
|
||||||
context: Context,
|
context: Context,
|
||||||
area: ScrollArea,
|
ScrollArea::Page: ScrollArea,
|
||||||
) -> Option<ScrollRestore> {
|
) -> Option<ScrollRestore> {
|
||||||
let index = match area {
|
let restore = self
|
||||||
ScrollArea::Canvas => 0,
|
.restore
|
||||||
ScrollArea::Page => 1,
|
.as_ref()
|
||||||
};
|
.filter(|r| r.document == context.document && r.owner == context.scenario)?;
|
||||||
let restore = self.restore.as_ref().filter(|r| {
|
|
||||||
r.document == context.document && r.owner == context.scenario && r.pending[index]
|
|
||||||
})?;
|
|
||||||
Some(ScrollRestore {
|
Some(ScrollRestore {
|
||||||
document: restore.document,
|
document: restore.document,
|
||||||
owner: restore.owner,
|
owner: restore.owner,
|
||||||
token: restore.token,
|
token: restore.token,
|
||||||
offset: restore.offsets[index],
|
offset: restore.offset,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub(crate) fn restored(
|
pub(crate) fn restored(
|
||||||
@@ -464,22 +456,12 @@ impl State {
|
|||||||
area: ScrollArea,
|
area: ScrollArea,
|
||||||
offset: iced::Vector,
|
offset: iced::Vector,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let index = match area {
|
if !self.restore.as_ref().is_some_and(|r| {
|
||||||
ScrollArea::Canvas => 0,
|
r.document == context.document && r.owner == context.scenario && r.token == token
|
||||||
ScrollArea::Page => 1,
|
}) {
|
||||||
};
|
|
||||||
let Some(restore) = self.restore.as_mut().filter(|r| {
|
|
||||||
r.document == context.document
|
|
||||||
&& r.owner == context.scenario
|
|
||||||
&& r.token == token
|
|
||||||
&& r.pending[index]
|
|
||||||
}) else {
|
|
||||||
return false;
|
return false;
|
||||||
};
|
|
||||||
restore.pending[index] = false;
|
|
||||||
if !restore.pending.contains(&true) {
|
|
||||||
self.restore = None;
|
|
||||||
}
|
}
|
||||||
|
self.restore = None;
|
||||||
self.scrolled(area, offset)
|
self.scrolled(area, offset)
|
||||||
}
|
}
|
||||||
pub(super) fn recovery_snapshot(&self) -> Result<Option<serde_json::Value>, String> {
|
pub(super) fn recovery_snapshot(&self) -> Result<Option<serde_json::Value>, String> {
|
||||||
@@ -535,23 +517,14 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn scrolled(&mut self, area: ScrollArea, offset: iced::Vector) -> bool {
|
pub(crate) fn scrolled(&mut self, ScrollArea::Page: ScrollArea, offset: iced::Vector) -> bool {
|
||||||
if !offset.x.is_finite() || !offset.y.is_finite() || offset.x < 0. || offset.y < 0. {
|
if !offset.x.is_finite() || !offset.y.is_finite() || offset.x < 0. || offset.y < 0. {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let index = match area {
|
let changed = self.details_offset != offset;
|
||||||
ScrollArea::Canvas => 0,
|
self.details_offset = offset;
|
||||||
ScrollArea::Page => 1,
|
|
||||||
};
|
|
||||||
let changed = self.scroll_offsets[index] != offset;
|
|
||||||
self.scroll_offsets[index] = offset;
|
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
|
||||||
fn scroll_offset(&self) -> iced::Vector {
|
|
||||||
// The former page scroll now belongs to a sibling outline shelf.
|
|
||||||
self.scroll_offsets[0]
|
|
||||||
}
|
|
||||||
fn call_choices(&self, p: &Project, context: Context, draft: &Draft) -> Vec<Choice> {
|
fn call_choices(&self, p: &Project, context: Context, draft: &Draft) -> Vec<Choice> {
|
||||||
let key = CallChoiceContext {
|
let key = CallChoiceContext {
|
||||||
document: context.document,
|
document: context.document,
|
||||||
@@ -2079,20 +2052,31 @@ pub(crate) mod direct_tests {
|
|||||||
navigation_event(&mut app, |ctx| {
|
navigation_event(&mut app, |ctx| {
|
||||||
CanvasEvent::Choose(ctx, CanvasTool::Add(Tool::Reuse))
|
CanvasEvent::Choose(ctx, CanvasTool::Add(Tool::Reuse))
|
||||||
});
|
});
|
||||||
|
navigation_event(&mut app, |ctx| CanvasEvent::Zoom(ctx, None));
|
||||||
|
let camera_epoch = app.camera_epoch;
|
||||||
navigation_event(&mut app, |ctx| {
|
navigation_event(&mut app, |ctx| {
|
||||||
CanvasEvent::Scrolled(ctx, ScrollArea::Canvas, iced::Vector::new(25., 170.))
|
CanvasEvent::Camera(
|
||||||
|
ctx,
|
||||||
|
camera_epoch,
|
||||||
|
super::super::canvas_workspace::Event::Pan(iced::Vector::new(25., 170.)),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
navigation_event(&mut app, |ctx| {
|
navigation_event(&mut app, |ctx| {
|
||||||
CanvasEvent::Scrolled(ctx, ScrollArea::Page, iced::Vector::new(0., 80.))
|
CanvasEvent::Scrolled(ctx, ScrollArea::Page, iced::Vector::new(0., 80.))
|
||||||
});
|
});
|
||||||
navigation_event(&mut app, |ctx| CanvasEvent::OpenUse(ctx, caller));
|
navigation_event(&mut app, |ctx| CanvasEvent::OpenUse(ctx, caller));
|
||||||
assert_eq!(app.selected, b);
|
assert_eq!(app.selected, b);
|
||||||
assert_eq!(app.behavior.scenario.scroll_offset(), iced::Vector::ZERO);
|
assert_eq!(app.behavior.scenario.presentation.pan, iced::Vector::ZERO);
|
||||||
navigation_event(&mut app, |ctx| {
|
navigation_event(&mut app, |ctx| {
|
||||||
CanvasEvent::Choose(ctx, CanvasTool::Message(MessageStyle::Timer))
|
CanvasEvent::Choose(ctx, CanvasTool::Message(MessageStyle::Timer))
|
||||||
});
|
});
|
||||||
|
let camera_epoch = app.camera_epoch;
|
||||||
navigation_event(&mut app, |ctx| {
|
navigation_event(&mut app, |ctx| {
|
||||||
CanvasEvent::Scrolled(ctx, ScrollArea::Canvas, iced::Vector::new(0., 115.))
|
CanvasEvent::Camera(
|
||||||
|
ctx,
|
||||||
|
camera_epoch,
|
||||||
|
super::super::canvas_workspace::Event::Pan(iced::Vector::new(0., 115.)),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
navigation_event(&mut app, |ctx| CanvasEvent::OpenUse(ctx, nested));
|
navigation_event(&mut app, |ctx| CanvasEvent::OpenUse(ctx, nested));
|
||||||
assert_eq!(app.selected, c);
|
assert_eq!(app.selected, c);
|
||||||
@@ -2103,7 +2087,7 @@ pub(crate) mod direct_tests {
|
|||||||
CanvasTool::Message(MessageStyle::Timer)
|
CanvasTool::Message(MessageStyle::Timer)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.behavior.scenario.scroll_offset(),
|
app.behavior.scenario.presentation.pan,
|
||||||
iced::Vector::new(0., 115.)
|
iced::Vector::new(0., 115.)
|
||||||
);
|
);
|
||||||
navigation_event(&mut app, CanvasEvent::Back);
|
navigation_event(&mut app, CanvasEvent::Back);
|
||||||
@@ -2114,11 +2098,11 @@ pub(crate) mod direct_tests {
|
|||||||
CanvasTool::Add(Tool::Reuse)
|
CanvasTool::Add(Tool::Reuse)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.behavior.scenario.scroll_offset(),
|
app.behavior.scenario.presentation.pan,
|
||||||
iced::Vector::new(25., 170.)
|
iced::Vector::new(25., 170.)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.behavior.scenario.scroll_offsets[1],
|
app.behavior.scenario.details_offset,
|
||||||
iced::Vector::new(0., 80.),
|
iced::Vector::new(0., 80.),
|
||||||
"The caller's independent outline scroll is restored separately"
|
"The caller's independent outline scroll is restored separately"
|
||||||
);
|
);
|
||||||
@@ -2296,22 +2280,24 @@ pub(crate) mod direct_tests {
|
|||||||
let before = app.document.bundle().clone();
|
let before = app.document.bundle().clone();
|
||||||
let mut scene = Scene::sequence(app.project(), f.owner, f.call);
|
let mut scene = Scene::sequence(app.project(), f.owner, f.call);
|
||||||
scene.interaction = Some((context, CanvasTool::Add(tool), true));
|
scene.interaction = Some((context, CanvasTool::Add(tool), true));
|
||||||
scene.scroll_offset = iced::Vector::new(23., 80.);
|
scene.camera_pan = iced::Vector::new(23., 80.);
|
||||||
let bounds = Rectangle::new(Point::new(20., 40.), scene.size);
|
let bounds = Rectangle::new(
|
||||||
|
Point::new(20., 40.),
|
||||||
|
Size::new(scene.size.width + 46., scene.size.height + 160.),
|
||||||
|
);
|
||||||
let y = scene.slots.iter().find(|s| s.slot == slot).unwrap().y;
|
let y = scene.slots.iter().find(|s| s.slot == slot).unwrap().y;
|
||||||
let x = if on_lifeline {
|
let x = if on_lifeline {
|
||||||
scene.lanes[&f.sender]
|
scene.lanes[&f.sender]
|
||||||
} else {
|
} else {
|
||||||
scene.size.width - 35.
|
scene.size.width - 35.
|
||||||
};
|
};
|
||||||
let point = Point::new(bounds.x + x, bounds.y + y);
|
let point = Point::new(bounds.x + x, bounds.y + y) + scene.camera_pan;
|
||||||
|
assert!(bounds.contains(point));
|
||||||
let mut input = SequenceInput::default();
|
let mut input = SequenceInput::default();
|
||||||
canvas_mouse(
|
canvas_mouse(
|
||||||
&scene,
|
&scene,
|
||||||
&mut input,
|
&mut input,
|
||||||
mouse::Event::CursorMoved {
|
mouse::Event::CursorMoved { position: point },
|
||||||
position: point - scene.scroll_offset,
|
|
||||||
},
|
|
||||||
point,
|
point,
|
||||||
bounds,
|
bounds,
|
||||||
);
|
);
|
||||||
@@ -2487,10 +2473,10 @@ pub(crate) mod direct_tests {
|
|||||||
assert!(!app.behavior.has_pending());
|
assert!(!app.behavior.has_pending());
|
||||||
}
|
}
|
||||||
let old_context = app.scenario_context().unwrap();
|
let old_context = app.scenario_context().unwrap();
|
||||||
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Camera(
|
||||||
old_context,
|
old_context,
|
||||||
ScrollArea::Canvas,
|
app.camera_epoch,
|
||||||
iced::Vector::new(0., 100.),
|
super::super::canvas_workspace::Event::Pan(iced::Vector::new(0., 100.)),
|
||||||
)));
|
)));
|
||||||
let _ = app.update(Message::ScenarioCanvas(event(old_context)));
|
let _ = app.update(Message::ScenarioCanvas(event(old_context)));
|
||||||
assert!(!app.behavior.has_pending());
|
assert!(!app.behavior.has_pending());
|
||||||
@@ -2681,26 +2667,29 @@ pub(crate) mod direct_tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn direct_busy_save_keeps_observed_scroll_current_for_the_next_pointer_gesture() {
|
fn direct_busy_save_keeps_details_current_and_camera_for_the_next_pointer_gesture() {
|
||||||
let f = fixture();
|
let f = fixture();
|
||||||
let mut app = App::with_project(f.project.clone());
|
let mut app = App::with_project(f.project.clone());
|
||||||
let _ = app.update(Message::Select(f.owner));
|
let _ = app.update(Message::Select(f.owner));
|
||||||
let _ = app.update(Message::Page(crate::app::Page::Behavior));
|
let _ = app.update(Message::Page(crate::app::Page::Behavior));
|
||||||
let context = app.scenario_context().unwrap();
|
let context = app.scenario_context().unwrap();
|
||||||
|
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Camera(
|
||||||
|
context,
|
||||||
|
app.camera_epoch,
|
||||||
|
super::super::canvas_workspace::Event::Pan(iced::Vector::new(-25., -100.)),
|
||||||
|
)));
|
||||||
|
let context = app.scenario_context().unwrap();
|
||||||
let original = app.document.snapshot();
|
let original = app.document.snapshot();
|
||||||
app.busy = true;
|
app.busy = true;
|
||||||
for (area, offset) in [
|
|
||||||
(ScrollArea::Canvas, iced::Vector::new(25., 100.)),
|
|
||||||
(ScrollArea::Page, iced::Vector::new(0., 40.)),
|
|
||||||
] {
|
|
||||||
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
||||||
context, area, offset,
|
context,
|
||||||
|
ScrollArea::Page,
|
||||||
|
iced::Vector::new(0., 40.),
|
||||||
)));
|
)));
|
||||||
}
|
|
||||||
assert!(app.busy);
|
assert!(app.busy);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.behavior.scenario.scroll_offset(),
|
app.behavior.scenario.details_offset,
|
||||||
iced::Vector::new(25., 100.)
|
iced::Vector::new(0., 40.)
|
||||||
);
|
);
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
app.scenario_context(),
|
app.scenario_context(),
|
||||||
@@ -2711,14 +2700,14 @@ pub(crate) mod direct_tests {
|
|||||||
stale.document = Id::new_v4();
|
stale.document = Id::new_v4();
|
||||||
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
|
||||||
stale,
|
stale,
|
||||||
ScrollArea::Canvas,
|
ScrollArea::Page,
|
||||||
iced::Vector::new(0., 900.),
|
iced::Vector::new(0., 900.),
|
||||||
)));
|
)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.behavior.scenario.scroll_offset(),
|
app.behavior.scenario.details_offset,
|
||||||
iced::Vector::new(25., 100.)
|
iced::Vector::new(0., 40.)
|
||||||
);
|
);
|
||||||
let root = std::path::PathBuf::from("scenario-scroll-save-fixture");
|
let root = std::path::PathBuf::from("scenario-details-save-fixture");
|
||||||
let receipt = crate::app::FileSnapshot {
|
let receipt = crate::app::FileSnapshot {
|
||||||
document: context.document,
|
document: context.document,
|
||||||
revision: context.revision,
|
revision: context.revision,
|
||||||
@@ -2739,12 +2728,17 @@ pub(crate) mod direct_tests {
|
|||||||
let context = app.scenario_context().unwrap();
|
let context = app.scenario_context().unwrap();
|
||||||
let mut scene = Scene::sequence(app.project(), f.owner, f.call);
|
let mut scene = Scene::sequence(app.project(), f.owner, f.call);
|
||||||
scene.interaction = Some((context, CanvasTool::Message(MessageStyle::CallReply), true));
|
scene.interaction = Some((context, CanvasTool::Message(MessageStyle::CallReply), true));
|
||||||
scene.scroll_offset = app.behavior.scenario.scroll_offset();
|
assert_eq!(
|
||||||
|
app.behavior.scenario.presentation.pan,
|
||||||
|
iced::Vector::new(-25., -100.)
|
||||||
|
);
|
||||||
|
scene.camera_pan = app.behavior.scenario.presentation.pan;
|
||||||
let slot = scene.slots.last().unwrap().slot;
|
let slot = scene.slots.last().unwrap().slot;
|
||||||
let bounds = Rectangle::new(Point::new(20., 250.), scene.size);
|
let bounds = Rectangle::new(Point::new(20., 250.), scene.size);
|
||||||
let y = bounds.y + scene.slots.last().unwrap().y;
|
let y = bounds.y + scene.slots.last().unwrap().y;
|
||||||
let a = Point::new(bounds.x + scene.lanes[&f.sender], y) - scene.scroll_offset;
|
let a = Point::new(bounds.x + scene.lanes[&f.sender], y) + scene.camera_pan;
|
||||||
let b = Point::new(bounds.x + scene.lanes[&f.receiver], y) - scene.scroll_offset;
|
let b = Point::new(bounds.x + scene.lanes[&f.receiver], y) + scene.camera_pan;
|
||||||
|
assert!(bounds.contains(a) && bounds.contains(b));
|
||||||
let mut input = super::super::SequenceInput::default();
|
let mut input = super::super::SequenceInput::default();
|
||||||
let mut published = vec![];
|
let mut published = vec![];
|
||||||
for event in [
|
for event in [
|
||||||
@@ -2758,7 +2752,7 @@ pub(crate) mod direct_tests {
|
|||||||
&mut input,
|
&mut input,
|
||||||
&canvas::Event::Mouse(event),
|
&canvas::Event::Mouse(event),
|
||||||
bounds,
|
bounds,
|
||||||
mouse::Cursor::Available(b + scene.scroll_offset),
|
mouse::Cursor::Available(b),
|
||||||
)
|
)
|
||||||
.and_then(|action| action.into_inner().0)
|
.and_then(|action| action.into_inner().0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ pub(crate) const NAME_INPUT: &str = "scenario-form-name";
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Shelf {
|
pub enum Shelf {
|
||||||
Tools,
|
|
||||||
Selection,
|
Selection,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ async fn production_scenario_canvas_keeps_bounds_draft_identity_and_camera_throu
|
|||||||
mounted.scroll(&app, "scenario-form", 500.);
|
mounted.scroll(&app, "scenario-form", 500.);
|
||||||
let offset = mounted.geometry(&app).scrolls[&widget::Id::new("scenario-form")].1;
|
let offset = mounted.geometry(&app).scrolls[&widget::Id::new("scenario-form")].1;
|
||||||
assert_eq!(mounted.geometry(&app).bounds("scenario-actions"), actions);
|
assert_eq!(mounted.geometry(&app).bounds("scenario-actions"), actions);
|
||||||
for shelf in [Shelf::Tools, Shelf::Selection, Shelf::Selection] {
|
for shelf in [Shelf::Selection, Shelf::Selection] {
|
||||||
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Shelf(
|
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Shelf(
|
||||||
app.scenario_context().unwrap(),
|
app.scenario_context().unwrap(),
|
||||||
shelf,
|
shelf,
|
||||||
@@ -1030,7 +1030,7 @@ fn scenario_workspace_viewport_events_are_passive_guarded_and_do_not_apply_raw_f
|
|||||||
assert_eq!(app.canvas_epoch, epoch);
|
assert_eq!(app.canvas_epoch, epoch);
|
||||||
}
|
}
|
||||||
app.busy = false;
|
app.busy = false;
|
||||||
navigation_event(&mut app, |ctx| CanvasEvent::Shelf(ctx, Shelf::Tools));
|
navigation_event(&mut app, |ctx| CanvasEvent::Shelf(ctx, Shelf::Selection));
|
||||||
navigation_event(&mut app, CanvasEvent::Focus);
|
navigation_event(&mut app, CanvasEvent::Focus);
|
||||||
navigation_event(&mut app, |ctx| CanvasEvent::Zoom(ctx, None));
|
navigation_event(&mut app, |ctx| CanvasEvent::Zoom(ctx, None));
|
||||||
assert_eq!(app.behavior.recovery_snapshot().unwrap(), raw);
|
assert_eq!(app.behavior.recovery_snapshot().unwrap(), raw);
|
||||||
@@ -1144,27 +1144,31 @@ fn scenario_workspace_choosing_tools_preserves_valid_and_invalid_pending_editors
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scenario_workspace_scaled_and_scrolled_scene_keeps_clicked_slots_and_message_endpoints() {
|
fn scenario_workspace_scaled_and_panned_scene_keeps_clicked_slots_and_message_endpoints() {
|
||||||
let f = super::super::direct_tests::fixture();
|
let f = super::super::direct_tests::fixture();
|
||||||
for scale in [0.25, 0.5, 1., 2.] {
|
for scale in [0.25, 0.5, 1., 2.] {
|
||||||
for drag in [false, true] {
|
for drag in [false, true] {
|
||||||
let mut scene = Scene::sequence(&f.project, f.owner, f.call);
|
let mut scene = Scene::sequence(&f.project, f.owner, f.call);
|
||||||
let context = super::super::direct_tests::context(f.owner);
|
let context = super::super::direct_tests::context(f.owner);
|
||||||
scene.scale = scale;
|
scene.scale = scale;
|
||||||
scene.scroll_offset = Vector::new(35., 40.);
|
scene.camera_pan = Vector::new(35., 40.);
|
||||||
scene.interaction = Some((context, CanvasTool::Message(MessageStyle::CallReply), true));
|
scene.interaction = Some((context, CanvasTool::Message(MessageStyle::CallReply), true));
|
||||||
let slot = scene.slots.last().unwrap();
|
let slot = scene.slots.last().unwrap();
|
||||||
let expected = slot.slot;
|
let expected = slot.slot;
|
||||||
let bounds = Rectangle::new(
|
let bounds = Rectangle::new(
|
||||||
Point::new(20., 80.),
|
Point::new(20., 80.),
|
||||||
Size::new(scene.size.width * scale, scene.size.height * scale),
|
Size::new(
|
||||||
|
scene.size.width * scale + 70.,
|
||||||
|
scene.size.height * scale + 80.,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let screen = |world: Point| {
|
let screen = |world: Point| {
|
||||||
Point::new(bounds.x + world.x * scale, bounds.y + world.y * scale)
|
Point::new(bounds.x + world.x * scale, bounds.y + world.y * scale)
|
||||||
- scene.scroll_offset
|
+ scene.camera_pan
|
||||||
};
|
};
|
||||||
let a = screen(Point::new(scene.lanes[&f.sender], slot.y));
|
let a = screen(Point::new(scene.lanes[&f.sender], slot.y));
|
||||||
let b = screen(Point::new(scene.lanes[&f.receiver], slot.y));
|
let b = screen(Point::new(scene.lanes[&f.receiver], slot.y));
|
||||||
|
assert!(bounds.contains(a) && bounds.contains(b));
|
||||||
let mut input = super::super::super::SequenceInput::default();
|
let mut input = super::super::super::SequenceInput::default();
|
||||||
let mut messages = vec![];
|
let mut messages = vec![];
|
||||||
let mut events = vec![
|
let mut events = vec![
|
||||||
@@ -1185,7 +1189,7 @@ fn scenario_workspace_scaled_and_scrolled_scene_keeps_clicked_slots_and_message_
|
|||||||
&mut input,
|
&mut input,
|
||||||
&canvas::Event::Mouse(event),
|
&canvas::Event::Mouse(event),
|
||||||
bounds,
|
bounds,
|
||||||
mouse::Cursor::Available(cursor + scene.scroll_offset),
|
mouse::Cursor::Available(cursor),
|
||||||
) && let Some(message) = action.into_inner().0
|
) && let Some(message) = action.into_inner().0
|
||||||
{
|
{
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
@@ -1207,14 +1211,17 @@ fn scenario_workspace_scaled_nested_occurrences_and_reorder_keep_exact_sequence_
|
|||||||
let mut scene = Scene::sequence(&f.project, f.owner, f.call);
|
let mut scene = Scene::sequence(&f.project, f.owner, f.call);
|
||||||
let context = super::super::direct_tests::context(f.owner);
|
let context = super::super::direct_tests::context(f.owner);
|
||||||
scene.scale = scale;
|
scene.scale = scale;
|
||||||
scene.scroll_offset = Vector::new(35., 40.);
|
scene.camera_pan = Vector::new(35., 40.);
|
||||||
let bounds = Rectangle::new(
|
let bounds = Rectangle::new(
|
||||||
Point::new(20., 80.),
|
Point::new(20., 80.),
|
||||||
Size::new(scene.size.width * scale, scene.size.height * scale),
|
Size::new(
|
||||||
|
scene.size.width * scale + 70.,
|
||||||
|
scene.size.height * scale + 80.,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let screen = |world: Point| {
|
let screen = |world: Point| {
|
||||||
Point::new(bounds.x + world.x * scale, bounds.y + world.y * scale)
|
Point::new(bounds.x + world.x * scale, bounds.y + world.y * scale)
|
||||||
- Vector::new(35., 40.)
|
+ Vector::new(35., 40.)
|
||||||
};
|
};
|
||||||
let send =
|
let send =
|
||||||
|scene: &Scene, input: &mut super::super::super::SequenceInput, event, cursor| {
|
|scene: &Scene, input: &mut super::super::super::SequenceInput, event, cursor| {
|
||||||
@@ -1223,7 +1230,7 @@ fn scenario_workspace_scaled_nested_occurrences_and_reorder_keep_exact_sequence_
|
|||||||
input,
|
input,
|
||||||
&canvas::Event::Mouse(event),
|
&canvas::Event::Mouse(event),
|
||||||
bounds,
|
bounds,
|
||||||
mouse::Cursor::Available(cursor + scene.scroll_offset),
|
mouse::Cursor::Available(cursor),
|
||||||
)
|
)
|
||||||
.and_then(|action| action.into_inner().0)
|
.and_then(|action| action.into_inner().0)
|
||||||
};
|
};
|
||||||
@@ -1245,6 +1252,7 @@ fn scenario_workspace_scaled_nested_occurrences_and_reorder_keep_exact_sequence_
|
|||||||
.find(|slot| slot.slot == expected)
|
.find(|slot| slot.slot == expected)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let point = screen(Point::new(scene.lanes[&f.sender], slot.y));
|
let point = screen(Point::new(scene.lanes[&f.sender], slot.y));
|
||||||
|
assert!(bounds.contains(point));
|
||||||
let mut input = super::super::super::SequenceInput::default();
|
let mut input = super::super::super::SequenceInput::default();
|
||||||
send(
|
send(
|
||||||
&scene,
|
&scene,
|
||||||
@@ -1921,18 +1929,15 @@ async fn scenario_workspace_open_and_back_restore_owner_and_mounted_camera_after
|
|||||||
details
|
details
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.behavior.scenario.scroll_offset(),
|
app.behavior.scenario.details_offset, details,
|
||||||
Vector::ZERO,
|
"the actual Details observer acknowledges the one-shot return offset"
|
||||||
"canvas uses camera pan, not a synthetic Scrollable offset"
|
|
||||||
);
|
);
|
||||||
for area in [ScrollArea::Canvas, ScrollArea::Page] {
|
|
||||||
assert!(
|
assert!(
|
||||||
app.behavior
|
app.behavior
|
||||||
.scenario
|
.scenario
|
||||||
.scroll_restore(app.scenario_context().unwrap(), area)
|
.scroll_restore(app.scenario_context().unwrap(), ScrollArea::Page)
|
||||||
.is_none()
|
.is_none()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
assert!(app.behavior.scenario.restore.is_none());
|
assert!(app.behavior.scenario.restore.is_none());
|
||||||
assert!(std::sync::Arc::ptr_eq(
|
assert!(std::sync::Arc::ptr_eq(
|
||||||
&before_back,
|
&before_back,
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ pub enum Event {
|
|||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Shelf {
|
pub enum Shelf {
|
||||||
Tools,
|
|
||||||
Selection,
|
Selection,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -80,7 +79,7 @@ pub struct State {
|
|||||||
identity: Option<(Id, Id)>,
|
identity: Option<(Id, Id)>,
|
||||||
tool: CanvasTool,
|
tool: CanvasTool,
|
||||||
expanded: BTreeSet<Id>,
|
expanded: BTreeSet<Id>,
|
||||||
scroll_offsets: [iced::Vector; 2],
|
details_offset: iced::Vector,
|
||||||
return_to: Vec<direct::ReturnFrame>,
|
return_to: Vec<direct::ReturnFrame>,
|
||||||
draft: Option<Draft>,
|
draft: Option<Draft>,
|
||||||
viewport: Option<Size>,
|
viewport: Option<Size>,
|
||||||
@@ -351,8 +350,8 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fit_area(&self, fallback_width: f32) -> Size {
|
fn fit_area(&self, fallback_width: f32) -> Size {
|
||||||
// The sensor measures the inner scroller, independent of graph content.
|
// The sensor measures the viewport, independent of graph content.
|
||||||
// Leave a small gutter for scrollbars; before first layout use a compact
|
// Leave a small content gutter; before first layout use a compact
|
||||||
// provisional viewport and recompute when the sensor reports actual size.
|
// provisional viewport and recompute when the sensor reports actual size.
|
||||||
let viewport = self.viewport.unwrap_or(Size::new(fallback_width, 180.));
|
let viewport = self.viewport.unwrap_or(Size::new(fallback_width, 180.));
|
||||||
Size::new(
|
Size::new(
|
||||||
@@ -361,11 +360,6 @@ impl State {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
fn canvas_offset(&self) -> iced::Vector {
|
|
||||||
self.scroll_offsets[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn fit_limited(&self, graph: Size, fallback_width: f32) -> bool {
|
fn fit_limited(&self, graph: Size, fallback_width: f32) -> bool {
|
||||||
let available = self.fit_area(fallback_width);
|
let available = self.fit_area(fallback_width);
|
||||||
@@ -984,7 +978,6 @@ mod contextual_routing;
|
|||||||
struct Graph<'a> {
|
struct Graph<'a> {
|
||||||
project: &'a Project,
|
project: &'a Project,
|
||||||
interaction: Option<(Context, CanvasTool, bool)>,
|
interaction: Option<(Context, CanvasTool, bool)>,
|
||||||
scroll_offset: iced::Vector,
|
|
||||||
camera_pan: iced::Vector,
|
camera_pan: iced::Vector,
|
||||||
pending_insertion: Option<Insertion>,
|
pending_insertion: Option<Insertion>,
|
||||||
nodes: Vec<GraphNode>,
|
nodes: Vec<GraphNode>,
|
||||||
@@ -1318,7 +1311,6 @@ impl<'a> Graph<'a> {
|
|||||||
Self {
|
Self {
|
||||||
project: p,
|
project: p,
|
||||||
interaction: None,
|
interaction: None,
|
||||||
scroll_offset: iced::Vector::ZERO,
|
|
||||||
camera_pan: iced::Vector::ZERO,
|
camera_pan: iced::Vector::ZERO,
|
||||||
pending_insertion: None,
|
pending_insertion: None,
|
||||||
nodes,
|
nodes,
|
||||||
@@ -2172,7 +2164,6 @@ mod tests {
|
|||||||
let accepted = app.document.snapshot();
|
let accepted = app.document.snapshot();
|
||||||
assert!(app.behavior.prepare_pending(app.project()).is_err());
|
assert!(app.behavior.prepare_pending(app.project()).is_err());
|
||||||
for (shelf, expected) in [
|
for (shelf, expected) in [
|
||||||
(Shelf::Tools, Some(Shelf::Tools)),
|
|
||||||
(Shelf::Selection, Some(Shelf::Selection)),
|
(Shelf::Selection, Some(Shelf::Selection)),
|
||||||
(Shelf::Selection, None),
|
(Shelf::Selection, None),
|
||||||
] {
|
] {
|
||||||
@@ -2189,10 +2180,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn process_fit_canvas_selection_ignores_retained_ordered_page_scroll() {
|
fn process_camera_selection_ignores_independent_details_scroll() {
|
||||||
let (p, owner, _, _, _, _) = fixture();
|
let (p, owner, _, _, _, _) = fixture();
|
||||||
let mut state = State::default();
|
let mut state = State::default();
|
||||||
state.scrolled(scenario::ScrollArea::Canvas, iced::Vector::new(11., 19.));
|
state.camera(crate::behavior::canvas_workspace::Event::Pan(
|
||||||
|
iced::Vector::new(11., 19.),
|
||||||
|
));
|
||||||
state.scrolled(scenario::ScrollArea::Page, iced::Vector::new(0., 370.));
|
state.scrolled(scenario::ScrollArea::Page, iced::Vector::new(0., 370.));
|
||||||
let context = Context {
|
let context = Context {
|
||||||
document: p.id,
|
document: p.id,
|
||||||
@@ -2203,16 +2196,17 @@ mod tests {
|
|||||||
for scale in [0.45, 1.] {
|
for scale in [0.45, 1.] {
|
||||||
let mut graph = Graph::new(&p, owner, owner);
|
let mut graph = Graph::new(&p, owner, owner);
|
||||||
graph.scale = scale;
|
graph.scale = scale;
|
||||||
graph.scroll_offset = state.canvas_offset();
|
graph.camera_pan = state.pan;
|
||||||
graph.interaction = Some((context, CanvasTool::Select, true));
|
graph.interaction = Some((context, CanvasTool::Select, true));
|
||||||
let node = &graph.nodes[0];
|
let node = &graph.nodes[0];
|
||||||
let id = node.id;
|
let id = node.id;
|
||||||
let point = node.rect.center();
|
let point = node.rect.center();
|
||||||
let bounds = Rectangle::new(Point::new(40., 100.), Size::new(800., 300.));
|
let bounds = Rectangle::new(Point::new(40., 100.), Size::new(800., 300.));
|
||||||
let screen = Point::new(
|
let screen = Point::new(
|
||||||
bounds.x + point.x * scale - 11.,
|
bounds.x + point.x * scale + 11.,
|
||||||
bounds.y + point.y * scale - 19.,
|
bounds.y + point.y * scale + 19.,
|
||||||
);
|
);
|
||||||
|
assert!(bounds.contains(screen));
|
||||||
let message = graph
|
let message = graph
|
||||||
.input(
|
.input(
|
||||||
&mut Input::default(),
|
&mut Input::default(),
|
||||||
|
|||||||
@@ -158,20 +158,23 @@ impl State {
|
|||||||
self.fit = false;
|
self.fit = false;
|
||||||
self.viewport = None;
|
self.viewport = None;
|
||||||
self.expanded.clear();
|
self.expanded.clear();
|
||||||
self.scroll_offsets = [iced::Vector::ZERO; 2];
|
self.details_offset = iced::Vector::ZERO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn scroll_offset(&self) -> iced::Vector {
|
pub(crate) fn details_offset(&self) -> iced::Vector {
|
||||||
self.scroll_offsets[0] + self.scroll_offsets[1]
|
self.details_offset
|
||||||
}
|
}
|
||||||
pub(crate) fn scrolled(&mut self, area: scenario::ScrollArea, offset: iced::Vector) -> bool {
|
pub(crate) fn scrolled(
|
||||||
let index = match area {
|
&mut self,
|
||||||
scenario::ScrollArea::Canvas => 0,
|
scenario::ScrollArea::Page: scenario::ScrollArea,
|
||||||
scenario::ScrollArea::Page => 1,
|
offset: iced::Vector,
|
||||||
};
|
) -> bool {
|
||||||
let changed = self.scroll_offsets[index] != offset;
|
if !offset.x.is_finite() || !offset.y.is_finite() || offset.x < 0. || offset.y < 0. {
|
||||||
self.scroll_offsets[index] = offset;
|
return false;
|
||||||
|
}
|
||||||
|
let changed = self.details_offset != offset;
|
||||||
|
self.details_offset = offset;
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
pub(crate) fn choose(&mut self, tool: CanvasTool) {
|
pub(crate) fn choose(&mut self, tool: CanvasTool) {
|
||||||
@@ -622,7 +625,7 @@ impl Graph<'_> {
|
|||||||
{
|
{
|
||||||
*moved |= position.distance(*start) >= 3.;
|
*moved |= position.distance(*start) >= 3.;
|
||||||
}
|
}
|
||||||
mouse::Cursor::Available(*position + self.scroll_offset)
|
mouse::Cursor::Available(*position)
|
||||||
}
|
}
|
||||||
canvas::Event::Mouse(mouse::Event::WheelScrolled { .. } | mouse::Event::CursorLeft)
|
canvas::Event::Mouse(mouse::Event::WheelScrolled { .. } | mouse::Event::CursorLeft)
|
||||||
| canvas::Event::Window(
|
| canvas::Event::Window(
|
||||||
@@ -641,11 +644,9 @@ impl Graph<'_> {
|
|||||||
}
|
}
|
||||||
canvas::Event::Mouse(
|
canvas::Event::Mouse(
|
||||||
mouse::Event::ButtonPressed(_) | mouse::Event::ButtonReleased(_),
|
mouse::Event::ButtonPressed(_) | mouse::Event::ButtonReleased(_),
|
||||||
) => state
|
) => state.pointer.map_or(cursor, mouse::Cursor::Available),
|
||||||
.pointer
|
|
||||||
.map_or(cursor, |p| mouse::Cursor::Available(p + self.scroll_offset)),
|
|
||||||
canvas::Event::Window(iced::window::Event::RedrawRequested(_)) => {
|
canvas::Event::Window(iced::window::Event::RedrawRequested(_)) => {
|
||||||
state.pointer = cursor.position().map(|p| p - self.scroll_offset);
|
state.pointer = cursor.position();
|
||||||
cursor
|
cursor
|
||||||
}
|
}
|
||||||
_ => cursor,
|
_ => cursor,
|
||||||
@@ -669,7 +670,7 @@ impl Graph<'_> {
|
|||||||
if !editable {
|
if !editable {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let start = cursor.position()? - self.scroll_offset;
|
let start = cursor.position()?;
|
||||||
match tool {
|
match tool {
|
||||||
CanvasTool::Add(..) => {
|
CanvasTool::Add(..) => {
|
||||||
state.press = Some(Press::Add {
|
state.press = Some(Press::Add {
|
||||||
@@ -829,7 +830,6 @@ impl Graph<'_> {
|
|||||||
}
|
}
|
||||||
let point = state
|
let point = state
|
||||||
.pointer
|
.pointer
|
||||||
.map(|p| p + self.scroll_offset)
|
|
||||||
.or_else(|| cursor.position())
|
.or_else(|| cursor.position())
|
||||||
.filter(|p| bounds.contains(*p))
|
.filter(|p| bounds.contains(*p))
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ fn send(
|
|||||||
input,
|
input,
|
||||||
&canvas::Event::Mouse(event),
|
&canvas::Event::Mouse(event),
|
||||||
bounds,
|
bounds,
|
||||||
mouse::Cursor::Available(point + graph.scroll_offset),
|
mouse::Cursor::Available(point),
|
||||||
)
|
)
|
||||||
.and_then(|a| a.into_inner().0)
|
.and_then(|a| a.into_inner().0)
|
||||||
}
|
}
|
||||||
@@ -554,16 +554,20 @@ fn position(graph: &Graph<'_>, id: Id) -> Point {
|
|||||||
.center()
|
.center()
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn direct_process_production_click_click_drag_insert_and_scroll_coordinates() {
|
fn direct_process_production_click_click_drag_insert_and_camera_coordinates() {
|
||||||
let f = fixture(Layer::Oa);
|
let f = fixture(Layer::Oa);
|
||||||
for drag in [false, true] {
|
for drag in [false, true] {
|
||||||
let mut graph = graph(&f, CanvasTool::Connect);
|
let mut graph = graph(&f, CanvasTool::Connect);
|
||||||
graph.scroll_offset = iced::Vector::new(35., 90.);
|
graph.camera_pan = iced::Vector::new(35., 90.);
|
||||||
let bounds = Rectangle::new(Point::new(10., 20.), graph.size);
|
let bounds = Rectangle::new(
|
||||||
|
Point::new(10., 20.),
|
||||||
|
Size::new(graph.size.width + 70., graph.size.height + 180.),
|
||||||
|
);
|
||||||
let screen =
|
let screen =
|
||||||
|id| position(&graph, id) + iced::Vector::new(bounds.x, bounds.y) - graph.scroll_offset;
|
|id| position(&graph, id) + iced::Vector::new(bounds.x, bounds.y) + graph.camera_pan;
|
||||||
let a = screen(f.nodes[0]);
|
let a = screen(f.nodes[0]);
|
||||||
let b = screen(f.nodes[2]);
|
let b = screen(f.nodes[2]);
|
||||||
|
assert!(bounds.contains(a) && bounds.contains(b));
|
||||||
let mut input = Input::default();
|
let mut input = Input::default();
|
||||||
send(
|
send(
|
||||||
&graph,
|
&graph,
|
||||||
@@ -1331,7 +1335,9 @@ fn direct_process_palette_preserves_fit_and_manual_camera() {
|
|||||||
let document = Id::new_v4();
|
let document = Id::new_v4();
|
||||||
state.enter(document, f.owner);
|
state.enter(document, f.owner);
|
||||||
state.viewport(Size::new(500., 300.));
|
state.viewport(Size::new(500., 300.));
|
||||||
state.scrolled(scenario::ScrollArea::Canvas, iced::Vector::new(35., 90.));
|
state.camera(super::super::canvas_workspace::Event::Pan(
|
||||||
|
iced::Vector::new(35., 90.),
|
||||||
|
));
|
||||||
state.scrolled(scenario::ScrollArea::Page, iced::Vector::new(0., 120.));
|
state.scrolled(scenario::ScrollArea::Page, iced::Vector::new(0., 120.));
|
||||||
for camera in [None, Some(0.75), Some(1.5)] {
|
for camera in [None, Some(0.75), Some(1.5)] {
|
||||||
state.zoom(camera);
|
state.zoom(camera);
|
||||||
@@ -1353,14 +1359,16 @@ fn direct_process_palette_preserves_fit_and_manual_camera() {
|
|||||||
"{layer:?} {camera:?} {tool:?}"
|
"{layer:?} {camera:?} {tool:?}"
|
||||||
);
|
);
|
||||||
assert_eq!(state.fit, camera.is_none());
|
assert_eq!(state.fit, camera.is_none());
|
||||||
assert_eq!(state.scroll_offset(), iced::Vector::new(35., 210.));
|
assert_eq!(state.pan, iced::Vector::new(35., 90.));
|
||||||
|
assert_eq!(state.details_offset(), iced::Vector::new(0., 120.));
|
||||||
state.enter(document, f.owner);
|
state.enter(document, f.owner);
|
||||||
assert_eq!(state.scale(size, 500.), before);
|
assert_eq!(state.scale(size, 500.), before);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.enter(Id::new_v4(), f.owner);
|
state.enter(Id::new_v4(), f.owner);
|
||||||
assert_eq!(state.scale(Size::new(690., 250.), 500.), 1.);
|
assert_eq!(state.scale(Size::new(690., 250.), 500.), 1.);
|
||||||
assert_eq!(state.scroll_offset(), iced::Vector::ZERO);
|
assert_eq!(state.pan, iced::Vector::ZERO);
|
||||||
|
assert_eq!(state.details_offset(), iced::Vector::ZERO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1371,15 +1379,18 @@ fn direct_process_scaled_control_and_link_gestures_use_visible_positions() {
|
|||||||
for scale in [0.15, 0.6898551, 1.5] {
|
for scale in [0.15, 0.6898551, 1.5] {
|
||||||
let mut graph = graph(&f, CanvasTool::Add(Tool::Control, ControlKind::Choice));
|
let mut graph = graph(&f, CanvasTool::Add(Tool::Control, ControlKind::Choice));
|
||||||
graph.scale = scale;
|
graph.scale = scale;
|
||||||
graph.scroll_offset = iced::Vector::new(11., 19.);
|
graph.camera_pan = iced::Vector::new(11., 19.);
|
||||||
let bounds = Rectangle::new(
|
let bounds = Rectangle::new(
|
||||||
Point::new(40., 70.),
|
Point::new(40., 70.),
|
||||||
Size::new(graph.size.width * scale, graph.size.height * scale),
|
Size::new(
|
||||||
|
graph.size.width * scale + 22.,
|
||||||
|
graph.size.height * scale + 38.,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let screen = |world: Point| {
|
let screen = |world: Point| {
|
||||||
Point::new(
|
Point::new(
|
||||||
world.x * scale + bounds.x - 11.,
|
world.x * scale + bounds.x + 11.,
|
||||||
world.y * scale + bounds.y - 19.,
|
world.y * scale + bounds.y + 19.,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let edge = &graph.edges[0];
|
let edge = &graph.edges[0];
|
||||||
@@ -1387,6 +1398,7 @@ fn direct_process_scaled_control_and_link_gestures_use_visible_positions() {
|
|||||||
(edge.points[0].x + edge.points[1].x) / 2.,
|
(edge.points[0].x + edge.points[1].x) / 2.,
|
||||||
edge.points[0].y,
|
edge.points[0].y,
|
||||||
));
|
));
|
||||||
|
assert!(bounds.contains(point));
|
||||||
let mut input = Input::default();
|
let mut input = Input::default();
|
||||||
send(
|
send(
|
||||||
&graph,
|
&graph,
|
||||||
@@ -1417,6 +1429,7 @@ fn direct_process_scaled_control_and_link_gestures_use_visible_positions() {
|
|||||||
let mut input = Input::default();
|
let mut input = Input::default();
|
||||||
let a = screen(position(&graph, f.nodes[0]));
|
let a = screen(position(&graph, f.nodes[0]));
|
||||||
let b = screen(position(&graph, f.nodes[2]));
|
let b = screen(position(&graph, f.nodes[2]));
|
||||||
|
assert!(bounds.contains(a) && bounds.contains(b));
|
||||||
send(
|
send(
|
||||||
&graph,
|
&graph,
|
||||||
&mut input,
|
&mut input,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ async fn production_process_canvas_scaled_path_creation_preserves_source_and_edi
|
|||||||
assert_eq!(app.behavior.topology.pan, pan);
|
assert_eq!(app.behavior.topology.pan, pan);
|
||||||
mounted.scroll(&app, "process-form", 500.);
|
mounted.scroll(&app, "process-form", 500.);
|
||||||
assert_eq!(mounted.geometry(&app).bounds("process-actions"), actions);
|
assert_eq!(mounted.geometry(&app).bounds("process-actions"), actions);
|
||||||
for shelf in [Shelf::Tools, Shelf::Selection, Shelf::Selection] {
|
for shelf in [Shelf::Selection, Shelf::Selection] {
|
||||||
let _ = app.update(event(Event::Shelf(shelf)));
|
let _ = app.update(event(Event::Shelf(shelf)));
|
||||||
assert_eq!(mounted.geometry(&app).bounds("process-viewport"), bounds);
|
assert_eq!(mounted.geometry(&app).bounds("process-viewport"), bounds);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
Reference in New Issue
Block a user