This PR adds a bunch of design improvements to the toolbar of (tab) views that display diffs: the uncommitted changes, branch diff, and agent diff tabs. This involves adjusting spacing, sizing, and other small tweaks across all of these views, including touching up the divider component API a little bit, adjusting Git icons design, adding diff stat numbers to the uncommitted changes tab so its consistent with the others, and more (e.g., moving the split buttons into its own component to ensure they look the same across all uses). Ended up also going on a slight de-tour to make all uses of the split button in the Git panel consistent design-wise. All in all, this is just tidying up the design of all of these surfaces that are all relatively similar. | Branch Diff | Uncommitted Diff | Single-file Diff | Git Panel | |--------|--------|--------|--------| | <img width="800" alt="Screenshot 2026-07-06 at 11 25@2x" src="https://github.com/user-attachments/assets/4ebf87e7-c52d-41d9-9de3-7944125114a1" /> | <img width="800" alt="Screenshot 2026-07-06 at 11 25 2@2x" src="https://github.com/user-attachments/assets/51ccd2eb-904b-455a-a708-b4cb50b3a7cb" /> | <img width="800" alt="Screenshot 2026-07-06 at 11 26@2x" src="https://github.com/user-attachments/assets/6ae7df68-d1bd-4d36-a250-ba16e43c4e8e" /> | <img width="800" alt="Screenshot 2026-07-06 at 11 26 2@2x" src="https://github.com/user-attachments/assets/e032bc60-4551-41f0-b578-90ed305167c2" /> | Release Notes: - Added diff stat numbers to the uncommitted changes view.
240 lines
7.6 KiB
Rust
240 lines
7.6 KiB
Rust
use gpui::{Hsla, IntoElement, PathBuilder, Refineable as _, StyleRefinement, canvas, point};
|
|
|
|
use crate::prelude::*;
|
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
enum DividerStyle {
|
|
Solid,
|
|
Dashed,
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
enum DividerDirection {
|
|
Horizontal,
|
|
Vertical,
|
|
}
|
|
|
|
/// The color of a [`Divider`].
|
|
#[derive(Default)]
|
|
pub enum DividerColor {
|
|
Border,
|
|
BorderFaded,
|
|
#[default]
|
|
BorderVariant,
|
|
}
|
|
|
|
impl DividerColor {
|
|
pub fn hsla(self, cx: &mut App) -> Hsla {
|
|
match self {
|
|
DividerColor::Border => cx.theme().colors().border,
|
|
DividerColor::BorderFaded => cx.theme().colors().border.opacity(0.6),
|
|
DividerColor::BorderVariant => cx.theme().colors().border_variant,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(IntoElement, RegisterComponent)]
|
|
pub struct Divider {
|
|
line_style: DividerStyle,
|
|
direction: DividerDirection,
|
|
color: DividerColor,
|
|
style: StyleRefinement,
|
|
inset: bool,
|
|
}
|
|
|
|
impl Divider {
|
|
pub fn horizontal() -> Self {
|
|
Self {
|
|
line_style: DividerStyle::Solid,
|
|
direction: DividerDirection::Horizontal,
|
|
color: DividerColor::default(),
|
|
style: StyleRefinement::default(),
|
|
inset: false,
|
|
}
|
|
}
|
|
|
|
pub fn vertical() -> Self {
|
|
Self {
|
|
line_style: DividerStyle::Solid,
|
|
direction: DividerDirection::Vertical,
|
|
color: DividerColor::default(),
|
|
style: StyleRefinement::default(),
|
|
inset: false,
|
|
}
|
|
}
|
|
|
|
pub fn horizontal_dashed() -> Self {
|
|
Self {
|
|
line_style: DividerStyle::Dashed,
|
|
direction: DividerDirection::Horizontal,
|
|
color: DividerColor::default(),
|
|
style: StyleRefinement::default(),
|
|
inset: false,
|
|
}
|
|
}
|
|
|
|
pub fn vertical_dashed() -> Self {
|
|
Self {
|
|
line_style: DividerStyle::Dashed,
|
|
direction: DividerDirection::Vertical,
|
|
color: DividerColor::default(),
|
|
style: StyleRefinement::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, base: Div, cx: &mut App) -> impl IntoElement {
|
|
base.bg(self.color.hsla(cx))
|
|
}
|
|
|
|
pub fn render_dashed(self, base: Div) -> impl IntoElement {
|
|
base.relative().child(
|
|
canvas(
|
|
|_, _, _| {},
|
|
move |bounds, _, window, cx| {
|
|
let mut builder = PathBuilder::stroke(px(1.)).dash_array(&[px(4.), px(2.)]);
|
|
let (start, end) = match self.direction {
|
|
DividerDirection::Horizontal => {
|
|
let x = bounds.origin.x;
|
|
let y = bounds.origin.y + px(0.5);
|
|
(point(x, y), point(x + bounds.size.width, y))
|
|
}
|
|
DividerDirection::Vertical => {
|
|
let x = bounds.origin.x + px(0.5);
|
|
let y = bounds.origin.y;
|
|
(point(x, y), point(x, y + bounds.size.height))
|
|
}
|
|
};
|
|
builder.move_to(start);
|
|
builder.line_to(end);
|
|
if let Ok(line) = builder.build() {
|
|
window.paint_path(line, self.color.hsla(cx));
|
|
}
|
|
},
|
|
)
|
|
.absolute()
|
|
.size_full(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Styled for Divider {
|
|
fn style(&mut self) -> &mut StyleRefinement {
|
|
&mut self.style
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for Divider {
|
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let mut base = match self.direction {
|
|
DividerDirection::Horizontal => div()
|
|
.min_w_0()
|
|
.h_px()
|
|
.max_h_px()
|
|
.w_full()
|
|
.when(self.inset, |this| this.mx_1p5()),
|
|
DividerDirection::Vertical => div()
|
|
.min_w_0()
|
|
.w_px()
|
|
.h_4()
|
|
.when(self.inset, |this| this.my_1p5()),
|
|
};
|
|
|
|
base.style().refine(&self.style);
|
|
|
|
match self.line_style {
|
|
DividerStyle::Solid => self.render_solid(base, cx).into_any_element(),
|
|
DividerStyle::Dashed => self.render_dashed(base).into_any_element(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Component for Divider {
|
|
fn scope() -> ComponentScope {
|
|
ComponentScope::Layout
|
|
}
|
|
|
|
fn description() -> &'static str {
|
|
"Visual separator used to create divisions between groups of content \
|
|
or sections in a layout."
|
|
}
|
|
|
|
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
|
v_flex()
|
|
.gap_6()
|
|
.children(vec![
|
|
example_group_with_title(
|
|
"Horizontal Dividers",
|
|
vec![
|
|
single_example("Default", Divider::horizontal().into_any_element()),
|
|
single_example(
|
|
"Border Color",
|
|
Divider::horizontal()
|
|
.color(DividerColor::Border)
|
|
.into_any_element(),
|
|
),
|
|
single_example("Inset", Divider::horizontal().inset().into_any_element()),
|
|
single_example("Dashed", Divider::horizontal_dashed().into_any_element()),
|
|
],
|
|
),
|
|
example_group_with_title(
|
|
"Vertical Dividers",
|
|
vec![
|
|
single_example(
|
|
"Default",
|
|
div().h_16().child(Divider::vertical()).into_any_element(),
|
|
),
|
|
single_example(
|
|
"Border Color",
|
|
div()
|
|
.h_16()
|
|
.child(Divider::vertical().color(DividerColor::Border))
|
|
.into_any_element(),
|
|
),
|
|
single_example(
|
|
"Inset",
|
|
div()
|
|
.h_16()
|
|
.child(Divider::vertical().inset())
|
|
.into_any_element(),
|
|
),
|
|
single_example(
|
|
"Dashed",
|
|
div()
|
|
.h_16()
|
|
.child(Divider::vertical_dashed())
|
|
.into_any_element(),
|
|
),
|
|
],
|
|
),
|
|
example_group_with_title(
|
|
"Example Usage",
|
|
vec![single_example(
|
|
"Between Content",
|
|
v_flex()
|
|
.w_full()
|
|
.gap_4()
|
|
.px_4()
|
|
.child(Label::new("Section One"))
|
|
.child(Divider::horizontal())
|
|
.child(Label::new("Section Two"))
|
|
.child(Divider::horizontal_dashed())
|
|
.child(Label::new("Section Three"))
|
|
.into_any_element(),
|
|
)],
|
|
),
|
|
])
|
|
.into_any_element()
|
|
}
|
|
}
|