Files
Ely-GPUI-Components/src/canvas/press.rs
T
ZacharyZhang-NY 4498ff09dc 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.
2026-09-27 08:22:54 -04:00

150 lines
5.3 KiB
Rust

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,
},
}
}