Canvas · Plane: an endless canvas, its grid, rulers, zoom and map
A new chapter, src/canvas. InfiniteCanvas lays artboards, shapes and guides on an endless plane over a dot grid whose step, one, two or five times a power of ten, keeps the dots apart as it zooms out. Rulers run along the top and left in canvas units. A drag on empty space pans from the press, a wheel scrolls, and Command or Control with the wheel zooms about the pointer; the owner keeps the viewport, seeded, so its own zoom shows at once. Layers lie over the plane in view pixels. Shapes are rectangles, ellipses, regular polygons, lines, arrows, text, paths and notes, each in a chart hue, painted to scale; text and notes scale their type. Artboards carry their names above them. ZoomControls step out and in through fixed stops, fit on request, and hold ZoomIndicator, whose press sets 100%. MiniMap draws what lies on the canvas and the view's box, and centers the view where it is pressed or dragged. The viewport math, the steps and the map's fit are tested without gpui. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
86336ee828
commit
2c099ceffb
15 files changed
+1530
-14
No files matched your search
@@ -0,0 +1,27 @@
|
||||
use gpui::{AnyElement, App, Window, div, prelude::*};
|
||||
|
||||
mod plane;
|
||||
|
||||
use super::Page;
|
||||
use crate::script::Step;
|
||||
|
||||
pub const PAGE: Page = Page {
|
||||
number: 32,
|
||||
slug: "canvas",
|
||||
title: "Canvas & Design",
|
||||
summary: "An endless plane to lay work on, the tools and panels around it, and graphs of nodes.",
|
||||
render,
|
||||
script: SCRIPT,
|
||||
};
|
||||
|
||||
const SCRIPT: &[Step] = &[
|
||||
Step::DownAt("canvas-plane", 420.0, 300.0),
|
||||
Step::DragTo("canvas-plane", 300.0, 240.0),
|
||||
Step::UpAt("canvas-plane", 300.0, 240.0),
|
||||
Step::Wait(200),
|
||||
Step::Shot("panned"),
|
||||
];
|
||||
|
||||
fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
div().child(plane::plane(window, cx)).into_any_element()
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
use ely_gpui_component::{
|
||||
canvas::{
|
||||
Artboard, Frame, Guide, InfiniteCanvas, MiniMap, Shape, ShapeKind, Viewport, ZoomControls,
|
||||
},
|
||||
theme::{ActiveTheme, Radius},
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, Styled, Window, div, px};
|
||||
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{keep, section, set},
|
||||
};
|
||||
|
||||
/// The demo's view, in pixels.
|
||||
const VIEW: (f32, f32) = (820.0, 460.0);
|
||||
|
||||
fn boards() -> Vec<Artboard> {
|
||||
vec![
|
||||
Artboard::new("home", "Home · 390", Frame::new(0.0, 0.0, 390.0, 640.0)),
|
||||
Artboard::new(
|
||||
"detail",
|
||||
"Detail · 390",
|
||||
Frame::new(470.0, 0.0, 390.0, 640.0),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn shapes() -> Vec<Shape> {
|
||||
vec![
|
||||
Shape::new(
|
||||
"hero",
|
||||
"Hero",
|
||||
ShapeKind::Rect,
|
||||
Frame::new(24.0, 32.0, 342.0, 180.0),
|
||||
)
|
||||
.hue(0),
|
||||
Shape::new(
|
||||
"title",
|
||||
"Title",
|
||||
ShapeKind::Text("Studio Atrium".into()),
|
||||
Frame::new(24.0, 232.0, 342.0, 32.0),
|
||||
),
|
||||
Shape::new(
|
||||
"dot",
|
||||
"Status",
|
||||
ShapeKind::Ellipse,
|
||||
Frame::new(24.0, 290.0, 56.0, 56.0),
|
||||
)
|
||||
.hue(5),
|
||||
Shape::new(
|
||||
"badge",
|
||||
"Badge",
|
||||
ShapeKind::Polygon(6),
|
||||
Frame::new(100.0, 290.0, 56.0, 56.0),
|
||||
)
|
||||
.hue(2),
|
||||
Shape::new(
|
||||
"rule",
|
||||
"Rule",
|
||||
ShapeKind::Line,
|
||||
Frame::new(24.0, 380.0, 342.0, 0.0),
|
||||
)
|
||||
.hue(4),
|
||||
Shape::new(
|
||||
"flow",
|
||||
"Flow",
|
||||
ShapeKind::Arrow,
|
||||
Frame::new(390.0, 120.0, 80.0, 0.0),
|
||||
)
|
||||
.hue(3),
|
||||
Shape::new(
|
||||
"sketch",
|
||||
"Sketch",
|
||||
ShapeKind::Path(vec![
|
||||
(0.0, 40.0),
|
||||
(40.0, 0.0),
|
||||
(80.0, 30.0),
|
||||
(120.0, 10.0),
|
||||
(160.0, 50.0),
|
||||
]),
|
||||
Frame::new(494.0, 60.0, 160.0, 50.0),
|
||||
)
|
||||
.hue(1),
|
||||
Shape::new(
|
||||
"note",
|
||||
"Note",
|
||||
ShapeKind::Note("Ask Chloé about the stair".into()),
|
||||
Frame::new(494.0, 150.0, 180.0, 120.0),
|
||||
)
|
||||
.hue(2),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn plane(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let view = keep(
|
||||
"canvas-view",
|
||||
|| Viewport::new(-60.0, -60.0, 0.7),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let now = *view.read(cx);
|
||||
let frames: Vec<Frame> = boards().iter().map(|board| board.frame).collect();
|
||||
let whole = frames
|
||||
.iter()
|
||||
.skip(1)
|
||||
.fold(frames[0], |whole, frame| whole.union(frame));
|
||||
let (panned, zoomed, fitted, steered) = (view.clone(), view.clone(), view.clone(), view);
|
||||
let theme = cx.theme();
|
||||
let canvas = InfiniteCanvas::new("canvas-plane", now)
|
||||
.artboards(boards())
|
||||
.shapes(shapes())
|
||||
.guides([Guide::Vertical(24.0), Guide::Horizontal(212.0)])
|
||||
.rulers()
|
||||
.on_viewport(move |next, _, cx| set(&panned, next, cx))
|
||||
.layer(
|
||||
div()
|
||||
.absolute()
|
||||
.bottom_3()
|
||||
.right_3()
|
||||
.p_1()
|
||||
.rounded(theme.radius(Radius::Md))
|
||||
.bg(theme.colors.surface)
|
||||
.border_1()
|
||||
.border_color(theme.colors.border)
|
||||
.child(
|
||||
ZoomControls::new("canvas-zoom", now.zoom)
|
||||
.on_zoom(move |zoom, _, cx| {
|
||||
let now = *zoomed.read(cx);
|
||||
let middle = (VIEW.0 / 2.0, VIEW.1 / 2.0);
|
||||
set(&zoomed, now.zoomed(zoom / now.zoom, middle), cx)
|
||||
})
|
||||
.on_fit(move |_, cx| {
|
||||
set(&fitted, Viewport::fitting(whole, VIEW, 48.0), cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.layer(
|
||||
div().absolute().bottom_3().left_8().child(
|
||||
MiniMap::new("canvas-map", frames, now, VIEW)
|
||||
.on_viewport(move |next, _, cx| set(&steered, next, cx)),
|
||||
),
|
||||
);
|
||||
section(
|
||||
"InfiniteCanvas · CanvasGrid / Guides · Ruler · ArtboardFrame · ZoomControls / ZoomIndicator · MiniMap",
|
||||
"An endless plane: a dot grid that thins as it zooms out, rulers in canvas units, guides, and artboards with their names. Drag to pan, scroll to move, and hold Command to zoom about the pointer. The zoom steps in fixed stops, and the minimap moves the view to a press.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"canvas-plane",
|
||||
div()
|
||||
.w(px(VIEW.0))
|
||||
.h(px(VIEW.1))
|
||||
.rounded(theme.radius(Radius::Lg))
|
||||
.border_1()
|
||||
.border_color(theme.colors.border)
|
||||
.overflow_hidden()
|
||||
.child(canvas),
|
||||
))
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod agent;
|
||||
mod buttons;
|
||||
mod calendar;
|
||||
mod canvas;
|
||||
mod charts;
|
||||
mod chat;
|
||||
mod collab;
|
||||
@@ -77,6 +78,7 @@ pub const ALL: &[Page] = &[
|
||||
mail::PAGE,
|
||||
calendar::PAGE,
|
||||
project::PAGE,
|
||||
canvas::PAGE,
|
||||
theme::PAGE,
|
||||
];
|
||||
|
||||
|
||||
Reference in new issue
Block a user