Files
Ely-GPUI-Components/examples/gallery/pages/canvas/panels.rs
T
ZacharyZhang-NY 07b875b0d3 Canvas · Panels: layers, inspector, alignment, assets, style and layout editors
LayerPanel lists the canvas's layers topmost first on lists::Tree: a box
shows or hides each, F2 renames, a drag restacks into a new paint order.
InspectorPanel edits the one selected shape's frame, fill, visibility and
lock. AlignmentToolbar lines up or spreads the selection through
`aligned`. AssetPanel finds shapes by name in a picks grid, each drawn
to fit its tile. ColorPanel, ShadowEditor, BorderEditor and
TypographyPanel edit a look; GradientPanel is forms::GradientEditor.
ConstraintsEditor pins a shape to its frame, with `Constraints::placed`;
AutoLayoutControls sets flow, gap, padding and a place grid, with
`AutoLayout::laid`; ExportPanel names each file once. The panels share
`canvas::panel`: editing, pair, caption, heading and color_well.
2026-09-27 07:58:27 -04:00

189 lines
6.2 KiB
Rust

use ely_gpui_component::canvas::{
AlignmentToolbar, AssetPanel, Corner, Frame, InspectorPanel, LayerPanel, Shape, ShapeKind,
aligned,
};
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
use super::tools::studio;
use crate::ui::{change, section};
fn asset(
key: &'static str,
name: &'static str,
kind: ShapeKind,
w: f32,
h: f32,
hue: usize,
) -> Shape {
Shape::new(key, name, kind, Frame::new(0.0, 0.0, w, h)).hue(hue)
}
fn shapes() -> Vec<Shape> {
vec![
asset("button", "Button", ShapeKind::Rect, 120.0, 40.0, 0),
asset("dot", "Dot", ShapeKind::Ellipse, 40.0, 40.0, 1),
asset("hexagon", "Hexagon", ShapeKind::Polygon(6), 60.0, 60.0, 2),
asset(
"pointer",
"Pointer",
ShapeKind::Arrow(Corner::BottomRight),
80.0,
40.0,
3,
),
asset(
"divider",
"Divider",
ShapeKind::Line(Corner::TopRight),
120.0,
0.0,
4,
),
asset(
"heading",
"Heading",
ShapeKind::Text("Heading".into()),
160.0,
32.0,
5,
),
]
}
fn notes() -> Vec<Shape> {
vec![
asset(
"idea",
"Idea",
ShapeKind::Note("An idea".into()),
120.0,
120.0,
6,
),
asset(
"todo",
"To do",
ShapeKind::Note("To do".into()),
120.0,
120.0,
7,
),
]
}
pub fn arrange(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let studio = studio(window, cx);
let now = studio.read(cx);
let (shapes_now, selected) = (now.shapes.clone(), now.selected.clone());
let chosen: Vec<Shape> = shapes_now
.iter()
.filter(|shape| selected.contains(&shape.key))
.cloned()
.collect();
let [picked, shown, named, stacked, lined, edited, placed] = [(); 7].map(|_| studio.clone());
let layers = LayerPanel::new("canvas-layers", shapes_now)
.selected(selected.clone())
.on_select(move |keys, _, cx| change(&picked, cx, |studio| studio.selected = keys.to_vec()))
.on_show(move |keys, _, cx| {
change(&shown, cx, |studio| {
for shape in &mut studio.shapes {
shape.hidden = !keys.contains(&shape.key);
}
studio.record("Show or hide".into());
})
})
.on_rename(move |key, name, _, cx| {
change(&named, cx, |studio| {
let shape = studio
.shapes
.iter_mut()
.find(|shape| shape.key == *key)
.expect("a listed shape");
shape.name = name.clone();
studio.record(format!("Rename {name}"));
})
})
.on_restack(move |order, _, cx| {
change(&stacked, cx, |studio| {
studio.shapes.sort_by_key(|shape| {
order
.iter()
.position(|key| *key == shape.key)
.expect("every layer restacked")
});
studio.record("Restack".into());
})
});
let toolbar =
AlignmentToolbar::new("canvas-align", selected.len()).on_align(move |align, _, cx| {
change(&lined, cx, |studio| {
let keys: Vec<SharedString> = studio.selected.clone();
let frames: Vec<Frame> = studio
.shapes
.iter()
.filter(|shape| keys.contains(&shape.key))
.map(|shape| shape.frame)
.collect();
let moved = aligned(&frames, align);
studio
.shapes
.iter_mut()
.filter(|shape| keys.contains(&shape.key))
.zip(moved)
.for_each(|(shape, frame)| shape.frame = frame);
studio.record(align.words().into());
})
});
let inspector =
InspectorPanel::new("canvas-inspector", chosen).on_change(move |shape, _, cx| {
change(&edited, cx, |studio| {
let at = studio
.shapes
.iter()
.position(|each| each.key == shape.key)
.expect("a listed shape");
let words = format!("Edit {}", shape.name);
studio.shapes[at] = shape;
studio.record(words);
})
});
let assets = AssetPanel::new("canvas-assets")
.group("Shapes", shapes())
.group("Notes", notes())
.on_pick(move |shape, _, cx| {
change(&placed, cx, |studio| {
studio.made += 1;
let key = SharedString::from(format!("made-{}", studio.made));
let mut copy = shape.clone();
copy.key = key.clone();
copy.frame.x = 280.0 + 12.0 * studio.made as f32;
copy.frame.y = 180.0 + 12.0 * studio.made as f32;
studio.shapes.push(copy);
studio.selected = vec![key];
studio.record(format!("Place {}", shape.name));
})
});
section(
"LayerPanel · InspectorPanel · AlignmentToolbar · AssetPanel",
"Panels on the drawing above. The layers list it topmost first: a box shows or hides a layer, F2 renames it, a drag restacks it. The inspector edits the one selected; the toolbar lines up or spreads several; an asset pressed lands on the canvas. Each change is a step in the history.",
cx,
)
.child(
div()
.flex()
.flex_wrap()
.gap_6()
.child(div().w(px(240.)).h(px(280.)).child(layers))
.child(
div()
.w(px(300.))
.flex()
.flex_col()
.gap_3()
.child(toolbar)
.child(inspector),
)
.child(div().w(px(256.)).child(assets)),
)
}