145 lines
5.2 KiB
Rust
145 lines
5.2 KiB
Rust
//! A palette operation is transient; only a completed connection enters history.
|
|
use crate::{connection_tool::Tool, route_edit::Context};
|
|
use oa_core::{Id, Project, projection::ProjectedView};
|
|
use std::collections::BTreeSet;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Event {
|
|
Choose(Option<Tool>),
|
|
Begin(Tool, Id),
|
|
Endpoint(Context, Id),
|
|
Restart(Context),
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Session {
|
|
pub context: Context,
|
|
pub tool: Tool,
|
|
pub source: Option<Id>,
|
|
pub candidates: BTreeSet<Id>,
|
|
}
|
|
|
|
impl Session {
|
|
pub fn new(project: &Project, view: &ProjectedView, context: Context, tool: Tool) -> Self {
|
|
let candidates = visible(view)
|
|
.filter(|id| tool.can_start(project, *id))
|
|
.collect();
|
|
Self {
|
|
context,
|
|
tool,
|
|
source: None,
|
|
candidates,
|
|
}
|
|
}
|
|
|
|
pub fn start(&mut self, project: &Project, view: &ProjectedView, source: Id) {
|
|
self.source = Some(source);
|
|
let compatible = self.tool.targets(project, source);
|
|
self.candidates = visible(view).filter(|id| compatible.contains(id)).collect();
|
|
}
|
|
}
|
|
|
|
pub fn visible(view: &ProjectedView) -> impl Iterator<Item = Id> + '_ {
|
|
view.nodes
|
|
.iter()
|
|
.map(|n| n.element)
|
|
.chain(view.ports.iter().map(|p| p.element))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::app::{App, Message, Page};
|
|
use oa_core::*;
|
|
|
|
include!("connection_allocation_tests.rs");
|
|
|
|
fn fixture() -> (App, Id, Id, Id) {
|
|
let mut p = Project::empty();
|
|
let a = p.add(Kind::Function, "Send command", 0., 0.);
|
|
let b = p.add(Kind::Function, "Receive command", 400., 0.);
|
|
let hidden = p
|
|
.add_element(Kind::Function, Layer::Sa, "Hidden work", None)
|
|
.unwrap();
|
|
(App::with_project(p), a, b, hidden)
|
|
}
|
|
fn send(app: &mut App, event: Event) {
|
|
let _ = app.update(Message::Connection(event));
|
|
}
|
|
|
|
#[test]
|
|
fn visible_endpoint_drawing_commits_ports_and_exchange_once_with_exact_history() {
|
|
let (mut app, a, b, hidden) = fixture();
|
|
let before = app.document.bundle().clone();
|
|
let revision = app.document.revision();
|
|
send(&mut app, Event::Choose(Some(Tool::FunctionalExchange)));
|
|
assert!(
|
|
!app.connection
|
|
.as_ref()
|
|
.unwrap()
|
|
.candidates
|
|
.contains(&hidden)
|
|
);
|
|
let context = app.route_context().unwrap();
|
|
send(&mut app, Event::Endpoint(context, a));
|
|
send(&mut app, Event::Endpoint(context, hidden));
|
|
assert_eq!(app.document.revision(), revision);
|
|
assert_eq!(app.connection.as_ref().unwrap().source, Some(a));
|
|
send(&mut app, Event::Endpoint(context, b));
|
|
assert!(app.connection.is_none());
|
|
assert_eq!(app.document.revision(), revision + 1);
|
|
let relation = &app.project().relations[&app.selected];
|
|
assert_eq!(relation.kind(), RelationKind::FunctionalExchange);
|
|
assert_eq!((relation.source(), relation.target()), (a, b));
|
|
assert_eq!(
|
|
app.project()
|
|
.elements
|
|
.values()
|
|
.filter(|e| e.kind() == Kind::Port)
|
|
.count(),
|
|
2
|
|
);
|
|
let after = app.document.bundle().clone();
|
|
let _ = app.update(Message::Undo);
|
|
assert_eq!(*app.document.bundle(), before);
|
|
let _ = app.update(Message::Redo);
|
|
assert_eq!(*app.document.bundle(), after);
|
|
}
|
|
|
|
#[test]
|
|
fn cancel_navigation_revision_and_dirty_drafts_cannot_commit_a_stale_connection() {
|
|
let (mut app, a, b, _) = fixture();
|
|
send(&mut app, Event::Choose(Some(Tool::FunctionalExchange)));
|
|
let context = app.route_context().unwrap();
|
|
send(&mut app, Event::Endpoint(context, a));
|
|
let _ = app.update(Message::Escape);
|
|
send(&mut app, Event::Endpoint(context, b));
|
|
assert!(app.project().relations.is_empty());
|
|
send(&mut app, Event::Choose(Some(Tool::FunctionalExchange)));
|
|
let current = app.route_context().unwrap();
|
|
send(&mut app, Event::Endpoint(current, a));
|
|
send(&mut app, Event::Endpoint(context, b));
|
|
assert_eq!(
|
|
app.connection.as_ref().unwrap().source,
|
|
Some(a),
|
|
"A stale click must leave the newer drawing session intact"
|
|
);
|
|
let revision = app.document.revision();
|
|
let _ = app.update(Message::Route(crate::route_edit::Event::AddBend));
|
|
assert_eq!(app.document.revision(), revision);
|
|
assert!(app.connection.is_some());
|
|
let _ = app.update(Message::Page(Page::Catalog));
|
|
assert!(app.connection.is_none());
|
|
let _ = app.update(Message::Page(Page::Diagram));
|
|
send(&mut app, Event::Choose(Some(Tool::FunctionalExchange)));
|
|
send(&mut app, Event::Endpoint(context, b));
|
|
assert!(app.project().relations.is_empty());
|
|
let _ = app.update(Message::Select(a));
|
|
let _ = app.update(Message::Field("/name".into(), "Pending rename".into()));
|
|
assert!(app.draft_dirty());
|
|
send(&mut app, Event::Choose(Some(Tool::FunctionalExchange)));
|
|
assert!(app.connection.is_none());
|
|
assert_eq!(app.project().named(a), "Send command");
|
|
}
|
|
}
|