199 lines
5.5 KiB
Rust
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 }
|
|
}
|