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