Retire obsolete behavior canvas scroll and tool paths

This commit is contained in:
2026-09-11 11:28:08 -07:00
parent 88c17a448f
commit a31b40d0f8
11 changed files with 403 additions and 426 deletions
+81 -87
View File
@@ -96,7 +96,6 @@ impl std::fmt::Display for CanvasTool {
}
#[derive(Debug, Clone)]
pub enum CanvasEvent {
First(Context, Tool),
Scrolled(Context, ScrollArea, iced::Vector),
Restored(Context, Id, ScrollArea, iced::Vector),
Viewport(Context, Size),
@@ -130,15 +129,13 @@ pub enum CanvasEvent {
}
#[derive(Debug, Clone, Copy)]
pub enum ScrollArea {
Canvas,
// Retained navigation channel, now the independent Selection/outline shelf.
// The independent Details/outline scroller; viewport motion uses Camera.
Page,
}
impl CanvasEvent {
pub fn context(&self) -> Context {
match self {
Self::First(context, _)
| Self::Scrolled(context, _, _)
Self::Scrolled(context, _, _)
| Self::Restored(context, ..)
| Self::Viewport(context, _)
| Self::FormShown(context, _)
@@ -211,9 +208,9 @@ pub struct State {
form_focus_pending: bool,
canvas_tool: Option<(Id, CanvasTool)>,
call_choices: RefCell<Option<(CallChoiceContext, Vec<Choice>)>>,
scroll_offsets: [iced::Vector; 2],
details_offset: iced::Vector,
return_to: Vec<Caller>,
restore: Option<ViewportRestore>,
restore: Option<DetailsRestore>,
presentation: workspace::Presentation,
}
const CALLER_LIMIT: usize = 32;
@@ -223,15 +220,14 @@ pub(crate) struct Caller {
pub occurrence: Id,
target: Id,
tool: CanvasTool,
offsets: [iced::Vector; 2],
details_offset: iced::Vector,
presentation: workspace::Presentation,
}
struct ViewportRestore {
struct DetailsRestore {
document: Id,
owner: Id,
token: Id,
offsets: [iced::Vector; 2],
pending: [bool; 2],
offset: iced::Vector,
}
#[derive(Clone, Copy)]
pub(super) struct ScrollRestore {
@@ -336,7 +332,7 @@ impl State {
focused: self.presentation.focused,
..Default::default()
};
self.scroll_offsets = [iced::Vector::ZERO; 2];
self.details_offset = iced::Vector::ZERO;
self.restore = None;
}
}
@@ -396,11 +392,11 @@ impl State {
occurrence,
target,
tool: self.canvas_tool(context.scenario),
offsets: self.scroll_offsets,
details_offset: self.details_offset,
presentation: self.presentation.clone(),
});
self.canvas_tool = Some((target, CanvasTool::Select));
self.scroll_offsets = [iced::Vector::ZERO; 2];
self.details_offset = iced::Vector::ZERO;
self.restore = None;
Ok(target)
}
@@ -427,34 +423,30 @@ impl State {
return;
}
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.identity = Some((context.document, context.scenario));
self.restore = Some(ViewportRestore {
self.restore = Some(DetailsRestore {
document: context.document,
owner: frame.owner,
token: Id::new_v4(),
offsets: frame.offsets,
pending: [false, true],
offset: frame.details_offset,
});
}
pub(super) fn scroll_restore(
&self,
context: Context,
area: ScrollArea,
ScrollArea::Page: ScrollArea,
) -> Option<ScrollRestore> {
let index = match area {
ScrollArea::Canvas => 0,
ScrollArea::Page => 1,
};
let restore = self.restore.as_ref().filter(|r| {
r.document == context.document && r.owner == context.scenario && r.pending[index]
})?;
let restore = self
.restore
.as_ref()
.filter(|r| r.document == context.document && r.owner == context.scenario)?;
Some(ScrollRestore {
document: restore.document,
owner: restore.owner,
token: restore.token,
offset: restore.offsets[index],
offset: restore.offset,
})
}
pub(crate) fn restored(
@@ -464,22 +456,12 @@ impl State {
area: ScrollArea,
offset: iced::Vector,
) -> bool {
let index = match area {
ScrollArea::Canvas => 0,
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 {
if !self.restore.as_ref().is_some_and(|r| {
r.document == context.document && r.owner == context.scenario && r.token == token
}) {
return false;
};
restore.pending[index] = false;
if !restore.pending.contains(&true) {
self.restore = None;
}
self.restore = None;
self.scrolled(area, offset)
}
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. {
return false;
}
let index = match area {
ScrollArea::Canvas => 0,
ScrollArea::Page => 1,
};
let changed = self.scroll_offsets[index] != offset;
self.scroll_offsets[index] = offset;
let changed = self.details_offset != offset;
self.details_offset = offset;
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> {
let key = CallChoiceContext {
document: context.document,
@@ -2079,20 +2052,31 @@ pub(crate) mod direct_tests {
navigation_event(&mut app, |ctx| {
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| {
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| {
CanvasEvent::Scrolled(ctx, ScrollArea::Page, iced::Vector::new(0., 80.))
});
navigation_event(&mut app, |ctx| CanvasEvent::OpenUse(ctx, caller));
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| {
CanvasEvent::Choose(ctx, CanvasTool::Message(MessageStyle::Timer))
});
let camera_epoch = app.camera_epoch;
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));
assert_eq!(app.selected, c);
@@ -2103,7 +2087,7 @@ pub(crate) mod direct_tests {
CanvasTool::Message(MessageStyle::Timer)
);
assert_eq!(
app.behavior.scenario.scroll_offset(),
app.behavior.scenario.presentation.pan,
iced::Vector::new(0., 115.)
);
navigation_event(&mut app, CanvasEvent::Back);
@@ -2114,11 +2098,11 @@ pub(crate) mod direct_tests {
CanvasTool::Add(Tool::Reuse)
);
assert_eq!(
app.behavior.scenario.scroll_offset(),
app.behavior.scenario.presentation.pan,
iced::Vector::new(25., 170.)
);
assert_eq!(
app.behavior.scenario.scroll_offsets[1],
app.behavior.scenario.details_offset,
iced::Vector::new(0., 80.),
"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 mut scene = Scene::sequence(app.project(), f.owner, f.call);
scene.interaction = Some((context, CanvasTool::Add(tool), true));
scene.scroll_offset = iced::Vector::new(23., 80.);
let bounds = Rectangle::new(Point::new(20., 40.), scene.size);
scene.camera_pan = iced::Vector::new(23., 80.);
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 x = if on_lifeline {
scene.lanes[&f.sender]
} else {
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();
canvas_mouse(
&scene,
&mut input,
mouse::Event::CursorMoved {
position: point - scene.scroll_offset,
},
mouse::Event::CursorMoved { position: point },
point,
bounds,
);
@@ -2487,10 +2473,10 @@ pub(crate) mod direct_tests {
assert!(!app.behavior.has_pending());
}
let old_context = app.scenario_context().unwrap();
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Camera(
old_context,
ScrollArea::Canvas,
iced::Vector::new(0., 100.),
app.camera_epoch,
super::super::canvas_workspace::Event::Pan(iced::Vector::new(0., 100.)),
)));
let _ = app.update(Message::ScenarioCanvas(event(old_context)));
assert!(!app.behavior.has_pending());
@@ -2681,26 +2667,29 @@ pub(crate) mod direct_tests {
);
}
#[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 mut app = App::with_project(f.project.clone());
let _ = app.update(Message::Select(f.owner));
let _ = app.update(Message::Page(crate::app::Page::Behavior));
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();
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(
context, area, offset,
)));
}
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
context,
ScrollArea::Page,
iced::Vector::new(0., 40.),
)));
assert!(app.busy);
assert_eq!(
app.behavior.scenario.scroll_offset(),
iced::Vector::new(25., 100.)
app.behavior.scenario.details_offset,
iced::Vector::new(0., 40.)
);
assert_ne!(
app.scenario_context(),
@@ -2711,14 +2700,14 @@ pub(crate) mod direct_tests {
stale.document = Id::new_v4();
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Scrolled(
stale,
ScrollArea::Canvas,
ScrollArea::Page,
iced::Vector::new(0., 900.),
)));
assert_eq!(
app.behavior.scenario.scroll_offset(),
iced::Vector::new(25., 100.)
app.behavior.scenario.details_offset,
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 {
document: context.document,
revision: context.revision,
@@ -2739,12 +2728,17 @@ pub(crate) mod direct_tests {
let context = app.scenario_context().unwrap();
let mut scene = Scene::sequence(app.project(), f.owner, f.call);
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 bounds = Rectangle::new(Point::new(20., 250.), scene.size);
let y = bounds.y + scene.slots.last().unwrap().y;
let a = Point::new(bounds.x + scene.lanes[&f.sender], y) - scene.scroll_offset;
let b = Point::new(bounds.x + scene.lanes[&f.receiver], 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.camera_pan;
assert!(bounds.contains(a) && bounds.contains(b));
let mut input = super::super::SequenceInput::default();
let mut published = vec![];
for event in [
@@ -2758,7 +2752,7 @@ pub(crate) mod direct_tests {
&mut input,
&canvas::Event::Mouse(event),
bounds,
mouse::Cursor::Available(b + scene.scroll_offset),
mouse::Cursor::Available(b),
)
.and_then(|action| action.into_inner().0)
{