1use blockworx_doc::block_model::Route;
2use blockworx_geom::{Align2, Pos2, vec2};
3
4use crate::{
5 edit::{lower::accent_from_role, naming::InterfaceLock},
6 grid::{PORT_RADIUS, SHIM, px_point},
7 presentation::{LocAndDirection, RouteDirection},
8 theme::{Role, RoleStroke, Style, accent_role},
9 widget::auto_route::{RouteGeometry, Wire, text_anchors},
10};
11use blockworx_paint::Renderer;
12
13use super::{ADD_ROUTE_LABEL_PLACEHOLDER, render_path_with_chamfered_corners};
14use crate::edit::naming::Authoring;
15
16#[derive(Clone, Copy, PartialEq, Eq)]
19pub enum LabelPass {
20 Draw,
21 Skip,
22}
23
24#[derive(Clone, Copy, PartialEq, Eq)]
25pub enum RouteRenderMode {
26 Normal,
27 Highlighted,
28 Selected {
29 authoring: Authoring,
33 },
34 Editing,
38}
39
40fn draw_text_anchor(
41 painter: &mut Style<'_, impl Renderer>,
42 ta: Pos2,
43 fill: Role,
44 stroke: impl Into<RoleStroke>,
45) {
46 painter.add_convex_polygon(
47 [
48 ta + vec2(0.0, -PORT_RADIUS.get()),
49 ta + vec2(PORT_RADIUS.get(), 0.0),
50 ta + vec2(0.0, PORT_RADIUS.get()),
51 ta + vec2(-PORT_RADIUS.get(), 0.0),
52 ]
53 .into(),
54 fill,
55 stroke,
56 );
57}
58
59const ROUTE_WIDTH: f32 = 1.7;
61const ROUTE_HOVER_WIDTH: f32 = 2.3;
63const ROUTE_SELECTION_HALO_WIDTH: f32 = 2.8;
66
67pub fn horizontal_label_anchor(pos: Pos2) -> Pos2 {
71 pos + vec2(0.0, -SHIM / 4.0)
72}
73
74pub fn route_text_color(route: &Route, mode: RouteRenderMode) -> Role {
77 let accent = accent_role(accent_from_role(route.role));
78 match mode {
79 RouteRenderMode::Highlighted => accent.unwrap_or(Role::RouteHighlighted),
80 _ => accent.unwrap_or(Role::RouteNormal),
81 }
82}
83
84pub fn render_route_label_at(
88 painter: &mut Style<'_, impl Renderer>,
89 route: &Route,
90 at: LocAndDirection,
91 color: Role,
92) {
93 let name = route.name.as_str();
94 let (text, color) = if name.is_empty() {
95 (ADD_ROUTE_LABEL_PLACEHOLDER, Role::PinLabelPlaceholder)
96 } else {
97 (name, color)
98 };
99 let route_font = &painter.theme().route_font;
100 match at.direction {
101 RouteDirection::Horizontal => {
102 painter.text(
103 horizontal_label_anchor(at.location),
104 Align2::CENTER_BOTTOM,
105 text,
106 route_font,
107 color,
108 );
109 }
110 RouteDirection::Vertical => {
111 let text_size = painter.text_size(text, route_font);
112 painter.rotated_text(
113 at.location + vec2(text_size.y + SHIM / 4.0, -text_size.x / 2.0),
114 Align2::LEFT_TOP,
115 text,
116 route_font,
117 color,
118 std::f32::consts::FRAC_PI_2,
119 );
120 }
121 }
122}
123
124pub fn render_waypoint_handles(
127 painter: &mut Style<'_, impl Renderer>,
128 handles: impl IntoIterator<Item = (Pos2, InterfaceLock)>,
129) {
130 for (pos, lock) in handles {
131 let (radius, fill) = if lock.is_locked() {
132 (PORT_RADIUS, Role::WaypointFill)
133 } else {
134 (PORT_RADIUS * 0.6, Role::ControlHandleFill)
135 };
136 painter.circle(pos, radius, fill, (0.5, Role::ControlHandleStroke));
137 }
138}
139
140pub fn render_text_anchors(
142 painter: &mut Style<'_, impl Renderer>,
143 anchors: impl IntoIterator<Item = Pos2>,
144) {
145 for ta in anchors {
146 draw_text_anchor(
147 painter,
148 ta,
149 Role::WaypointFill,
150 (0.5, Role::ControlHandleStroke),
151 );
152 }
153}
154
155pub(crate) fn render_route(
156 painter: &mut Style<'_, impl Renderer>,
157 wire: &Wire<'_>,
158 geometry: &RouteGeometry,
159 mode: RouteRenderMode,
160 labels: LabelPass,
161) {
162 let route = wire.route;
163 let accent = accent_role(accent_from_role(route.role));
164 let wire_color = accent.unwrap_or(Role::RouteNormal);
167 let crossings = &geometry.crossings;
168 let points = render_path_with_chamfered_corners(&geometry.points());
169 match mode {
170 RouteRenderMode::Normal => {
171 points.render_with_hops(painter, (ROUTE_WIDTH, wire_color), crossings);
172 }
173 RouteRenderMode::Highlighted => {
174 let hl = accent.unwrap_or(Role::RouteHighlighted);
175 points.render_with_hops(painter, (ROUTE_HOVER_WIDTH, hl), crossings);
176 }
177 RouteRenderMode::Selected { .. } | RouteRenderMode::Editing => {
178 points.render_with_hops(
180 painter,
181 (ROUTE_SELECTION_HALO_WIDTH, Role::RouteSelected),
182 crossings,
183 );
184 points.render_with_hops(painter, (ROUTE_WIDTH, wire_color), crossings);
185 }
186 }
187 if labels == LabelPass::Draw {
188 let color = route_text_color(route, mode);
189 for &(_, label) in &wire.labels {
190 let at = geometry.map_linear_distance_to_position(label);
191 render_route_label_at(painter, route, at, color);
192 }
193 }
194 if matches!(
195 mode,
196 RouteRenderMode::Selected {
197 authoring: Authoring::Offered
198 }
199 ) {
200 render_waypoint_handles(
203 painter,
204 route.waypoints.iter().map(|wp| {
205 let lock = if wp.locked {
206 InterfaceLock::Locked
207 } else {
208 InterfaceLock::Unlocked
209 };
210 (px_point(wp.pos), lock)
211 }),
212 );
213 render_text_anchors(painter, text_anchors(&wire.labels, geometry));
214 }
215}