Skip to main content

blockworx/tools/
move_label.rs

1use crate::render::{RouteRenderMode, render_route_label_at, route_text_color};
2use crate::theme::Style;
3use blockworx_doc::id::{RouteId, RouteLabelId};
4use blockworx_paint::{Canvas, Cursor, Event, Interaction};
5
6use crate::{
7    tools::{
8        names::ToolName,
9        tool::{Action, ToolTrait},
10    },
11    widget::{DrawingPasses, drawing::Drawing},
12};
13
14pub enum MoveLabel {
15    Idle,
16    Dragging {
17        route_id: RouteId,
18        label_id: RouteLabelId,
19        /// Where the drag has slid the label to, in arc length along the wire.
20        /// The document keeps the pre-drag distance until the release commits
21        /// this one.
22        preview: blockworx_doc::geometry::FracVal,
23    },
24}
25
26impl MoveLabel {
27    /// Begin dragging `label_id`, seeding the preview from its stored distance.
28    /// `None` if the label no longer resolves on `route_id`.
29    pub(crate) fn drag_from(
30        data: &Drawing,
31        route_id: RouteId,
32        label_id: RouteLabelId,
33    ) -> Option<Self> {
34        let preview = data.auto_route(route_id).and_then(|wire| {
35            wire.labels
36                .iter()
37                .find(|(id, _)| *id == label_id)
38                .map(|&(_, dist)| dist)
39        })?;
40        Some(MoveLabel::Dragging {
41            route_id,
42            label_id,
43            preview,
44        })
45    }
46}
47
48impl ToolTrait for MoveLabel {
49    fn name(&self) -> ToolName {
50        ToolName::MoveLabel
51    }
52
53    fn widget<C: Canvas>(
54        &mut self,
55        data: &mut Drawing,
56        interaction: &Interaction,
57        painter: &mut Style<'_, C>,
58    ) -> Option<Action> {
59        match self {
60            MoveLabel::Idle => {
61                crate::widget::display::widget(data, interaction, painter);
62                if let Some(Event::HoverAt(pos)) = interaction.event {
63                    if data.route_label_at_pos(pos, painter).is_some() {
64                        painter.set_cursor(Cursor::Grab);
65                    }
66                } else if let Some(Event::DragStarted { pos }) = interaction.event
67                    && let Some((route_id, label_id)) = data.route_label_at_pos(pos, painter)
68                    && let Some(dragging) = MoveLabel::drag_from(data, route_id, label_id)
69                {
70                    *self = dragging;
71                }
72            }
73            MoveLabel::Dragging {
74                route_id,
75                label_id,
76                preview,
77            } => {
78                render_dragging(data, painter, *route_id, *preview);
79                painter.set_cursor(Cursor::Grabbing);
80                if let Some(Event::DragStopped { .. }) = interaction.event {
81                    data.place_route_label(*label_id, *preview);
82                    return Some(Action::default());
83                }
84                if let Some(Event::Dragging { delta, .. }) = interaction.event
85                    && let Some(geometry) = data.route_geometry(*route_id)
86                {
87                    *preview = geometry.slide_along(*preview, delta);
88                }
89            }
90        }
91        None
92    }
93}
94
95/// Draw the drag frame: the stored label distance is stale until the release
96/// commits, so suppress it and draw the previewed one in its place.
97fn render_dragging<C: Canvas>(
98    data: &Drawing,
99    painter: &mut Style<'_, C>,
100    route_id: RouteId,
101    preview: blockworx_doc::geometry::FracVal,
102) {
103    DrawingPasses::new(data)
104        .route_mode(move |id| {
105            if id == route_id {
106                RouteRenderMode::Highlighted
107            } else {
108                RouteRenderMode::Normal
109            }
110        })
111        .suppress_route_label(route_id)
112        .overlay(move |painter| {
113            if let (Some(wire), Some(geometry)) =
114                (data.auto_route(route_id), data.route_geometry(route_id))
115            {
116                let at = geometry.map_linear_distance_to_position(preview);
117                render_route_label_at(
118                    painter,
119                    wire.route,
120                    at,
121                    route_text_color(wire.route, RouteRenderMode::Highlighted),
122                );
123            }
124        })
125        .draw(painter);
126}