Canvas · Graphs and boards: node graph, flow chart, mind map, whiteboard
NodeGraph joins Nodes by wires between typed Ports: a title drags a node, a drag out of an output lands on an input of the same kind, an input takes one wire, and a press on a wired input lifts its wire. Links join shapes on the plane and follow them, straight or at right angles, with an arrow and words; the Connector tool draws them and the Note tool drops a sticky note. A double press with Select writes on a shape. FlowChart draws Steps as ellipses, boxes and diamonds with their words. MindMap lays topics out from a root, half to each side, and asks the owner to add, remove and rename by key. Whiteboard keeps its own tool, view and selection over a board it hands back after each change. Painting moves to canvas::paint; a press on the tool layer goes through canvas::press, and the layer focuses itself and asks to delete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5d58ee4577
commit
f3822a8d98
28 files changed
+3068
-240
No files matched your search
@@ -0,0 +1,113 @@
|
||||
use gpui::SharedString;
|
||||
|
||||
use super::view::Frame;
|
||||
|
||||
/// A line from one shape to another that follows them as they move: straight or turning at right angles, with an arrow at its end and words at its middle.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Link {
|
||||
pub key: SharedString,
|
||||
pub from: SharedString,
|
||||
pub to: SharedString,
|
||||
pub label: Option<SharedString>,
|
||||
pub elbow: bool,
|
||||
}
|
||||
|
||||
impl Link {
|
||||
pub fn new(
|
||||
key: impl Into<SharedString>,
|
||||
from: impl Into<SharedString>,
|
||||
to: impl Into<SharedString>,
|
||||
) -> Self {
|
||||
let (from, to) = (from.into(), to.into());
|
||||
assert!(from != to, "a link from {from} to itself");
|
||||
Self {
|
||||
key: key.into(),
|
||||
from,
|
||||
to,
|
||||
label: None,
|
||||
elbow: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(mut self, text: impl Into<SharedString>) -> Self {
|
||||
self.label = Some(text.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Turns at right angles instead of running straight.
|
||||
pub fn elbow(mut self) -> Self {
|
||||
self.elbow = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The points a link runs through, in canvas units: out of the side of `from` that faces `to`, into the side of `to` that faces back. An elbow turns halfway between.
|
||||
pub(crate) fn route(from: &Frame, to: &Frame, elbow: bool) -> Vec<(f32, f32)> {
|
||||
let (a, b) = (from.center(), to.center());
|
||||
let across = (b.0 - a.0).abs() >= (b.1 - a.1).abs();
|
||||
let (exit, entry) = match (across, b.0 >= a.0, b.1 >= a.1) {
|
||||
(true, true, _) => ((from.right(), a.1), (to.x, b.1)),
|
||||
(true, false, _) => ((from.x, a.1), (to.right(), b.1)),
|
||||
(false, _, true) => ((a.0, from.bottom()), (b.0, to.y)),
|
||||
(false, _, false) => ((a.0, from.y), (b.0, to.bottom())),
|
||||
};
|
||||
match (elbow, across) {
|
||||
(false, _) => vec![exit, entry],
|
||||
(true, true) => {
|
||||
let middle = (exit.0 + entry.0) / 2.0;
|
||||
vec![exit, (middle, exit.1), (middle, entry.1), entry]
|
||||
}
|
||||
(true, false) => {
|
||||
let middle = (exit.1 + entry.1) / 2.0;
|
||||
vec![exit, (exit.0, middle), (entry.0, middle), entry]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The point halfway along a run of points.
|
||||
pub(crate) fn halfway(points: &[(f32, f32)]) -> (f32, f32) {
|
||||
let length =
|
||||
|(a, b): (&(f32, f32), &(f32, f32))| ((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt();
|
||||
let whole: f32 = points.iter().zip(&points[1..]).map(length).sum();
|
||||
let mut left = whole / 2.0;
|
||||
for (a, b) in points.iter().zip(&points[1..]) {
|
||||
let span = length((a, b));
|
||||
if left <= span && span > 0.0 {
|
||||
let share = left / span;
|
||||
return (a.0 + (b.0 - a.0) * share, a.1 + (b.1 - a.1) * share);
|
||||
}
|
||||
left -= span;
|
||||
}
|
||||
*points.last().expect("a run has points")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{halfway, route};
|
||||
use crate::canvas::Frame;
|
||||
|
||||
#[test]
|
||||
fn a_link_leaves_the_side_that_faces_the_other_shape() {
|
||||
let (a, b) = (
|
||||
Frame::new(0.0, 0.0, 100.0, 50.0),
|
||||
Frame::new(200.0, 100.0, 100.0, 50.0),
|
||||
);
|
||||
assert_eq!(route(&a, &b, false), [(100.0, 25.0), (200.0, 125.0)]);
|
||||
assert_eq!(route(&b, &a, false), [(200.0, 125.0), (100.0, 25.0)]);
|
||||
let below = Frame::new(0.0, 300.0, 100.0, 50.0);
|
||||
assert_eq!(route(&a, &below, false), [(50.0, 50.0), (50.0, 300.0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_elbow_turns_halfway() {
|
||||
let (a, b) = (
|
||||
Frame::new(0.0, 0.0, 100.0, 50.0),
|
||||
Frame::new(200.0, 100.0, 100.0, 50.0),
|
||||
);
|
||||
assert_eq!(
|
||||
route(&a, &b, true),
|
||||
[(100.0, 25.0), (150.0, 25.0), (150.0, 125.0), (200.0, 125.0)]
|
||||
);
|
||||
assert_eq!(halfway(&route(&a, &b, true)), (150.0, 75.0));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user