wip
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m50s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m48s

This commit is contained in:
2026-01-23 15:59:19 +07:00
parent 2fa4436e2d
commit 6617c8eea3
33 changed files with 1568 additions and 1269 deletions

View File

@@ -0,0 +1,27 @@
use gpui::{canvas, App, Bounds, ParentElement, Pixels, Styled as _, Window};
/// A trait to extend [`gpui::Element`] with additional functionality.
pub trait ElementExt: ParentElement + Sized {
/// Add a prepaint callback to the element.
///
/// This is a helper method to get the bounds of the element after paint.
///
/// The first argument is the bounds of the element in pixels.
///
/// See also [`gpui::canvas`].
fn on_prepaint<F>(self, f: F) -> Self
where
F: FnOnce(Bounds<Pixels>, &mut Window, &mut App) + 'static,
{
self.child(
canvas(
move |bounds, window, cx| f(bounds, window, cx),
|_, _, _, _| {},
)
.absolute()
.size_full(),
)
}
}
impl<T: ParentElement> ElementExt for T {}

View File

@@ -37,6 +37,12 @@ pub enum IconName {
Settings,
Sun,
Upload,
PanelLeft,
PanelLeftOpen,
PanelRight,
PanelRightOpen,
PanelBottom,
PanelBottomOpen,
Warning,
WindowClose,
WindowMaximize,
@@ -76,6 +82,12 @@ impl IconName {
Self::Settings => "icons/settings.svg",
Self::Sun => "icons/sun.svg",
Self::Upload => "icons/upload.svg",
Self::PanelLeft => "icons/panel-left.svg",
Self::PanelLeftOpen => "icons/panel-left-open.svg",
Self::PanelRight => "icons/panel-right.svg",
Self::PanelRightOpen => "icons/panel-right-open.svg",
Self::PanelBottom => "icons/panel-bottom.svg",
Self::PanelBottomOpen => "icons/panel-bottom-open.svg",
Self::Warning => "icons/warning.svg",
Self::WindowClose => "icons/window-close.svg",
Self::WindowMaximize => "icons/window-maximize.svg",

View File

@@ -1,3 +1,4 @@
pub use element_ext::ElementExt;
pub use event::InteractiveElementExt;
pub use focusable::FocusableCycle;
pub use icon::*;
@@ -24,13 +25,12 @@ pub mod menu;
pub mod modal;
pub mod notification;
pub mod popover;
pub mod resizable;
pub mod scroll;
pub mod skeleton;
pub mod switch;
pub mod tab;
pub mod tooltip;
mod element_ext;
mod event;
mod focusable;
mod icon;

View File

@@ -1,24 +0,0 @@
use gpui::{Axis, Context, Window};
mod panel;
mod resize_handle;
pub use panel::*;
pub use resize_handle::*;
pub fn h_resizable(
window: &mut Window,
cx: &mut Context<ResizablePanelGroup>,
) -> ResizablePanelGroup {
ResizablePanelGroup::new(window, cx).axis(Axis::Horizontal)
}
pub fn v_resizable(
window: &mut Window,
cx: &mut Context<ResizablePanelGroup>,
) -> ResizablePanelGroup {
ResizablePanelGroup::new(window, cx).axis(Axis::Vertical)
}
pub fn resizable_panel() -> ResizablePanel {
ResizablePanel::new()
}

View File

@@ -1,561 +0,0 @@
use std::rc::Rc;
use gpui::prelude::FluentBuilder;
use gpui::{
canvas, div, px, relative, Along, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context,
Element, Entity, EntityId, EventEmitter, IntoElement, IsZero, MouseMoveEvent, MouseUpEvent,
ParentElement, Pixels, Render, StatefulInteractiveElement as _, Style, Styled, WeakEntity,
Window,
};
use super::resize_handle;
use crate::{h_flex, v_flex, AxisExt};
pub const PANEL_MIN_SIZE: Pixels = px(100.);
pub enum ResizablePanelEvent {
Resized,
}
#[derive(Clone, Render)]
pub struct DragPanel(pub (EntityId, usize, Axis));
#[derive(Clone)]
pub struct ResizablePanelGroup {
panels: Vec<Entity<ResizablePanel>>,
sizes: Vec<Pixels>,
axis: Axis,
size: Option<Pixels>,
bounds: Bounds<Pixels>,
resizing_panel_ix: Option<usize>,
}
impl ResizablePanelGroup {
pub(super) fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
Self {
axis: Axis::Horizontal,
sizes: Vec::new(),
panels: Vec::new(),
size: None,
bounds: Bounds::default(),
resizing_panel_ix: None,
}
}
pub fn load(&mut self, sizes: Vec<Pixels>, panels: Vec<Entity<ResizablePanel>>) {
self.sizes = sizes;
self.panels = panels;
}
/// Set the axis of the resizable panel group, default is horizontal.
pub fn axis(mut self, axis: Axis) -> Self {
self.axis = axis;
self
}
pub fn set_axis(&mut self, axis: Axis, _window: &mut Window, cx: &mut Context<Self>) {
self.axis = axis;
cx.notify();
}
/// Add a resizable panel to the group.
pub fn child(
mut self,
panel: ResizablePanel,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
self.add_child(panel, window, cx);
self
}
/// Add a ResizablePanelGroup as a child to the group.
pub fn group(
self,
group: ResizablePanelGroup,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let group: ResizablePanelGroup = group;
let size = group.size;
let panel = ResizablePanel::new()
.content_view(cx.new(|_| group).into())
.when_some(size, |this, size| this.size(size));
self.child(panel, window, cx)
}
/// Set size of the resizable panel group
///
/// - When the axis is horizontal, the size is the height of the group.
/// - When the axis is vertical, the size is the width of the group.
pub fn size(mut self, size: Pixels) -> Self {
self.size = Some(size);
self
}
/// Calculates the sum of all panel sizes within the group.
pub fn total_size(&self) -> Pixels {
self.sizes.iter().fold(px(0.0), |acc, &size| acc + size)
}
pub fn add_child(
&mut self,
panel: ResizablePanel,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let mut panel = panel;
panel.axis = self.axis;
panel.group = Some(cx.entity().downgrade());
self.sizes.push(panel.initial_size.unwrap_or_default());
self.panels.push(cx.new(|_| panel));
}
pub fn insert_child(
&mut self,
panel: ResizablePanel,
ix: usize,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let mut panel = panel;
panel.axis = self.axis;
panel.group = Some(cx.entity().downgrade());
self.sizes
.insert(ix, panel.initial_size.unwrap_or_default());
self.panels.insert(ix, cx.new(|_| panel));
cx.notify()
}
/// Replace a child panel with a new panel at the given index.
pub fn replace_child(
&mut self,
panel: ResizablePanel,
ix: usize,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let mut panel = panel;
let old_panel = self.panels[ix].clone();
let old_panel_initial_size = old_panel.read(cx).initial_size;
let old_panel_size_ratio = old_panel.read(cx).size_ratio;
panel.initial_size = old_panel_initial_size;
panel.size_ratio = old_panel_size_ratio;
panel.axis = self.axis;
panel.group = Some(cx.entity().downgrade());
self.sizes[ix] = panel.initial_size.unwrap_or_default();
self.panels[ix] = cx.new(|_| panel);
cx.notify()
}
pub fn remove_child(&mut self, ix: usize, _window: &mut Window, cx: &mut Context<Self>) {
self.sizes.remove(ix);
self.panels.remove(ix);
cx.notify()
}
pub fn remove_all_children(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.sizes.clear();
self.panels.clear();
cx.notify()
}
fn render_resize_handle(
&self,
ix: usize,
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let view = cx.entity().clone();
resize_handle(("resizable-handle", ix), self.axis).on_drag(
DragPanel((cx.entity_id(), ix, self.axis)),
move |drag_panel, _, _window, cx| {
cx.stop_propagation();
// Set current resizing panel ix
view.update(cx, |view, _| {
view.resizing_panel_ix = Some(ix);
});
cx.new(|_| drag_panel.clone())
},
)
}
fn done_resizing(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
cx.emit(ResizablePanelEvent::Resized);
self.resizing_panel_ix = None;
}
fn sync_real_panel_sizes(&mut self, _window: &Window, cx: &App) {
for (i, panel) in self.panels.iter().enumerate() {
self.sizes[i] = panel.read(cx).bounds.size.along(self.axis)
}
}
/// The `ix`` is the index of the panel to resize,
/// and the `size` is the new size for the panel.
fn resize_panels(
&mut self,
ix: usize,
size: Pixels,
window: &mut Window,
cx: &mut Context<Self>,
) {
let mut ix = ix;
// Only resize the left panels.
if ix >= self.panels.len() - 1 {
return;
}
let size = size.floor();
let container_size = self.bounds.size.along(self.axis);
self.sync_real_panel_sizes(window, cx);
let mut changed = size - self.sizes[ix];
let is_expand = changed > px(0.);
let main_ix = ix;
let mut new_sizes = self.sizes.clone();
if is_expand {
new_sizes[ix] = size;
// Now to expand logic is correct.
while changed > px(0.) && ix < self.panels.len() - 1 {
ix += 1;
let available_size = (new_sizes[ix] - PANEL_MIN_SIZE).max(px(0.));
let to_reduce = changed.min(available_size);
new_sizes[ix] -= to_reduce;
changed -= to_reduce;
}
} else {
let new_size = size.max(PANEL_MIN_SIZE);
new_sizes[ix] = new_size;
changed = size - PANEL_MIN_SIZE;
new_sizes[ix + 1] += self.sizes[ix] - new_size;
while changed < px(0.) && ix > 0 {
ix -= 1;
let available_size = self.sizes[ix] - PANEL_MIN_SIZE;
let to_increase = (changed).min(available_size);
new_sizes[ix] += to_increase;
changed += to_increase;
}
}
// If total size exceeds container size, adjust the main panel
let total_size: Pixels = new_sizes.iter().map(|s| s.signum()).sum::<f32>().into();
if total_size > container_size {
let overflow = total_size - container_size;
new_sizes[main_ix] = (new_sizes[main_ix] - overflow).max(PANEL_MIN_SIZE);
}
let total_size = new_sizes.iter().fold(px(0.0), |acc, &size| acc + size);
self.sizes = new_sizes;
for (i, panel) in self.panels.iter().enumerate() {
let size = self.sizes[i];
if size > px(0.) {
panel.update(cx, |this, _| {
this.size = Some(size);
this.size_ratio = Some(size / total_size);
});
}
}
}
}
impl EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}
impl Render for ResizablePanelGroup {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity().clone();
let container = if self.axis.is_horizontal() {
h_flex()
} else {
v_flex()
};
container
.size_full()
.children(self.panels.iter().enumerate().map(|(ix, panel)| {
if ix > 0 {
let handle = self.render_resize_handle(ix - 1, window, cx);
panel.update(cx, |view, _| {
view.resize_handle = Some(handle.into_any_element())
});
}
panel.clone()
}))
.child({
canvas(
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|_, _, _, _| {},
)
.absolute()
.size_full()
})
.child(ResizePanelGroupElement {
view: cx.entity().clone(),
axis: self.axis,
})
}
}
type ContentBuilder = Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>>;
type ContentVisible = Rc<Box<dyn Fn(&App) -> bool>>;
pub struct ResizablePanel {
group: Option<WeakEntity<ResizablePanelGroup>>,
/// Initial size is the size that the panel has when it is created.
initial_size: Option<Pixels>,
/// size is the size that the panel has when it is resized or adjusted by flex layout.
size: Option<Pixels>,
/// the size ratio that the panel has relative to its group
size_ratio: Option<f32>,
axis: Axis,
content_builder: ContentBuilder,
content_view: Option<AnyView>,
content_visible: ContentVisible,
/// The bounds of the resizable panel, when render the bounds will be updated.
bounds: Bounds<Pixels>,
resize_handle: Option<AnyElement>,
}
impl ResizablePanel {
pub(super) fn new() -> Self {
Self {
group: None,
initial_size: None,
size: None,
size_ratio: None,
axis: Axis::Horizontal,
content_builder: None,
content_view: None,
content_visible: Rc::new(Box::new(|_| true)),
bounds: Bounds::default(),
resize_handle: None,
}
}
pub fn content<F>(mut self, content: F) -> Self
where
F: Fn(&mut Window, &mut App) -> AnyElement + 'static,
{
self.content_builder = Some(Rc::new(content));
self
}
pub fn content_visible<F>(mut self, content_visible: F) -> Self
where
F: Fn(&App) -> bool + 'static,
{
self.content_visible = Rc::new(Box::new(content_visible));
self
}
pub fn content_view(mut self, content: AnyView) -> Self {
self.content_view = Some(content);
self
}
/// Set the initial size of the panel.
pub fn size(mut self, size: Pixels) -> Self {
self.initial_size = Some(size);
self
}
/// Save the real panel size, and update group sizes
fn update_size(
&mut self,
bounds: Bounds<Pixels>,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let new_size = bounds.size.along(self.axis);
self.bounds = bounds;
self.size_ratio = None;
self.size = Some(new_size);
let entity_id = cx.entity_id();
if let Some(group) = self.group.as_ref() {
_ = group.update(cx, |view, _| {
if let Some(ix) = view.panels.iter().position(|v| v.entity_id() == entity_id) {
view.sizes[ix] = new_size;
}
});
}
cx.notify();
}
}
impl FluentBuilder for ResizablePanel {}
impl Render for ResizablePanel {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if !(self.content_visible)(cx) {
// To keep size as initial size, to make sure the size will not be changed.
self.initial_size = self.size;
self.size = None;
return div();
}
let total_size = self
.group
.as_ref()
.and_then(|group| group.upgrade())
.map(|group| group.read(cx).total_size());
let view = cx.entity();
div()
.flex()
.flex_grow()
.size_full()
.relative()
.when(self.initial_size.is_none(), |this| this.flex_shrink())
.when(self.axis.is_vertical(), |this| this.min_h(PANEL_MIN_SIZE))
.when(self.axis.is_horizontal(), |this| this.min_w(PANEL_MIN_SIZE))
.when_some(self.initial_size, |this, size| {
if size.is_zero() {
this
} else {
// The `self.size` is None, that mean the initial size for the panel, so we need set flex_shrink_0
// To let it keep the initial size.
this.when(self.size.is_none() && size > px(0.), |this| {
this.flex_shrink_0()
})
.flex_basis(size)
}
})
.map(|this| match (self.size_ratio, self.size, total_size) {
(Some(size_ratio), _, _) => this.flex_basis(relative(size_ratio)),
(None, Some(size), Some(total_size)) => {
this.flex_basis(relative(size / total_size))
}
(None, Some(size), None) => this.flex_basis(size),
_ => this,
})
.child({
canvas(
move |bounds, window, cx| {
view.update(cx, |r, cx| r.update_size(bounds, window, cx))
},
|_, _, _, _| {},
)
.absolute()
.size_full()
})
.when_some(self.content_builder.clone(), |this, c| {
this.child(c(window, cx))
})
.when_some(self.content_view.clone(), |this, c| this.child(c))
.when_some(self.resize_handle.take(), |this, c| this.child(c))
}
}
struct ResizePanelGroupElement {
axis: Axis,
view: Entity<ResizablePanelGroup>,
}
impl IntoElement for ResizePanelGroupElement {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for ResizePanelGroupElement {
type PrepaintState = ();
type RequestLayoutState = ();
fn id(&self) -> Option<gpui::ElementId> {
None
}
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
(window.request_layout(Style::default(), None, cx), ())
}
fn prepaint(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: Option<&gpui::InspectorElementId>,
_: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_window: &mut Window,
_cx: &mut App,
) -> Self::PrepaintState {
}
fn paint(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: Option<&gpui::InspectorElementId>,
_: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
window.on_mouse_event({
let view = self.view.clone();
let axis = self.axis;
let current_ix = view.read(cx).resizing_panel_ix;
move |e: &MouseMoveEvent, phase, window, cx| {
if !phase.bubble() {
return;
}
let Some(ix) = current_ix else { return };
view.update(cx, |view, cx| {
let panel = view
.panels
.get(ix)
.expect("BUG: invalid panel index")
.read(cx);
match axis {
Axis::Horizontal => {
view.resize_panels(ix, e.position.x - panel.bounds.left(), window, cx)
}
Axis::Vertical => {
view.resize_panels(ix, e.position.y - panel.bounds.top(), window, cx);
}
}
})
}
});
// When any mouse up, stop dragging
window.on_mouse_event({
let view = self.view.clone();
let current_ix = view.read(cx).resizing_panel_ix;
move |_: &MouseUpEvent, phase, window, cx| {
if current_ix.is_none() {
return;
}
if phase.bubble() {
view.update(cx, |view, cx| view.done_resizing(window, cx));
}
}
})
}
}

View File

@@ -1,74 +0,0 @@
use gpui::prelude::FluentBuilder as _;
use gpui::{
div, px, App, Axis, Div, ElementId, InteractiveElement, IntoElement, ParentElement as _,
Pixels, RenderOnce, Stateful, StatefulInteractiveElement, Styled as _, Window,
};
use theme::ActiveTheme;
use crate::AxisExt as _;
pub const HANDLE_PADDING: Pixels = px(8.);
pub const HANDLE_SIZE: Pixels = px(2.);
#[derive(IntoElement)]
pub struct ResizeHandle {
base: Stateful<Div>,
axis: Axis,
}
impl ResizeHandle {
fn new(id: impl Into<ElementId>, axis: Axis) -> Self {
Self {
base: div().id(id.into()),
axis,
}
}
}
/// Create a resize handle for a resizable panel.
pub fn resize_handle(id: impl Into<ElementId>, axis: Axis) -> ResizeHandle {
ResizeHandle::new(id, axis)
}
impl InteractiveElement for ResizeHandle {
fn interactivity(&mut self) -> &mut gpui::Interactivity {
self.base.interactivity()
}
}
impl StatefulInteractiveElement for ResizeHandle {}
impl RenderOnce for ResizeHandle {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
self.base
.occlude()
.absolute()
.flex_shrink_0()
.when(self.axis.is_horizontal(), |this| {
this.cursor_col_resize()
.top_0()
.left(px(-1.))
.w(HANDLE_SIZE)
.h_full()
.pt_12()
.pb_4()
})
.when(self.axis.is_vertical(), |this| {
this.cursor_row_resize()
.top(px(-1.))
.left_0()
.w_full()
.h(HANDLE_SIZE)
.px_6()
})
.child(
div()
.rounded_full()
.hover(|this| this.bg(cx.theme().border_variant))
.when(self.axis.is_horizontal(), |this| {
this.h_full().w(HANDLE_SIZE)
})
.when(self.axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)),
)
}
}

View File

@@ -213,6 +213,7 @@ impl Root {
cx.notify();
}
/// Clear a notification by its ID.
pub fn clear_notification<T>(&mut self, id: T, window: &mut Window, cx: &mut Context<Self>)
where
T: Into<SharedString>,

View File

@@ -1,126 +0,0 @@
use gpui::prelude::FluentBuilder;
use gpui::{
div, px, AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, ParentElement,
RenderOnce, Stateful, StatefulInteractiveElement, Styled, Window,
};
use theme::ActiveTheme;
use crate::Selectable;
pub mod tab_bar;
#[derive(IntoElement)]
pub struct Tab {
base: Stateful<Div>,
label: AnyElement,
prefix: Option<AnyElement>,
suffix: Option<AnyElement>,
disabled: bool,
selected: bool,
}
impl Tab {
pub fn new(id: impl Into<ElementId>, label: impl IntoElement) -> Self {
let id: ElementId = id.into();
Self {
base: div().id(id),
label: label.into_any_element(),
disabled: false,
selected: false,
prefix: None,
suffix: None,
}
}
/// Set the left side of the tab
pub fn prefix(mut self, prefix: impl Into<AnyElement>) -> Self {
self.prefix = Some(prefix.into());
self
}
/// Set the right side of the tab
pub fn suffix(mut self, suffix: impl Into<AnyElement>) -> Self {
self.suffix = Some(suffix.into());
self
}
/// Set disabled state to the tab
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl Selectable for Tab {
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
fn is_selected(&self) -> bool {
self.selected
}
}
impl InteractiveElement for Tab {
fn interactivity(&mut self) -> &mut gpui::Interactivity {
self.base.interactivity()
}
}
impl StatefulInteractiveElement for Tab {}
impl Styled for Tab {
fn style(&mut self) -> &mut gpui::StyleRefinement {
self.base.style()
}
}
impl RenderOnce for Tab {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let (text_color, bg_color, hover_bg_color) = match (self.selected, self.disabled) {
(true, false) => (
cx.theme().text,
cx.theme().tab_active_background,
cx.theme().tab_hover_background,
),
(false, false) => (
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
),
(true, true) => (
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
),
(false, true) => (
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
),
};
self.base
.h(px(30.))
.px_2()
.relative()
.flex()
.items_center()
.flex_shrink_0()
.cursor_pointer()
.overflow_hidden()
.text_xs()
.text_ellipsis()
.text_color(text_color)
.bg(bg_color)
.rounded(cx.theme().radius_lg)
.hover(|this| this.bg(hover_bg_color))
.when_some(self.prefix, |this, prefix| {
this.child(prefix).text_color(text_color)
})
.child(self.label)
.when_some(self.suffix, |this, suffix| this.child(suffix))
}
}

View File

@@ -1,85 +0,0 @@
use gpui::prelude::FluentBuilder as _;
use gpui::{
div, px, AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, ParentElement,
RenderOnce, ScrollHandle, StatefulInteractiveElement as _, Styled, Window,
};
use smallvec::SmallVec;
use crate::h_flex;
#[derive(IntoElement)]
pub struct TabBar {
base: Div,
id: ElementId,
scroll_handle: ScrollHandle,
prefix: Option<AnyElement>,
suffix: Option<AnyElement>,
children: SmallVec<[AnyElement; 2]>,
}
impl TabBar {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
base: div().px(px(-1.)),
id: id.into(),
children: SmallVec::new(),
scroll_handle: ScrollHandle::new(),
prefix: None,
suffix: None,
}
}
/// Track the scroll of the TabBar
pub fn track_scroll(mut self, scroll_handle: ScrollHandle) -> Self {
self.scroll_handle = scroll_handle;
self
}
/// Set the prefix element of the TabBar
pub fn prefix(mut self, prefix: impl IntoElement) -> Self {
self.prefix = Some(prefix.into_any_element());
self
}
/// Set the suffix element of the TabBar
pub fn suffix(mut self, suffix: impl IntoElement) -> Self {
self.suffix = Some(suffix.into_any_element());
self
}
}
impl ParentElement for TabBar {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements)
}
}
impl Styled for TabBar {
fn style(&mut self) -> &mut gpui::StyleRefinement {
self.base.style()
}
}
impl RenderOnce for TabBar {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
self.base
.id(self.id)
.group("tab-bar")
.relative()
.px_1()
.flex()
.flex_none()
.items_center()
.when_some(self.prefix, |this, prefix| this.child(prefix))
.child(
h_flex()
.id("tabs")
.flex_grow()
.gap_1()
.overflow_x_scroll()
.track_scroll(&self.scroll_handle)
.children(self.children),
)
.when_some(self.suffix, |this, suffix| this.child(suffix))
}
}