289 lines
9.2 KiB
Rust
289 lines
9.2 KiB
Rust
use crate::{
|
|
app::{App, Message},
|
|
connection_edit,
|
|
connection_tool::Tool,
|
|
context_menu::Entry,
|
|
diagram::{DiagramCanvas, State},
|
|
model_menu, port_edit,
|
|
};
|
|
use iced::{
|
|
Point, Rectangle, Size, Vector, keyboard, mouse,
|
|
widget::canvas::{self, Program},
|
|
};
|
|
use oa_core::*;
|
|
|
|
fn fixture() -> (App, Id, Id) {
|
|
let mut p = Project::empty();
|
|
let a = p.add(Kind::Function, "Transmit", 100., 200.);
|
|
let b = p.add(Kind::Function, "Receive", 500., 200.);
|
|
let output = Id::new_v4();
|
|
let input = Id::new_v4();
|
|
p.elements.insert(
|
|
output,
|
|
make_port(
|
|
&p,
|
|
output,
|
|
a,
|
|
"Send",
|
|
PortContract::FunctionOutput {
|
|
outgoing_items: vec![],
|
|
},
|
|
)
|
|
.unwrap(),
|
|
);
|
|
p.elements.insert(
|
|
input,
|
|
make_port(
|
|
&p,
|
|
input,
|
|
b,
|
|
"Receive",
|
|
PortContract::FunctionInput {
|
|
incoming_items: vec![],
|
|
},
|
|
)
|
|
.unwrap(),
|
|
);
|
|
let mut app = App::with_project(p);
|
|
app.pan = Vector::new(20., 30.);
|
|
(app, output, input)
|
|
}
|
|
fn bounds() -> Rectangle {
|
|
Rectangle::new(Point::new(260., 100.), Size::new(2200., 1600.))
|
|
}
|
|
fn canvas(app: &App) -> DiagramCanvas<'_> {
|
|
DiagramCanvas {
|
|
project: app.project(),
|
|
view_index: None,
|
|
diagram: app.active_diagram.unwrap(),
|
|
selected: app.selected,
|
|
navigation_focus: None,
|
|
zoom: 1.,
|
|
pan: app.pan,
|
|
context: app.route_context().unwrap(),
|
|
selected_bend: None,
|
|
selected_attachment: None,
|
|
editable: true,
|
|
placing: false,
|
|
connection: app.connection.as_ref(),
|
|
allocation: None,
|
|
}
|
|
}
|
|
fn screen(app: &App, id: Id) -> Point {
|
|
let v = projection::project_view(app.project(), app.active_diagram.unwrap());
|
|
let p = v.ports.iter().find(|p| p.element == id).unwrap().position;
|
|
Point::new(bounds().x + app.pan.x + p.x, bounds().y + app.pan.y + p.y)
|
|
}
|
|
fn event(c: &DiagramCanvas<'_>, s: &mut State, e: mouse::Event, p: Point) -> Option<Message> {
|
|
c.update(
|
|
s,
|
|
&canvas::Event::Mouse(e),
|
|
bounds(),
|
|
mouse::Cursor::Available(p),
|
|
)
|
|
.and_then(|a| a.into_inner().0)
|
|
}
|
|
fn choose(app: &App, caption: &str) -> Message {
|
|
model_menu::popup(app)
|
|
.unwrap()
|
|
.entries
|
|
.into_iter()
|
|
.find_map(|e| match e {
|
|
Entry::Action { label, message, .. } if label == caption => message,
|
|
_ => None,
|
|
})
|
|
.unwrap_or_else(|| panic!("No action {caption}"))
|
|
}
|
|
fn send(app: &mut App, m: Message) {
|
|
let _ = app.update(m);
|
|
}
|
|
|
|
#[test]
|
|
fn middle_and_arrow_panning_preserve_source_and_history() {
|
|
for armed in [false, true] {
|
|
let (mut app, a, _) = fixture();
|
|
send(
|
|
&mut app,
|
|
Message::Connection(connection_edit::Event::Choose(Some(
|
|
Tool::FunctionalExchange,
|
|
))),
|
|
);
|
|
let context = app.route_context().unwrap();
|
|
if armed {
|
|
send(
|
|
&mut app,
|
|
Message::Connection(connection_edit::Event::Endpoint(context, a)),
|
|
);
|
|
}
|
|
let before = app.document.bundle().clone();
|
|
let pan = app.pan;
|
|
let mut state = State::default();
|
|
let start = screen(&app, a);
|
|
let end = start + Vector::new(140., 70.);
|
|
let c = canvas(&app);
|
|
let messages: Vec<_> = [
|
|
mouse::Event::CursorMoved { position: start },
|
|
mouse::Event::ButtonPressed(mouse::Button::Middle),
|
|
mouse::Event::CursorMoved { position: end },
|
|
mouse::Event::ButtonReleased(mouse::Button::Middle),
|
|
]
|
|
.into_iter()
|
|
.filter_map(|e| event(&c, &mut state, e, end))
|
|
.collect();
|
|
for m in messages {
|
|
send(&mut app, m);
|
|
}
|
|
assert_eq!(app.pan, pan + Vector::new(140., 70.));
|
|
assert_eq!(app.connection.as_ref().unwrap().source, armed.then_some(a));
|
|
let e = canvas::Event::Keyboard(keyboard::Event::KeyPressed {
|
|
key: keyboard::Key::Named(keyboard::key::Named::ArrowRight),
|
|
modified_key: keyboard::Key::Named(keyboard::key::Named::ArrowRight),
|
|
physical_key: keyboard::key::Physical::Code(keyboard::key::Code::ArrowRight),
|
|
location: keyboard::Location::Standard,
|
|
modifiers: keyboard::Modifiers::empty(),
|
|
text: None,
|
|
repeat: false,
|
|
});
|
|
let message = canvas(&app)
|
|
.update(&mut state, &e, bounds(), mouse::Cursor::Available(end))
|
|
.unwrap()
|
|
.into_inner()
|
|
.0
|
|
.unwrap();
|
|
send(&mut app, message);
|
|
assert_eq!(app.pan, pan + Vector::new(60., 70.));
|
|
assert_eq!(app.connection.as_ref().unwrap().context, context);
|
|
assert_eq!(app.document.bundle(), &before);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn menu_reset_and_draw_use_fresh_open_context() {
|
|
let (mut app, a, b) = fixture();
|
|
let c = app.route_context().unwrap();
|
|
let v = projection::project_view(app.project(), c.diagram);
|
|
let owner = v.ports.iter().find(|p| p.element == a).unwrap().owner;
|
|
send(
|
|
&mut app,
|
|
Message::PortAnchor(port_edit::Event::Commit(
|
|
c,
|
|
owner,
|
|
PortAnchor {
|
|
port: a,
|
|
side: PortAnchorSide::Top,
|
|
offset: 0.4,
|
|
},
|
|
)),
|
|
);
|
|
let before = app.document.bundle().clone();
|
|
let revision = app.document.revision();
|
|
let mut request = model_menu::Request::object(a, screen(&app, a));
|
|
request.canvas = app.route_context();
|
|
let old = request.canvas.unwrap();
|
|
send(&mut app, Message::Menu(model_menu::Event::Open(request)));
|
|
assert_ne!(app.route_context().unwrap().epoch, old.epoch);
|
|
let action = choose(&app, "Reset automatic position");
|
|
send(&mut app, action);
|
|
assert!(
|
|
app.project().diagrams[&c.diagram].nodes[&owner]
|
|
.port_anchors
|
|
.is_empty()
|
|
);
|
|
assert_eq!(app.document.revision(), revision + 1);
|
|
send(&mut app, Message::Undo);
|
|
assert_eq!(app.document.bundle(), &before);
|
|
let mut request = model_menu::Request::object(a, screen(&app, a));
|
|
request.canvas = app.route_context();
|
|
send(&mut app, Message::Menu(model_menu::Event::Open(request)));
|
|
let caption = model_menu::popup(&app)
|
|
.unwrap()
|
|
.entries
|
|
.iter()
|
|
.find_map(|e| match e {
|
|
Entry::Action { label, .. } if label.starts_with("Draw functional exchange") => {
|
|
Some(label.clone())
|
|
}
|
|
_ => None,
|
|
})
|
|
.unwrap();
|
|
let action = choose(&app, &caption);
|
|
send(&mut app, action);
|
|
assert_eq!(app.connection.as_ref().unwrap().source, Some(a));
|
|
let context = app.route_context().unwrap();
|
|
assert_eq!(app.connection.as_ref().unwrap().context, context);
|
|
send(
|
|
&mut app,
|
|
Message::Connection(connection_edit::Event::Endpoint(context, b)),
|
|
);
|
|
assert_eq!(app.project().relations.len(), 1);
|
|
let accepted = app.document.bundle().clone();
|
|
assert_eq!(
|
|
source_project::compile_bundle(&accepted, Default::default())
|
|
.unwrap()
|
|
.decoded
|
|
.project,
|
|
*app.project()
|
|
);
|
|
send(&mut app, Message::Undo);
|
|
assert_eq!(app.document.bundle(), &before);
|
|
}
|
|
|
|
#[test]
|
|
fn motion_after_release_must_not_move_clicked_port() {
|
|
let (mut app, a, _) = fixture();
|
|
let before = app.document.bundle().clone();
|
|
let start = screen(&app, a);
|
|
let end = start + Vector::new(-120., -150.);
|
|
let mut state = State::default();
|
|
let c = canvas(&app);
|
|
// Iced batches all window events until AboutToWait and passes the final
|
|
// window cursor to every event, even when the final motion follows release.
|
|
let messages: Vec<_> = [
|
|
mouse::Event::CursorMoved { position: start },
|
|
mouse::Event::ButtonPressed(mouse::Button::Left),
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
mouse::Event::CursorMoved { position: end },
|
|
]
|
|
.into_iter()
|
|
.filter_map(|e| event(&c, &mut state, e, end))
|
|
.collect();
|
|
for m in messages {
|
|
send(&mut app, m);
|
|
}
|
|
assert!(
|
|
app.document.bundle() == &before,
|
|
"Moving the cursor after release must not author a port anchor"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn motion_after_source_click_release_must_not_create_connection() {
|
|
let (mut app, a, b) = fixture();
|
|
send(
|
|
&mut app,
|
|
Message::Connection(connection_edit::Event::Choose(Some(
|
|
Tool::FunctionalExchange,
|
|
))),
|
|
);
|
|
let start = screen(&app, a);
|
|
let end = screen(&app, b);
|
|
let mut state = State::default();
|
|
let c = canvas(&app);
|
|
let messages: Vec<_> = [
|
|
mouse::Event::CursorMoved { position: start },
|
|
mouse::Event::ButtonPressed(mouse::Button::Left),
|
|
mouse::Event::ButtonReleased(mouse::Button::Left),
|
|
mouse::Event::CursorMoved { position: end },
|
|
]
|
|
.into_iter()
|
|
.filter_map(|e| event(&c, &mut state, e, end))
|
|
.collect();
|
|
for m in messages {
|
|
send(&mut app, m);
|
|
}
|
|
assert!(
|
|
app.project().relations.is_empty(),
|
|
"Hover after releasing the source is not a connection commit"
|
|
);
|
|
}
|