1use blockworx_doc::id::PinId;
2use blockworx_geom::{Align, Pos2, Rect, Vec2, pos2, vec2};
3
4use crate::{
5 grid::{BLOCK_STROKE_WIDTH, GRID_SIZE, pin_offset_y},
6 shape::pin::{Pin, PinDir, PinSide, slot},
7 theme::{Role, RoleStroke, Style, accent_role},
8};
9use blockworx_paint::Renderer;
10
11use super::{draw_pin_labels, pin_tag_location, pin_text_location};
12
13const PIN_ARROW_LEN: f32 = GRID_SIZE * 0.4;
15const PIN_ARROW_HALF_W: f32 = GRID_SIZE * 0.22;
17const FRAME_HALF_STROKE: f32 = BLOCK_STROKE_WIDTH / 2.0;
23
24pub fn draw_pin_stub(
34 edge: Pos2,
35 tip: Pos2,
36 kind: PinDir,
37 stroke: impl Into<RoleStroke>,
38 painter: &Style<'_, impl Renderer>,
39) {
40 let stroke = stroke.into();
41 let outward = (tip - edge).normalized();
42 painter.line_segment([edge, tip], stroke);
43 match kind {
44 PinDir::InOut => {}
45 PinDir::Input => draw_arrowhead(edge, -outward, stroke.role, painter),
47 PinDir::Output => draw_arrowhead(
49 edge + outward * PIN_ARROW_LEN,
50 outward,
51 stroke.role,
52 painter,
53 ),
54 }
55}
56
57fn draw_arrowhead(head: Pos2, dir: Vec2, color: Role, painter: &Style<'_, impl Renderer>) {
60 let dir = dir.normalized();
61 let perp = vec2(-dir.y, dir.x);
62 let base = head - dir * PIN_ARROW_LEN;
63 let p1 = base + perp * PIN_ARROW_HALF_W;
64 let p2 = base - perp * PIN_ARROW_HALF_W;
65 painter.add_convex_polygon(vec![head, p1, p2], color, RoleStroke::NONE);
66}
67
68#[derive(Clone, Copy, Default)]
72pub struct PinPaint {
73 pub delta_y: f32,
74 pub side: Option<PinSide>,
75 pub accent: Option<u8>,
76}
77
78pub fn draw_pin(bbox: Rect, pin: &Pin, paint: PinPaint, painter: &mut Style<'_, impl Renderer>) {
81 let stub_stroke = (1.7, accent_role(paint.accent).unwrap_or(Role::PinStem));
82 let tag_color = Role::PinTag;
83 let side = paint.side.unwrap_or(slot(pin).side);
84 let delta_y = paint.delta_y;
85 let (text_pos, _) = pin_text_location(bbox, pin, side, delta_y);
86 let (stub_a, stub_b) = pin_stub_endpoints(bbox, pin, side, delta_y);
87 draw_pin_stub(stub_a, stub_b, pin.dir, stub_stroke, painter);
89 let h_align = match side {
90 PinSide::East => Align::Max,
91 PinSide::West => Align::Min,
92 };
93 draw_pin_labels(text_pos, h_align, &pin.name, &pin.type_name, painter);
97 if !pin.tag_hidden && !pin.tag.is_empty() {
98 let (tag_pos, tag_align) = pin_tag_location(bbox.left(), bbox.right(), side, stub_a.y);
99 painter.text(
100 tag_pos,
101 tag_align,
102 pin.tag.clone(),
103 &painter.theme().tag_font,
104 tag_color,
105 );
106 }
107}
108
109pub fn draw_pin_placeholders(bbox: Rect, pin: &Pin, painter: &mut Style<'_, impl Renderer>) {
113 let slot = slot(pin);
114 let (text_pos, _) = pin_text_location(bbox, pin, slot.side, 0.0);
115 let h_align = match slot.side {
116 PinSide::East => Align::Max,
117 PinSide::West => Align::Min,
118 };
119 super::draw_label_placeholders(text_pos, h_align, &pin.name, &pin.type_name, painter);
120 if !pin.tag_hidden && pin.tag.is_empty() {
121 let line_y = pin_offset_y(bbox.top(), slot.offset);
122 super::draw_tag_placeholder(bbox.left(), bbox.right(), slot.side, line_y, painter);
123 }
124}
125
126fn pin_stub_endpoints(bbox: Rect, pin: &Pin, side: PinSide, delta_y: f32) -> (Pos2, Pos2) {
131 let y = pin_offset_y(bbox.top(), slot(pin).offset) + delta_y;
132 match side {
133 PinSide::East => (
134 pos2(bbox.right() + FRAME_HALF_STROKE, y),
135 pos2(bbox.right() + GRID_SIZE, y),
136 ),
137 PinSide::West => (
138 pos2(bbox.left() - FRAME_HALF_STROKE, y),
139 pos2(bbox.left() - GRID_SIZE, y),
140 ),
141 }
142}
143
144pub fn render_pins_with_box<'a>(
145 iter: impl Iterator<Item = (PinId, &'a Pin)>,
146 bbox: Rect,
147 accents: crate::presentation::ShapeAccents<'_>,
148 painter: &mut Style<'_, impl Renderer>,
149) {
150 for (id, pin) in iter {
151 let paint = PinPaint {
152 accent: accents.pin(id),
153 ..PinPaint::default()
154 };
155 draw_pin(bbox, pin, paint, painter);
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use crate::shape::block::tests::test_pin as pin;
163
164 #[test]
165 fn stub_butts_against_the_outline_outer_face() {
166 let bbox = Rect::from_min_max(pos2(60.0, 30.0), pos2(180.0, 150.0));
170
171 let (east_a, east_b) = pin_stub_endpoints(bbox, &pin(PinSide::East, 0), PinSide::East, 0.0);
172 assert_eq!(east_a.x, bbox.right() + FRAME_HALF_STROKE);
173 assert_eq!(east_b.x, bbox.right() + GRID_SIZE);
174 assert!(east_a.x > bbox.right(), "stub starts outside the edge");
175
176 let (west_a, west_b) = pin_stub_endpoints(bbox, &pin(PinSide::West, 0), PinSide::West, 0.0);
177 assert_eq!(west_a.x, bbox.left() - FRAME_HALF_STROKE);
178 assert_eq!(west_b.x, bbox.left() - GRID_SIZE);
179 assert!(west_a.x < bbox.left(), "stub starts outside the edge");
180 }
181}