tab: Improve segmented tab style and add for icon mode tab support. (#688)
<img width="873" alt="image" src="https://github.com/user-attachments/assets/f616a9ba-0a60-49f5-8291-04f79f5b582c" /> <img width="948" alt="image" src="https://github.com/user-attachments/assets/5ba1b50e-5b5f-45e1-89ca-683bcac8331a" /> <img width="929" alt="image" src="https://github.com/user-attachments/assets/6545b67f-6c53-482f-a3ac-b7929c9bec99" /> <img width="1153" alt="image" src="https://github.com/user-attachments/assets/06abb411-43a7-4472-8e58-d7c8032bed7b" />
This commit is contained in:
parent
6bf26f6f72
commit
4c96679cf7
5 changed files with 122 additions and 86 deletions
|
|
@ -156,7 +156,7 @@ impl Render for TabsStory {
|
|||
}))
|
||||
.child(Tab::new("Account"))
|
||||
.child(Tab::new("Profile").disabled(true))
|
||||
.child(Tab::new("Documents"))
|
||||
.child(Tab::new("Documents & Files"))
|
||||
.child(Tab::new("Mail"))
|
||||
.child(Tab::new("Appearance"))
|
||||
.child(Tab::new("Settings"))
|
||||
|
|
@ -174,16 +174,10 @@ impl Render for TabsStory {
|
|||
.on_click(cx.listener(|this, ix: &usize, window, cx| {
|
||||
this.set_active_tab(*ix, window, cx);
|
||||
}))
|
||||
.children(vec![
|
||||
"Account",
|
||||
"Profile",
|
||||
"Documents",
|
||||
"Mail",
|
||||
"Appearance",
|
||||
"Settings",
|
||||
"About",
|
||||
"License",
|
||||
]),
|
||||
.child(IconName::Bot)
|
||||
.child(IconName::Calendar)
|
||||
.child(IconName::Map)
|
||||
.children(vec!["Appearance", "Settings", "About", "License"]),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
|
|
|
|||
|
|
@ -528,7 +528,7 @@ impl Render for PopupMenu {
|
|||
v_flex()
|
||||
.id("popup-menu")
|
||||
.key_context("PopupMenu")
|
||||
.track_focus(&self.focus_handle)
|
||||
// .track_focus(&self.focus_handle)
|
||||
.on_action(cx.listener(Self::select_next))
|
||||
.on_action(cx.listener(Self::select_prev))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{ActiveTheme, Selectable, Sizable, Size, StyledExt};
|
||||
use crate::{h_flex, ActiveTheme, Icon, IconName, Selectable, Sizable, Size, StyledExt};
|
||||
use gpui::prelude::FluentBuilder as _;
|
||||
use gpui::{
|
||||
div, px, AnyElement, App, ClickEvent, Div, Edges, ElementId, Hsla, InteractiveElement,
|
||||
|
|
@ -48,28 +48,17 @@ impl TabVariant {
|
|||
fn height(&self, size: Size) -> Pixels {
|
||||
match size {
|
||||
Size::XSmall => match self {
|
||||
TabVariant::Tab => px(22.),
|
||||
TabVariant::Pill => px(20.),
|
||||
TabVariant::Segmented => px(20.),
|
||||
TabVariant::Underline => px(26.),
|
||||
_ => px(20.),
|
||||
},
|
||||
Size::Small => match self {
|
||||
TabVariant::Tab => px(24.),
|
||||
TabVariant::Pill => px(24.),
|
||||
TabVariant::Segmented => px(24.),
|
||||
TabVariant::Underline => px(30.),
|
||||
_ => px(24.),
|
||||
},
|
||||
Size::Large => match self {
|
||||
TabVariant::Tab => px(36.),
|
||||
TabVariant::Pill => px(36.),
|
||||
TabVariant::Segmented => px(36.),
|
||||
TabVariant::Underline => px(42.),
|
||||
},
|
||||
Size::Large => px(42.),
|
||||
_ => match self {
|
||||
TabVariant::Tab => px(30.),
|
||||
TabVariant::Pill => px(31.),
|
||||
TabVariant::Segmented => px(30.),
|
||||
TabVariant::Underline => px(36.),
|
||||
_ => px(32.),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -77,54 +66,45 @@ impl TabVariant {
|
|||
fn inner_height(&self, size: Size) -> Pixels {
|
||||
match size {
|
||||
Size::XSmall => match self {
|
||||
TabVariant::Tab => px(20.),
|
||||
TabVariant::Pill => px(20.),
|
||||
TabVariant::Segmented => px(20.),
|
||||
TabVariant::Tab | TabVariant::Pill => px(20.),
|
||||
TabVariant::Segmented => px(16.),
|
||||
TabVariant::Underline => px(20.),
|
||||
},
|
||||
Size::Small => match self {
|
||||
TabVariant::Tab => px(24.),
|
||||
TabVariant::Pill => px(24.),
|
||||
TabVariant::Segmented => px(24.),
|
||||
TabVariant::Tab | TabVariant::Pill => px(24.),
|
||||
TabVariant::Segmented => px(20.),
|
||||
TabVariant::Underline => px(22.),
|
||||
},
|
||||
Size::Large => match self {
|
||||
TabVariant::Tab => px(36.),
|
||||
TabVariant::Pill => px(36.),
|
||||
TabVariant::Segmented => px(36.),
|
||||
TabVariant::Tab | TabVariant::Pill => px(36.),
|
||||
TabVariant::Segmented => px(34.),
|
||||
TabVariant::Underline => px(30.),
|
||||
},
|
||||
_ => match self {
|
||||
TabVariant::Tab => px(30.),
|
||||
TabVariant::Pill => px(31.),
|
||||
TabVariant::Segmented => px(30.),
|
||||
TabVariant::Pill => px(26.),
|
||||
TabVariant::Segmented => px(24.),
|
||||
TabVariant::Underline => px(24.),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_paddings(&self, size: Size) -> Edges<Pixels> {
|
||||
match size {
|
||||
Size::XSmall => Edges {
|
||||
left: px(8.),
|
||||
right: px(8.),
|
||||
..Default::default()
|
||||
},
|
||||
Size::Small => Edges {
|
||||
left: px(12.),
|
||||
right: px(12.),
|
||||
..Default::default()
|
||||
},
|
||||
Size::Large => Edges {
|
||||
left: px(20.),
|
||||
right: px(20.),
|
||||
..Default::default()
|
||||
},
|
||||
_ => Edges {
|
||||
left: px(16.),
|
||||
right: px(16.),
|
||||
..Default::default()
|
||||
},
|
||||
let mut px = match size {
|
||||
Size::XSmall => px(8.),
|
||||
Size::Small => px(12.),
|
||||
Size::Large => px(20.),
|
||||
_ => px(16.),
|
||||
};
|
||||
|
||||
if *self == TabVariant::Segmented {
|
||||
px = px / 2.;
|
||||
}
|
||||
|
||||
Edges {
|
||||
left: px,
|
||||
right: px,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +132,7 @@ impl TabVariant {
|
|||
fn normal(&self, cx: &App) -> TabStyle {
|
||||
match self {
|
||||
TabVariant::Tab => TabStyle {
|
||||
fg: cx.theme().foreground,
|
||||
fg: cx.theme().tab_foreground,
|
||||
bg: cx.theme().transparent,
|
||||
borders: Edges {
|
||||
top: px(1.),
|
||||
|
|
@ -164,7 +144,7 @@ impl TabVariant {
|
|||
..Default::default()
|
||||
},
|
||||
TabVariant::Pill => TabStyle {
|
||||
fg: cx.theme().foreground,
|
||||
fg: cx.theme().tab_foreground,
|
||||
bg: cx.theme().transparent,
|
||||
borders: Edges::all(px(1.)),
|
||||
border_color: cx.theme().border,
|
||||
|
|
@ -172,13 +152,13 @@ impl TabVariant {
|
|||
..Default::default()
|
||||
},
|
||||
TabVariant::Segmented => TabStyle {
|
||||
fg: cx.theme().foreground,
|
||||
fg: cx.theme().tab_foreground,
|
||||
bg: cx.theme().transparent,
|
||||
radius: cx.theme().radius,
|
||||
inner_radius: cx.theme().radius,
|
||||
..Default::default()
|
||||
},
|
||||
TabVariant::Underline => TabStyle {
|
||||
fg: cx.theme().foreground,
|
||||
fg: cx.theme().tab_foreground,
|
||||
bg: cx.theme().transparent,
|
||||
radius: px(0.),
|
||||
inner_bg: cx.theme().transparent,
|
||||
|
|
@ -193,7 +173,7 @@ impl TabVariant {
|
|||
}
|
||||
}
|
||||
|
||||
fn hovered(&self, cx: &App) -> TabStyle {
|
||||
fn hovered(&self, selected: bool, cx: &App) -> TabStyle {
|
||||
match self {
|
||||
TabVariant::Tab => TabStyle {
|
||||
fg: cx.theme().tab_foreground,
|
||||
|
|
@ -217,8 +197,13 @@ impl TabVariant {
|
|||
},
|
||||
TabVariant::Segmented => TabStyle {
|
||||
fg: cx.theme().tab_foreground,
|
||||
bg: cx.theme().transparent,
|
||||
radius: cx.theme().radius,
|
||||
bg: cx.theme().tab_bar,
|
||||
inner_bg: if selected {
|
||||
cx.theme().background
|
||||
} else {
|
||||
cx.theme().transparent
|
||||
},
|
||||
inner_radius: cx.theme().radius,
|
||||
..Default::default()
|
||||
},
|
||||
TabVariant::Underline => TabStyle {
|
||||
|
|
@ -261,8 +246,9 @@ impl TabVariant {
|
|||
},
|
||||
TabVariant::Segmented => TabStyle {
|
||||
fg: cx.theme().tab_active_foreground,
|
||||
bg: cx.theme().background,
|
||||
radius: cx.theme().radius,
|
||||
bg: cx.theme().tab_bar,
|
||||
inner_radius: cx.theme().radius,
|
||||
inner_bg: cx.theme().background,
|
||||
shadow: true,
|
||||
..Default::default()
|
||||
},
|
||||
|
|
@ -311,12 +297,13 @@ impl TabVariant {
|
|||
},
|
||||
TabVariant::Segmented => TabStyle {
|
||||
fg: cx.theme().muted_foreground,
|
||||
bg: if selected {
|
||||
bg: cx.theme().tab_bar,
|
||||
inner_bg: if selected {
|
||||
cx.theme().background
|
||||
} else {
|
||||
cx.theme().transparent
|
||||
},
|
||||
radius: cx.theme().radius,
|
||||
inner_radius: cx.theme().radius,
|
||||
..Default::default()
|
||||
},
|
||||
TabVariant::Underline => TabStyle {
|
||||
|
|
@ -343,6 +330,7 @@ pub struct Tab {
|
|||
id: ElementId,
|
||||
base: Div,
|
||||
label: SharedString,
|
||||
icon: Option<Icon>,
|
||||
prefix: Option<AnyElement>,
|
||||
suffix: Option<AnyElement>,
|
||||
children: Vec<AnyElement>,
|
||||
|
|
@ -373,12 +361,25 @@ impl From<SharedString> for Tab {
|
|||
}
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
impl From<Icon> for Tab {
|
||||
fn from(icon: Icon) -> Self {
|
||||
Self::icon(icon)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IconName> for Tab {
|
||||
fn from(icon_name: IconName) -> Self {
|
||||
Self::icon(Icon::new(icon_name))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Tab {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: ElementId::Integer(0),
|
||||
base: div().gap_1(),
|
||||
label: label.into(),
|
||||
label: SharedString::new(""),
|
||||
icon: None,
|
||||
children: Vec::new(),
|
||||
disabled: false,
|
||||
selected: false,
|
||||
|
|
@ -389,6 +390,22 @@ impl Tab {
|
|||
on_click: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
/// Create a new tab with a label.
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
let mut this = Self::default();
|
||||
this.label = label.into();
|
||||
this
|
||||
}
|
||||
|
||||
/// Create a Icon tab.
|
||||
pub fn icon(icon: impl Into<Icon>) -> Self {
|
||||
let mut this = Self::default();
|
||||
this.icon = Some(icon.into());
|
||||
this
|
||||
}
|
||||
|
||||
/// Set id to the tab.
|
||||
pub fn id(mut self, id: impl Into<ElementId>) -> Self {
|
||||
|
|
@ -493,7 +510,7 @@ impl RenderOnce for Tab {
|
|||
} else {
|
||||
self.variant.normal(cx)
|
||||
};
|
||||
let mut hover_style = self.variant.hovered(cx);
|
||||
let mut hover_style = self.variant.hovered(self.selected, cx);
|
||||
if self.disabled {
|
||||
tab_style = self.variant.disabled(self.selected, cx);
|
||||
hover_style = self.variant.disabled(self.selected, cx);
|
||||
|
|
@ -512,7 +529,6 @@ impl RenderOnce for Tab {
|
|||
.flex_shrink_0()
|
||||
.cursor_pointer()
|
||||
.overflow_hidden()
|
||||
.line_height(height)
|
||||
.h(height)
|
||||
.overflow_hidden()
|
||||
.text_color(tab_style.fg)
|
||||
|
|
@ -528,7 +544,6 @@ impl RenderOnce for Tab {
|
|||
.border_b(tab_style.borders.bottom)
|
||||
.border_color(tab_style.border_color)
|
||||
.rounded(tab_style.radius)
|
||||
.when(tab_style.shadow, |this| this.shadow_sm())
|
||||
.when(!self.selected && !self.disabled, |this| {
|
||||
this.hover(|this| {
|
||||
this.text_color(hover_style.fg)
|
||||
|
|
@ -543,17 +558,33 @@ impl RenderOnce for Tab {
|
|||
})
|
||||
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
||||
.child(
|
||||
div()
|
||||
h_flex()
|
||||
.h(inner_height)
|
||||
.line_height(inner_height)
|
||||
.paddings(inner_paddings)
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.overflow_hidden()
|
||||
.margins(inner_margins)
|
||||
.text_ellipsis()
|
||||
.flex_shrink_0()
|
||||
.when(has_label, |this| this.child(self.label))
|
||||
.when(!has_label, |this| this.children(self.children))
|
||||
.map(|this| match self.icon {
|
||||
Some(icon) => {
|
||||
this.w(inner_height * 1.25)
|
||||
.child(icon.map(|this| match self.size {
|
||||
Size::XSmall => this.size_2p5(),
|
||||
Size::Small => this.size_3p5(),
|
||||
Size::Large => this.size_5(),
|
||||
_ => this.size_4(),
|
||||
}))
|
||||
}
|
||||
None => this
|
||||
.paddings(inner_paddings)
|
||||
.when(has_label, |this| this.child(self.label))
|
||||
.when(!has_label, |this| this.children(self.children)),
|
||||
})
|
||||
.bg(tab_style.inner_bg)
|
||||
.rounded(tab_style.inner_radius)
|
||||
.when(tab_style.shadow, |this| this.shadow_sm())
|
||||
.hover(|this| {
|
||||
this.bg(hover_style.inner_bg)
|
||||
.rounded(hover_style.inner_radius)
|
||||
|
|
|
|||
|
|
@ -148,8 +148,19 @@ impl RenderOnce for TabBar {
|
|||
(cx.theme().transparent, padding, default_gap)
|
||||
}
|
||||
TabVariant::Segmented => {
|
||||
let padding = Edges::all(px(4.));
|
||||
(cx.theme().accent, padding, default_gap / 2.)
|
||||
let padding_x = match self.size {
|
||||
Size::XSmall => px(3.),
|
||||
Size::Small => px(3.),
|
||||
Size::Large => px(6.),
|
||||
_ => px(4.),
|
||||
};
|
||||
let padding = Edges {
|
||||
left: padding_x,
|
||||
right: padding_x,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
(cx.theme().tab_bar, padding, px(2.))
|
||||
}
|
||||
TabVariant::Underline => {
|
||||
let padding = Edges::all(px(0.));
|
||||
|
|
|
|||
|
|
@ -274,8 +274,8 @@ impl ThemeColor {
|
|||
tab: gpui::transparent_black(),
|
||||
tab_active: hsl(0.0, 0.0, 100.0),
|
||||
tab_active_foreground: hsl(240.0, 10., 3.9),
|
||||
tab_bar: hsl(240.0, 4.8, 95.9),
|
||||
tab_foreground: hsl(240.0, 10., 3.9),
|
||||
tab_bar: hsl(240.0, 14.3, 95.9),
|
||||
tab_foreground: hsl(240.0, 10., 33.9),
|
||||
table: hsl(0.0, 0.0, 100.),
|
||||
table_active: hsl(211.0, 97.0, 85.0).opacity(0.2),
|
||||
table_active_border: hsl(211.0, 97.0, 85.0),
|
||||
|
|
|
|||
Loading…
Reference in a new issue