Extract Tab component (#3539)

This PR extracts a new `Tab` component from the tabs implementation in
the workspace.

This will allow us to reuse this component anywhere that we need to use
tabs.

Like our other newer components, the `Tab` component has a relatively
open API.

It accepts `children` (or `child`) as well as a `start_slot` and
`end_slot` to position content in the slots on either end of the
content. These slots also respect the `TabCloseSide` and will switch
positions based on this value.

<img width="763" alt="Screenshot 2023-12-07 at 12 19 42 PM"
src="https://github.com/zed-industries/zed/assets/1486634/78e4db6a-f807-431e-8777-f52f2631e6ed">

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2023-12-07 12:30:43 -05:00 committed by GitHub
parent c91207b450
commit 393be3cedf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 358 additions and 93 deletions

View file

@ -28,10 +28,10 @@ use std::{
use ui::{
h_stack, prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize,
Indicator, Label, Tooltip,
Indicator, Label, Tab, TabPosition, Tooltip,
};
use ui::{v_stack, ContextMenu};
use util::truncate_and_remove_front;
use util::{maybe, truncate_and_remove_front};
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
@ -1438,42 +1438,49 @@ impl Pane {
let is_active = ix == self.active_item_index;
let indicator = {
let indicator = maybe!({
let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
(true, _) => Some(Color::Warning),
(_, true) => Some(Color::Accent),
(false, false) => None,
(true, _) => Color::Warning,
(_, true) => Color::Accent,
(false, false) => return None,
};
h_stack()
.w_3()
.h_3()
.justify_center()
.absolute()
.map(|this| match close_side {
ClosePosition::Left => this.right_1(),
ClosePosition::Right => this.left_1(),
})
.when_some(indicator_color, |this, indicator_color| {
this.child(Indicator::dot().color(indicator_color))
})
};
Some(Indicator::dot().color(indicator_color))
});
let close_button = {
let id = item.item_id();
let id = item.item_id();
h_stack()
.invisible()
.w_3()
.h_3()
.justify_center()
.absolute()
.map(|this| match close_side {
ClosePosition::Left => this.left_1(),
ClosePosition::Right => this.right_1(),
let is_first_item = ix == 0;
let is_last_item = ix == self.items.len() - 1;
let position_relative_to_active_item = ix.cmp(&self.active_item_index);
let tab =
Tab::new(ix)
.position(if is_first_item {
TabPosition::First
} else if is_last_item {
TabPosition::Last
} else {
TabPosition::Middle(position_relative_to_active_item)
})
.group_hover("", |style| style.visible())
.child(
.close_side(match close_side {
ClosePosition::Left => ui::TabCloseSide::Start,
ClosePosition::Right => ui::TabCloseSide::End,
})
.selected(ix == self.active_item_index())
.on_click(cx.listener(move |pane: &mut Self, event, cx| {
pane.activate_item(ix, true, true, cx)
}))
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
// .on_drop(|_view, state: View<DraggedTab>, cx| {
// eprintln!("{:?}", state.read(cx));
// })
.when_some(item.tab_tooltip_text(cx), |tab, text| {
tab.tooltip(move |cx| Tooltip::text(text.clone(), cx))
})
.start_slot::<Indicator>(indicator)
.end_slot(
// TODO: Fix button size
IconButton::new("close tab", Icon::Close)
.icon_color(Color::Muted)
@ -1484,67 +1491,7 @@ impl Pane {
.detach_and_log_err(cx);
})),
)
};
let tab = div()
.border_color(cx.theme().colors().border)
.bg(tab_bg)
// 30px @ 16px/rem
.h(rems(1.875))
.map(|this| {
let is_first_item = ix == 0;
let is_last_item = ix == self.items.len() - 1;
match ix.cmp(&self.active_item_index) {
cmp::Ordering::Less => {
if is_first_item {
this.pl_px().pr_px().border_b()
} else {
this.border_l().pr_px().border_b()
}
}
cmp::Ordering::Greater => {
if is_last_item {
this.pr_px().pl_px().border_b()
} else {
this.border_r().pl_px().border_b()
}
}
cmp::Ordering::Equal => {
if is_first_item {
this.pl_px().border_r().pb_px()
} else {
this.border_l().border_r().pb_px()
}
}
}
})
.child(
h_stack()
.group("")
.id(ix)
.relative()
.h_full()
.cursor_pointer()
.when_some(item.tab_tooltip_text(cx), |div, text| {
div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
})
.on_click(
cx.listener(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx)),
)
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
// .on_drop(|_view, state: View<DraggedTab>, cx| {
// eprintln!("{:?}", state.read(cx));
// })
.px_5()
// .hover(|h| h.bg(tab_hover_bg))
// .active(|a| a.bg(tab_active_bg))
.gap_1()
.text_color(text_color)
.child(indicator)
.child(close_button)
.child(label),
);
.child(label);
right_click_menu(ix).trigger(tab).menu(|cx| {
ContextMenu::build(cx, |menu, cx| {