gpui & ui: Use shader for dashed dividers (#23839)

TODO:
- [x] BackgroundOrientation
- [x] PatternDash
- [x] `pattern_horizontal_dash` & `pattern_vertical_dash`
- [x] Metal dash shader
- [x] Blade dash shader
- [x] Update ui::Divider to use new pattern

---

This PR introduces proper dashed dividers using the new `PatternDash`
background shader.

![CleanShot 2025-01-29 at 09 33
06@2x](https://github.com/user-attachments/assets/2db5af58-1aa9-4ad7-aa52-b9046fbf8584)

Before this we were using 128 elements to create a dashed divider, which
is both expensive, and would not scale beyond a certain size. This
allows us to simplify the divider element as well.

Changes:

- Adds `BackgroundOrientation` to `gpui::color::Background` to allow
specifying a direction for a pattern
- Adds the PatternDash pattern variant
- Updates `ui::Divider`'s dashed variants to be more efficient

Misc:
- Documents the `ui::Divider` component
- Treat `.metal` files as `C` in the Zed project until we get some metal
syntax highlighting.

Release Notes:

- N/A
This commit is contained in:
Nate Butler 2025-01-29 12:18:34 -05:00 committed by GitHub
parent 8442e2b9d8
commit e5943975f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 210 additions and 106 deletions

View file

@ -1,14 +1,7 @@
#![allow(missing_docs)]
use gpui::{Hsla, IntoElement};
use gpui::{pattern_horizontal_dash, pattern_vertical_dash, Background, Hsla, IntoElement};
use crate::prelude::*;
#[derive(Clone, Copy, PartialEq)]
enum DividerStyle {
Solid,
Dashed,
}
#[derive(Clone, Copy, PartialEq)]
enum DividerDirection {
Horizontal,
@ -18,12 +11,15 @@ enum DividerDirection {
/// The color of a [`Divider`].
#[derive(Default)]
pub enum DividerColor {
Border,
/// The default border color.
#[default]
Border,
/// Usually a de-emphasized border color.
BorderVariant,
}
impl DividerColor {
/// Returns the divider's HSLA color.
pub fn hsla(self, cx: &mut App) -> Hsla {
match self {
DividerColor::Border => cx.theme().colors().border,
@ -32,71 +28,29 @@ impl DividerColor {
}
}
/// A component that can be used to separate sections of content.
///
/// Can be rendered horizontally or vertically.
#[derive(IntoElement)]
pub struct Divider {
style: DividerStyle,
direction: DividerDirection,
color: DividerColor,
inset: bool,
is_dashed: bool,
}
impl RenderOnce for Divider {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
match self.style {
DividerStyle::Solid => self.render_solid(cx).into_any_element(),
DividerStyle::Dashed => self.render_dashed(cx).into_any_element(),
}
}
}
let color = self.color.hsla(cx);
let background = if self.is_dashed {
match self.direction {
DividerDirection::Horizontal => pattern_horizontal_dash(color),
DividerDirection::Vertical => pattern_vertical_dash(color),
}
} else {
Background::from(color)
};
impl Divider {
pub fn horizontal() -> Self {
Self {
style: DividerStyle::Solid,
direction: DividerDirection::Horizontal,
color: DividerColor::default(),
inset: false,
}
}
pub fn vertical() -> Self {
Self {
style: DividerStyle::Solid,
direction: DividerDirection::Vertical,
color: DividerColor::default(),
inset: false,
}
}
pub fn horizontal_dashed() -> Self {
Self {
style: DividerStyle::Dashed,
direction: DividerDirection::Horizontal,
color: DividerColor::default(),
inset: false,
}
}
pub fn vertical_dashed() -> Self {
Self {
style: DividerStyle::Dashed,
direction: DividerDirection::Vertical,
color: DividerColor::default(),
inset: false,
}
}
pub fn inset(mut self) -> Self {
self.inset = true;
self
}
pub fn color(mut self, color: DividerColor) -> Self {
self.color = color;
self
}
pub fn render_solid(self, cx: &mut App) -> impl IntoElement {
div()
.map(|this| match self.direction {
DividerDirection::Horizontal => {
@ -106,38 +60,60 @@ impl Divider {
this.w_px().h_full().when(self.inset, |this| this.my_1p5())
}
})
.bg(self.color.hsla(cx))
}
// TODO: Use canvas or a shader here
// This obviously is a short term approach
pub fn render_dashed(self, cx: &mut App) -> impl IntoElement {
let segment_count = 128;
let segment_count_f = segment_count as f32;
let segment_min_w = 6.;
let base = match self.direction {
DividerDirection::Horizontal => h_flex(),
DividerDirection::Vertical => v_flex(),
};
let (w, h) = match self.direction {
DividerDirection::Horizontal => (px(segment_min_w), px(1.)),
DividerDirection::Vertical => (px(1.), px(segment_min_w)),
};
let color = self.color.hsla(cx);
let total_min_w = segment_min_w * segment_count_f * 2.; // * 2 because of the gap
base.min_w(px(total_min_w))
.map(|this| {
if self.direction == DividerDirection::Horizontal {
this.w_full().h_px()
} else {
this.w_px().h_full()
}
})
.gap(px(segment_min_w))
.overflow_hidden()
.children(
(0..segment_count).map(|_| div().flex_grow().flex_shrink_0().w(w).h(h).bg(color)),
)
.bg(background)
}
}
impl Divider {
/// Creates a solid horizontal divider.
pub fn horizontal() -> Self {
Self {
direction: DividerDirection::Horizontal,
color: DividerColor::default(),
inset: false,
is_dashed: false,
}
}
/// Creates a solid vertical divider.
pub fn vertical() -> Self {
Self {
direction: DividerDirection::Vertical,
color: DividerColor::default(),
inset: false,
is_dashed: false,
}
}
/// Creates a dashed horizontal divider.
pub fn horizontal_dashed() -> Self {
Self {
direction: DividerDirection::Horizontal,
color: DividerColor::default(),
inset: false,
is_dashed: true,
}
}
/// Creates a dashed vertical divider.
pub fn vertical_dashed() -> Self {
Self {
direction: DividerDirection::Vertical,
color: DividerColor::default(),
inset: false,
is_dashed: true,
}
}
/// Pads the divider with a margin.
pub fn inset(mut self) -> Self {
self.inset = true;
self
}
/// Sets the color of the divider.
pub fn color(mut self, color: DividerColor) -> Self {
self.color = color;
self
}
}