tab: Add Pill, Segmented, Underline style Tab and complete the Tab API. (#676)
<img width="985" alt="image" src="https://github.com/user-attachments/assets/3d3860ff-ac7c-459a-bb6e-d0ea09b8eac8" /> <img width="936" alt="image" src="https://github.com/user-attachments/assets/abb39b8f-4139-4f41-9c12-2a22f767bec0" /> <img width="985" alt="image" src="https://github.com/user-attachments/assets/9fccce5f-8da5-4c78-8679-1a85dcfc56f0" /> Run example: ``` cargo run --example tabs ```
This commit is contained in:
parent
76efa2715b
commit
1aa43b1bfe
8 changed files with 824 additions and 43 deletions
35
crates/story/examples/tabs.rs
Normal file
35
crates/story/examples/tabs.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use gpui::*;
|
||||||
|
use story::{Assets, TabsStory};
|
||||||
|
|
||||||
|
pub struct Example {
|
||||||
|
root: Entity<TabsStory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Example {
|
||||||
|
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
let root = TabsStory::view(window, cx);
|
||||||
|
|
||||||
|
Self { root }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||||
|
cx.new(|cx| Self::new(window, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Example {
|
||||||
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
div().p_4().size_full().child(self.root.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = Application::new().with_assets(Assets);
|
||||||
|
|
||||||
|
app.run(move |cx| {
|
||||||
|
story::init(cx);
|
||||||
|
cx.activate(true);
|
||||||
|
|
||||||
|
story::create_new_window("Tabs Example", Example::view, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ mod scrollable_story;
|
||||||
mod sidebar_story;
|
mod sidebar_story;
|
||||||
mod switch_story;
|
mod switch_story;
|
||||||
mod table_story;
|
mod table_story;
|
||||||
|
mod tabs_story;
|
||||||
mod text_story;
|
mod text_story;
|
||||||
mod title_bar;
|
mod title_bar;
|
||||||
mod tooltip_story;
|
mod tooltip_story;
|
||||||
|
|
@ -28,6 +29,7 @@ pub use button_story::ButtonStory;
|
||||||
pub use calendar_story::CalendarStory;
|
pub use calendar_story::CalendarStory;
|
||||||
pub use dropdown_story::DropdownStory;
|
pub use dropdown_story::DropdownStory;
|
||||||
pub use form_story::FormStory;
|
pub use form_story::FormStory;
|
||||||
|
pub use tabs_story::TabsStory;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, impl_internal_actions, prelude::FluentBuilder as _, px, size, AnyElement,
|
actions, div, impl_internal_actions, prelude::FluentBuilder as _, px, size, AnyElement,
|
||||||
|
|
|
||||||
200
crates/story/src/tabs_story.rs
Normal file
200
crates/story/src/tabs_story.rs
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
use gpui::{
|
||||||
|
App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled, Window,
|
||||||
|
};
|
||||||
|
|
||||||
|
use gpui_component::{
|
||||||
|
button::{Button, ButtonGroup, ButtonVariants},
|
||||||
|
h_flex,
|
||||||
|
tab::{Tab, TabBar},
|
||||||
|
v_flex, IconName, Selectable as _, Sizable, Size,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::section;
|
||||||
|
|
||||||
|
pub struct TabsStory {
|
||||||
|
focus_handle: gpui::FocusHandle,
|
||||||
|
active_tab_ix: usize,
|
||||||
|
size: Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Story for TabsStory {
|
||||||
|
fn title() -> &'static str {
|
||||||
|
"Tabs"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description() -> &'static str {
|
||||||
|
"A set of layered sections of content—known as tab panels—that are displayed one at a time."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render + Focusable> {
|
||||||
|
Self::view(window, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabsStory {
|
||||||
|
pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||||
|
cx.new(|cx| Self::new(window, cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
active_tab_ix: 0,
|
||||||
|
size: Size::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_active_tab(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.active_tab_ix = ix;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_size(&mut self, size: Size, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.size = size;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Focusable for TabsStory {
|
||||||
|
fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for TabsStory {
|
||||||
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.child(
|
||||||
|
ButtonGroup::new("toggle-size")
|
||||||
|
.child(
|
||||||
|
Button::new("xsmall")
|
||||||
|
.label("XSmall")
|
||||||
|
.selected(self.size == Size::XSmall),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("small")
|
||||||
|
.label("Small")
|
||||||
|
.selected(self.size == Size::Small),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("medium")
|
||||||
|
.label("Medium")
|
||||||
|
.selected(self.size == Size::Medium),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("large")
|
||||||
|
.label("Large")
|
||||||
|
.selected(self.size == Size::Large),
|
||||||
|
)
|
||||||
|
.on_click(cx.listener(|this, selecteds: &Vec<usize>, window, cx| {
|
||||||
|
let size = match selecteds[0] {
|
||||||
|
0 => Size::XSmall,
|
||||||
|
1 => Size::Small,
|
||||||
|
2 => Size::Medium,
|
||||||
|
3 => Size::Large,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
this.set_size(size, window, cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
section("Normal Tabs", cx).child(
|
||||||
|
TabBar::new("normal-tabs")
|
||||||
|
.w_full()
|
||||||
|
.with_size(self.size)
|
||||||
|
.selected_index(self.active_tab_ix)
|
||||||
|
.on_click(cx.listener(|this, ix: &usize, window, cx| {
|
||||||
|
this.set_active_tab(*ix, window, cx);
|
||||||
|
}))
|
||||||
|
.prefix(
|
||||||
|
h_flex()
|
||||||
|
.mx_1()
|
||||||
|
.child(
|
||||||
|
Button::new("back")
|
||||||
|
.ghost()
|
||||||
|
.xsmall()
|
||||||
|
.icon(IconName::ArrowLeft),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("forward")
|
||||||
|
.ghost()
|
||||||
|
.xsmall()
|
||||||
|
.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"))
|
||||||
|
.suffix(
|
||||||
|
h_flex()
|
||||||
|
.mx_1()
|
||||||
|
.child(Button::new("inbox").ghost().xsmall().icon(IconName::Inbox))
|
||||||
|
.child(
|
||||||
|
Button::new("more")
|
||||||
|
.ghost()
|
||||||
|
.xsmall()
|
||||||
|
.icon(IconName::Ellipsis),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
section("Pills Tabs", cx).child(
|
||||||
|
TabBar::new("pills-tabs")
|
||||||
|
.w_full()
|
||||||
|
.pill()
|
||||||
|
.with_size(self.size)
|
||||||
|
.selected_index(self.active_tab_ix)
|
||||||
|
.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(
|
||||||
|
section("Segmented Tabs", cx).child(
|
||||||
|
TabBar::new("segmented-tabs")
|
||||||
|
.w_full()
|
||||||
|
.segmented()
|
||||||
|
.with_size(self.size)
|
||||||
|
.selected_index(self.active_tab_ix)
|
||||||
|
.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(
|
||||||
|
section("Underline Tabs", cx).child(
|
||||||
|
TabBar::new("underline-tabs")
|
||||||
|
.w_full()
|
||||||
|
.underline()
|
||||||
|
.with_size(self.size)
|
||||||
|
.selected_index(self.active_tab_ix)
|
||||||
|
.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")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -651,6 +651,7 @@ impl TabPanel {
|
||||||
let tabs_count = self.panels.len();
|
let tabs_count = self.panels.len();
|
||||||
|
|
||||||
TabBar::new("tab-bar")
|
TabBar::new("tab-bar")
|
||||||
|
.mt(-px(1.))
|
||||||
.track_scroll(self.tab_bar_scroll_handle.clone())
|
.track_scroll(self.tab_bar_scroll_handle.clone())
|
||||||
.when(
|
.when(
|
||||||
left_dock_button.is_some() || bottom_dock_button.is_some(),
|
left_dock_button.is_some() || bottom_dock_button.is_some(),
|
||||||
|
|
@ -672,6 +673,9 @@ 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)| {
|
.children(self.panels.iter().enumerate().filter_map(|(ix, panel)| {
|
||||||
let mut active = state.active_panel.as_ref() == Some(panel);
|
let mut active = state.active_panel.as_ref() == Some(panel);
|
||||||
let disabled = self.collapsed;
|
let disabled = self.collapsed;
|
||||||
|
|
@ -691,10 +695,7 @@ impl TabPanel {
|
||||||
.selected(active)
|
.selected(active)
|
||||||
.disabled(disabled)
|
.disabled(disabled)
|
||||||
.when(!disabled, |this| {
|
.when(!disabled, |this| {
|
||||||
this.on_click(cx.listener(move |view, _, window, cx| {
|
this.when(state.draggable, |this| {
|
||||||
view.set_active_ix(ix, window, cx);
|
|
||||||
}))
|
|
||||||
.when(state.draggable, |this| {
|
|
||||||
this.on_drag(
|
this.on_drag(
|
||||||
DragPanel::new(panel.clone(), view.clone()),
|
DragPanel::new(panel.clone(), view.clone()),
|
||||||
|drag, _, _, cx| {
|
|drag, _, _, cx| {
|
||||||
|
|
@ -720,7 +721,7 @@ impl TabPanel {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
.child(
|
.last_empty_space(
|
||||||
// empty space to allow move to last tab right
|
// empty space to allow move to last tab right
|
||||||
div()
|
div()
|
||||||
.id("tab-bar-empty-space")
|
.id("tab-bar-empty-space")
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,18 @@ pub trait StyledExt: Styled + Sized {
|
||||||
.pr(paddings.right.into())
|
.pr(paddings.right.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply margins to the element.
|
||||||
|
fn margins<L>(self, margins: impl Into<Edges<L>>) -> Self
|
||||||
|
where
|
||||||
|
L: Into<DefiniteLength> + Clone + Default + std::fmt::Debug,
|
||||||
|
{
|
||||||
|
let margins = margins.into();
|
||||||
|
self.mt(margins.top.into())
|
||||||
|
.mb(margins.bottom.into())
|
||||||
|
.ml(margins.left.into())
|
||||||
|
.mr(margins.right.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Render a border with a width of 1px, color red
|
/// Render a border with a width of 1px, color red
|
||||||
fn debug_red(self) -> Self {
|
fn debug_red(self) -> Self {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,324 @@
|
||||||
use crate::{ActiveTheme, Selectable};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{ActiveTheme, Selectable, Sizable, Size, StyledExt};
|
||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::prelude::FluentBuilder as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, ParentElement as _,
|
div, px, AnyElement, App, ClickEvent, Div, Edges, ElementId, Hsla, InteractiveElement,
|
||||||
RenderOnce, Stateful, StatefulInteractiveElement, Styled, Window,
|
IntoElement, ParentElement as _, Pixels, RenderOnce, Stateful, StatefulInteractiveElement,
|
||||||
|
Styled, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum TabVariant {
|
||||||
|
#[default]
|
||||||
|
Tab,
|
||||||
|
Pill,
|
||||||
|
Segmented,
|
||||||
|
Underline,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct TabStyle {
|
||||||
|
borders: Edges<Pixels>,
|
||||||
|
border_color: Hsla,
|
||||||
|
bg: Hsla,
|
||||||
|
fg: Hsla,
|
||||||
|
radius: Pixels,
|
||||||
|
shadow: bool,
|
||||||
|
inner_bg: Hsla,
|
||||||
|
inner_radius: Pixels,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TabStyle {
|
||||||
|
fn default() -> Self {
|
||||||
|
TabStyle {
|
||||||
|
borders: Edges::all(px(0.)),
|
||||||
|
border_color: gpui::transparent_white(),
|
||||||
|
bg: gpui::transparent_white(),
|
||||||
|
fg: gpui::transparent_white(),
|
||||||
|
radius: px(0.),
|
||||||
|
shadow: false,
|
||||||
|
inner_bg: gpui::transparent_white(),
|
||||||
|
inner_radius: px(0.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabVariant {
|
||||||
|
fn inner_height(&self, size: Size) -> Pixels {
|
||||||
|
match size {
|
||||||
|
Size::Small | Size::XSmall => match self {
|
||||||
|
TabVariant::Tab => px(25.),
|
||||||
|
TabVariant::Pill => px(26.),
|
||||||
|
TabVariant::Segmented => px(26.),
|
||||||
|
TabVariant::Underline => px(22.),
|
||||||
|
},
|
||||||
|
_ => match self {
|
||||||
|
TabVariant::Tab => px(30.),
|
||||||
|
TabVariant::Pill => px(31.),
|
||||||
|
TabVariant::Segmented => px(30.),
|
||||||
|
TabVariant::Underline => px(25.),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_paddings(&self, size: Size) -> Edges<Pixels> {
|
||||||
|
match size {
|
||||||
|
Size::Small | Size::XSmall => match self {
|
||||||
|
TabVariant::Tab => Edges {
|
||||||
|
left: px(10.),
|
||||||
|
right: px(10.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Pill => Edges {
|
||||||
|
left: px(14.),
|
||||||
|
right: px(14.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Segmented => Edges {
|
||||||
|
left: px(8.),
|
||||||
|
right: px(8.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Underline => Edges {
|
||||||
|
left: px(10.),
|
||||||
|
right: px(10.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => match self {
|
||||||
|
TabVariant::Tab => Edges {
|
||||||
|
left: px(12.),
|
||||||
|
right: px(12.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Pill => Edges {
|
||||||
|
left: px(16.),
|
||||||
|
right: px(16.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Segmented => Edges {
|
||||||
|
left: px(10.),
|
||||||
|
right: px(10.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Underline => Edges {
|
||||||
|
left: px(12.),
|
||||||
|
right: px(12.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_margins(&self, size: Size) -> Edges<Pixels> {
|
||||||
|
match size {
|
||||||
|
Size::Small | Size::XSmall => match self {
|
||||||
|
TabVariant::Underline => Edges {
|
||||||
|
bottom: px(2.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
_ => Edges::all(px(0.)),
|
||||||
|
},
|
||||||
|
_ => match self {
|
||||||
|
TabVariant::Underline => Edges {
|
||||||
|
bottom: px(4.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
_ => Edges::all(px(0.)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normal(&self, cx: &App) -> TabStyle {
|
||||||
|
match self {
|
||||||
|
TabVariant::Tab => TabStyle {
|
||||||
|
fg: cx.theme().foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
borders: Edges {
|
||||||
|
top: px(1.),
|
||||||
|
left: px(1.),
|
||||||
|
right: px(1.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
border_color: cx.theme().transparent,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Pill => TabStyle {
|
||||||
|
fg: cx.theme().foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
borders: Edges::all(px(1.)),
|
||||||
|
border_color: cx.theme().border,
|
||||||
|
radius: px(99.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Segmented => TabStyle {
|
||||||
|
fg: cx.theme().foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
radius: cx.theme().radius,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Underline => TabStyle {
|
||||||
|
fg: cx.theme().foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
radius: px(0.),
|
||||||
|
inner_bg: cx.theme().transparent,
|
||||||
|
inner_radius: cx.theme().radius,
|
||||||
|
borders: Edges {
|
||||||
|
bottom: px(2.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
border_color: cx.theme().transparent,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hovered(&self, cx: &App) -> TabStyle {
|
||||||
|
match self {
|
||||||
|
TabVariant::Tab => TabStyle {
|
||||||
|
fg: cx.theme().tab_foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
borders: Edges {
|
||||||
|
top: px(1.),
|
||||||
|
left: px(1.),
|
||||||
|
right: px(1.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
border_color: cx.theme().transparent,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Pill => TabStyle {
|
||||||
|
fg: cx.theme().accent_foreground,
|
||||||
|
bg: cx.theme().accent,
|
||||||
|
borders: Edges::all(px(1.)),
|
||||||
|
border_color: cx.theme().border,
|
||||||
|
radius: px(99.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Segmented => TabStyle {
|
||||||
|
fg: cx.theme().tab_foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
radius: cx.theme().radius,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Underline => TabStyle {
|
||||||
|
fg: cx.theme().tab_foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
radius: px(0.),
|
||||||
|
inner_bg: cx.theme().accent,
|
||||||
|
inner_radius: cx.theme().radius,
|
||||||
|
borders: Edges {
|
||||||
|
bottom: px(2.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
border_color: cx.theme().transparent,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected(&self, cx: &App) -> TabStyle {
|
||||||
|
match self {
|
||||||
|
TabVariant::Tab => TabStyle {
|
||||||
|
fg: cx.theme().tab_active_foreground,
|
||||||
|
bg: cx.theme().tab_active,
|
||||||
|
borders: Edges {
|
||||||
|
top: px(1.),
|
||||||
|
left: px(1.),
|
||||||
|
right: px(1.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
border_color: cx.theme().border,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Pill => TabStyle {
|
||||||
|
fg: cx.theme().accent_foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
borders: Edges::all(px(1.)),
|
||||||
|
border_color: cx.theme().primary,
|
||||||
|
radius: px(99.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Segmented => TabStyle {
|
||||||
|
fg: cx.theme().tab_active_foreground,
|
||||||
|
bg: cx.theme().background,
|
||||||
|
radius: cx.theme().radius,
|
||||||
|
shadow: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Underline => TabStyle {
|
||||||
|
fg: cx.theme().tab_active_foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
borders: Edges {
|
||||||
|
bottom: px(2.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
border_color: cx.theme().primary,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disabled(&self, selected: bool, cx: &App) -> TabStyle {
|
||||||
|
match self {
|
||||||
|
TabVariant::Tab => TabStyle {
|
||||||
|
fg: cx.theme().muted_foreground,
|
||||||
|
bg: cx.theme().tab,
|
||||||
|
border_color: if selected {
|
||||||
|
cx.theme().border
|
||||||
|
} else {
|
||||||
|
cx.theme().transparent
|
||||||
|
},
|
||||||
|
borders: Edges {
|
||||||
|
top: px(1.),
|
||||||
|
left: px(1.),
|
||||||
|
right: px(1.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Pill => TabStyle {
|
||||||
|
fg: cx.theme().muted_foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
borders: Edges::all(px(1.)),
|
||||||
|
border_color: if selected {
|
||||||
|
cx.theme().primary
|
||||||
|
} else {
|
||||||
|
cx.theme().border
|
||||||
|
},
|
||||||
|
radius: px(99.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Segmented => TabStyle {
|
||||||
|
fg: cx.theme().muted_foreground,
|
||||||
|
bg: if selected {
|
||||||
|
cx.theme().background
|
||||||
|
} else {
|
||||||
|
cx.theme().transparent
|
||||||
|
},
|
||||||
|
radius: cx.theme().radius,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TabVariant::Underline => TabStyle {
|
||||||
|
fg: cx.theme().muted_foreground,
|
||||||
|
bg: cx.theme().transparent,
|
||||||
|
radius: cx.theme().radius,
|
||||||
|
border_color: if selected {
|
||||||
|
cx.theme().border
|
||||||
|
} else {
|
||||||
|
cx.theme().transparent
|
||||||
|
},
|
||||||
|
borders: Edges {
|
||||||
|
bottom: px(2.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
|
|
@ -12,8 +326,11 @@ pub struct Tab {
|
||||||
label: AnyElement,
|
label: AnyElement,
|
||||||
prefix: Option<AnyElement>,
|
prefix: Option<AnyElement>,
|
||||||
suffix: Option<AnyElement>,
|
suffix: Option<AnyElement>,
|
||||||
|
variant: TabVariant,
|
||||||
|
size: Size,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
on_click: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
|
|
@ -21,15 +338,42 @@ impl Tab {
|
||||||
let id: ElementId = id.into();
|
let id: ElementId = id.into();
|
||||||
Self {
|
Self {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
base: div().id(id).gap_1().py_1p5().px_3().h(px(30.)),
|
base: div().id(id).gap_1(),
|
||||||
label: label.into_any_element(),
|
label: label.into_any_element(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
selected: false,
|
selected: false,
|
||||||
prefix: None,
|
prefix: None,
|
||||||
suffix: None,
|
suffix: None,
|
||||||
|
variant: TabVariant::default(),
|
||||||
|
size: Size::default(),
|
||||||
|
on_click: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set Tab Variant.
|
||||||
|
pub fn variant(mut self, variant: TabVariant) -> Self {
|
||||||
|
self.variant = variant;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use Pill variant.
|
||||||
|
pub fn pill(mut self) -> Self {
|
||||||
|
self.variant = TabVariant::Pill;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use Segmented variant.
|
||||||
|
pub fn segmented(mut self) -> Self {
|
||||||
|
self.variant = TabVariant::Segmented;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use Underline variant.
|
||||||
|
pub fn underline(mut self) -> Self {
|
||||||
|
self.variant = TabVariant::Underline;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the left side of the tab
|
/// Set the left side of the tab
|
||||||
pub fn prefix(mut self, prefix: impl Into<AnyElement>) -> Self {
|
pub fn prefix(mut self, prefix: impl Into<AnyElement>) -> Self {
|
||||||
self.prefix = Some(prefix.into());
|
self.prefix = Some(prefix.into());
|
||||||
|
|
@ -47,6 +391,15 @@ impl Tab {
|
||||||
self.disabled = disabled;
|
self.disabled = disabled;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the click handler for the tab.
|
||||||
|
pub fn on_click(
|
||||||
|
mut self,
|
||||||
|
on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.on_click = Some(Arc::new(on_click));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selectable for Tab {
|
impl Selectable for Tab {
|
||||||
|
|
@ -74,14 +427,32 @@ impl Styled for Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Tab {
|
||||||
|
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||||
|
self.size = size.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Tab {
|
impl RenderOnce for Tab {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (text_color, bg_color) = match (self.selected, self.disabled) {
|
let mut tab_style = if self.selected {
|
||||||
(true, false) => (cx.theme().tab_active_foreground, cx.theme().tab_active),
|
self.variant.selected(cx)
|
||||||
(false, false) => (cx.theme().muted_foreground, cx.theme().tab),
|
} else {
|
||||||
// disabled
|
self.variant.normal(cx)
|
||||||
(true, true) => (cx.theme().muted_foreground, cx.theme().tab_active),
|
};
|
||||||
(false, true) => (cx.theme().muted_foreground, cx.theme().tab),
|
let mut hover_style = self.variant.hovered(cx);
|
||||||
|
if self.disabled {
|
||||||
|
tab_style = self.variant.disabled(self.selected, cx);
|
||||||
|
hover_style = self.variant.disabled(self.selected, cx);
|
||||||
|
}
|
||||||
|
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.),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.base
|
self.base
|
||||||
|
|
@ -90,16 +461,53 @@ impl RenderOnce for Tab {
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.text_color(text_color)
|
.line_height(height)
|
||||||
.bg(bg_color)
|
.h(height)
|
||||||
.border_x_1()
|
.overflow_hidden()
|
||||||
.border_color(cx.theme().transparent)
|
.text_color(tab_style.fg)
|
||||||
.when(self.selected, |this| this.border_color(cx.theme().border))
|
.bg(tab_style.bg)
|
||||||
.text_sm()
|
.border_l(tab_style.borders.left)
|
||||||
.when_some(self.prefix, |this, prefix| {
|
.border_r(tab_style.borders.right)
|
||||||
this.child(prefix).text_color(text_color)
|
.border_t(tab_style.borders.top)
|
||||||
|
.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)
|
||||||
|
.bg(hover_style.bg)
|
||||||
|
.border_l(hover_style.borders.left)
|
||||||
|
.border_r(hover_style.borders.right)
|
||||||
|
.border_t(hover_style.borders.top)
|
||||||
|
.border_b(hover_style.borders.bottom)
|
||||||
|
.border_color(hover_style.border_color)
|
||||||
|
.rounded(tab_style.radius)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.child(div().text_ellipsis().child(self.label))
|
.text_sm()
|
||||||
|
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.h(inner_height)
|
||||||
|
.line_height(inner_height)
|
||||||
|
.paddings(inner_paddings)
|
||||||
|
.margins(inner_margins)
|
||||||
|
.text_ellipsis()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.child(self.label)
|
||||||
|
.bg(tab_style.inner_bg)
|
||||||
|
.rounded(tab_style.inner_radius)
|
||||||
|
.hover(|this| {
|
||||||
|
this.bg(hover_style.inner_bg)
|
||||||
|
.rounded(hover_style.inner_radius)
|
||||||
|
}),
|
||||||
|
)
|
||||||
.when_some(self.suffix, |this, suffix| this.child(suffix))
|
.when_some(self.suffix, |this, suffix| this.child(suffix))
|
||||||
|
.when(!self.disabled, |this| {
|
||||||
|
this.when_some(self.on_click.clone(), |this, on_click| {
|
||||||
|
this.on_click(move |event, window, cx| on_click(event, window, cx))
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
use crate::{h_flex, ActiveTheme};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{h_flex, ActiveTheme, Selectable, Sizable, Size, StyledExt};
|
||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::prelude::FluentBuilder as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, AnyElement, App, Div, ElementId, IntoElement, ParentElement, RenderOnce, ScrollHandle,
|
div, rems, AbsoluteLength, AnyElement, App, Div, Edges, ElementId, IntoElement, ParentElement,
|
||||||
StatefulInteractiveElement as _, Styled, Window,
|
RenderOnce, ScrollHandle, StatefulInteractiveElement as _, Styled, Window,
|
||||||
};
|
};
|
||||||
use gpui::{px, InteractiveElement};
|
use gpui::{px, InteractiveElement};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use super::{Tab, TabVariant};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct TabBar {
|
pub struct TabBar {
|
||||||
base: Div,
|
base: Div,
|
||||||
|
|
@ -14,7 +18,12 @@ pub struct TabBar {
|
||||||
scroll_handle: ScrollHandle,
|
scroll_handle: ScrollHandle,
|
||||||
prefix: Option<AnyElement>,
|
prefix: Option<AnyElement>,
|
||||||
suffix: Option<AnyElement>,
|
suffix: Option<AnyElement>,
|
||||||
children: SmallVec<[AnyElement; 2]>,
|
children: SmallVec<[Tab; 2]>,
|
||||||
|
last_empty_space: Option<AnyElement>,
|
||||||
|
selected_index: Option<usize>,
|
||||||
|
variant: TabVariant,
|
||||||
|
size: Size,
|
||||||
|
on_click: Option<Arc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TabBar {
|
impl TabBar {
|
||||||
|
|
@ -26,9 +35,38 @@ impl TabBar {
|
||||||
scroll_handle: ScrollHandle::new(),
|
scroll_handle: ScrollHandle::new(),
|
||||||
prefix: None,
|
prefix: None,
|
||||||
suffix: None,
|
suffix: None,
|
||||||
|
variant: TabVariant::default(),
|
||||||
|
size: Size::default(),
|
||||||
|
last_empty_space: None,
|
||||||
|
selected_index: None,
|
||||||
|
on_click: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the Tab variant, all children will inherit the variant.
|
||||||
|
pub fn variant(mut self, variant: TabVariant) -> Self {
|
||||||
|
self.variant = variant;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the Tab variant to Pill, all children will inherit the variant.
|
||||||
|
pub fn pill(mut self) -> Self {
|
||||||
|
self.variant = TabVariant::Pill;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the Tab variant to Segmented, all children will inherit the variant.
|
||||||
|
pub fn segmented(mut self) -> Self {
|
||||||
|
self.variant = TabVariant::Segmented;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the Tab variant to Underline, all children will inherit the variant.
|
||||||
|
pub fn underline(mut self) -> Self {
|
||||||
|
self.variant = TabVariant::Underline;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Track the scroll of the TabBar
|
/// Track the scroll of the TabBar
|
||||||
pub fn track_scroll(mut self, scroll_handle: ScrollHandle) -> Self {
|
pub fn track_scroll(mut self, scroll_handle: ScrollHandle) -> Self {
|
||||||
self.scroll_handle = scroll_handle;
|
self.scroll_handle = scroll_handle;
|
||||||
|
|
@ -46,11 +84,37 @@ impl TabBar {
|
||||||
self.suffix = Some(suffix.into_any_element());
|
self.suffix = Some(suffix.into_any_element());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl ParentElement for TabBar {
|
/// Add children of the TabBar, all children will inherit the variant.
|
||||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
pub fn children(mut self, children: impl IntoIterator<Item = Tab>) -> Self {
|
||||||
self.children.extend(elements)
|
self.children.extend(children);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add child of the TabBar, tab will inherit the variant.
|
||||||
|
pub fn child(mut self, child: Tab) -> Self {
|
||||||
|
self.children.push(child);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the selected index of the TabBar.
|
||||||
|
pub fn selected_index(mut self, index: usize) -> Self {
|
||||||
|
self.selected_index = Some(index);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the on_click callback of the TabBar, the first parameter is the index of the clicked tab.
|
||||||
|
///
|
||||||
|
/// When this is set, the children's on_click will be ignored.
|
||||||
|
pub fn on_click(mut self, on_click: impl Fn(&usize, &mut Window, &mut App) + 'static) -> Self {
|
||||||
|
self.on_click = Some(Arc::new(on_click));
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,8 +124,38 @@ impl Styled for TabBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for TabBar {
|
||||||
|
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||||
|
self.size = size.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for TabBar {
|
impl RenderOnce for TabBar {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let default_gap = match self.size {
|
||||||
|
Size::Small | Size::XSmall => px(8.),
|
||||||
|
_ => px(12.),
|
||||||
|
};
|
||||||
|
let (bg, paddings, gap) = match self.variant {
|
||||||
|
TabVariant::Tab => {
|
||||||
|
let padding = Edges::all(AbsoluteLength::Pixels(px(0.)));
|
||||||
|
(cx.theme().tab_bar, padding, px(0.))
|
||||||
|
}
|
||||||
|
TabVariant::Pill => {
|
||||||
|
let padding = Edges::all(AbsoluteLength::Rems(rems(0.25)));
|
||||||
|
(cx.theme().transparent, padding, default_gap)
|
||||||
|
}
|
||||||
|
TabVariant::Segmented => {
|
||||||
|
let padding = Edges::all(AbsoluteLength::Rems(rems(0.25)));
|
||||||
|
(cx.theme().accent, padding, default_gap / 2.)
|
||||||
|
}
|
||||||
|
TabVariant::Underline => {
|
||||||
|
let padding = Edges::all(AbsoluteLength::Pixels(px(0.)));
|
||||||
|
(cx.theme().transparent, padding, default_gap / 2.)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.base
|
self.base
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.group("tab-bar")
|
.group("tab-bar")
|
||||||
|
|
@ -69,16 +163,26 @@ impl RenderOnce for TabBar {
|
||||||
.flex()
|
.flex()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.items_center()
|
.items_center()
|
||||||
.bg(cx.theme().tab_bar)
|
.bg(bg)
|
||||||
|
.paddings(paddings)
|
||||||
.text_color(cx.theme().tab_foreground)
|
.text_color(cx.theme().tab_foreground)
|
||||||
.child(
|
.when(
|
||||||
div()
|
self.variant == TabVariant::Underline || self.variant == TabVariant::Tab,
|
||||||
.id("border-b")
|
|this| {
|
||||||
.absolute()
|
this.child(
|
||||||
.bottom_0()
|
div()
|
||||||
.size_full()
|
.id("border-b")
|
||||||
.border_b_1()
|
.absolute()
|
||||||
.border_color(cx.theme().border),
|
.bottom_0()
|
||||||
|
.size_full()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().border),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.when(
|
||||||
|
self.variant == TabVariant::Pill || self.variant == TabVariant::Segmented,
|
||||||
|
|this| this.rounded(cx.theme().radius),
|
||||||
)
|
)
|
||||||
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
||||||
.child(
|
.child(
|
||||||
|
|
@ -87,7 +191,26 @@ impl RenderOnce for TabBar {
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.overflow_x_scroll()
|
.overflow_x_scroll()
|
||||||
.track_scroll(&self.scroll_handle)
|
.track_scroll(&self.scroll_handle)
|
||||||
.children(self.children),
|
.gap(gap)
|
||||||
|
.children(
|
||||||
|
self.children
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(move |(ix, child)| {
|
||||||
|
child
|
||||||
|
.variant(self.variant)
|
||||||
|
.with_size(self.size)
|
||||||
|
.when_some(self.selected_index, |this, selected_ix| {
|
||||||
|
this.selected(selected_ix == ix)
|
||||||
|
})
|
||||||
|
.when_some(self.on_click.clone(), move |this, on_click| {
|
||||||
|
this.on_click(move |_, window, cx| {
|
||||||
|
on_click(&ix, window, cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.children(self.last_empty_space),
|
||||||
)
|
)
|
||||||
.when_some(self.suffix, |this, suffix| this.child(suffix))
|
.when_some(self.suffix, |this, suffix| this.child(suffix))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ pub struct ThemeColor {
|
||||||
impl ThemeColor {
|
impl ThemeColor {
|
||||||
pub fn light() -> Self {
|
pub fn light() -> Self {
|
||||||
Self {
|
Self {
|
||||||
accent: hsl(240.0, 5.0, 96.0),
|
accent: hsl(240.0, 4.8, 93.9),
|
||||||
accent_foreground: hsl(240.0, 5.9, 10.0),
|
accent_foreground: hsl(240.0, 5.9, 10.0),
|
||||||
accordion: hsl(0.0, 0.0, 100.0),
|
accordion: hsl(0.0, 0.0, 100.0),
|
||||||
accordion_active: hsl(240.0, 5.9, 90.0),
|
accordion_active: hsl(240.0, 5.9, 90.0),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue