tab: Improve TabBar API. (#678)
- Fixed scroll support for TabBar. - Remove `id` argument from Tab, it will set from TabBar iter. - Add `id` to TabBar to ensure scroll_handle state will store correct. - Update `label` argument of Tab, to use `impl Into<SharedString>` type for better use. - Added `child` to Tab for render custom element as label.
This commit is contained in:
parent
bc89ce2536
commit
c4331c9419
4 changed files with 110 additions and 64 deletions
|
|
@ -100,7 +100,7 @@ impl Render for TabsStory {
|
|||
)
|
||||
.child(
|
||||
section("Tabs", cx).child(
|
||||
TabBar::new()
|
||||
TabBar::new("tabs")
|
||||
.w_full()
|
||||
.with_size(self.size)
|
||||
.selected_index(self.active_tab_ix)
|
||||
|
|
@ -123,12 +123,14 @@ impl Render for TabsStory {
|
|||
.icon(IconName::ArrowRight),
|
||||
),
|
||||
)
|
||||
.child(Tab::new("tab-account", "Account"))
|
||||
.child(Tab::new("tab-profile", "Profile").disabled(true))
|
||||
.child(Tab::new("tab-documents", "Documents"))
|
||||
.child(Tab::new("tab-mail", "Mail"))
|
||||
.child(Tab::new("tab-appearance", "Appearance"))
|
||||
.child(Tab::new("tab-settings", "Settings"))
|
||||
.child(Tab::new("Account"))
|
||||
.child(Tab::new("Profile").disabled(true))
|
||||
.child(Tab::new("Documents"))
|
||||
.child(Tab::new("Mail"))
|
||||
.child(Tab::new("Appearance"))
|
||||
.child(Tab::new("Settings"))
|
||||
.child(Tab::new("About"))
|
||||
.child(Tab::new("License"))
|
||||
.suffix(
|
||||
h_flex()
|
||||
.mx_1()
|
||||
|
|
@ -144,7 +146,7 @@ impl Render for TabsStory {
|
|||
)
|
||||
.child(
|
||||
section("Pill Tabs", cx).child(
|
||||
TabBar::new()
|
||||
TabBar::new("pill")
|
||||
.w_full()
|
||||
.pill()
|
||||
.with_size(self.size)
|
||||
|
|
@ -152,17 +154,19 @@ impl Render for TabsStory {
|
|||
.on_click(cx.listener(|this, ix: &usize, window, cx| {
|
||||
this.set_active_tab(*ix, window, cx);
|
||||
}))
|
||||
.child(Tab::new("tab-account", "Account"))
|
||||
.child(Tab::new("tab-profile", "Profile").disabled(true))
|
||||
.child(Tab::new("tab-documents", "Documents"))
|
||||
.child(Tab::new("tab-mail", "Mail"))
|
||||
.child(Tab::new("tab-appearance", "Appearance"))
|
||||
.child(Tab::new("tab-settings", "Settings")),
|
||||
.child(Tab::new("Account"))
|
||||
.child(Tab::new("Profile").disabled(true))
|
||||
.child(Tab::new("Documents"))
|
||||
.child(Tab::new("Mail"))
|
||||
.child(Tab::new("Appearance"))
|
||||
.child(Tab::new("Settings"))
|
||||
.child(Tab::new("About"))
|
||||
.child(Tab::new("License")),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Segmented Tabs", cx).child(
|
||||
TabBar::new()
|
||||
TabBar::new("segmented")
|
||||
.w_full()
|
||||
.segmented()
|
||||
.with_size(self.size)
|
||||
|
|
@ -177,13 +181,17 @@ impl Render for TabsStory {
|
|||
"Mail",
|
||||
"Appearance",
|
||||
"Settings",
|
||||
"About",
|
||||
"License",
|
||||
]),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Underline Tabs", cx).child(
|
||||
TabBar::new()
|
||||
TabBar::new("underline")
|
||||
.w_full()
|
||||
.px_2()
|
||||
.mx_3()
|
||||
.underline()
|
||||
.with_size(self.size)
|
||||
.selected_index(self.active_tab_ix)
|
||||
|
|
@ -195,7 +203,9 @@ impl Render for TabsStory {
|
|||
.child("Documents")
|
||||
.child("Mail")
|
||||
.child("Appearance")
|
||||
.child("Settings"),
|
||||
.child("Settings")
|
||||
.child("About")
|
||||
.child("License"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -650,7 +650,7 @@ impl TabPanel {
|
|||
|
||||
let tabs_count = self.panels.len();
|
||||
|
||||
TabBar::new()
|
||||
TabBar::new("tab-bar")
|
||||
.mt(-px(1.))
|
||||
.track_scroll(self.tab_bar_scroll_handle.clone())
|
||||
.when(
|
||||
|
|
@ -673,9 +673,6 @@ impl TabPanel {
|
|||
)
|
||||
},
|
||||
)
|
||||
.on_click(cx.listener(move |view, ix: &usize, window, cx| {
|
||||
view.set_active_ix(*ix, window, cx);
|
||||
}))
|
||||
.children(self.panels.iter().enumerate().filter_map(|(ix, panel)| {
|
||||
let mut active = state.active_panel.as_ref() == Some(panel);
|
||||
let disabled = self.collapsed;
|
||||
|
|
@ -690,12 +687,16 @@ impl TabPanel {
|
|||
}
|
||||
|
||||
Some(
|
||||
Tab::new(("tab", ix), panel.title(window, cx))
|
||||
Tab::new("")
|
||||
.child(panel.title(window, cx))
|
||||
.py_2()
|
||||
.selected(active)
|
||||
.disabled(disabled)
|
||||
.when(!disabled, |this| {
|
||||
this.when(state.draggable, |this| {
|
||||
this.on_click(cx.listener(move |view, _, window, cx| {
|
||||
view.set_active_ix(ix, window, cx);
|
||||
}))
|
||||
.when(state.draggable, |this| {
|
||||
this.on_drag(
|
||||
DragPanel::new(panel.clone(), view.clone()),
|
||||
|drag, _, _, cx| {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ use crate::{ActiveTheme, Selectable, Sizable, Size, StyledExt};
|
|||
use gpui::prelude::FluentBuilder as _;
|
||||
use gpui::{
|
||||
div, px, AnyElement, App, ClickEvent, Div, Edges, ElementId, Hsla, InteractiveElement,
|
||||
IntoElement, ParentElement as _, Pixels, RenderOnce, SharedString, Stateful,
|
||||
StatefulInteractiveElement, Styled, Window,
|
||||
IntoElement, ParentElement, Pixels, RenderOnce, SharedString, StatefulInteractiveElement,
|
||||
Styled, Window,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]
|
||||
|
|
@ -45,6 +45,23 @@ impl Default for TabStyle {
|
|||
}
|
||||
|
||||
impl TabVariant {
|
||||
fn height(&self, size: Size) -> Pixels {
|
||||
match size {
|
||||
Size::Small | Size::XSmall => match self {
|
||||
TabVariant::Tab => px(24.),
|
||||
TabVariant::Pill => px(24.),
|
||||
TabVariant::Segmented => px(24.),
|
||||
TabVariant::Underline => px(30.),
|
||||
},
|
||||
_ => match self {
|
||||
TabVariant::Tab => px(30.),
|
||||
TabVariant::Pill => px(31.),
|
||||
TabVariant::Segmented => px(30.),
|
||||
TabVariant::Underline => px(36.),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_height(&self, size: Size) -> Pixels {
|
||||
match size {
|
||||
Size::Small | Size::XSmall => match self {
|
||||
|
|
@ -57,7 +74,7 @@ impl TabVariant {
|
|||
TabVariant::Tab => px(30.),
|
||||
TabVariant::Pill => px(31.),
|
||||
TabVariant::Segmented => px(30.),
|
||||
TabVariant::Underline => px(25.),
|
||||
TabVariant::Underline => px(24.),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -122,7 +139,8 @@ impl TabVariant {
|
|||
},
|
||||
_ => match self {
|
||||
TabVariant::Underline => Edges {
|
||||
bottom: px(4.),
|
||||
top: px(5.),
|
||||
bottom: px(3.),
|
||||
..Default::default()
|
||||
},
|
||||
_ => Edges::all(px(0.)),
|
||||
|
|
@ -233,7 +251,7 @@ impl TabVariant {
|
|||
..Default::default()
|
||||
},
|
||||
TabVariant::Pill => TabStyle {
|
||||
fg: cx.theme().accent_foreground,
|
||||
fg: cx.theme().primary,
|
||||
bg: cx.theme().transparent,
|
||||
borders: Edges::all(px(1.)),
|
||||
border_color: cx.theme().primary,
|
||||
|
|
@ -322,10 +340,11 @@ impl TabVariant {
|
|||
#[derive(IntoElement)]
|
||||
pub struct Tab {
|
||||
id: ElementId,
|
||||
base: Stateful<Div>,
|
||||
label: AnyElement,
|
||||
base: Div,
|
||||
label: SharedString,
|
||||
prefix: Option<AnyElement>,
|
||||
suffix: Option<AnyElement>,
|
||||
children: Vec<AnyElement>,
|
||||
variant: TabVariant,
|
||||
size: Size,
|
||||
disabled: bool,
|
||||
|
|
@ -336,31 +355,30 @@ pub struct Tab {
|
|||
impl From<&'static str> for Tab {
|
||||
fn from(label: &'static str) -> Self {
|
||||
let label = SharedString::from(label);
|
||||
Self::new(label.clone(), label)
|
||||
Self::new(label)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Tab {
|
||||
fn from(label: String) -> Self {
|
||||
let label = SharedString::from(label);
|
||||
Self::new(label.clone(), label)
|
||||
Self::new(label)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SharedString> for Tab {
|
||||
fn from(label: SharedString) -> Self {
|
||||
let label = SharedString::from(label);
|
||||
Self::new(label.clone(), label)
|
||||
Self::new(label)
|
||||
}
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub fn new(id: impl Into<ElementId>, label: impl IntoElement) -> Self {
|
||||
let id: ElementId = id.into();
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
id: id.clone(),
|
||||
base: div().id(id).gap_1(),
|
||||
label: label.into_any_element(),
|
||||
id: ElementId::Integer(0),
|
||||
base: div().gap_1(),
|
||||
label: label.into(),
|
||||
children: Vec::new(),
|
||||
disabled: false,
|
||||
selected: false,
|
||||
prefix: None,
|
||||
|
|
@ -371,6 +389,12 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set id to the tab.
|
||||
pub fn id(mut self, id: impl Into<ElementId>) -> Self {
|
||||
self.id = id.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set Tab Variant.
|
||||
pub fn variant(mut self, variant: TabVariant) -> Self {
|
||||
self.variant = variant;
|
||||
|
|
@ -423,6 +447,12 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
|
||||
impl ParentElement for Tab {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements);
|
||||
}
|
||||
}
|
||||
|
||||
impl Selectable for Tab {
|
||||
fn element_id(&self) -> &ElementId {
|
||||
&self.id
|
||||
|
|
@ -470,13 +500,11 @@ impl RenderOnce for Tab {
|
|||
let inner_paddings = self.variant.inner_paddings(self.size);
|
||||
let inner_margins = self.variant.inner_margins(self.size);
|
||||
let inner_height = self.variant.inner_height(self.size);
|
||||
|
||||
let height = match self.size {
|
||||
Size::Small | Size::XSmall => px(24.),
|
||||
_ => px(31.),
|
||||
};
|
||||
let height = self.variant.height(self.size);
|
||||
let has_label = !self.label.is_empty();
|
||||
|
||||
self.base
|
||||
.id(self.id)
|
||||
.flex()
|
||||
.items_center()
|
||||
.flex_shrink_0()
|
||||
|
|
@ -516,7 +544,8 @@ impl RenderOnce for Tab {
|
|||
.margins(inner_margins)
|
||||
.text_ellipsis()
|
||||
.flex_shrink_0()
|
||||
.child(self.label)
|
||||
.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)
|
||||
.hover(|this| {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use std::sync::Arc;
|
|||
use crate::{h_flex, ActiveTheme, Selectable, Sizable, Size, StyledExt};
|
||||
use gpui::prelude::FluentBuilder as _;
|
||||
use gpui::{
|
||||
div, rems, AbsoluteLength, AnyElement, App, Div, Edges, IntoElement, ParentElement, RenderOnce,
|
||||
ScrollHandle, StatefulInteractiveElement as _, Styled, Window,
|
||||
div, AnyElement, App, Div, Edges, ElementId, IntoElement, ParentElement, RenderOnce,
|
||||
ScrollHandle, Stateful, StatefulInteractiveElement as _, Styled, Window,
|
||||
};
|
||||
use gpui::{px, InteractiveElement};
|
||||
use smallvec::SmallVec;
|
||||
|
|
@ -13,12 +13,12 @@ use super::{Tab, TabVariant};
|
|||
|
||||
#[derive(IntoElement)]
|
||||
pub struct TabBar {
|
||||
base: Div,
|
||||
scroll_handle: ScrollHandle,
|
||||
base: Stateful<Div>,
|
||||
scroll_handle: Option<ScrollHandle>,
|
||||
prefix: Option<AnyElement>,
|
||||
suffix: Option<AnyElement>,
|
||||
children: SmallVec<[Tab; 2]>,
|
||||
last_empty_space: Option<AnyElement>,
|
||||
last_empty_space: AnyElement,
|
||||
selected_index: Option<usize>,
|
||||
variant: TabVariant,
|
||||
size: Size,
|
||||
|
|
@ -27,16 +27,16 @@ pub struct TabBar {
|
|||
|
||||
impl TabBar {
|
||||
/// Create a new TabBar.
|
||||
pub fn new() -> Self {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self {
|
||||
base: div().px(px(-1.)),
|
||||
base: div().id(id).px(px(-1.)),
|
||||
children: SmallVec::new(),
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
scroll_handle: None,
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
variant: TabVariant::default(),
|
||||
size: Size::default(),
|
||||
last_empty_space: None,
|
||||
last_empty_space: div().w_3().into_any_element(),
|
||||
selected_index: None,
|
||||
on_click: None,
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ impl TabBar {
|
|||
|
||||
/// Track the scroll of the TabBar
|
||||
pub fn track_scroll(mut self, scroll_handle: ScrollHandle) -> Self {
|
||||
self.scroll_handle = scroll_handle;
|
||||
self.scroll_handle = Some(scroll_handle);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ impl TabBar {
|
|||
|
||||
/// Set the last empty space element of the TabBar
|
||||
pub fn last_empty_space(mut self, last_empty_space: impl IntoElement) -> Self {
|
||||
self.last_empty_space = Some(last_empty_space.into_any_element());
|
||||
self.last_empty_space = last_empty_space.into_any_element();
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -139,31 +139,32 @@ impl RenderOnce for TabBar {
|
|||
};
|
||||
let (bg, paddings, gap) = match self.variant {
|
||||
TabVariant::Tab => {
|
||||
let padding = Edges::all(AbsoluteLength::Pixels(px(0.)));
|
||||
let padding = Edges::all(px(0.));
|
||||
(cx.theme().tab_bar, padding, px(0.))
|
||||
}
|
||||
TabVariant::Pill => {
|
||||
let padding = Edges::all(AbsoluteLength::Rems(rems(0.25)));
|
||||
let padding = Edges::all(px(0.));
|
||||
(cx.theme().transparent, padding, default_gap)
|
||||
}
|
||||
TabVariant::Segmented => {
|
||||
let padding = Edges::all(AbsoluteLength::Rems(rems(0.25)));
|
||||
let padding = Edges::all(px(4.));
|
||||
(cx.theme().accent, padding, default_gap / 2.)
|
||||
}
|
||||
TabVariant::Underline => {
|
||||
let padding = Edges::all(AbsoluteLength::Pixels(px(0.)));
|
||||
let padding = Edges::all(px(0.));
|
||||
(cx.theme().transparent, padding, default_gap / 2.)
|
||||
}
|
||||
};
|
||||
|
||||
self.base
|
||||
.group("tab-bar")
|
||||
.w_full()
|
||||
.relative()
|
||||
.flex()
|
||||
.flex_none()
|
||||
.flex_nowrap()
|
||||
.items_center()
|
||||
.bg(bg)
|
||||
.paddings(paddings)
|
||||
.text_color(cx.theme().tab_foreground)
|
||||
.when(
|
||||
self.variant == TabVariant::Underline || self.variant == TabVariant::Tab,
|
||||
|
|
@ -172,6 +173,7 @@ impl RenderOnce for TabBar {
|
|||
div()
|
||||
.id("border-b")
|
||||
.absolute()
|
||||
.left_0()
|
||||
.bottom_0()
|
||||
.size_full()
|
||||
.border_b_1()
|
||||
|
|
@ -187,16 +189,20 @@ impl RenderOnce for TabBar {
|
|||
.child(
|
||||
h_flex()
|
||||
.id("tabs")
|
||||
.flex_grow()
|
||||
.flex_1()
|
||||
.overflow_x_scroll()
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.when_some(self.scroll_handle, |this, scroll_handle| {
|
||||
this.track_scroll(&scroll_handle)
|
||||
})
|
||||
.gap(gap)
|
||||
.paddings(paddings)
|
||||
.children(
|
||||
self.children
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(ix, child)| {
|
||||
child
|
||||
.id(ix)
|
||||
.variant(self.variant)
|
||||
.with_size(self.size)
|
||||
.when_some(self.selected_index, |this, selected_ix| {
|
||||
|
|
@ -209,7 +215,7 @@ impl RenderOnce for TabBar {
|
|||
})
|
||||
}),
|
||||
)
|
||||
.children(self.last_empty_space),
|
||||
.child(self.last_empty_space),
|
||||
)
|
||||
.when_some(self.suffix, |this, suffix| this.child(suffix))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue