1use std::{cell::RefCell, rc::Rc};
2
3use blockworx_doc::id::PinId;
4use blockworx_geom::{Rect, vec2};
5
6use crate::theme::{Role, Style};
7use crate::{
8 grid::PORT_TEXT_SIZE,
9 shape::ShapeId,
10 tools::{
11 block_edit::{self, EditTarget},
12 names::ToolName,
13 resize_block::ResizeBlock,
14 tool::{Action, ToolTrait},
15 },
16 widget::drawing::Drawing,
17};
18use blockworx_paint::{Canvas, EditColors, EditId, EditText, Interaction, Renderer};
19pub struct RetypePin {
24 anchor: PinId,
25 label: Rc<RefCell<String>>,
26}
27
28impl RetypePin {
29 pub fn new_with_anchor(data: &Drawing, anchor: PinId) -> Option<Self> {
30 if data.pin_owner_locked(anchor) {
32 return None;
33 }
34 let (_, pin) = data.pin_on_shape(anchor)?;
35 Some(RetypePin {
36 anchor,
37 label: Rc::new(RefCell::new(pin.type_name.clone())),
38 })
39 }
40}
41
42fn editor_position(
46 data: &Drawing,
47 anchor: PinId,
48 painter: &Style<'_, impl Renderer>,
49) -> Option<Rect> {
50 let (shape, pin) = data.pin_on_shape(anchor)?;
51 let type_box = shape.pin_type_rect(anchor, painter)?;
52 let width = (painter
53 .text_size(pin.type_name.clone(), &painter.theme().pin_subtitle_font)
54 .x
55 + 30.0)
56 .max(60.0);
57 Some(Rect::from_center_size(
58 type_box.center(),
59 vec2(width, PORT_TEXT_SIZE * 1.4),
60 ))
61}
62
63impl ToolTrait for RetypePin {
64 fn name(&self) -> ToolName {
65 ToolName::RetypePin
66 }
67
68 fn widget<C: Canvas>(
69 &mut self,
70 data: &mut Drawing,
71 interaction: &Interaction,
72 painter: &mut Style<'_, C>,
73 ) -> Option<Action> {
74 crate::widget::display::widget(data, interaction, painter);
75 let anchor = self.anchor;
76 if interaction.escape_pressed {
77 let Some(shape) = data.pin_shape(anchor) else {
79 return Some(Action::default());
80 };
81 return Some(Action::SwitchTool(ResizeBlock::Selected { shape }.into()));
82 }
83 if interaction.tab_pressed
84 && let Some(ShapeId::Rect(block)) = data.pin_shape(anchor)
85 {
86 commit_type(data, anchor, &self.label.borrow().clone());
87 return block_edit::advance(data, block, EditTarget::PinType(anchor));
88 }
89 if interaction.lost_focus || interaction.enter_pressed {
90 commit_type(data, anchor, &self.label.borrow().clone());
91 return Some(Action::SwitchTool(
92 crate::tools::tool::select_tool_for_anchor(data, anchor),
93 ));
94 }
95 let Some(position) = editor_position(data, anchor, painter) else {
96 return Some(Action::default());
98 };
99 painter.set_edit_text(EditText {
100 position,
101 buffer: self.label.clone(),
102 font: painter.theme().pin_subtitle_font.clone(),
103 id: EditId::of(("pin_type_edit", anchor)),
104 multiline: false,
106 char_limit: Some(crate::grid::MAX_LABEL_CHARS),
107 tab_cycle: true,
108 select_all_on_focus: false,
109 hint: Some(crate::render::ADD_TYPE_PLACEHOLDER),
110 colors: Some(EditColors {
111 text: painter.theme().resolve(Role::PinText),
112 background: painter.theme().resolve(Role::ShapeFill),
113 }),
114 wrap_width: None,
115 });
116 None
117 }
118}
119
120fn commit_type(data: &mut Drawing, anchor: PinId, text: &str) {
122 let fit = data.label_fit(anchor, None, Some(text));
123 data.retype_pin(anchor, text, fit);
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
133 fn commit_type_is_a_no_op_on_a_locked_block() {
134 use crate::widget::test_fixtures::{self as fx, Scene};
135 use blockworx_doc::{fixtures::pin_id, values::PinSide as DocPinSide};
136 let pin = pin_id(3);
137 let mut scene = Scene::new(vec![
138 fx::block(1, 0.0),
139 fx::pin(3, 1, DocPinSide::East, 0),
140 fx::locked(1),
141 ]);
142 let before = scene.doc.stamp();
143
144 scene.commit(|data| commit_type(data, pin, "u8"));
145
146 assert_eq!(
147 scene.doc.stamp(),
148 before,
149 "a gesture a locked interface refuses authors nothing"
150 );
151 assert_eq!(
152 scene
153 .drawing()
154 .held_pin(pin)
155 .expect("the pin survives")
156 .type_name,
157 "",
158 "locked block's pin type is unchanged"
159 );
160 }
161}