Skip to main content

blockworx/tools/
new_block.rs

1use crate::theme::Style;
2use blockworx_geom::{Pos2, Rect, WorldPx};
3use blockworx_paint::{Canvas, Cursor, Event, Interaction};
4
5use crate::{
6    grid::GRID_SIZE,
7    shape::ShapeId,
8    theme::Role,
9    tools::{
10        RenameTitle,
11        names::ToolName,
12        tool::{Action, ToolTrait},
13    },
14    widget::drawing::Drawing,
15};
16
17/// Two ways to place a block, both ending in the same rect: press-drag-release,
18/// or click one corner and click the opposite one.
19pub enum NewBlock {
20    Idle,
21    /// The button is held; the rect follows the pointer until release.
22    Dragging {
23        start: Pos2,
24    },
25    /// A click set the first corner; the rect follows the pointer until the
26    /// second corner is clicked. Escape cancels.
27    Placing {
28        start: Pos2,
29        corner: Pos2,
30    },
31}
32
33impl ToolTrait for NewBlock {
34    fn name(&self) -> ToolName {
35        ToolName::NewBlock
36    }
37    fn widget<C: Canvas>(
38        &mut self,
39        data: &mut Drawing,
40        interaction: &Interaction,
41        painter: &mut Style<'_, C>,
42    ) -> Option<Action> {
43        // Draw the background
44        crate::widget::display::widget(data, interaction, painter);
45        painter.set_cursor(Cursor::Crosshair);
46        if interaction.escape_pressed {
47            *self = NewBlock::Idle;
48            return None;
49        }
50        match self {
51            NewBlock::Idle => {
52                match interaction.event {
53                    Some(Event::DragStarted { pos }) => *self = NewBlock::Dragging { start: pos },
54                    Some(Event::Clicked { pos }) => {
55                        *self = NewBlock::Placing {
56                            start: pos,
57                            corner: pos,
58                        }
59                    }
60                    _ => {}
61                }
62                None
63            }
64            NewBlock::Dragging { start } => {
65                let start = *start;
66                match interaction.event {
67                    Some(Event::Dragging { pos, .. }) => {
68                        preview(painter, start, pos);
69                        None
70                    }
71                    // A drag too small to be a block just cancels.
72                    Some(Event::DragStopped { pos }) => {
73                        *self = NewBlock::Idle;
74                        create(data, start, pos)
75                    }
76                    _ => None,
77                }
78            }
79            NewBlock::Placing { start, corner } => {
80                let start = *start;
81                match interaction.event {
82                    Some(Event::HoverAt(pos) | Event::Dragging { pos, .. }) => *corner = pos,
83                    // A second click (or the release of a drag started from the
84                    // first corner) completes the block; one that would make a
85                    // degenerate block leaves the corner placed to try again.
86                    Some(Event::Clicked { pos } | Event::DragStopped { pos }) => {
87                        if let Some(action) = create(data, start, pos) {
88                            *self = NewBlock::Idle;
89                            return Some(action);
90                        }
91                        *corner = pos;
92                    }
93                    _ => {}
94                }
95                let corner = *corner;
96                preview(painter, start, corner);
97                None
98            }
99        }
100    }
101}
102
103fn preview<C: Canvas>(painter: &mut Style<'_, C>, start: Pos2, corner: Pos2) {
104    painter.rect(
105        Rect::from_two_pos(start, corner),
106        WorldPx::ZERO,
107        Role::Transparent,
108        (1.0, Role::NewBlockPreviewStroke),
109    );
110}
111
112/// Add the block spanning the two corners and drop straight into naming it.
113/// `None` when the corners are too close to make a block — a block's smallest
114/// valid height is 4 grid cells (one pin slot).
115fn create(data: &mut Drawing, start: Pos2, corner: Pos2) -> Option<Action> {
116    let candidate = Rect::from_two_pos(start, corner);
117    if candidate.width() <= GRID_SIZE || candidate.height() < 4.0 * GRID_SIZE {
118        return None;
119    }
120    let shape = ShapeId::Rect(data.add_rect_box(start, corner));
121    Some(RenameTitle::action(data, shape))
122}