breakpoint_indicator.rs

  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}