Skip to main content

blockworx_tools/
move_title.rs

1use crate::theme::Style;
2use blockworx_geom::{Align, Vec2};
3use blockworx_paint::{Canvas, Cursor, Event, Interaction};
4
5use crate::{
6    grid::round_to_grid,
7    names::ToolName,
8    render::{clamp_label_offset, label_side_for_y},
9    resize_block::ResizeBlock,
10    shape::ShapeId,
11    state::RenderMode,
12    tool::{ToolTrait, Transition},
13    widget::{DrawingPasses, drawing::Drawing},
14};
15
16pub enum MoveTitle {
17    Idle,
18    Dragging { shape: ShapeId, delta_pos: Vec2 },
19}
20
21impl ToolTrait for MoveTitle {
22    fn name(&self) -> ToolName {
23        ToolName::MoveTitle
24    }
25
26    fn widget<C: Canvas>(
27        &mut self,
28        data: &mut Drawing,
29        interaction: &Interaction,
30        painter: &mut Style<'_, C>,
31    ) -> Option<Transition> {
32        match self {
33            MoveTitle::Idle => {
34                crate::widget::display::widget(data, interaction, painter);
35                if let Some(Event::HoverAt(hover_pos)) = interaction.event {
36                    if data.title_anchor_at_pos(hover_pos).is_some() {
37                        painter.set_cursor(Cursor::PointingHand);
38                    }
39                } else if let Some(Event::DragStarted { pos }) = interaction.event
40                    && let Some(shape) = data.title_anchor_at_pos(pos)
41                {
42                    *self = MoveTitle::Dragging {
43                        shape,
44                        delta_pos: Vec2::ZERO,
45                    };
46                }
47            }
48            MoveTitle::Dragging { shape, delta_pos } => {
49                if let Some(Event::Dragging { delta, .. }) = interaction.event {
50                    *delta_pos += delta;
51                }
52                if let Some(Event::DragStopped { .. }) = interaction.event {
53                    let placement = data.shape(*shape).and_then(|shape_ref| {
54                        let title = shape_ref.title()?;
55                        let bbox = shape_ref.gui_rect();
56                        let proposed = title.offset + round_to_grid(delta_pos.x);
57                        // Keep the title within the block's width; a center-anchored
58                        // label whose offset runs past the edge would float outside it.
59                        let text_w = painter.text_size(title.name, &painter.theme().title_font).x;
60                        Some(crate::widget::drawing::LabelPlacement {
61                            offset: clamp_label_offset(bbox, text_w, proposed, Align::Center),
62                            side: shape_ref
63                                .title_anchor()
64                                .map(|anchor| label_side_for_y(bbox, anchor.y + delta_pos.y)),
65                        })
66                    });
67                    if let Some(placement) = placement {
68                        data.place_title(*shape, placement);
69                    }
70                    // Paint the committed state this frame. The tool switches
71                    // next frame, so without rendering here the frame goes blank
72                    // and the title flashes.
73                    crate::widget::display::widget(data, interaction, painter);
74                    // Keep the shape whose title we moved selected.
75                    return Some(Transition::SwitchTool(
76                        ResizeBlock::Selected { shape: *shape }.into(),
77                    ));
78                }
79                let dragged = *shape;
80                let delta = *delta_pos;
81                DrawingPasses::new(data)
82                    .shape_mode(move |id| {
83                        if id == dragged {
84                            RenderMode::TitleDragged { delta }
85                        } else {
86                            RenderMode::Normal
87                        }
88                    })
89                    .draw(painter);
90            }
91        }
92        None
93    }
94}