Skip to main content

blockworx/shape/
area.rs

1use crate::theme::Style;
2use blockworx_geom::{Align, Pos2, Rect};
3use blockworx_paint::Renderer;
4
5use blockworx_doc::block_model::Area;
6
7use crate::render::block_title_position;
8use crate::{
9    edit::{
10        lower::{accent_from_role, shape_label},
11        naming::InterfaceLock,
12    },
13    grid::{px_rect, snap_rect},
14    shape::{BaseShape, ShapeLabel, block::resize_rect},
15    state::RenderMode,
16    theme::Role,
17};
18
19/// The [`Role`] the outline strokes with, chosen by the area's accent
20/// `role` register: `Accent1..=Accent8` → [`Role::Accent0`]..[`Role::Accent7`],
21/// the plain `Accent0` → [`Role::AreaStroke`].
22fn accent(area: &Area) -> Role {
23    crate::theme::accent_role(accent_from_role(area.role)).unwrap_or(Role::AreaStroke)
24}
25
26/// Draw the area's outline (no fill) plus its title, at `bbox`.
27fn draw<R: Renderer>(area: &Area, bbox: Rect, title: &ShapeLabel<'_>, painter: &mut Style<'_, R>) {
28    crate::render::draw_box_outline(bbox, Role::Transparent, (1.0, accent(area)), painter);
29    crate::render::draw_block_title(bbox, title, InterfaceLock::Unlocked, painter);
30}
31
32impl BaseShape for Area {
33    fn title(&self) -> Option<ShapeLabel<'_>> {
34        Some(shape_label(&self.title))
35    }
36    fn gui_rect(&self) -> Rect {
37        px_rect(self.rect)
38    }
39    fn title_anchor(&self) -> Option<Pos2> {
40        let (pos, _) = block_title_position(self.gui_rect(), &shape_label(&self.title));
41        Some(pos)
42    }
43    fn resizable(&self) -> bool {
44        true
45    }
46    fn render_ng<R: Renderer>(&self, mode: RenderMode, painter: &mut Style<'_, R>) {
47        let bbox = self.gui_rect();
48        let title = shape_label(&self.title);
49        match mode {
50            RenderMode::Moving { delta } => {
51                let shifted = bbox.translate(delta);
52                draw(self, shifted, &title, painter);
53            }
54            RenderMode::Selected { .. } => {
55                draw(self, bbox, &title, painter);
56                crate::render::draw_selection_frame(bbox, None, painter);
57            }
58            RenderMode::Resizing { mode, delta } => {
59                let resized = snap_rect(resize_rect(&bbox, mode, delta));
60                draw(self, resized, &title, painter);
61                crate::render::draw_selection_frame(resized, Some(mode), painter);
62            }
63            RenderMode::TitleDragged { delta } => {
64                // Mirror `Block`'s title drag: snap the side to the nearest third
65                // of the box and slide the horizontal offset, previewing where the
66                // label will land before the drop commits.
67                let (anchor_pos, _) = block_title_position(bbox, &title);
68                let text_w = painter.text_size(title.name, &painter.theme().title_font).x;
69                let shifted = ShapeLabel {
70                    side: crate::render::label_side_for_y(bbox, anchor_pos.y + delta.y),
71                    offset: crate::render::clamp_label_offset(
72                        bbox,
73                        text_w,
74                        title.offset + delta.x,
75                        Align::Center,
76                    ),
77                    ..title
78                };
79                draw(self, bbox, &shifted, painter);
80                painter.line_segment(
81                    [bbox.left_center(), bbox.right_center()],
82                    (2.0, Role::PinDragIndicator),
83                );
84            }
85            // Areas have no pins, image, or tag, so every other mode renders
86            // the same as `Normal`: the outline and title.
87            _ => draw(self, bbox, &title, painter),
88        }
89    }
90}