//! 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), Begin(Tool, Id), Endpoint(Context, Id), Restart(Context), } #[derive(Debug, Clone)] pub struct Session { pub context: Context, pub tool: Tool, pub source: Option, pub candidates: BTreeSet, } 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 + '_ { 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"); } }