Files
ARCADIA-Studio/crates/oa-desktop/src/route_edit.rs
T

199 lines
5.5 KiB
Rust

//! Transient connection gestures. Only their final route enters model history.
use iced::Point;
use oa_core::{routing::*, *};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Context {
pub document: Id,
pub diagram: Id,
pub revision: u64,
pub epoch: u64,
}
#[derive(Debug, Clone)]
pub enum Event {
SelectBend(Id, usize),
Commit(Context, EdgeRoute, Option<usize>),
CommitAttachment(Context, EdgeRoute, EdgeEnd, Id),
ChooseAttachment(EdgeEnd),
ResetAttachment(EdgeEnd),
AddBend,
AddBendAt(oa_core::Position),
RemoveBend,
Reset,
}
#[derive(Debug, Clone)]
pub enum Handle {
Bend(usize),
Label {
original: Position,
},
Attachment {
end: EdgeEnd,
owner: projection::ProjectedNode,
original: Option<EdgeAttachment>,
},
}
#[derive(Debug, Clone)]
pub struct Gesture {
pub context: Context,
pub start: Point,
pub handle: Handle,
pub route: EdgeRoute,
pub changed: bool,
pub baseline: std::sync::Arc<RoutedView>,
}
impl Gesture {
pub fn move_to(&mut self, point: Point, zoom: f32) {
let delta = point - self.start;
if !self.changed && (delta.x * delta.x + delta.y * delta.y) * zoom * zoom < 9. {
return;
}
self.changed = true;
match self.handle {
Handle::Bend(index) => {
self.route.waypoints[index] = Position {
x: (point.x / 5.).round() * 5.,
y: (point.y / 5.).round() * 5.,
}
}
Handle::Label { original } => {
if let Some(label) = &mut self.route.label {
label.offset = Position {
x: original.x + delta.x,
y: original.y + delta.y,
};
}
}
Handle::Attachment { .. } => {}
}
}
pub fn move_attachment_to(&mut self, point: Point, zoom: f32) {
let Handle::Attachment {
end,
ref owner,
original,
} = self.handle
else {
self.move_to(point, zoom);
return;
};
if point.distance(self.start) * zoom < 3. {
self.route.set_attachment(end, original);
self.changed = false;
return;
}
self.changed = true;
let attachment = attachment_from_point(
owner,
position(point),
self.route.attachment(end).map(|a| a.side),
6. / zoom,
);
self.route.set_attachment(end, Some(attachment));
}
}
/// The painted endpoint must still be the actual unported placement, not a folded proxy.
pub fn attachment_node<'a>(
project: &Project,
diagram: &Diagram,
view: &'a projection::ProjectedView,
edge: Id,
end: EdgeEnd,
) -> Option<&'a projection::ProjectedNode> {
if semantic_port_endpoint(project, edge, end).is_some() {
return None;
}
let projected = view.edges.iter().find(|e| e.id == edge)?;
let actual = projection::edge_endpoint_placements(project, diagram, edge)?;
if actual != (projected.source, projected.target) {
return None;
}
let placement = match end {
EdgeEnd::Source => projected.source,
EdgeEnd::Target => projected.target,
};
view.nodes.iter().find(|node| node.id == placement)
}
pub fn stored_route(diagram: &Diagram, edge: Id) -> EdgeRoute {
diagram
.routes
.iter()
.find(|r| r.edge == edge)
.cloned()
.unwrap_or(EdgeRoute {
edge,
waypoints: Vec::new(),
label: None,
source_attachment: None,
target_attachment: None,
})
}
/// Insert after the pins preceding the clicked segment along the routed path.
pub fn insert_pin_on_segment(
route: &mut EdgeRoute,
geometry: &RoutedEdge,
segment: usize,
point: Position,
) -> Option<usize> {
if route.edge != geometry.id
|| geometry.custom_suspended
|| route.waypoints != geometry.pins
|| route.waypoints.len() >= 128
|| !point.x.is_finite()
|| !point.y.is_finite()
|| geometry
.points
.get(segment..segment.checked_add(2)?)
.is_none()
{
return None;
}
let index = pin_insertion_index(geometry, segment, point);
if index > route.waypoints.len() {
return None;
}
route.waypoints.insert(index, point);
Some(index)
}
/// Find a segment when an ordinary caller has only a point, not a captured hit.
pub fn insert_pin(route: &mut EdgeRoute, geometry: &RoutedEdge, point: Position) -> usize {
let segment = geometry
.points
.windows(2)
.enumerate()
.min_by(|(_, a), (_, b)| {
let distance = |span: &[Position]| {
project_onto_polyline(span, point).map_or(f32::INFINITY, |p| p.distance)
};
distance(a).total_cmp(&distance(b))
})
.map_or(0, |(index, _)| index);
let index = pin_insertion_index(geometry, segment, point);
route.waypoints.insert(index, point);
index
}
pub fn with_route(diagram: &mut Diagram, route: EdgeRoute) {
if let Some(existing) = diagram.routes.iter_mut().find(|r| r.edge == route.edge) {
*existing = route;
} else {
diagram.routes.push(route);
}
}
pub fn point(p: Position) -> Point {
Point::new(p.x, p.y)
}
pub fn position(p: Point) -> Position {
Position { x: p.x, y: p.y }
}