update ui

This commit is contained in:
2026-07-29 10:37:55 +07:00
parent 0faa8694f8
commit ef7698d248
24 changed files with 208 additions and 891 deletions

View File

@@ -2,11 +2,10 @@ use std::sync::Arc;
use gpui::prelude::FluentBuilder;
use gpui::{
AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Decorations, Edges, Entity,
EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement, ParentElement as _,
Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window, actions, div, px,
AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges, Entity, EntityId,
EventEmitter, Focusable, InteractiveElement as _, IntoElement, ParentElement as _, Pixels,
Render, SharedString, Styled, Subscription, WeakEntity, Window, actions, div, px,
};
use theme::CLIENT_SIDE_DECORATION_ROUNDING;
use crate::ElementExt;
@@ -110,15 +109,8 @@ impl DockItem {
window: &mut Window,
cx: &mut App,
) -> Self {
let mut items = items;
let stack_panel = cx.new(|cx| {
let mut stack_panel = StackPanel::new(axis, window, cx);
for (i, item) in items.iter_mut().enumerate() {
let view = item.view();
let size = sizes.get(i).copied().flatten();
stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx)
}
for (i, item) in items.iter().enumerate() {
let view = item.view();
@@ -745,34 +737,22 @@ impl EventEmitter<DockEvent> for DockArea {}
impl Render for DockArea {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity().clone();
let decorations = window.window_decorations();
div()
.id("dock-area")
.relative()
.size_full()
.overflow_hidden()
.on_prepaint(move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds))
.map(|this| {
if let Some(zoom_view) = self.zoom_view.clone() {
this.map(|this| match decorations {
Decorations::Server => this,
Decorations::Client { tiling } => this
.when(!(tiling.top || tiling.right), |div| {
div.rounded_br(CLIENT_SIDE_DECORATION_ROUNDING)
})
.when(!(tiling.top || tiling.left), |div| {
div.rounded_bl(CLIENT_SIDE_DECORATION_ROUNDING)
}),
})
.child(zoom_view)
this.child(zoom_view)
} else {
// render dock
this.child(
div()
.flex()
.flex_row()
.h_full()
.size_full()
// Left dock
.when_some(self.left_dock.clone(), |this, dock| {
this.child(div().flex().flex_none().child(dock))
@@ -783,14 +763,8 @@ impl Render for DockArea {
.flex()
.flex_1()
.flex_col()
.overflow_hidden()
// Top center
.child(
div()
.flex_1()
.overflow_hidden()
.child(self.render_items(window, cx)),
)
.child(div().flex_1().child(self.render_items(window, cx)))
// Bottom Dock
.when_some(self.bottom_dock.clone(), |this, dock| {
this.child(dock)

View File

@@ -1,13 +1,12 @@
use std::sync::Arc;
use gpui::prelude::FluentBuilder;
use gpui::{
App, AppContext, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
IntoElement, ParentElement, Pixels, Render, SharedString, Styled, Subscription, WeakEntity,
Window,
};
use smallvec::SmallVec;
use theme::{ActiveTheme, AxisExt as _, CLIENT_SIDE_DECORATION_ROUNDING, Placement};
use theme::{AxisExt as _, Placement};
use super::{DockArea, PanelEvent};
use crate::dock::panel::{Panel, PanelView};
@@ -369,26 +368,20 @@ impl Focusable for StackPanel {
}
impl EventEmitter<PanelEvent> for StackPanel {}
impl EventEmitter<DismissEvent> for StackPanel {}
impl Render for StackPanel {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.size_full()
.overflow_hidden()
.bg(cx.theme().panel_background)
.when(cx.theme().platform.is_linux(), |this| {
this.rounded_br(CLIENT_SIDE_DECORATION_ROUNDING)
})
.child(
ResizablePanelGroup::new("stack-panel-group")
.with_state(&self.state)
.axis(self.axis)
.children(self.panels.clone().into_iter().map(|panel| {
resizable_panel()
.child(panel.view())
.visible(panel.visible(cx))
})),
)
h_flex().size_full().overflow_hidden().child(
ResizablePanelGroup::new("stack-panel-group")
.with_state(&self.state)
.axis(self.axis)
.children(self.panels.clone().into_iter().map(|panel| {
resizable_panel()
.child(panel.view())
.visible(panel.visible(cx))
})),
)
}
}

View File

@@ -7,7 +7,7 @@ use gpui::{
ParentElement, Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled,
WeakEntity, Window, div, px, rems,
};
use theme::{ActiveTheme, AxisExt, CLIENT_SIDE_DECORATION_ROUNDING, Placement, TABBAR_HEIGHT};
use theme::{ActiveTheme, AxisExt, Placement, TABBAR_HEIGHT};
use crate::button::{Button, ButtonVariants as _};
use crate::dock::dock::DockPlacement;
@@ -567,6 +567,7 @@ impl TabPanel {
let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, window, cx);
let bottom_dock_button = self.render_dock_toggle_button(DockPlacement::Bottom, window, cx);
let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, window, cx);
let has_extend_dock_button = left_dock_button.is_some() || bottom_dock_button.is_some();
let tabs_count = self.panels.len();
let is_bottom_dock = bottom_dock_button.is_some();
@@ -586,6 +587,7 @@ impl TabPanel {
.py_2()
.pl_3()
.pr_2()
.rounded_t(cx.theme().radius_lg)
.bg(cx.theme().panel_background)
.when(left_dock_button.is_some(), |this| this.pl_2())
.when(right_dock_button.is_some(), |this| this.pr_2())
@@ -641,17 +643,14 @@ impl TabPanel {
TabBar::new("tab-bar")
.track_scroll(&self.tab_bar_scroll_handle)
.h(TABBAR_HEIGHT)
.bg(cx.theme().panel_background)
.rounded_t(cx.theme().radius_lg)
.when(has_extend_dock_button, |this| {
this.prefix(
h_flex()
.items_center()
.top_0()
.right(-px(1.))
.border_r_1()
.border_b_1()
.h_full()
.border_color(cx.theme().border)
.bg(cx.theme().tab_background)
.pl_0p5()
.pr_1()
.children(left_dock_button)
@@ -780,12 +779,8 @@ impl TabPanel {
.top_0()
.right_0()
.h_full()
.border_l_1()
.border_b_1()
.px_0p5()
.gap_1()
.border_color(cx.theme().border)
.bg(cx.theme().tab_background)
.child(self.render_toolbar(state, window, cx))
.when_some(right_dock_button, |this, btn| this.child(btn)),
)
@@ -815,10 +810,8 @@ impl TabPanel {
.child(
div()
.size_full()
.rounded_b(cx.theme().radius_lg)
.bg(cx.theme().panel_background)
.when(cx.theme().platform.is_linux(), |this| {
this.rounded_b(CLIENT_SIDE_DECORATION_ROUNDING)
})
.overflow_hidden()
.child(
active_panel
@@ -1140,17 +1133,24 @@ impl Render for TabPanel {
state.closable = false;
}
v_flex()
div()
.when(!self.collapsed, |this| {
this.on_action(cx.listener(Self::on_action_toggle_zoom))
.on_action(cx.listener(Self::on_action_close_panel))
})
.id("tab-panel")
.tab_group()
.track_focus(&focus_handle)
.size_full()
.p_1()
.overflow_hidden()
.child(self.render_title_bar(&state, window, cx))
.child(self.render_active_panel(&state, window, cx))
.child(
v_flex()
.rounded(cx.theme().radius_lg)
.when(cx.theme().shadow, |this| this.shadow_xs())
.size_full()
.tab_group()
.child(self.render_title_bar(&state, window, cx))
.child(self.render_active_panel(&state, window, cx)),
)
}
}

View File

@@ -35,8 +35,6 @@ pub struct Input {
cleanable: bool,
mask_toggle: bool,
disabled: bool,
bordered: bool,
focus_bordered: bool,
tab_index: isize,
selected: bool,
}
@@ -73,8 +71,6 @@ impl Input {
cleanable: false,
mask_toggle: false,
disabled: false,
bordered: true,
focus_bordered: true,
tab_index: 0,
selected: false,
}
@@ -108,18 +104,6 @@ impl Input {
self
}
/// Set the bordered for the input, default: true
pub fn bordered(mut self, bordered: bool) -> Self {
self.bordered = bordered;
self
}
/// Set focus border for the input, default is true.
pub fn focus_bordered(mut self, bordered: bool) -> Self {
self.focus_bordered = bordered;
self
}
/// Set whether to show the clear button when the input field is not empty, default is false.
pub fn cleanable(mut self, cleanable: bool) -> Self {
self.cleanable = cleanable;
@@ -332,11 +316,6 @@ impl RenderOnce for Input {
this.bg(bg)
.when(self.disabled, |this| this.opacity(0.5))
.rounded(cx.theme().radius)
.when(self.bordered, |this| {
this.border_color(cx.theme().border)
.border_1()
.when(cx.theme().shadow, |this| this.shadow_xs())
})
})
.items_center()
.gap(gap_x)

View File

@@ -117,7 +117,7 @@ impl<T: 'static, E: 'static + Render> Element for ResizeHandle<T, E> {
cx.theme().border
};
let mut el = div()
let mut ele = div()
.id(self.id.clone())
.occlude()
.absolute()
@@ -160,16 +160,15 @@ impl<T: 'static, E: 'static + Render> Element for ResizeHandle<T, E> {
})
.child(
div()
.bg(bg_color)
.group_hover("handle", |this| this.bg(bg_color))
.when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))
.when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)),
)
.into_any_element();
let layout_id = el.request_layout(window, cx);
let layout_id = ele.request_layout(window, cx);
((layout_id, el), state)
((layout_id, ele), state)
})
}

View File

@@ -2,396 +2,15 @@ use std::rc::Rc;
use gpui::prelude::FluentBuilder as _;
use gpui::{
AnyElement, App, ClickEvent, Div, Edges, Hsla, InteractiveElement, IntoElement, MouseButton,
ParentElement, Pixels, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
div, px, relative,
AnyElement, App, ClickEvent, Div, InteractiveElement, IntoElement, MouseButton, ParentElement,
RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px, relative,
};
use theme::ActiveTheme;
use theme::{ActiveTheme, TABBAR_HEIGHT};
use crate::{Icon, IconName, Selectable, Sizable, Size, StyledExt, h_flex};
use crate::{Icon, IconName, Selectable, h_flex};
pub mod tab_bar;
/// Tab variants.
#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]
pub enum TabVariant {
#[default]
Tab,
Outline,
Pill,
Segmented,
Underline,
}
impl TabVariant {
fn height(&self, size: Size) -> Pixels {
match size {
Size::XSmall => match self {
TabVariant::Underline => px(26.),
_ => px(20.),
},
Size::Small => match self {
TabVariant::Underline => px(30.),
_ => px(24.),
},
Size::Large => match self {
TabVariant::Underline => px(44.),
_ => px(36.),
},
_ => match self {
TabVariant::Underline => px(36.),
_ => px(32.),
},
}
}
fn inner_height(&self, size: Size) -> Pixels {
match size {
Size::XSmall => match self {
TabVariant::Tab | TabVariant::Outline | TabVariant::Pill => px(18.),
TabVariant::Segmented => px(16.),
TabVariant::Underline => px(20.),
},
Size::Small => match self {
TabVariant::Tab | TabVariant::Outline | TabVariant::Pill => px(22.),
TabVariant::Segmented => px(18.),
TabVariant::Underline => px(22.),
},
Size::Large => match self {
TabVariant::Tab | TabVariant::Outline | TabVariant::Pill => px(36.),
TabVariant::Segmented => px(28.),
TabVariant::Underline => px(32.),
},
_ => match self {
TabVariant::Tab => px(30.),
TabVariant::Outline | TabVariant::Pill => px(26.),
TabVariant::Segmented => px(24.),
TabVariant::Underline => px(26.),
},
}
}
/// Default px(12) to match panel px_3, See [`crate::dock::TabPanel`]
fn inner_paddings(&self, size: Size) -> Edges<Pixels> {
let mut padding_x = match size {
Size::XSmall => px(8.),
Size::Small => px(10.),
Size::Large => px(16.),
_ => px(12.),
};
if matches!(self, TabVariant::Underline) {
padding_x = px(0.);
}
Edges {
left: padding_x,
right: padding_x,
..Default::default()
}
}
fn inner_margins(&self, size: Size) -> Edges<Pixels> {
match size {
Size::XSmall => match self {
TabVariant::Underline => Edges {
top: px(1.),
bottom: px(2.),
..Default::default()
},
_ => Edges::all(px(0.)),
},
Size::Small => match self {
TabVariant::Underline => Edges {
top: px(2.),
bottom: px(3.),
..Default::default()
},
_ => Edges::all(px(0.)),
},
Size::Large => match self {
TabVariant::Underline => Edges {
top: px(5.),
bottom: px(6.),
..Default::default()
},
_ => Edges::all(px(0.)),
},
_ => match self {
TabVariant::Underline => Edges {
top: px(3.),
bottom: px(4.),
..Default::default()
},
_ => Edges::all(px(0.)),
},
}
}
fn normal(&self, cx: &App) -> TabStyle {
match self {
TabVariant::Tab => TabStyle {
fg: cx.theme().tab_foreground,
bg: gpui::transparent_black(),
borders: Edges {
left: px(1.),
right: px(1.),
..Default::default()
},
border_color: gpui::transparent_black(),
..Default::default()
},
TabVariant::Outline => TabStyle {
fg: cx.theme().tab_foreground,
bg: gpui::transparent_black(),
borders: Edges::all(px(1.)),
border_color: cx.theme().border,
..Default::default()
},
TabVariant::Pill => TabStyle {
fg: cx.theme().text,
bg: gpui::transparent_black(),
..Default::default()
},
TabVariant::Segmented => TabStyle {
fg: cx.theme().tab_foreground,
bg: gpui::transparent_black(),
..Default::default()
},
TabVariant::Underline => TabStyle {
fg: cx.theme().tab_foreground,
bg: gpui::transparent_black(),
inner_bg: gpui::transparent_black(),
borders: Edges {
bottom: px(2.),
..Default::default()
},
border_color: gpui::transparent_black(),
..Default::default()
},
}
}
fn hovered(&self, selected: bool, cx: &App) -> TabStyle {
match self {
TabVariant::Tab => TabStyle {
fg: cx.theme().tab_foreground,
bg: gpui::transparent_black(),
borders: Edges {
left: px(1.),
right: px(1.),
..Default::default()
},
border_color: gpui::transparent_black(),
..Default::default()
},
TabVariant::Outline => TabStyle {
fg: cx.theme().secondary_foreground,
bg: cx.theme().secondary_hover,
borders: Edges::all(px(1.)),
border_color: cx.theme().border,
..Default::default()
},
TabVariant::Pill => TabStyle {
fg: cx.theme().secondary_foreground,
bg: cx.theme().secondary_background,
..Default::default()
},
TabVariant::Segmented => TabStyle {
fg: cx.theme().tab_foreground,
bg: gpui::transparent_black(),
inner_bg: if selected {
cx.theme().background
} else {
gpui::transparent_black()
},
..Default::default()
},
TabVariant::Underline => TabStyle {
fg: cx.theme().tab_foreground,
bg: gpui::transparent_black(),
inner_bg: gpui::transparent_black(),
borders: Edges {
bottom: px(2.),
..Default::default()
},
border_color: gpui::transparent_black(),
..Default::default()
},
}
}
fn selected(&self, cx: &App) -> TabStyle {
match self {
TabVariant::Tab => TabStyle {
fg: cx.theme().tab_active_foreground,
bg: cx.theme().tab_active_background,
borders: Edges {
left: px(1.),
right: px(1.),
..Default::default()
},
border_color: cx.theme().border,
..Default::default()
},
TabVariant::Outline => TabStyle {
fg: cx.theme().text_accent,
bg: gpui::transparent_black(),
borders: Edges::all(px(1.)),
border_color: cx.theme().element_active,
..Default::default()
},
TabVariant::Pill => TabStyle {
fg: cx.theme().element_foreground,
bg: cx.theme().element_background,
..Default::default()
},
TabVariant::Segmented => TabStyle {
fg: cx.theme().tab_active_foreground,
bg: gpui::transparent_black(),
inner_bg: cx.theme().background,
shadow: true,
..Default::default()
},
TabVariant::Underline => TabStyle {
fg: cx.theme().tab_active_foreground,
bg: gpui::transparent_black(),
borders: Edges {
bottom: px(2.),
..Default::default()
},
border_color: cx.theme().element_active,
..Default::default()
},
}
}
fn disabled(&self, selected: bool, cx: &App) -> TabStyle {
match self {
TabVariant::Tab => TabStyle {
fg: cx.theme().text_muted,
bg: gpui::transparent_black(),
border_color: if selected {
cx.theme().border
} else {
gpui::transparent_black()
},
borders: Edges {
left: px(1.),
right: px(1.),
..Default::default()
},
..Default::default()
},
TabVariant::Outline => TabStyle {
fg: cx.theme().text_muted,
bg: gpui::transparent_black(),
borders: Edges::all(px(1.)),
border_color: if selected {
cx.theme().element_active
} else {
cx.theme().border
},
..Default::default()
},
TabVariant::Pill => TabStyle {
fg: if selected {
cx.theme().element_foreground.opacity(0.5)
} else {
cx.theme().text_muted
},
bg: if selected {
cx.theme().element_background.opacity(0.5)
} else {
gpui::transparent_black()
},
..Default::default()
},
TabVariant::Segmented => TabStyle {
fg: cx.theme().text_muted,
bg: cx.theme().tab_background,
inner_bg: if selected {
cx.theme().background
} else {
gpui::transparent_black()
},
..Default::default()
},
TabVariant::Underline => TabStyle {
fg: cx.theme().text_muted,
bg: gpui::transparent_black(),
border_color: if selected {
cx.theme().border
} else {
gpui::transparent_black()
},
borders: Edges {
bottom: px(2.),
..Default::default()
},
..Default::default()
},
}
}
pub(super) fn tab_bar_radius(&self, size: Size, cx: &App) -> Pixels {
if *self != TabVariant::Segmented {
return px(0.);
}
match size {
Size::XSmall | Size::Small => cx.theme().radius,
Size::Large => cx.theme().radius_lg,
_ => cx.theme().radius_lg,
}
}
fn radius(&self, size: Size, cx: &App) -> Pixels {
match self {
TabVariant::Outline | TabVariant::Pill => px(99.),
TabVariant::Segmented => match size {
Size::XSmall | Size::Small => cx.theme().radius,
Size::Large => cx.theme().radius_lg,
_ => cx.theme().radius_lg,
},
_ => px(0.),
}
}
fn inner_radius(&self, size: Size, cx: &App) -> Pixels {
match self {
TabVariant::Segmented => match size {
Size::Large => self.tab_bar_radius(size, cx) - px(3.),
_ => self.tab_bar_radius(size, cx) - px(2.),
},
_ => px(0.),
}
}
}
#[allow(dead_code)]
struct TabStyle {
borders: Edges<Pixels>,
border_color: Hsla,
bg: Hsla,
fg: Hsla,
shadow: bool,
inner_bg: Hsla,
}
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(),
shadow: false,
inner_bg: gpui::transparent_white(),
}
}
}
#[allow(clippy::type_complexity)]
/// A Tab element for the [`super::TabBar`].
#[derive(IntoElement)]
@@ -404,8 +23,6 @@ pub struct Tab {
pub(super) tab_bar_prefix: Option<bool>,
suffix: Option<AnyElement>,
children: Vec<AnyElement>,
variant: TabVariant,
size: Size,
pub(super) disabled: bool,
pub(super) selected: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
@@ -454,8 +71,6 @@ impl Default for Tab {
selected: false,
prefix: None,
suffix: None,
variant: TabVariant::default(),
size: Size::default(),
on_click: None,
}
}
@@ -479,36 +94,6 @@ impl Tab {
self
}
/// Set Tab Variant.
pub fn with_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 outline variant.
pub fn outline(mut self) -> Self {
self.variant = TabVariant::Outline;
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
pub fn prefix(mut self, prefix: impl IntoElement) -> Self {
self.prefix = Some(prefix.into_any_element());
@@ -580,42 +165,16 @@ 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 {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let mut tab_style = if self.selected {
self.variant.selected(cx)
let fg = if self.disabled {
cx.theme().text_muted
} else if self.selected {
cx.theme().tab_active_foreground
} else {
self.variant.normal(cx)
cx.theme().tab_foreground
};
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);
}
let tab_bar_prefix = self.tab_bar_prefix.unwrap_or_default();
if !tab_bar_prefix && self.ix == 0 && self.variant == TabVariant::Tab {
tab_style.borders.left = px(0.);
hover_style.borders.left = px(0.);
}
let radius = self.variant.radius(self.size, cx);
let inner_radius = self.variant.inner_radius(self.size, 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 = self.variant.height(self.size);
self.base
.id(self.ix)
.flex()
@@ -623,74 +182,39 @@ impl RenderOnce for Tab {
.gap_1()
.items_center()
.flex_shrink_0()
.h(height)
.h(TABBAR_HEIGHT)
.overflow_hidden()
.text_color(tab_style.fg)
.map(|this| match self.size {
Size::XSmall => this.text_xs(),
Size::Large => this.text_base(),
_ => this.text_sm(),
})
.bg(tab_style.bg)
.border_l(tab_style.borders.left)
.border_r(tab_style.borders.right)
.border_t(tab_style.borders.top)
.border_b(tab_style.borders.bottom)
.border_color(tab_style.border_color)
.rounded(radius)
.text_color(fg)
.text_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(radius)
})
this.hover(|this| this.text_color(cx.theme().secondary_foreground))
})
.when_some(self.prefix, |this, prefix| this.child(prefix))
.child(
h_flex()
.flex_1()
.h(inner_height)
.h(px(30.))
.line_height(relative(1.))
.whitespace_nowrap()
.items_center()
.justify_center()
.overflow_hidden()
.margins(inner_margins)
.flex_shrink_0()
.px_3()
.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_4(),
_ => this.size_4(),
}))
}
Some(icon) => this.w(px(38.)).child(icon.size_4()),
None => this
.paddings(inner_paddings)
.map(|this| match self.label {
Some(label) => this.child(label),
None => this,
})
.children(self.children),
})
.bg(tab_style.inner_bg)
.rounded(inner_radius)
.when(tab_style.shadow, |this| this.shadow_xs())
.hover(|this| this.bg(hover_style.inner_bg).rounded(inner_radius)),
}),
)
.when_some(self.suffix, |this, suffix| {
this.child(div().pr_2().child(suffix))
})
.on_mouse_down(MouseButton::Left, |_, _, cx| {
// Stop propagation behavior, for works on TitleBar.
// https://github.com/longbridge/gpui-component/issues/1836
.on_mouse_down(MouseButton::Left, |_ev, _window, cx| {
cx.stop_propagation();
})
.when(!self.disabled, |this| {

View File

@@ -2,19 +2,17 @@ use std::rc::Rc;
use gpui::prelude::FluentBuilder as _;
use gpui::{
Anchor, AnyElement, App, Div, Edges, ElementId, InteractiveElement, IntoElement, ParentElement,
Anchor, AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, ParentElement,
RenderOnce, ScrollHandle, Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled,
Window, div, px,
};
use smallvec::SmallVec;
use theme::ActiveTheme;
use super::{Tab, TabVariant};
use super::Tab;
use crate::button::{Button, ButtonVariants as _};
use crate::menu::{DropdownMenu as _, PopupMenuItem};
use crate::{IconName, Selectable, Sizable, Size, StyledExt, h_flex};
use crate::{IconName, Selectable, Sizable, StyledExt, h_flex};
#[allow(clippy::type_complexity)]
/// A TabBar element that contains multiple [`Tab`] items.
#[derive(IntoElement)]
pub struct TabBar {
@@ -26,9 +24,8 @@ pub struct TabBar {
children: SmallVec<[Tab; 2]>,
last_empty_space: AnyElement,
selected_index: Option<usize>,
variant: TabVariant,
size: Size,
menu: bool,
#[allow(clippy::type_complexity)]
on_click: Option<Rc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>,
}
@@ -42,8 +39,6 @@ impl TabBar {
scroll_handle: None,
prefix: None,
suffix: None,
variant: TabVariant::default(),
size: Size::default(),
last_empty_space: div().w_3().into_any_element(),
selected_index: None,
on_click: None,
@@ -51,36 +46,6 @@ impl TabBar {
}
}
/// Set the Tab variant, all children will inherit the variant.
pub fn with_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 Outline, all children will inherit the variant.
pub fn outline(mut self) -> Self {
self.variant = TabVariant::Outline;
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
}
/// Set whether to show the menu button when tabs overflow, default is false.
pub fn menu(mut self, menu: bool) -> Self {
self.menu = menu;
@@ -105,13 +70,13 @@ impl TabBar {
self
}
/// Add children of the TabBar, all children will inherit the variant.
/// Add children of the TabBar.
pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Tab>>) -> Self {
self.children.extend(children.into_iter().map(Into::into));
self
}
/// Add child of the TabBar, tab will inherit the variant.
/// Add child of the TabBar.
pub fn child(mut self, child: impl Into<Tab>) -> Self {
self.children.push(child.into());
self
@@ -147,60 +112,8 @@ 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 {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let default_gap = match self.size {
Size::Small | Size::XSmall => px(8.),
Size::Large => px(16.),
_ => px(12.),
};
let (bg, paddings, gap) = match self.variant {
TabVariant::Tab => {
let padding = Edges::all(px(0.));
(cx.theme().tab_background, padding, px(0.))
}
TabVariant::Outline => {
let padding = Edges::all(px(0.));
(gpui::transparent_black(), padding, default_gap)
}
TabVariant::Pill => {
let padding = Edges::all(px(0.));
(gpui::transparent_black(), padding, px(4.))
}
TabVariant::Segmented => {
let padding_x = match self.size {
Size::XSmall => px(2.),
Size::Small => px(3.),
_ => px(4.),
};
let padding = Edges {
left: padding_x,
right: padding_x,
..Default::default()
};
(cx.theme().tab_background, padding, px(2.))
}
TabVariant::Underline => {
// This gap is same as the tab inner_paddings
let gap = match self.size {
Size::XSmall => px(10.),
Size::Small => px(12.),
Size::Large => px(20.),
_ => px(16.),
};
(gpui::transparent_black(), Edges::all(px(0.)), gap)
}
};
fn render(self, _: &mut Window, _cx: &mut App) -> impl IntoElement {
let mut item_labels = Vec::new();
let selected_index = self.selected_index;
let on_click = self.on_click.clone();
@@ -210,25 +123,6 @@ impl RenderOnce for TabBar {
.relative()
.flex()
.items_center()
.bg(bg)
.text_color(cx.theme().tab_foreground)
.when(
self.variant == TabVariant::Underline || self.variant == TabVariant::Tab,
|this| {
this.child(
div()
.id("border-b")
.absolute()
.left_0()
.bottom_0()
.size_full()
.border_b_1()
.border_color(cx.theme().border),
)
},
)
.rounded(self.variant.tab_bar_radius(self.size, cx))
.paddings(paddings)
.refine_style(&self.style)
.when_some(self.prefix, |this, prefix| this.child(prefix))
.child(
@@ -239,15 +133,13 @@ impl RenderOnce for TabBar {
.when_some(self.scroll_handle, |this, scroll_handle| {
this.track_scroll(&scroll_handle)
})
.gap(gap)
.gap(px(0.))
.children(self.children.into_iter().enumerate().map(|(ix, child)| {
item_labels.push((child.label.clone(), child.disabled));
let tab_bar_prefix = child.tab_bar_prefix.unwrap_or(true);
child
.ix(ix)
.tab_bar_prefix(tab_bar_prefix)
.with_variant(self.variant)
.with_size(self.size)
.when_some(self.selected_index, |this, selected_ix| {
this.selected(selected_ix == ix)
})

View File

@@ -283,8 +283,6 @@ impl RenderOnce for TitleBar {
this.px_2()
}
})
.border_b_1()
.border_color(cx.theme().border)
.bg(cx.theme().title_bar)
.refine_style(&self.style)
.when(is_linux, |this| {