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.
This commit is contained in:
1 parent
c637347a2a
commit
07b875b0d3
32 files changed
+3118
-18
No files matched your search
@@ -7,6 +7,7 @@ use gpui::{
|
||||
use super::{Frame, InfiniteCanvas, MiniMap, Tool, Viewport, ZoomControls};
|
||||
use crate::{primitives::FocusNext, theme::Theme};
|
||||
|
||||
mod panels;
|
||||
mod tools;
|
||||
|
||||
/// A view that shows one canvas part and keeps the viewports and words it heard.
|
||||
@@ -41,6 +42,7 @@ fn stage(
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
Theme::update(cx, |theme| theme.reduced_motion = true);
|
||||
crate::forms::bind_keys(cx);
|
||||
cx.bind_keys([gpui::KeyBinding::new("tab", FocusNext, None)]);
|
||||
});
|
||||
let (host, cx) = cx.add_window_view(|_, _| Stage {
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
use gpui::{AnyElement, Entity, IntoElement, TestAppContext, VisualTestContext};
|
||||
|
||||
use super::{Stage, said, say, settle, stage};
|
||||
use crate::canvas::{
|
||||
AssetPanel, AutoLayout, AutoLayoutControls, ExportFormat, ExportPanel, ExportSetting, Flow,
|
||||
Frame, LayerPanel, Place, Shape, ShapeKind,
|
||||
};
|
||||
|
||||
fn tab(stops: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
(0..stops).for_each(|_| window.focus_next());
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
fn tap(key: &str, cx: &mut VisualTestContext) {
|
||||
cx.simulate_keystrokes(key);
|
||||
cx.simulate_event(gpui::KeyUpEvent {
|
||||
keystroke: gpui::Keystroke::parse(key).expect("a key"),
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
fn layers(_: &Stage, owner: Entity<Stage>) -> AnyElement {
|
||||
let shapes = ["a", "b", "c"].map(|key| {
|
||||
Shape::new(
|
||||
key,
|
||||
key.to_uppercase(),
|
||||
ShapeKind::Rect,
|
||||
Frame::new(0.0, 0.0, 10.0, 10.0),
|
||||
)
|
||||
});
|
||||
LayerPanel::new("layers", shapes)
|
||||
.on_show(move |keys, _, cx| {
|
||||
let mut keys = keys.to_vec();
|
||||
keys.sort();
|
||||
say(&owner, format!("shown {keys:?}"), cx)
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn space_on_the_top_layer_hides_it(cx: &mut TestAppContext) {
|
||||
let (host, cx) = stage(layers, cx);
|
||||
tab(1, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(
|
||||
said(&host, cx),
|
||||
["shown [\"a\", \"b\"]"],
|
||||
"c paints last, so it heads the list"
|
||||
);
|
||||
}
|
||||
|
||||
fn places(_: &Stage, owner: Entity<Stage>) -> AnyElement {
|
||||
let layout = AutoLayout {
|
||||
flow: Flow::Row,
|
||||
gap: 8.0,
|
||||
padding: (8.0, 8.0),
|
||||
place: (Place::Start, Place::Start),
|
||||
};
|
||||
AutoLayoutControls::new("auto", layout)
|
||||
.on_change(move |next, _, cx| say(&owner, format!("{:?}", next.place), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn arrows_move_the_place_and_stop_at_the_edge(cx: &mut TestAppContext) {
|
||||
let (host, cx) = stage(places, cx);
|
||||
let grid = cx.debug_bounds("places").expect("the place grid");
|
||||
cx.simulate_click(grid.center(), gpui::Modifiers::none());
|
||||
settle(cx);
|
||||
for key in ["right", "down", "left", "up"] {
|
||||
tap(key, cx);
|
||||
}
|
||||
assert_eq!(
|
||||
said(&host, cx),
|
||||
["(Center, Center)", "(Center, Start)", "(Start, Center)"],
|
||||
"a press picks; each arrow steps from the owner's place, and none steps past the edge"
|
||||
);
|
||||
}
|
||||
|
||||
fn sizes(_: &Stage, owner: Entity<Stage>) -> AnyElement {
|
||||
let (added, sent) = (owner.clone(), owner);
|
||||
ExportPanel::new(
|
||||
"export",
|
||||
"Card",
|
||||
[ExportSetting {
|
||||
scale: 1.0,
|
||||
format: ExportFormat::Png,
|
||||
}],
|
||||
)
|
||||
.on_change(move |next, _, cx| {
|
||||
let scales: Vec<f32> = next.iter().map(|setting| setting.scale).collect();
|
||||
say(&added, format!("sizes {scales:?}"), cx)
|
||||
})
|
||||
.on_export(move |files, _, cx| say(&sent, format!("files {files:?}"), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn plus_adds_the_next_size_and_export_hands_on_the_files(cx: &mut TestAppContext) {
|
||||
let (host, cx) = stage(sizes, cx);
|
||||
tab(1, cx);
|
||||
tap("space", cx);
|
||||
tab(5, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(
|
||||
said(&host, cx),
|
||||
["sizes [1.0, 2.0]", "files [\"Card.png\"]"]
|
||||
);
|
||||
}
|
||||
|
||||
fn assets(_: &Stage, owner: Entity<Stage>) -> AnyElement {
|
||||
let shape =
|
||||
|key: &'static str, kind| Shape::new(key, key, kind, Frame::new(0.0, 0.0, 40.0, 40.0));
|
||||
AssetPanel::new("assets")
|
||||
.group(
|
||||
"Shapes",
|
||||
[
|
||||
shape("Button", ShapeKind::Rect),
|
||||
shape("Hexagon", ShapeKind::Polygon(6)),
|
||||
],
|
||||
)
|
||||
.on_pick(move |shape, _, cx| say(&owner, format!("pick {}", shape.key), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_search_narrows_the_assets_and_enter_picks_the_first(cx: &mut TestAppContext) {
|
||||
let (host, cx) = stage(assets, cx);
|
||||
tab(1, cx);
|
||||
cx.simulate_input("hex");
|
||||
settle(cx);
|
||||
tap("enter", cx);
|
||||
assert_eq!(said(&host, cx), ["pick Hexagon"]);
|
||||
}
|
||||
Reference in new issue
Block a user