Skip to main content

blockworx/shape/
text_box.rs

1use crate::grid::px_point;
2use crate::theme::{Role, Style};
3use blockworx_geom::{Pos2, Rect};
4use blockworx_paint::Renderer;
5
6use blockworx_doc::{block_model::Text, geometry::GridSize};
7
8use crate::{
9    edit::lower::accent_from_role,
10    render::text_box::{box_rect, draw_anchor, draw_boundary, draw_text},
11    shape::BaseShape,
12    state::RenderMode,
13};
14
15/// A free-floating text annotation paired with the extent it was last
16/// measured at. The measurement is derived state
17/// ([`TextExtents`](crate::presentation::TextExtents)) resolved by the
18/// caller; `None` falls back to the character-count estimate, which is all a
19/// bare `&Text` can honestly offer.
20#[derive(Clone, Copy)]
21pub struct TextShape<'a> {
22    pub text: &'a Text,
23    pub extent: Option<GridSize>,
24}
25
26impl TextShape<'_> {
27    /// The [`Role`] the boundary outline strokes with, chosen by the accent
28    /// `role` register: `Accent1..=Accent8` →
29    /// [`Role::Accent0`]..[`Role::Accent7`], the plain `Accent0` →
30    /// [`Role::TextBoxStroke`].
31    fn accent_role(&self) -> Role {
32        crate::theme::accent_role(accent_from_role(self.text.role)).unwrap_or(Role::TextBoxStroke)
33    }
34
35    fn anchor(&self) -> Pos2 {
36        px_point(self.text.pos)
37    }
38
39    fn body(&self) -> &str {
40        &self.text.text
41    }
42}
43
44impl BaseShape for TextShape<'_> {
45    fn gui_rect(&self) -> Rect {
46        box_rect(self.anchor(), self.extent, self.body())
47    }
48
49    fn render_ng<R: Renderer>(&self, mode: RenderMode, painter: &mut Style<'_, R>) {
50        let anchor = self.anchor();
51        let (extent, text, accent) = (self.extent, self.body(), self.accent_role());
52        // The boundary is drawn first (its faint fill sits behind the text) and
53        // tracks the same anchor as the text, so the visible frame always
54        // matches the clickable `gui_rect`.
55        match mode {
56            // Omitted while its in-place editor is open (see `EditTextBox`).
57            RenderMode::Hidden => {}
58            RenderMode::Moving { delta } => {
59                let shifted = anchor + delta;
60                draw_boundary(shifted, extent, text, accent, painter);
61                draw_text(shifted, text, painter);
62                draw_anchor(shifted, painter);
63            }
64            RenderMode::Selected { .. } => {
65                draw_boundary(anchor, extent, text, accent, painter);
66                draw_text(anchor, text, painter);
67                draw_anchor(anchor, painter);
68            }
69            // Text boxes have no pins, image, title, tag, or resize, so every
70            // other mode renders the same as `Normal`: the boundary and text.
71            _ => {
72                draw_boundary(anchor, extent, text, accent, painter);
73                draw_text(anchor, text, painter);
74            }
75        }
76    }
77}