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