Skip to main content

blockworx/tools/
rename_route.rs

1use std::{cell::RefCell, rc::Rc};
2
3use blockworx_doc::id::{RouteId, RouteLabelId};
4use blockworx_geom::{Rect, vec2};
5
6use crate::theme::Style;
7use crate::{
8    grid::ROUTE_TEXT_SIZE,
9    tools::{
10        names::ToolName,
11        tool::{Action, ToolTrait},
12    },
13    widget::drawing::Drawing,
14};
15use blockworx_paint::{Canvas, EditId, EditText, Event, Interaction, Renderer};
16pub enum RenameRoute {
17    Idle,
18    Renaming {
19        route_id: RouteId,
20        label_id: RouteLabelId,
21        text_buffer: Rc<RefCell<String>>,
22        position: Rect,
23    },
24}
25
26impl RenameRoute {
27    /// Switch to naming `label_id`, or to nothing when the label is not
28    /// where the route says it is.
29    pub fn action(
30        data: &Drawing,
31        route_id: RouteId,
32        label_id: RouteLabelId,
33        painter: &Style<'_, impl Renderer>,
34    ) -> Action {
35        match RenameRoute::new_with_route_and_label(data, route_id, label_id, painter) {
36            Some(tool) => Action::SwitchTool(tool.into()),
37            None => Action::default(),
38        }
39    }
40
41    pub fn new_with_route_and_label(
42        data: &Drawing,
43        route_id: RouteId,
44        label_id: RouteLabelId,
45        painter: &Style<'_, impl Renderer>,
46    ) -> Option<Self> {
47        let wire = data.auto_route(route_id)?;
48        let geometry = data.route_geometry(route_id)?;
49        let name = wire.route.name.clone();
50        let dist = wire
51            .labels
52            .iter()
53            .find(|(id, _)| *id == label_id)
54            .map(|&(_, dist)| dist)?;
55        let label_pos = geometry.map_linear_distance_to_position(dist).location;
56        let editor_width =
57            (painter.text_size(&name, &painter.theme().route_font).x + 10.0).max(60.0);
58        let height = ROUTE_TEXT_SIZE * 1.5;
59        // Sit the editor exactly where the renderer draws a horizontal label:
60        // bottom-center on `horizontal_label_anchor` (a rotated/vertical label
61        // keeps this horizontal editor — only the horizontal case is matched).
62        let anchor = crate::render::horizontal_label_anchor(label_pos);
63        let position =
64            Rect::from_center_size(anchor - vec2(0.0, height / 2.0), vec2(editor_width, height));
65        Some(RenameRoute::Renaming {
66            route_id,
67            label_id,
68            text_buffer: Rc::new(RefCell::new(name)),
69            position,
70        })
71    }
72}
73
74impl ToolTrait for RenameRoute {
75    fn name(&self) -> ToolName {
76        ToolName::RenameRoute
77    }
78
79    fn widget<C: Canvas>(
80        &mut self,
81        data: &mut Drawing,
82        interaction: &Interaction,
83        painter: &mut Style<'_, C>,
84    ) -> Option<Action> {
85        match self {
86            RenameRoute::Idle => {
87                crate::widget::display::widget(data, interaction, painter);
88                if let Some(Event::DoubleClicked { pos }) = interaction.event
89                    && let Some((route_id, label_id)) = data.route_label_at_pos(pos, painter)
90                    && let Some(tool) =
91                        RenameRoute::new_with_route_and_label(data, route_id, label_id, painter)
92                {
93                    *self = tool;
94                }
95                None
96            }
97            RenameRoute::Renaming {
98                route_id,
99                label_id,
100                text_buffer,
101                position,
102            } => {
103                crate::widget::DrawingPasses::new(data)
104                    .suppress_route_label(*route_id)
105                    .draw(painter);
106                if interaction.lost_focus || interaction.enter_pressed {
107                    let new_name = text_buffer.borrow().clone();
108                    data.set_route_name(*route_id, *label_id, &new_name);
109                    // The route stays the standing target for further edits,
110                    // its overlay anchored at the label just written.
111                    return Some(Action::SwitchTool(
112                        crate::tools::EditRoute::Selected {
113                            id: *route_id,
114                            anchor: position.center(),
115                        }
116                        .into(),
117                    ));
118                }
119                painter.set_edit_text(EditText {
120                    position: *position,
121                    buffer: text_buffer.clone(),
122                    font: painter.theme().route_font.clone(),
123                    id: EditId::of("route_name_edit"),
124                    multiline: false,
125                    char_limit: Some(crate::grid::MAX_LABEL_CHARS),
126                    tab_cycle: false,
127                    // Highlight any existing name so typing replaces it (a
128                    // freshly added label starts empty, where this is a no-op).
129                    select_all_on_focus: true,
130                    hint: Some(crate::render::ADD_ROUTE_LABEL_PLACEHOLDER),
131                    colors: None,
132                    wrap_width: None,
133                });
134                None
135            }
136        }
137    }
138}