Skip to main content

blockworx/tools/
select_pin.rs

1use crate::theme::Style;
2use blockworx_paint::{Canvas, Cursor, Event, Interaction};
3
4use crate::edit::naming::Authoring;
5use crate::{
6    tools::{
7        names::ToolName,
8        tool::{Action, Deletable, ToolTrait},
9    },
10    widget::{drawing::Drawing, hit_target::HitTarget},
11};
12use blockworx_doc::id::PinId;
13
14/// Per-pin selection. Entered by single-clicking a pin (its name, stub, or tag);
15/// the clicked pin becomes the standing target for edits. While selected, only
16/// that pin shows its tag-visibility overlay (eye / "+"), its stub and tag
17/// render brighter, and the block's other pins are dimmed — so it stands out the
18/// way a selected route does. The per-pin edits hand off to focused tools that
19/// each return here when done, so the pin stays selected:
20/// - drag it → [`MovePin`](crate::tools::MovePin)
21/// - double-click the name → [`RenamePin`](crate::tools::RenamePin) (`Name`)
22/// - double-click the type → [`RetypePin`](crate::tools::RetypePin)
23/// - double-click the tag, or press the "+" button →
24///   [`RenamePin`](crate::tools::RenamePin) (`Tag`)
25/// - click the red stub → cycle the pin's I/O type in place (stays selected)
26///
27/// Only ever holds a child block's pin; ports keep their block-style
28/// [`ResizeBlock`](crate::tools::resize_block::ResizeBlock) selection.
29#[derive(Default)]
30pub enum SelectPin {
31    /// Placeholder for `mem::take`; never a resting state.
32    #[default]
33    Idle,
34    Selected {
35        anchor: PinId,
36    },
37}
38
39impl ToolTrait for SelectPin {
40    fn name(&self) -> ToolName {
41        ToolName::SelectPin
42    }
43
44    fn selection(&self) -> Option<Deletable> {
45        match self {
46            SelectPin::Selected { anchor } => Some(Deletable::Pins(vec![*anchor])),
47            SelectPin::Idle => None,
48        }
49    }
50
51    fn widget<C: Canvas>(
52        &mut self,
53        data: &mut Drawing,
54        interaction: &Interaction,
55        painter: &mut Style<'_, C>,
56    ) -> Option<Action> {
57        self.render(data, interaction, painter);
58        let state = std::mem::take(self);
59        if let SelectPin::Selected { anchor } = state {
60            if interaction.delete_pressed {
61                return Some(Action::Delete(Deletable::Pins(vec![anchor])));
62            }
63            match interaction.event {
64                Some(Event::HoverAt(pos)) => {
65                    if matches!(
66                        data.resolve_at_pos(pos, painter),
67                        Some(HitTarget::Pin { .. })
68                    ) {
69                        painter.set_cursor(Cursor::PointingHand);
70                    }
71                }
72                Some(Event::DoubleClicked { pos }) => {
73                    // Open the editor for whatever editable label was hit: the
74                    // selected pin's own name/type/tag, or another pin / title /
75                    // block tag / route label / text box. Routing through the
76                    // shared resolver lets an in-place edit hand straight off to
77                    // the next thing double-clicked instead of just deselecting.
78                    if let Some(tool) = crate::tools::select_tool::editor_at_pos(data, pos, painter)
79                    {
80                        return Some(Action::SwitchTool(tool));
81                    }
82                }
83                Some(Event::DragStarted { pos }) => {
84                    // Grabbing this pin moves it; dragging anything else starts
85                    // moving that object directly.
86                    return Some(crate::tools::select_tool::drag_to_move(data, pos, painter));
87                }
88                Some(Event::Clicked { pos }) => {
89                    if data.authoring().is_withheld() {
90                        // The stub cycle is an edit; the click still selects.
91                        return Some(
92                            crate::tools::select_tool::click_to_select(data, pos, painter)
93                                .unwrap_or_default(),
94                        );
95                    }
96                    if data.pin_stub_at_pos(pos).map(|(a, _)| a) == Some(anchor) {
97                        // The selected pin's own stub cycles its I/O type in place;
98                        // every other click selects whatever it landed on (another
99                        // pin, a body, a route) or deselects on empty canvas.
100                        data.cycle_pin_kind(anchor);
101                    } else {
102                        return Some(
103                            crate::tools::select_tool::click_to_select(data, pos, painter)
104                                .unwrap_or_default(),
105                        );
106                    }
107                }
108                _ => {}
109            }
110        }
111        // No transition: restore the (possibly stub-cycled) selection.
112        *self = state;
113        None
114    }
115}
116
117impl SelectPin {
118    fn render<C: Canvas>(
119        &self,
120        data: &Drawing,
121        interaction: &Interaction,
122        painter: &mut Style<'_, C>,
123    ) {
124        match self {
125            SelectPin::Idle => {
126                crate::widget::display::widget(data, interaction, painter);
127            }
128            SelectPin::Selected { anchor } => {
129                crate::widget::display::widget(data, interaction, painter);
130                super::multi_pin_select::draw_pin_selection_frame(data, &[*anchor], painter);
131                // Prompt for the selected pin's empty fields (add name/type/tag)
132                // only where those edits are on offer.
133                if let Some(owner) = data.pin_shape(*anchor)
134                    && data.authoring_of(owner) == Authoring::Offered
135                    && let Some((shape, pin)) = data.pin_on_shape(*anchor)
136                {
137                    crate::render::draw_pin_placeholders(shape.gui_rect(), pin, painter);
138                }
139            }
140        }
141    }
142}