Skip to main content

blockworx_editor/widget/
display.rs

1use crate::theme::Style;
2use blockworx_paint::{Interaction, Renderer};
3
4use crate::{
5    grid::{HIT_RADIUS, px_point},
6    shape::ShapeId,
7    state::RenderMode,
8    theme::Role,
9    widget::drawing::Drawing,
10};
11
12/// Draw the current level's shapes and routes in their normal (non-interactive)
13/// state, in the canonical layer order. Generic over the [`Renderer`] backend so
14/// it drives both the on-screen `Painter` and the SVG exporter. The layer order
15/// itself lives in [`DrawingPasses`](crate::widget::DrawingPasses).
16#[tracing::instrument(level = "info", skip_all)]
17pub fn render(data: &Drawing, painter: &mut Style<'_, impl Renderer>) {
18    crate::widget::DrawingPasses::new(data).draw(painter);
19}
20
21/// Draw the current level with one shape highlighted in
22/// [`RenderMode::Selected`], in the canonical layer order. Shared by the
23/// selection tools (which layer their own overlays on top) so the selected-state
24/// render lives in one place.
25pub fn render_selected(data: &Drawing, selected: ShapeId, painter: &mut Style<'_, impl Renderer>) {
26    let authoring = data.authoring_of(selected);
27    crate::widget::DrawingPasses::new(data)
28        .shape_mode(move |id| {
29            if id == selected {
30                RenderMode::Selected { authoring }
31            } else {
32                RenderMode::Normal
33            }
34        })
35        .draw(painter);
36}
37
38pub fn widget(data: &Drawing, _interaction: &Interaction, painter: &mut Style<'_, impl Renderer>) {
39    render(data, painter);
40}
41
42/// Draw the current level with one shape omitted — used while an in-place editor
43/// stands in for it (a text box being edited), so the editor isn't drawn over the
44/// shape it replaces.
45pub fn render_hidden(data: &Drawing, hidden: ShapeId, painter: &mut Style<'_, impl Renderer>) {
46    crate::widget::DrawingPasses::new(data)
47        .shape_mode(move |id| {
48            if id == hidden {
49                RenderMode::Hidden
50            } else {
51                RenderMode::Normal
52            }
53        })
54        .draw(painter);
55}
56
57/// Ring what the selection raises into the foreground: the shapes themselves,
58/// and every wire this gesture may re-solve. What is *not* ringed is the
59/// background — the geometry a mutation of this selection will route around
60/// rather than re-decide (`docs/foreground-router-playbook.md`).
61pub fn draw_foreground(
62    data: &Drawing,
63    foreground: &crate::widget::foreground::Foreground,
64    painter: &mut Style<'_, impl Renderer>,
65) {
66    for id in foreground.shapes() {
67        let Some(shape) = data.shape(id) else {
68            continue;
69        };
70        painter.rect(
71            shape.gui_rect().expand(HIT_RADIUS.get()),
72            blockworx_geom::WorldPx::ZERO,
73            Role::Transparent,
74            (1.5, Role::DebugMark),
75        );
76    }
77    for id in foreground.routes() {
78        let Some(geometry) = data.route_geometry(id) else {
79            continue;
80        };
81        for (_, edge) in geometry.iter_edges() {
82            painter.line_segment(
83                [px_point(edge.start), px_point(edge.end)],
84                (3.0, Role::DebugMark),
85            );
86        }
87    }
88}