Files
Ely-GPUI-Components/examples/gallery/pages/canvas/boards.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

241 lines
8.0 KiB
Rust

use ely_gpui_component::{
canvas::{
FlowChart, Frame, Link, MindMap, Step, StepKind, Tool, ToolLayer, ToolPalette, Topic,
Viewport,
},
theme::{ActiveTheme, Radius},
};
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
use crate::{
probe::probe,
ui::{change, keep, section},
};
/// The flow demo's steps, links, view, tool and selection.
struct Flow {
steps: Vec<Step>,
links: Vec<Link>,
view: Viewport,
tool: Tool,
selected: Vec<SharedString>,
}
impl Flow {
fn new() -> Self {
let step = |key, kind, text, x, y, w| Step::new(key, kind, text, Frame::new(x, y, w, 56.0));
Self {
steps: vec![
step("start", StepKind::Terminal, "Start", 20.0, 132.0, 110.0),
step(
"draft",
StepKind::Process,
"Write the draft",
180.0,
132.0,
150.0,
),
step(
"check",
StepKind::Decision,
"Reviewed?",
380.0,
122.0,
140.0,
),
step("ship", StepKind::Process, "Publish", 580.0, 132.0, 120.0),
step("end", StepKind::Terminal, "End", 580.0, 250.0, 120.0),
step(
"revise",
StepKind::Process,
"Ask for changes",
370.0,
250.0,
160.0,
),
],
links: vec![
Link::new("l1", "start", "draft"),
Link::new("l2", "draft", "check"),
Link::new("l3", "check", "ship").label("Yes"),
Link::new("l4", "check", "revise").label("No"),
Link::new("l5", "ship", "end"),
],
view: Viewport::new(0.0, 0.0, 0.8),
tool: Tool::Select,
selected: Vec::new(),
}
}
}
fn boxed(width: f32, height: f32, child: impl IntoElement, cx: &App) -> gpui::Div {
let theme = cx.theme();
div()
.w(px(width))
.h(px(height))
.rounded(theme.radius(Radius::Lg))
.border_1()
.border_color(theme.colors.border)
.overflow_hidden()
.child(child)
}
pub fn flow(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let state = keep("canvas-flow", Flow::new, window, cx);
let now = state.read(cx);
let (steps, links, view, tool, selected) = (
now.steps.clone(),
now.links.clone(),
now.view,
now.tool,
now.selected.clone(),
);
let [picked, chosen, moved, sized, linked, written, viewed] = [(); 7].map(|_| state.clone());
let at = |steps: &mut Vec<Step>, key: &SharedString| {
steps
.iter()
.position(|step| step.key == *key)
.expect("a step of the flow")
};
let layer = ToolLayer::new(
"canvas-flow-tools",
tool,
view,
steps.iter().map(Step::shape),
)
.selected(selected)
.on_select(move |keys, _, cx| change(&chosen, cx, |flow| flow.selected = keys.to_vec()))
.on_move(move |keys, (dx, dy), _, cx| {
change(&moved, cx, |flow| {
for step in flow
.steps
.iter_mut()
.filter(|step| keys.contains(&step.key))
{
step.frame = Frame::new(
step.frame.x + dx,
step.frame.y + dy,
step.frame.w,
step.frame.h,
);
}
})
})
.on_resize(move |key, frame, _, cx| {
change(&sized, cx, |flow| {
let ix = at(&mut flow.steps, key);
flow.steps[ix].frame = frame;
})
})
.on_link(move |from, to, _, cx| {
change(&linked, cx, |flow| {
let key = format!("made-{}", flow.links.len() + 1);
flow.links.push(Link::new(key, from.clone(), to.clone()));
})
})
.on_text(move |key, text, _, cx| {
change(&written, cx, |flow| {
let ix = at(&mut flow.steps, key);
flow.steps[ix].text = text.clone();
})
});
section(
"FlowChart / DiagramEditor · Connector Line",
"Steps joined by links that follow them: ends as ellipses, work as boxes, choices as diamonds, and words on a link. With Select a step moves, resizes, and takes new words on a double press; the connector draws a link from one step to another.",
cx,
)
.child(
div()
.flex()
.flex_col()
.items_start()
.gap_2()
.child(
ToolPalette::new("canvas-flow-palette", tool)
.tools([Tool::Select, Tool::Connector])
.on_change(move |next, _, cx| change(&picked, cx, |flow| flow.tool = next)),
)
.child(probe(
"canvas-flow",
boxed(
620.0,
300.0,
FlowChart::new("canvas-flow-chart", view, steps, links)
.layer(layer)
.on_viewport(move |next, _, cx| change(&viewed, cx, |flow| flow.view = next)),
cx,
),
)),
)
}
/// The mind map demo's topics, selection, view and count of topics made.
struct Thinking {
root: Topic,
selected: Option<SharedString>,
view: Viewport,
made: usize,
}
impl Thinking {
fn new() -> Self {
let topic = Topic::new;
Self {
root: topic("root", "Product launch")
.child(
topic("site", "Website")
.child(topic("copy", "Copy"))
.child(topic("shots", "Screenshots")),
)
.child(topic("press", "Press").child(topic("kit", "Press kit")))
.child(topic("budget", "Budget"))
.child(topic("team", "Team").child(topic("roles", "Roles"))),
selected: Some("site".into()),
view: Viewport::fitting(
Frame::new(-520.0, -140.0, 1040.0, 280.0),
(620.0, 300.0),
16.0,
),
made: 0,
}
}
}
pub fn mind(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let state = keep("canvas-mind", Thinking::new, window, cx);
let now = state.read(cx);
let (root, selected, view) = (now.root.clone(), now.selected.clone(), now.view);
let [picked, added, removed, renamed, viewed] = [(); 5].map(|_| state.clone());
section(
"MindMap",
"Topics branching from a root, half to each side. A press selects; arrows walk, Tab adds a child and Enter a sibling, Delete removes one, and F2 or a double press rewrites it. The owner keeps the map.",
cx,
)
.child(boxed(
620.0,
300.0,
MindMap::new("canvas-mind-map", root, view)
.selected(selected)
.on_select(move |key, _, cx| change(&picked, cx, |mind| mind.selected = key.cloned()))
.on_add(move |parent, _, cx| {
change(&added, cx, |mind| {
mind.made += 1;
let key = SharedString::from(format!("idea-{}", mind.made));
mind.root = mind.root.adding(parent, Topic::new(key.clone(), "New idea"));
mind.selected = Some(key);
})
})
.on_remove(move |key, _, cx| {
change(&removed, cx, |mind| {
mind.selected = mind.root.parent_of(key);
mind.root = mind.root.removing(key);
})
})
.on_rename(move |key, text, _, cx| {
change(&renamed, cx, |mind| mind.root = mind.root.renaming(key, text.clone()))
})
.on_viewport(move |next, _, cx| change(&viewed, cx, |mind| mind.view = next)),
cx,
))
}