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