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}