1use crate::theme::Style;
2use blockworx_geom::{Align, Pos2, Rect, Vec2, pos2};
3use blockworx_paint::Renderer;
4
5use blockworx_doc::{block_model::Pin, id::PinId};
6
7use crate::edit::naming::Authoring;
8use crate::{
9 edit::lower::accent_from_role,
10 grid::{GRID_SIZE, px_rect, snap_rect},
11 render::draw_selection_frame,
12 shape::{
13 BaseShape,
14 block::resize_rect,
15 pin::{PinSide, orientation},
16 },
17 state::RenderMode,
18 theme::{Role, accent_role},
19};
20
21pub const PORT_HEIGHT: u32 = 2;
23
24pub const MIN_PORT_WIDTH: u32 = 5;
27
28pub fn width_for_labels(name: &str, type_label: &str) -> u32 {
31 let longest = name.chars().count().max(type_label.chars().count());
32 (longest as u32 / 2).max(MIN_PORT_WIDTH)
33}
34
35#[derive(Clone, Copy)]
43pub struct PortShape<'a> {
44 pub id: PinId,
45 pub pin: &'a Pin,
46}
47
48impl<'a> PortShape<'a> {
49 fn body(&self) -> Rect {
50 px_rect(self.pin.rect)
51 }
52
53 fn matching(&self, id: PinId) -> Option<&'a Pin> {
56 (id == self.id).then_some(self.pin)
57 }
58}
59
60impl BaseShape for PortShape<'_> {
61 fn gui_rect(&self) -> Rect {
62 self.body()
63 }
64 fn constrain_resize_delta(&self, mut delta: Vec2) -> Vec2 {
65 delta.y = 0.0;
66 delta
67 }
68 fn pin(&self, id: PinId) -> Option<&Pin> {
69 self.matching(id)
70 }
71 fn anchor_point_with_rect(&self, rect: Rect, id: PinId) -> Option<Pos2> {
72 self.matching(id)?;
73 Some(match orientation(self.pin) {
74 PinSide::East => pos2(rect.right() + GRID_SIZE, rect.top() + GRID_SIZE),
75 PinSide::West => pos2(rect.left() - GRID_SIZE, rect.top() + GRID_SIZE),
76 })
77 }
78 fn pin_text_rect<R: Renderer>(&self, id: PinId, painter: &Style<'_, R>) -> Option<Rect> {
79 self.matching(id)?;
80 let center = crate::render::port_text_center(self.body(), orientation(self.pin));
81 Some(crate::render::pin_name_rect_at(
82 center,
83 Align::Center,
84 &self.pin.name,
85 painter,
86 ))
87 }
88 fn pin_type_rect<R: Renderer>(&self, id: PinId, painter: &Style<'_, R>) -> Option<Rect> {
89 self.matching(id)?;
90 let center = crate::render::port_text_center(self.body(), orientation(self.pin));
91 Some(crate::render::pin_type_rect_at(
92 center,
93 Align::Center,
94 &self.pin.type_name,
95 painter,
96 ))
97 }
98 fn tag_text_rect_for<R: Renderer>(
99 &self,
100 id: PinId,
101 text: &str,
102 painter: &Style<'_, R>,
103 ) -> Option<Rect> {
104 self.matching(id)?;
105 let bbox = self.body();
106 Some(
107 crate::render::TagSlot {
108 left: bbox.left(),
109 right: bbox.right(),
110 side: orientation(self.pin),
111 line_y: bbox.center().y,
112 }
113 .bbox(text, painter),
114 )
115 }
116 fn pin_stub_rect(&self, id: PinId) -> Option<Rect> {
117 self.matching(id)?;
118 let bbox = self.body();
119 Some(crate::render::estimate_bbox_for_pin_stub(
120 bbox.left(),
121 bbox.right(),
122 orientation(self.pin),
123 bbox.center().y,
124 ))
125 }
126 fn resizable(&self) -> bool {
127 true
128 }
129}
130
131impl PortShape<'_> {
132 pub fn render_ng<R: Renderer>(
136 &self,
137 accents: crate::presentation::ShapeAccents<'_>,
138 mode: RenderMode,
139 painter: &mut Style<'_, R>,
140 ) {
141 let pin = self.pin;
142 let bbox = self.body();
143 let side = orientation(pin);
145 let kind = pin.dir;
146 let tag_hidden = pin.tag_hidden;
147 let (name, type_label, tag): (&str, &str, &str) = (&pin.name, &pin.type_name, &pin.tag);
148 let outline = accent_role(accent_from_role(pin.port_accent)).unwrap_or(Role::ShapeStroke);
152 let stub = accent_role(accents.pin(self.id)).unwrap_or(Role::PinStem);
153 let draw_normal = |bbox: Rect, side: PinSide, painter: &mut Style<'_, R>| {
154 crate::render::draw_port(
155 bbox,
156 side,
157 name,
158 type_label,
159 tag,
160 tag_hidden.into(),
161 kind,
162 Role::ShapeFill,
163 (1.0, outline),
164 painter,
165 stub,
166 );
167 };
168
169 match mode {
170 RenderMode::Moving { delta } => {
171 let shifted = bbox.translate(delta);
172 let predicted = snap_rect(shifted);
173 crate::render::draw_port_outline(
177 shifted,
178 side,
179 Role::Transparent,
180 (1.0, Role::DragPreviewStroke),
181 painter,
182 );
183 crate::render::draw_port_outline(
184 predicted,
185 side,
186 Role::DragActiveFill,
187 (2.0, Role::DragActiveStroke),
188 painter,
189 );
190 crate::render::draw_pin_labels(
191 crate::render::port_text_center(predicted, side),
192 Align::Center,
193 name,
194 type_label,
195 painter,
196 );
197 }
198 RenderMode::Resizing { mode, delta } => {
199 let resized = resize_rect(&bbox, mode, delta);
200 let predicted = snap_rect(resized);
201 crate::render::draw_port_outline(
205 resized,
206 side,
207 Role::Transparent,
208 (1.0, Role::DragPreviewStroke),
209 painter,
210 );
211 crate::render::draw_port_outline(
212 predicted,
213 side,
214 Role::DragActiveFill,
215 (2.0, Role::DragActiveStroke),
216 painter,
217 );
218 crate::render::draw_pin_labels(
219 crate::render::port_text_center(predicted, side),
220 Align::Center,
221 name,
222 type_label,
223 painter,
224 );
225 draw_selection_frame(predicted, Some(mode), painter);
226 }
227 RenderMode::Selected { authoring } => {
228 draw_normal(bbox, side, painter);
229 if authoring == Authoring::Offered {
230 crate::render::draw_label_placeholders(
231 crate::render::port_text_center(bbox, side),
232 Align::Center,
233 name,
234 type_label,
235 painter,
236 );
237 if !tag_hidden && tag.is_empty() {
238 crate::render::draw_tag_placeholder(
239 bbox.left(),
240 bbox.right(),
241 side,
242 bbox.center().y,
243 painter,
244 );
245 }
246 }
247 draw_selection_frame(bbox, None, painter);
248 }
249 _ => {
250 draw_normal(bbox, side, painter);
251 }
252 }
253 }
254}