1use gpui::{Bounds, Path, PathBuilder, PathStyle, StrokeOptions, point};
2use ui::{Pixels, px};
3
4/// Draw the path for the breakpoint indicator.
5///
6/// Note: The indicator needs to be a minimum of MIN_WIDTH px wide.
7/// wide to draw without graphical issues, so it will ignore narrower width.
8pub(crate) fn breakpoint_indicator_path(
9 bounds: Bounds<Pixels>,
10 scale: f32,
11 stroke: bool,
12) -> Path<Pixels> {
13 // Constants for the breakpoint shape dimensions
14 // The shape is designed based on a 50px wide by 15px high template
15 // and uses 9-slice style scaling to allow the shape to be stretched
16 // vertically and horizontally.
17 const SHAPE_BASE_HEIGHT: f32 = 15.0;
18 const SHAPE_FIXED_WIDTH: f32 = 32.0; // Width of non-stretchable parts (corners)
19 const SHAPE_MIN_WIDTH: f32 = 34.0; // Minimum width to render properly
20 const PIXEL_ROUNDING_FACTOR: f32 = 8.0; // Round to nearest 1/8 pixel
21
22 // Key points in the shape (in base coordinates)
23 const CORNER_RADIUS: f32 = 5.0;
24 const CENTER_Y: f32 = 7.5;
25 const TOP_Y: f32 = 0.0;
26 const BOTTOM_Y: f32 = 15.0;
27 const CURVE_CONTROL_OFFSET: f32 = 1.5;
28 const RIGHT_CORNER_START: f32 = 4.0;
29 const RIGHT_CORNER_WIDTH: f32 = 13.0;
30
31 // Helper function to round pixels to nearest 1/8
32 let round_to_pixel_grid = |value: Pixels| -> Pixels {
33 let value_f32: f32 = value.into();
34 px((value_f32 * PIXEL_ROUNDING_FACTOR).round() / PIXEL_ROUNDING_FACTOR)
35 };
36
37 // Calculate actual dimensions with scaling
38 let min_allowed_width = px(SHAPE_MIN_WIDTH * scale);
39 let actual_width = if bounds.size.width < min_allowed_width {
40 min_allowed_width
41 } else {
42 bounds.size.width
43 };
44 let actual_height = bounds.size.height;
45
46 // Debug input parameters and initial calculations
47 dbg!(&bounds);
48 dbg!(scale);
49 dbg!(stroke);
50 dbg!(min_allowed_width);
51 dbg!(actual_width);
52 dbg!(actual_height);
53
54 // Origin point for positioning
55 let origin_x = bounds.origin.x;
56 let origin_y = bounds.origin.y;
57
58 // Calculate the scale factor based on height and user scale
59 let shape_scale = (actual_height / px(SHAPE_BASE_HEIGHT)) * scale;
60
61 // Calculate the width of fixed and stretchable sections
62 let fixed_sections_width = px(SHAPE_FIXED_WIDTH) * shape_scale;
63 let stretchable_middle_width = actual_width - fixed_sections_width;
64
65 // Debug scaling calculations
66 dbg!(shape_scale);
67 dbg!(fixed_sections_width);
68 dbg!(stretchable_middle_width);
69
70 // Pre-calculate all the key x-coordinates
71 let left_edge_x = round_to_pixel_grid(origin_x);
72 let left_corner_end_x = round_to_pixel_grid(origin_x + px(CORNER_RADIUS) * shape_scale);
73 let middle_section_end_x =
74 round_to_pixel_grid(origin_x + px(CORNER_RADIUS) * shape_scale + stretchable_middle_width);
75 let right_corner_start_x = round_to_pixel_grid(
76 origin_x
77 + px(CORNER_RADIUS) * shape_scale
78 + stretchable_middle_width
79 + px(RIGHT_CORNER_START) * shape_scale,
80 );
81 let right_edge_x = round_to_pixel_grid(
82 origin_x
83 + px(CORNER_RADIUS) * shape_scale
84 + stretchable_middle_width
85 + px(RIGHT_CORNER_WIDTH) * shape_scale,
86 );
87
88 // Debug x-coordinates
89 dbg!(origin_x);
90 dbg!(left_edge_x);
91 dbg!(left_corner_end_x);
92 dbg!(middle_section_end_x);
93 dbg!(right_corner_start_x);
94 dbg!(right_edge_x);
95
96 // Pre-calculate all the key y-coordinates
97 let top_edge_y = round_to_pixel_grid(origin_y);
98 let center_y = round_to_pixel_grid(origin_y + px(CENTER_Y) * shape_scale);
99 let bottom_edge_y = round_to_pixel_grid(origin_y + px(BOTTOM_Y) * shape_scale);
100
101 // Y-coordinates for the left side curves
102 let left_upper_curve_start_y = round_to_pixel_grid(origin_y + px(CORNER_RADIUS) * shape_scale);
103 let left_lower_curve_end_y = round_to_pixel_grid(origin_y + px(10.0) * shape_scale);
104
105 // Y-coordinates for the right side curves
106 let right_upper_curve_control_y = round_to_pixel_grid(origin_y + px(6.0) * shape_scale);
107 let right_lower_curve_control_y = round_to_pixel_grid(origin_y + px(9.0) * shape_scale);
108
109 // Control point offsets
110 let control_offset = px(CURVE_CONTROL_OFFSET) * shape_scale;
111 let right_control_offset = px(9.0) * shape_scale;
112
113 // Debug y-coordinates
114 dbg!(origin_y);
115 dbg!(top_edge_y);
116 dbg!(center_y);
117 dbg!(bottom_edge_y);
118 dbg!(left_upper_curve_start_y);
119 dbg!(left_lower_curve_end_y);
120 dbg!(right_upper_curve_control_y);
121 dbg!(right_lower_curve_control_y);
122
123 // Create the path builder
124 let mut builder = if stroke {
125 let stroke_width = px(1.0 * scale);
126 let options = StrokeOptions::default().with_line_width(stroke_width.0);
127 PathBuilder::stroke(stroke_width).with_style(PathStyle::Stroke(options))
128 } else {
129 PathBuilder::fill()
130 };
131
132 // Build the path - starting from left center
133 builder.move_to(point(left_edge_x, center_y));
134
135 // === Upper half of the shape ===
136
137 // Move up to start of left upper curve
138 builder.line_to(point(left_edge_x, left_upper_curve_start_y));
139
140 // Top-left corner curve
141 builder.cubic_bezier_to(
142 point(left_corner_end_x, top_edge_y),
143 point(left_edge_x, round_to_pixel_grid(origin_y + control_offset)),
144 point(round_to_pixel_grid(origin_x + control_offset), top_edge_y),
145 );
146
147 // Top edge - stretchable middle section
148 builder.line_to(point(middle_section_end_x, top_edge_y));
149
150 // Top edge - right corner start
151 builder.line_to(point(right_corner_start_x, top_edge_y));
152
153 // Top-right corner curve
154 builder.cubic_bezier_to(
155 point(right_edge_x, center_y),
156 point(
157 round_to_pixel_grid(
158 origin_x
159 + px(CORNER_RADIUS) * shape_scale
160 + stretchable_middle_width
161 + right_control_offset,
162 ),
163 top_edge_y,
164 ),
165 point(right_edge_x, right_upper_curve_control_y),
166 );
167
168 // === Lower half of the shape (mirrored) ===
169
170 // Bottom-right corner curve
171 builder.cubic_bezier_to(
172 point(right_corner_start_x, bottom_edge_y),
173 point(right_edge_x, right_lower_curve_control_y),
174 point(
175 round_to_pixel_grid(
176 origin_x
177 + px(CORNER_RADIUS) * shape_scale
178 + stretchable_middle_width
179 + right_control_offset,
180 ),
181 bottom_edge_y,
182 ),
183 );
184
185 // Bottom edge - right corner to middle
186 builder.line_to(point(middle_section_end_x, bottom_edge_y));
187
188 // Bottom edge - stretchable middle section
189 builder.line_to(point(left_corner_end_x, bottom_edge_y));
190
191 // Bottom-left corner curve
192 builder.cubic_bezier_to(
193 point(left_edge_x, left_lower_curve_end_y),
194 point(
195 round_to_pixel_grid(origin_x + control_offset),
196 bottom_edge_y,
197 ),
198 point(
199 left_edge_x,
200 round_to_pixel_grid(origin_y + px(13.5) * shape_scale),
201 ),
202 );
203
204 // Close the path by returning to start
205 builder.line_to(point(left_edge_x, center_y));
206
207 builder.build().unwrap()
208}