Add support for dashed borders to GPUI (#27139)

Features:

* Scales dash spacing with border width.
* Laying out dashes around rounded corners.
* Varying border widths with rounded corners - now uses an ellipse for the inner edge of the border.
* When there are no rounded corners, each straight border is laid out separately, so that the dashes to meet at the corners.
* All sides of each dash are antialiased.

![image](https://github.com/user-attachments/assets/b3789a98-a5be-4f97-9736-c4e59615afe6)

![image](https://github.com/user-attachments/assets/739bdc57-4580-42c8-bfc3-6e287411a408)

Release Notes:

- N/A

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Ben <ben@zed.dev>
This commit is contained in:
Nathan Sobo 2025-03-25 11:11:04 -06:00 committed by GitHub
parent 2fe2028e20
commit cd1e56d6c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 869 additions and 159 deletions

View file

@ -1,18 +1,18 @@
use crate::{
point, prelude::*, px, size, transparent_black, Action, AnyDrag, AnyElement, AnyTooltip,
AnyView, App, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Background, Bounds,
BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler, IsZero,
KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent,
MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render,
RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Replay, ResizeEdge,
ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style, SubscriberSet,
Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement, TransformationMatrix,
Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
AnyView, App, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Background,
BorderStyle, Bounds, BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels,
DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity,
EntityId, EventEmitter, FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs,
Hsla, InputHandler, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke,
KeystrokeEvent, LayoutId, LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite,
MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams,
Replay, ResizeEdge, ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style,
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Context as _, Result};
@ -2335,13 +2335,13 @@ impl Window {
let opacity = self.element_opacity();
self.next_frame.scene.insert_primitive(Quad {
order: 0,
pad: 0,
bounds: quad.bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
background: quad.background.opacity(opacity),
border_color: quad.border_color.opacity(opacity),
corner_radii: quad.corner_radii.scale(scale_factor),
border_widths: quad.border_widths.scale(scale_factor),
border_style: quad.border_style,
});
}
@ -4107,6 +4107,8 @@ pub struct PaintQuad {
pub border_widths: Edges<Pixels>,
/// The color of the quad's borders.
pub border_color: Hsla,
/// The style of the quad's borders.
pub border_style: BorderStyle,
}
impl PaintQuad {
@ -4150,6 +4152,7 @@ pub fn quad(
background: impl Into<Background>,
border_widths: impl Into<Edges<Pixels>>,
border_color: impl Into<Hsla>,
border_style: BorderStyle,
) -> PaintQuad {
PaintQuad {
bounds,
@ -4157,6 +4160,7 @@ pub fn quad(
background: background.into(),
border_widths: border_widths.into(),
border_color: border_color.into(),
border_style,
}
}
@ -4168,16 +4172,22 @@ pub fn fill(bounds: impl Into<Bounds<Pixels>>, background: impl Into<Background>
background: background.into(),
border_widths: (0.).into(),
border_color: transparent_black(),
border_style: BorderStyle::default(),
}
}
/// Creates a rectangle outline with the given bounds, border color, and a 1px border width
pub fn outline(bounds: impl Into<Bounds<Pixels>>, border_color: impl Into<Hsla>) -> PaintQuad {
pub fn outline(
bounds: impl Into<Bounds<Pixels>>,
border_color: impl Into<Hsla>,
border_style: BorderStyle,
) -> PaintQuad {
PaintQuad {
bounds: bounds.into(),
corner_radii: (0.).into(),
background: transparent_black().into(),
border_widths: (1.).into(),
border_color: border_color.into(),
border_style,
}
}