Skip to main content

blockworx_editor/render/
port.rs

1use crate::{
2    edit::naming::TagVisibility,
3    grid::{GRID_SIZE, PORT_RENDER_HEIGHT},
4    shape::pin::{PinDir, PinSide, flipped_dir},
5    theme::{Role, RoleStroke, Style},
6};
7use blockworx_geom::{Align, Pos2, Rect, pos2, vec2};
8use blockworx_paint::Renderer;
9
10use super::{draw_pin_labels, draw_pin_stub, pin_tag_location, port_text_center};
11
12fn rounded_pentagon(
13    vertices: &[Pos2],
14    radius: f32,
15    fill: Role,
16    stroke: RoleStroke,
17    painter: &mut Style<'_, impl Renderer>,
18) {
19    let n = vertices.len();
20    let mut points = Vec::with_capacity(n * 5);
21    for i in 0..n {
22        let prev = vertices[(i + n - 1) % n];
23        let curr = vertices[i];
24        let next = vertices[(i + 1) % n];
25        let dir_in = (curr - prev).normalized();
26        let dir_out = (next - curr).normalized();
27        let r = radius
28            .min((curr - prev).length() * 0.5)
29            .min((next - curr).length() * 0.5);
30        let p0 = curr - dir_in * r;
31        let p2 = curr + dir_out * r;
32        points.push(p0);
33        for s in 1..=4_u8 {
34            let t = s as f32 / 4.0;
35            let it = 1.0 - t;
36            points.push(pos2(
37                it * it * p0.x + 2.0 * it * t * curr.x + t * t * p2.x,
38                it * it * p0.y + 2.0 * it * t * curr.y + t * t * p2.y,
39            ));
40        }
41    }
42    painter.add_convex_polygon(points, fill, stroke);
43}
44
45/// Draws a pentagon whose point faces the direction of `side` (East or West).
46pub fn draw_port_outline(
47    bbox: Rect,
48    side: PinSide,
49    fill: Role,
50    stroke: impl Into<RoleStroke>,
51    painter: &mut Style<'_, impl Renderer>,
52) {
53    let render_rect = Rect::from_center_size(bbox.center(), vec2(bbox.width(), PORT_RENDER_HEIGHT));
54    let center_y = render_rect.center().y;
55    let vertices = match side {
56        PinSide::East => [
57            render_rect.left_top(),
58            render_rect.right_top() - vec2(GRID_SIZE, 0.0),
59            pos2(render_rect.right(), center_y),
60            render_rect.right_bottom() - vec2(GRID_SIZE, 0.0),
61            render_rect.left_bottom(),
62        ],
63        PinSide::West => [
64            render_rect.right_top(),
65            render_rect.left_top() + vec2(GRID_SIZE, 0.0),
66            pos2(render_rect.left(), center_y),
67            render_rect.left_bottom() + vec2(GRID_SIZE, 0.0),
68            render_rect.right_bottom(),
69        ],
70    };
71    rounded_pentagon(&vertices, 3.0, fill, stroke.into(), painter);
72}
73
74#[allow(clippy::too_many_arguments)]
75pub fn draw_port(
76    bbox: Rect,
77    side: PinSide,
78    text: &str,
79    type_label: &str,
80    tag: &str,
81    tag_visibility: TagVisibility,
82    kind: PinDir,
83    fill: Role,
84    stroke: impl Into<RoleStroke>,
85    painter: &mut Style<'_, impl Renderer>,
86    pin_role: Role,
87) {
88    draw_port_outline(bbox, side, fill, stroke, painter);
89    let center_y = bbox.center().y;
90    let (stub_a, stub_b) = match side {
91        PinSide::East => (
92            pos2(bbox.right(), center_y),
93            pos2(bbox.right() + GRID_SIZE, center_y),
94        ),
95        PinSide::West => (
96            pos2(bbox.left(), center_y),
97            pos2(bbox.left() - GRID_SIZE, center_y),
98        ),
99    };
100    // Rendered as a port: the pin's stored sense is flipped.
101    draw_pin_stub(stub_a, stub_b, flipped_dir(kind), (1.7, pin_role), painter);
102    // Empty labels draw nothing. A hidden (or empty) tag also draws nothing here;
103    // the placeholder overlay shows empty slots faint when the port is selected.
104    draw_pin_labels(
105        port_text_center(bbox, side),
106        Align::Center,
107        text,
108        type_label,
109        painter,
110    );
111    if !tag_visibility.is_hidden() && !tag.is_empty() {
112        let (tag_pos, tag_align) = pin_tag_location(bbox.left(), bbox.right(), side, center_y);
113        painter.text(
114            tag_pos,
115            tag_align,
116            tag,
117            &painter.theme().tag_font,
118            Role::PinTag,
119        );
120    }
121}