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.
137 lines
4.3 KiB
Rust
137 lines
4.3 KiB
Rust
use std::rc::Rc;
|
|
|
|
use gpui::{
|
|
AnyElement, App, ElementId, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
|
|
Window, canvas, div,
|
|
};
|
|
|
|
use super::{
|
|
paint::paint_shape,
|
|
shape::{Shape, ShapeKind},
|
|
view::Viewport,
|
|
};
|
|
use crate::{
|
|
forms::{Grid, finder, grid, rows},
|
|
theme::{ActiveTheme, TextSize},
|
|
};
|
|
|
|
type OnShape = Rc<dyn Fn(&Shape, &mut Window, &mut App)>;
|
|
|
|
const COLUMNS: usize = 4;
|
|
|
|
/// Shapes to place, in titled groups, each drawn to fit its tile and found by name. A press or Enter hands the owner the shape.
|
|
#[derive(IntoElement)]
|
|
pub struct AssetPanel {
|
|
id: ElementId,
|
|
groups: Vec<(SharedString, Vec<Shape>)>,
|
|
on_pick: Option<OnShape>,
|
|
}
|
|
|
|
impl AssetPanel {
|
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
|
Self {
|
|
id: id.into(),
|
|
groups: Vec::new(),
|
|
on_pick: None,
|
|
}
|
|
}
|
|
|
|
pub fn group(
|
|
mut self,
|
|
title: impl Into<SharedString>,
|
|
shapes: impl IntoIterator<Item = Shape>,
|
|
) -> Self {
|
|
self.groups
|
|
.push((title.into(), shapes.into_iter().collect()));
|
|
self
|
|
}
|
|
|
|
pub fn on_pick(mut self, handler: impl Fn(&Shape, &mut Window, &mut App) + 'static) -> Self {
|
|
self.on_pick = Some(Rc::new(handler));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for AssetPanel {
|
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let finder = finder(&self.id, "Search assets", window, cx);
|
|
let query = finder.read(cx).query.clone();
|
|
let found: Vec<(SharedString, Vec<Shape>)> = self
|
|
.groups
|
|
.into_iter()
|
|
.map(|(title, shapes)| {
|
|
let named = shapes
|
|
.into_iter()
|
|
.filter(|shape| shape.name.to_lowercase().contains(&query))
|
|
.collect();
|
|
(title, named)
|
|
})
|
|
.collect();
|
|
let layout = if query.is_empty() {
|
|
rows(
|
|
found
|
|
.iter()
|
|
.map(|(title, shapes)| (Some(title.clone()), shapes.len())),
|
|
COLUMNS,
|
|
)
|
|
} else {
|
|
rows(
|
|
[(None, found.iter().map(|(_, shapes)| shapes.len()).sum())],
|
|
COLUMNS,
|
|
)
|
|
};
|
|
let shapes: Rc<Vec<Shape>> =
|
|
Rc::new(found.into_iter().flat_map(|(_, shapes)| shapes).collect());
|
|
let (painted, picked, on_pick) = (shapes.clone(), shapes.clone(), self.on_pick);
|
|
let cell = cx.theme().canvas().asset;
|
|
grid(
|
|
Grid {
|
|
id: self.id,
|
|
names: Rc::new(shapes.iter().map(|shape| shape.name.clone()).collect()),
|
|
rows: Rc::new(layout),
|
|
selected: None,
|
|
draw: Rc::new(move |ix, cx| thumbnail(painted[ix].clone(), cx)),
|
|
pick: Rc::new(move |ix, window, cx| {
|
|
log::info!("asset panel: {}", picked[ix].key);
|
|
if let Some(on_pick) = &on_pick {
|
|
on_pick(&picked[ix], window, cx);
|
|
}
|
|
}),
|
|
none: "No assets found",
|
|
columns: COLUMNS,
|
|
cell,
|
|
},
|
|
&finder,
|
|
window,
|
|
cx,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// A shape drawn to fit its tile. Text drawn so small would not read, so a text tile shows a sample of letters.
|
|
fn thumbnail(shape: Shape, cx: &App) -> AnyElement {
|
|
if let ShapeKind::Text(_) = shape.kind {
|
|
let theme = cx.theme();
|
|
return div()
|
|
.text_size(theme.text_size(TextSize::Lg))
|
|
.text_color(theme.colors.fg)
|
|
.child("Aa")
|
|
.into_any_element();
|
|
}
|
|
canvas(
|
|
|_, _, _| {},
|
|
move |bounds, _, window, cx| {
|
|
let theme = cx.theme();
|
|
let rem = window.rem_size();
|
|
let sizes = theme.canvas();
|
|
let inset = f32::from(sizes.asset_inset.to_pixels(rem));
|
|
let room = (f32::from(bounds.size.width), f32::from(bounds.size.height));
|
|
let view = Viewport::fitting(shape.frame, room, inset);
|
|
let stroke = sizes.stroke.to_pixels(rem);
|
|
paint_shape(&shape, &view, bounds.origin, &theme.colors, stroke, window);
|
|
},
|
|
)
|
|
.size_full()
|
|
.into_any_element()
|
|
}
|