GPUI Box GitHub

Components / canvas

CanvasToolbar builder

Compact canvas chrome: a host-formatted zoom and the intents the host can actually carry out.

A builder is RenderOnce. Construct and mount it in one expression.

use gpui_kit::canvas::CanvasToolbar;

Reviewed in canvas-tools.

Construct
new(ident: impl Into<Ident>, zoom: impl Into<SharedString>) -> Self
Options

Chain onto the value.

actions(actions: impl IntoIterator<Item = CanvasToolbarAction>) -> Self
snap(snap: bool) -> Self
glass(preset: GlassPreset) -> Self
on_action(handler: impl Fn(CanvasToolbarAction, &mut Window, &mut App) + 'static) -> Self
Reports

The variants of the event it emits. It never applies the change itself.

  • Action
Example · canvas-tools
fn canvas_tools(_window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneCanvasTools>() {
        cx.set_global(SceneCanvasTools {
            viewport: GraphViewport::default(),
            fit: 0,
            snap: true,
            selected: vec!["scene.canvas.graph.validate".into()],
            nodes: arranged_canvas_tools_nodes(),
        });
    }
    let scene = cx.global::<SceneCanvasTools>();
    let viewport = scene.viewport;
    let fit = scene.fit;
    let snap = scene.snap;
    let selected = scene.selected.clone();
    let [ingest, validate, observe, publish] = scene.nodes;
    let theme = cx.theme().clone();
    let graph_node =
        |id: &'static str, title: &'static str, color: &'static str, state: NodeState| {
            GraphNode::new(id, title)
                .color(color)
                .width(180.0)
                .state(state)
                .port(GraphPort::input("in", "In"))
                .port(GraphPort::output("out", "Out"))
                .selected(selected.iter().any(|selected| selected == id))
        };
    let minimap_mark = |id: &'static str, position: gpui::Point<f32>, color: &'static str| {
        MinimapMark::new(
            id,
            position.x / CANVAS_TOOLS_WORLD_WIDTH,
            position.y / CANVAS_TOOLS_WORLD_HEIGHT,
            180.0 / CANVAS_TOOLS_WORLD_WIDTH,
            120.0 / CANVAS_TOOLS_WORLD_HEIGHT,
        )
        .color(color)
    };
    let member = |id: &'static str,
                  title: &'static str,
                  color: &'static str,
                  state: NodeState,
                  action: &'static str,
                  metric: (&'static str, &'static str)| {
        GraphNode::new(id, title)
            .color(color)
            .width(236.0)
            .state(state)
            .action(action)
            .metric(metric.0, metric.1)
    };

    stack(&theme)
        .w(px(840.0))
        .child(caption(
            &theme,
            "the overview, glass chrome, snap, arrange, and fit all act on this controlled canvas",
        ))
        .child(
            div()
                .row()
                .items_start()
                .gap_token(&theme, Space::Md)
                .child(
                    div()
                        .w(px(CANVAS_TOOLS_FRAME_WIDTH))
                        .h(px(CANVAS_TOOLS_FRAME_HEIGHT))
                        .child(
                            NodeGraph::new("scene.canvas.graph")
                                .viewport(viewport)
                                .toolbar(
                                    CanvasToolbar::new(
                                        "scene.canvas.toolbar",
                                        cx.numbers().percent(viewport.zoom),
                                    )
                                    .snap(snap)
                                    .glass(GlassPreset::Frosted)
                                    .on_action(|action, _, cx| {
                                        cx.update_global::<SceneCanvasTools, ()>(
                                            |scene, _| match action {
                                                CanvasToolbarAction::Fit => {
                                                    scene.fit = scene.fit.wrapping_add(1);
                                                }
                                                CanvasToolbarAction::Snap => {
                                                    scene.snap = !scene.snap;
                                                }
                                                CanvasToolbarAction::Arrange => {
                                                    scene.nodes = arranged_canvas_tools_nodes();
                                                    scene.fit = scene.fit.wrapping_add(1);
                                                }
                                            },
                                        );
                                        cx.refresh_windows();
                                    }),
                                )
                                .fit(GraphFit::Whole(fit))
                                .interaction(GraphInteraction::Arrange)
                                .node(
                                    graph_node(
                                        "scene.canvas.graph.ingest",
                                        "Stream ingest",
                                        "teal",
                                        NodeState::Succeeded,
                                    ),
                                    ingest.x,
                                    ingest.y,
                                )
                                .node(
                                    graph_node(
                                        "scene.canvas.graph.validate",
                                        "Validate & enrich",
                                        "indigo",
                                        NodeState::Running,
                                    ),
                                    validate.x,
                                    validate.y,
                                )
                                .node(
                                    graph_node(
                                        "scene.canvas.graph.observe",
                                        "Observe quality",
                                        "orange",
                                        NodeState::Failed,
                                    ),
                                    observe.x,
                                    observe.y,
                                )
                                .node(
                                    graph_node(
                                        "scene.canvas.graph.publish",
                                        "Publish artifact",
                                        "lime",
                                        NodeState::Pending,
                                    ),
                                    publish.x,
                                    publish.y,
                                )
                                .edges([
                                    GraphEdge::new(
                                        "scene.canvas.graph.ingest",
                                        "scene.canvas.graph.validate",
                                    )
                                    .ports("out", "in")
                                    .active(true),
                                    GraphEdge::new(
                                        "scene.canvas.graph.validate",
                                        "scene.canvas.graph.observe",
                                    )
                                    .ports("out", "in"),
                                    GraphEdge::new(
                                        "scene.canvas.graph.observe",
                                        "scene.canvas.graph.publish",
                                    )
                                    .ports("out", "in"),
                                ])
                                .on_event(|event, _, cx| {
                                    cx.update_global::<SceneCanvasTools, ()>(
                                        |scene, _| match event {
                                            NodeGraphEvent::ViewportChanged(viewport) => {
                                                scene.viewport = *viewport;
                                            }
                                            NodeGraphEvent::SelectionChanged { ids } => {
                                                scene.selected = ids.clone();
                                            }
                                            NodeGraphEvent::NodeMoved { id, position } => {
                                                let position = if scene.snap {
                                                    canvas_tools_snap(*position)
                                                } else {
                                                    *position
                                                };
                                                match id.as_ref() {
                                                    "scene.canvas.graph.ingest" => {
                                                        scene.nodes[0] = position;
                                                    }
                                                    "scene.canvas.graph.validate" => {
                                                        scene.nodes[1] = position;
                                                    }
                                                    "scene.canvas.graph.observe" => {
                                                        scene.nodes[2] = position;
                                                    }
                                                    "scene.canvas.graph.publish" => {
                                                        scene.nodes[3] = position;
                                                    }
                                                    _ => {}
                                                }
                                            }
                                            NodeGraphEvent::NodeDeleted { .. }
                                            | NodeGraphEvent::NodeResized { .. }
                                            | NodeGraphEvent::SurfacePressed { .. }
                                            | NodeGraphEvent::ConnectionRequested { .. }
                                            | NodeGraphEvent::ConnectionDropped { .. }
                                            | NodeGraphEvent::DisconnectRequested { .. } => {}
                                        },
                                    );
                                    cx.refresh_windows();
                                }),
                            )
                )
                .child(
                    div()
                        .w(px(160.0))
                        .flex_none()
                        .child(
                            Minimap::new("scene.canvas.minimap")
                                .marks([
                                    minimap_mark("ingest", ingest, "teal"),
                                    minimap_mark("validate", validate, "indigo"),
                                    minimap_mark("observe", observe, "orange"),
                                    minimap_mark("publish", publish, "lime"),
                                ])
                                .view(canvas_tools_minimap_view(viewport))
                                .on_pan(|x, y, _, cx| {
                                    cx.update_global::<SceneCanvasTools, ()>(|scene, _| {
                                        scene.viewport =
                                            canvas_tools_viewport_at(scene.viewport, x, y);
                                    });
                                    cx.refresh_windows();
                                }),
                        ),
                ),
        )
        .child(caption(
            &theme,
            "a NodeGroup remains host layout: it names these related cards, not a world-space region",
        ))
        .child(
            div().relative().w(px(540.0)).child(
                NodeGroup::new("scene.canvas.group", "Ingest")
                    .selected(true)
                    .child(
                        div()
                            .row()
                            .items_start()
                            .gap_token(&theme, Space::Md)
                            .child(
                                member(
                                    "scene.canvas.node.ingest",
                                    "Stream ingest",
                                    "teal",
                                    NodeState::Succeeded,
                                    "orders.v2 · partition 18",
                                    ("rate", "3.2k/s"),
                                )
                                .into_any_element(),
                            )
                            .child(
                                member(
                                    "scene.canvas.node.validate",
                                    "Validate & enrich",
                                    "indigo",
                                    NodeState::Running,
                                    "schema + fraud signals",
                                    ("p95", "18 ms"),
                                )
                                .into_any_element(),
                            ),
                    ),
            ),
        )
        .into_any_element()
}

Source: crates/gpui-kit/src/canvas/toolbar.rs