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,149 @@
|
||||
use gpui::{App, Entity, SharedString, Window};
|
||||
|
||||
use super::{
|
||||
edit::{Tool, hit},
|
||||
gesture::{Gesture, Hand, Handlers, PEN, REACH, Scene, path, spanned},
|
||||
marks::handle_boxes,
|
||||
paint::in_view,
|
||||
shape::ShapeKind,
|
||||
view::Frame,
|
||||
writing,
|
||||
};
|
||||
use crate::forms::Editing;
|
||||
|
||||
/// A press on a tool layer: the tool, where it landed in canvas units and in view pixels, how many presses it counts, whether Shift is held, and a handle's side.
|
||||
pub(super) struct Press {
|
||||
pub tool: Tool,
|
||||
pub at: (f32, f32),
|
||||
pub view_at: (f32, f32),
|
||||
pub count: usize,
|
||||
pub shift: bool,
|
||||
pub side: f32,
|
||||
}
|
||||
|
||||
/// The gesture a press sets going, after what it asks the owner at once: a selection, a dropped text or note, an ended path, or words to rewrite.
|
||||
pub(super) fn pressed(
|
||||
press: &Press,
|
||||
scene: &Scene,
|
||||
handlers: &Handlers,
|
||||
hand: &Entity<Hand>,
|
||||
editing: &Entity<Editing>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Gesture {
|
||||
let (at, view) = (press.at, scene.view);
|
||||
let drop = |kind: ShapeKind, frame: Frame, window: &mut Window, cx: &mut App| {
|
||||
log::info!("tool layer: {kind:?} at {at:?}");
|
||||
if let Some(on_draw) = &handlers.on_draw {
|
||||
on_draw(kind, frame, window, cx);
|
||||
}
|
||||
};
|
||||
match press.tool {
|
||||
Tool::Select => {
|
||||
let held = (scene.selected.len() == 1)
|
||||
.then(|| spanned(&scene.shapes, &scene.selected))
|
||||
.flatten()
|
||||
.and_then(|frame| {
|
||||
handle_boxes(&in_view(&view, &frame), press.side)
|
||||
.into_iter()
|
||||
.find(|(_, square)| square.contains(press.view_at))
|
||||
});
|
||||
match (held, hit(&scene.shapes, at, REACH / view.zoom)) {
|
||||
(Some((handle, _)), _) => Gesture::Resizing {
|
||||
key: scene.selected[0].clone(),
|
||||
handle,
|
||||
from: at,
|
||||
to: at,
|
||||
},
|
||||
(None, Some(ix)) if press.count >= 2 => {
|
||||
let shape = &scene.shapes[ix];
|
||||
window.prevent_default();
|
||||
hand.update(cx, |hand, _| hand.writing = Some(shape.key.clone()));
|
||||
let words = shape
|
||||
.words()
|
||||
.map(|words| words.to_string())
|
||||
.unwrap_or_default();
|
||||
writing::begin(
|
||||
editing,
|
||||
shape.key.clone(),
|
||||
words,
|
||||
handlers.on_text.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
Gesture::Idle
|
||||
}
|
||||
(None, Some(ix)) => {
|
||||
let key = scene.shapes[ix].key.clone();
|
||||
let selected = &scene.selected;
|
||||
let next: Vec<SharedString> = match (press.shift, selected.contains(&key)) {
|
||||
(true, true) => selected
|
||||
.iter()
|
||||
.filter(|each| **each != key)
|
||||
.cloned()
|
||||
.collect(),
|
||||
(true, false) => selected.iter().cloned().chain([key]).collect(),
|
||||
(false, true) => selected.to_vec(),
|
||||
(false, false) => vec![key],
|
||||
};
|
||||
if next != **selected {
|
||||
log::info!("tool layer: select {next:?}");
|
||||
if let Some(on_select) = &handlers.on_select {
|
||||
on_select(&next, window, cx);
|
||||
}
|
||||
}
|
||||
Gesture::Moving { from: at, to: at }
|
||||
}
|
||||
(None, None) => Gesture::Marquee { from: at, to: at },
|
||||
}
|
||||
}
|
||||
Tool::Connector => match hit(&scene.shapes, at, REACH / view.zoom) {
|
||||
Some(ix) => Gesture::Linking {
|
||||
from: scene.shapes[ix].key.clone(),
|
||||
to: at,
|
||||
},
|
||||
None => Gesture::Idle,
|
||||
},
|
||||
Tool::Pen => {
|
||||
let ends = press.count >= 2;
|
||||
let points = hand.update(cx, |hand, _| {
|
||||
if !ends {
|
||||
hand.pen.push(at);
|
||||
}
|
||||
match ends {
|
||||
true => std::mem::take(&mut hand.pen),
|
||||
false => Vec::new(),
|
||||
}
|
||||
});
|
||||
if ends && points.len() > 1 {
|
||||
let (kind, frame) = path(&points, PEN);
|
||||
drop(kind, frame, window, cx);
|
||||
}
|
||||
Gesture::Idle
|
||||
}
|
||||
Tool::Text => {
|
||||
drop(
|
||||
ShapeKind::Text("Text".into()),
|
||||
Frame::new(at.0, at.1, 160.0, 32.0),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
Gesture::Idle
|
||||
}
|
||||
Tool::Note => {
|
||||
drop(
|
||||
ShapeKind::Note("Note".into()),
|
||||
Frame::new(at.0, at.1, 160.0, 160.0),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
Gesture::Idle
|
||||
}
|
||||
Tool::Brush => Gesture::Stroke { points: vec![at] },
|
||||
drawer => Gesture::Drawing {
|
||||
kind: drawer.draws().expect("the rest draw shapes"),
|
||||
from: at,
|
||||
to: at,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user