Complete canvas input wrappers and verify saved-removal event batches
This commit is contained in:
@@ -8,10 +8,22 @@ fn point(x: f32, y: f32) -> Position {
|
||||
}
|
||||
|
||||
fn saved(project: &mut Project, layer: Layer, projection: ProjectionKind) -> Id {
|
||||
let id = project.ensure_diagram(layer);
|
||||
let view = project.diagrams.get_mut(&id).unwrap();
|
||||
view.projection = projection;
|
||||
view.description = "Authored view metadata must survive".into();
|
||||
let id = Id::new_v4();
|
||||
project.diagrams.insert(
|
||||
id,
|
||||
Diagram {
|
||||
id,
|
||||
alias: format!("saved_{}", id.simple()),
|
||||
name: "Saved view".into(),
|
||||
description: "Authored view metadata must survive".into(),
|
||||
layer,
|
||||
projection,
|
||||
nodes: Default::default(),
|
||||
routes: vec![],
|
||||
layout: None,
|
||||
view_options: Default::default(),
|
||||
},
|
||||
);
|
||||
id
|
||||
}
|
||||
|
||||
@@ -70,6 +82,153 @@ fn source_matches(app: &App) {
|
||||
);
|
||||
}
|
||||
|
||||
fn mounted_fixture(state_view: bool) -> App {
|
||||
if state_view {
|
||||
let mut f = state_fixture(Layer::Sa);
|
||||
choose(
|
||||
&mut f.app,
|
||||
&[Target::Placement(f.na), Target::Placement(f.nb)],
|
||||
);
|
||||
f.app
|
||||
} else {
|
||||
architecture_fixture(Layer::Sa).app
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_canvas(mounted: &mut crate::behavior::canvas_workspace::tests::Mounted, app: &App) {
|
||||
use iced::advanced::widget::{self, operation::Focusable};
|
||||
struct Focus;
|
||||
impl widget::Operation for Focus {
|
||||
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) {
|
||||
visit(self);
|
||||
}
|
||||
fn focusable(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
_: iced::Rectangle,
|
||||
state: &mut dyn Focusable,
|
||||
) {
|
||||
if id == Some(&widget::Id::from("diagram-keyboard-owner")) {
|
||||
state.focus();
|
||||
} else {
|
||||
state.unfocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
mounted.operate(app, &mut Focus);
|
||||
}
|
||||
|
||||
fn shift_delete() -> iced::Event {
|
||||
let mut event =
|
||||
crate::behavior::canvas_workspace::tests::key(iced::keyboard::key::Named::Delete);
|
||||
if let iced::Event::Keyboard(iced::keyboard::Event::KeyPressed { modifiers, .. }) = &mut event {
|
||||
*modifiers = iced::keyboard::Modifiers::SHIFT;
|
||||
}
|
||||
event
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn saved_removal_mounted_shift_delete_review_query_and_stamped_escape_preserve_source() {
|
||||
use crate::behavior::canvas_workspace::tests::{Mounted, key};
|
||||
use iced::{keyboard::key::Named, mouse};
|
||||
for state_view in [false, true] {
|
||||
let mut app = mounted_fixture(state_view);
|
||||
let mut mounted = Mounted::new(&mut app).await;
|
||||
focus_canvas(&mut mounted, &app);
|
||||
let selected = app.multiselect_snapshot().unwrap();
|
||||
let before = app.document.snapshot();
|
||||
let messages = mounted
|
||||
.batch(&mut app, &[shift_delete()], mouse::Cursor::Unavailable)
|
||||
.await;
|
||||
assert!(messages.iter().any(|message| matches!(message, Message::SavedRemoval(event) if matches!(&**event, Event::Begin { snapshot, .. } if snapshot.targets() == selected.targets()))), "{state_view}: {messages:?}");
|
||||
let confirmation = app.delete_target.as_ref().unwrap();
|
||||
assert!(app.saved_removal_review_valid(confirmation));
|
||||
let token = confirmation.snapshot.token;
|
||||
send(
|
||||
&mut app,
|
||||
Event::Query {
|
||||
token,
|
||||
query: "no matching record".into(),
|
||||
},
|
||||
);
|
||||
mounted.settle(&mut app).await;
|
||||
assert!(app.saved_removal_review_valid(app.delete_target.as_ref().unwrap()));
|
||||
let messages = mounted
|
||||
.batch(&mut app, &[key(Named::Escape)], mouse::Cursor::Unavailable)
|
||||
.await;
|
||||
assert!(messages.iter().any(|message| matches!(message, Message::SavedRemoval(event) if matches!(&**event, Event::Cancel { token: t } if *t == token))), "{messages:?}");
|
||||
assert!(app.delete_target.is_none());
|
||||
assert!(Arc::ptr_eq(&before, &app.document.snapshot()));
|
||||
assert_eq!(
|
||||
app.multiselect_snapshot().unwrap().targets(),
|
||||
selected.targets()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn saved_removal_mounted_later_press_or_tab_revokes_queued_review_before_app() {
|
||||
use crate::behavior::canvas_workspace::tests::{Mounted, key};
|
||||
use iced::{Event as Input, keyboard::key::Named, mouse};
|
||||
for state_view in [false, true] {
|
||||
for tab in [false, true] {
|
||||
let mut app = mounted_fixture(state_view);
|
||||
let mut mounted = Mounted::new(&mut app).await;
|
||||
focus_canvas(&mut mounted, &app);
|
||||
let geometry = mounted.geometry(&app);
|
||||
let bounds = geometry
|
||||
.focused
|
||||
.iter()
|
||||
.find(|(id, _, _)| {
|
||||
id.as_ref() == Some(&iced::advanced::widget::Id::from("diagram-keyboard-owner"))
|
||||
})
|
||||
.unwrap()
|
||||
.1;
|
||||
let at = iced::Point::new(
|
||||
bounds.x + bounds.width - 12.,
|
||||
bounds.y + bounds.height - 12.,
|
||||
);
|
||||
let before = app.document.snapshot();
|
||||
let boundary = if tab {
|
||||
key(Named::Tab)
|
||||
} else {
|
||||
Input::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle))
|
||||
};
|
||||
let messages = mounted
|
||||
.batch(
|
||||
&mut app,
|
||||
&[
|
||||
shift_delete(),
|
||||
Input::Mouse(mouse::Event::CursorMoved { position: at }),
|
||||
boundary,
|
||||
],
|
||||
mouse::Cursor::Available(at),
|
||||
)
|
||||
.await;
|
||||
assert!(messages.iter().any(|message| matches!(message, Message::SavedRemoval(event) if matches!(&**event, Event::Begin { .. }))), "{state_view}/{tab}: {messages:?}");
|
||||
assert!(
|
||||
app.delete_target.is_none(),
|
||||
"later input must retire the queued request"
|
||||
);
|
||||
assert!(Arc::ptr_eq(&before, &app.document.snapshot()));
|
||||
if !tab {
|
||||
assert!(begin(&app).is_none(), "active pan owns an input lease");
|
||||
mounted
|
||||
.batch(
|
||||
&mut app,
|
||||
&[Input::Window(iced::window::Event::Unfocused)],
|
||||
mouse::Cursor::Unavailable,
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
app.saved_removal_input.idle().is_some(),
|
||||
"abandoned gesture releases its lease"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Architecture {
|
||||
app: App,
|
||||
diagram: Id,
|
||||
|
||||
@@ -1529,9 +1529,8 @@ fn kind_for_tool(tool: Tool) -> Kind {
|
||||
}
|
||||
}
|
||||
|
||||
impl canvas::Program<Event> for StateCanvas<'_> {
|
||||
type State = State;
|
||||
fn update(
|
||||
impl StateCanvas<'_> {
|
||||
fn update_input(
|
||||
&self,
|
||||
state: &mut State,
|
||||
event: &canvas::Event,
|
||||
|
||||
@@ -610,9 +610,8 @@ impl DiagramCanvas<'_> {
|
||||
});
|
||||
}
|
||||
}
|
||||
impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
type State = State;
|
||||
fn update(
|
||||
impl DiagramCanvas<'_> {
|
||||
fn update_input(
|
||||
&self,
|
||||
state: &mut State,
|
||||
event: &canvas::Event,
|
||||
|
||||
@@ -206,6 +206,16 @@ impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Owner<'_, Mes
|
||||
// The focus task runs after the native batch. Retain focus for its
|
||||
// traversal count, but reject later keys in this same batch now.
|
||||
state.traversing = true;
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
&InputEvent::Window(iced::window::Event::Unfocused),
|
||||
layout,
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
shell.publish((self.traverse)(modifiers.shift()));
|
||||
shell.capture_event();
|
||||
return;
|
||||
|
||||
@@ -113,4 +113,20 @@ mod tests {
|
||||
lifetime.revoke_idle();
|
||||
assert_ne!(lifetime.idle(), after_drop);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removal_input_saturation_and_replaced_owner_cannot_reuse_a_packet() {
|
||||
let terminal = Lifetime(Arc::new(AtomicU64::new(u64::MAX - 1)));
|
||||
terminal.revoke_idle();
|
||||
assert!(terminal.idle().is_none() && terminal.acquire().is_none());
|
||||
let original = Lifetime::default();
|
||||
let replacement = Lifetime::default();
|
||||
let captured = original.idle();
|
||||
let mut lease = original.acquire();
|
||||
replacement.sync(&mut lease, true);
|
||||
assert_ne!(original.idle(), captured);
|
||||
assert!(replacement.idle().is_none());
|
||||
drop(lease);
|
||||
assert!(replacement.idle().is_some());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user