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}