179 lines
5.0 KiB
Rust
179 lines
5.0 KiB
Rust
use gpui::prelude::*;
|
|
use gpui::{App, ClickEvent, ElementId, SharedString, StyleRefinement, Window, div, px, relative};
|
|
use gpui_base::{Button as BaseButton, StyledExt};
|
|
use gpui_component::ActiveTheme;
|
|
|
|
/// A small count badge shown after a label.
|
|
/// Used on segmented filter buttons, like `All 12`, and on tabs.
|
|
/// Rendered from theme tokens and sized for the compact header buttons it lives on.
|
|
#[derive(IntoElement)]
|
|
pub struct CountBadge {
|
|
count: usize,
|
|
style: StyleRefinement,
|
|
}
|
|
|
|
impl CountBadge {
|
|
pub fn new(count: usize) -> Self {
|
|
Self {
|
|
count,
|
|
style: StyleRefinement::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Styled for CountBadge {
|
|
fn style(&mut self) -> &mut StyleRefinement {
|
|
&mut self.style
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for CountBadge {
|
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
div()
|
|
.refine_style(&self.style)
|
|
.h_flex()
|
|
.justify_center()
|
|
.ml_2()
|
|
.px_1()
|
|
.py_0p5()
|
|
.min_w_4()
|
|
.text_size(px(8.))
|
|
.bg(cx.theme().muted)
|
|
.text_color(cx.theme().muted_foreground)
|
|
.rounded(cx.theme().radius)
|
|
.line_height(relative(1.))
|
|
.child(SharedString::from(self.count.to_string()))
|
|
}
|
|
}
|
|
|
|
/// A segmented filter/tab button with an icon, a label and an optional [`CountBadge`].
|
|
/// The selected state renders the button pressed, styled from theme button tokens.
|
|
/// Built on the unstyled `gpui_base::Button`, like the app's other custom controls.
|
|
/// The `primary` variant uses the primary button tokens.
|
|
/// It suits call-to-action buttons such as `New issue` and `New PR`.
|
|
#[allow(clippy::type_complexity)]
|
|
#[derive(IntoElement)]
|
|
pub struct SegmentButton {
|
|
id: ElementId,
|
|
style: StyleRefinement,
|
|
icon: Option<gpui::AnyElement>,
|
|
label: SharedString,
|
|
count: Option<usize>,
|
|
selected: bool,
|
|
primary: bool,
|
|
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
|
}
|
|
|
|
impl SegmentButton {
|
|
pub fn new<I, L>(id: I, label: L) -> Self
|
|
where
|
|
I: Into<ElementId>,
|
|
L: Into<SharedString>,
|
|
{
|
|
Self {
|
|
id: id.into(),
|
|
label: label.into(),
|
|
style: StyleRefinement::default(),
|
|
icon: None,
|
|
count: None,
|
|
selected: false,
|
|
primary: false,
|
|
on_click: None,
|
|
}
|
|
}
|
|
|
|
/// The leading icon, e.g. `Icon::new(CustomIconName::GitIssueDone)`.
|
|
pub fn icon(mut self, icon: impl IntoElement) -> Self {
|
|
self.icon = Some(icon.into_any_element());
|
|
self
|
|
}
|
|
|
|
/// A count shown in a badge after the label.
|
|
pub fn count(mut self, count: usize) -> Self {
|
|
self.count = Some(count);
|
|
self
|
|
}
|
|
|
|
/// Whether the button reflects an active filter/tab.
|
|
pub fn selected(mut self, selected: bool) -> Self {
|
|
self.selected = selected;
|
|
self
|
|
}
|
|
|
|
/// Use the primary button tokens, for call-to-action buttons.
|
|
pub fn primary(mut self) -> Self {
|
|
self.primary = true;
|
|
self
|
|
}
|
|
|
|
pub fn on_click(
|
|
mut self,
|
|
listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
|
) -> Self {
|
|
self.on_click = Some(Box::new(listener));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Styled for SegmentButton {
|
|
fn style(&mut self) -> &mut StyleRefinement {
|
|
&mut self.style
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for SegmentButton {
|
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let Self {
|
|
id,
|
|
style,
|
|
icon,
|
|
label,
|
|
count,
|
|
selected,
|
|
primary,
|
|
on_click,
|
|
} = self;
|
|
|
|
let theme = cx.theme();
|
|
let fg = if primary {
|
|
theme.button_primary_foreground
|
|
} else {
|
|
theme.button_foreground
|
|
};
|
|
let base = if primary {
|
|
theme.button_primary
|
|
} else {
|
|
theme.button_active
|
|
};
|
|
let hover = if primary {
|
|
theme.button_primary_hover
|
|
} else {
|
|
theme.button_hover
|
|
};
|
|
let active = if primary {
|
|
theme.button_primary_active
|
|
} else {
|
|
theme.button_active
|
|
};
|
|
|
|
BaseButton::new(id)
|
|
.refine_style(&style)
|
|
.flex()
|
|
.items_center()
|
|
.h_7()
|
|
.px_2()
|
|
.gap_1()
|
|
.when_some(icon, |this, icon| this.child(icon))
|
|
.child(div().text_sm().child(label))
|
|
.when_some(count, |this, count| this.child(CountBadge::new(count)))
|
|
.text_color(fg)
|
|
.rounded(theme.radius)
|
|
.hover(move |this| this.bg(hover))
|
|
.active(move |this| this.bg(active))
|
|
.selected(selected)
|
|
.when(primary, |this| this.bg(base))
|
|
.when(selected, |this| this.bg(active))
|
|
.when_some(on_click, |this, listener| this.on_click(listener))
|
|
}
|
|
}
|