From f78d50b5e932c6a7d486eae149c2add97cc6dce8 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 4 Jul 2024 01:05:32 +0800 Subject: [PATCH] Update dock layout --- crates/app/src/story_workspace.rs | 54 ++++----- crates/story/src/checkbox_story.rs | 1 + crates/story/src/input_story.rs | 1 + crates/story/src/lib.rs | 185 +++++++++++++++++++---------- crates/story/src/list_story.rs | 96 ++++++++------- crates/story/src/popover_story.rs | 1 + crates/story/src/tooltip_story.rs | 2 +- crates/ui/src/styled_ext.rs | 22 +++- crates/ui/src/tab/tab.rs | 12 +- crates/ui/src/tab/tab_bar.rs | 14 ++- crates/ui/src/theme.rs | 6 +- crates/workspace/src/dock.rs | 14 ++- crates/workspace/src/pane.rs | 30 +++-- 13 files changed, 279 insertions(+), 159 deletions(-) diff --git a/crates/app/src/story_workspace.rs b/crates/app/src/story_workspace.rs index 51e9655d..338264de 100644 --- a/crates/app/src/story_workspace.rs +++ b/crates/app/src/story_workspace.rs @@ -4,7 +4,7 @@ use story::{ ButtonStory, CheckboxStory, DropdownStory, ImageStory, InputStory, ListStory, PickerStory, PopoverStory, StoryContainer, SwitchStory, TooltipStory, }; -use workspace::{TitleBar, Workspace}; +use workspace::{dock::DockPosition, TitleBar, Workspace}; use std::sync::Arc; use ui::{ @@ -40,7 +40,7 @@ impl StoryWorkspace { }) .detach(); - StoryContainer::open( + StoryContainer::add_pane( "Buttons", "Displays a button or a component that looks like a button.", ButtonStory::view(cx).into(), @@ -49,25 +49,23 @@ impl StoryWorkspace { ) .detach(); - StoryContainer::open( - "Inputs", - "Displays a text input field.", + StoryContainer::add_panel( InputStory::view(cx).into(), workspace.clone(), + DockPosition::Right, + px(500.0), cx, - ) - .detach(); + ); - StoryContainer::open( - "Checkbox", - "A control that allows the user to toggle between checked and not checked.", + StoryContainer::add_panel( CheckboxStory::view(cx).into(), workspace.clone(), + DockPosition::Bottom, + px(300.), cx, - ) - .detach(); + ); - StoryContainer::open( + StoryContainer::add_pane( "Switch", "A control that allows the user to toggle between two states.", SwitchStory::view(cx).into(), @@ -76,7 +74,7 @@ impl StoryWorkspace { ) .detach(); - StoryContainer::open( + StoryContainer::add_pane( "Dropdowns", "Displays a list of options for the user to pick from—triggered by a button.", DropdownStory::new(cx).into(), @@ -85,7 +83,7 @@ impl StoryWorkspace { ) .detach(); - StoryContainer::open( + StoryContainer::add_pane( "Picker", "Picker is a component that allows the user to select an item from a list of options.", PickerStory::view(cx).into(), @@ -94,7 +92,7 @@ impl StoryWorkspace { ) .detach(); - StoryContainer::open( + StoryContainer::add_pane( "Popover", "Displays rich content in a portal, triggered by a button.", PopoverStory::view(cx).into(), @@ -103,24 +101,24 @@ impl StoryWorkspace { ) .detach(); - StoryContainer::open( + StoryContainer::add_pane( "Tooltip", - "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.", + "Displays a short message when users hover over an element.", TooltipStory::view(cx).into(), workspace.clone(), cx, - ).detach(); - - StoryContainer::open( - "List", - "A complex list example, includes use Picker, Scrollbar, and more.", - ListStory::view(cx).into(), - workspace.clone(), - cx, ) .detach(); - StoryContainer::open( + StoryContainer::add_panel( + ListStory::view(cx).into(), + workspace.clone(), + DockPosition::Left, + px(360.), + cx, + ); + + StoryContainer::add_pane( "Image", "Render SVG image and Chart", ImageStory::view(cx).into(), @@ -136,7 +134,7 @@ impl StoryWorkspace { app_state: Arc, cx: &mut AppContext, ) -> Task>> { - let window_bounds = Bounds::centered(None, size(px(1200.0), px(900.0)), cx); + let window_bounds = Bounds::centered(None, size(px(1600.0), px(1200.0)), cx); cx.spawn(|mut cx| async move { let options = WindowOptions { diff --git a/crates/story/src/checkbox_story.rs b/crates/story/src/checkbox_story.rs index 96321f8a..9ed98052 100644 --- a/crates/story/src/checkbox_story.rs +++ b/crates/story/src/checkbox_story.rs @@ -33,6 +33,7 @@ impl CheckboxStory { impl Render for CheckboxStory { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_flex() + .p_4() .gap_6() .child( v_flex().items_start().justify_start().gap_6().child( diff --git a/crates/story/src/input_story.rs b/crates/story/src/input_story.rs index bac1bf17..7e30fc8b 100644 --- a/crates/story/src/input_story.rs +++ b/crates/story/src/input_story.rs @@ -83,6 +83,7 @@ impl Render for InputStory { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_flex() .size_full() + .p_4() .justify_start() .gap_3() .child( diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 61420b7e..4a05cea1 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -21,9 +21,10 @@ pub use switch_story::SwitchStory; pub use tooltip_story::TooltipStory; use gpui::{ - div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, EventEmitter, - FocusableView, IntoElement, ParentElement, Render, SharedString, Styled as _, Task, View, - ViewContext, VisualContext, WindowContext, + div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, Element, + EventEmitter, FocusableView, InteractiveElement, IntoElement, ParentElement, Pixels, Render, + ScrollHandle, SharedString, StatefulInteractiveElement, Styled as _, Task, View, ViewContext, + VisualContext, WindowContext, }; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -32,7 +33,7 @@ use workspace::{ }; use anyhow::Result; -use ui::{divider::Divider, h_flex, label::Label, v_flex}; +use ui::{divider::Divider, h_flex, label::Label, v_flex, StyledExt}; pub fn story_case( name: &'static str, @@ -61,6 +62,7 @@ pub fn section(title: impl Into, cx: &WindowContext) -> Div { pub struct StoryContainer { focus_handle: gpui::FocusHandle, + scroll_handle: gpui::ScrollHandle, name: SharedString, description: SharedString, position: DockPosition, @@ -76,48 +78,6 @@ impl FocusableView for StoryContainer { } } -impl EventEmitter for StoryContainer {} - -impl Panel for StoryContainer { - fn persistent_name() -> &'static str { - "story-container" - } - - fn position(&self, _: &WindowContext) -> workspace::dock::DockPosition { - self.position - } - - fn can_position(&self, _: workspace::dock::DockPosition, _: &WindowContext) -> bool { - true - } - - fn set_position(&mut self, position: workspace::dock::DockPosition, _: &mut WindowContext) { - self.position = position; - } - - fn size(&self, _: &WindowContext) -> gpui::Pixels { - match self.position { - DockPosition::Left | DockPosition::Right => self.width.unwrap_or(px(360.)), - DockPosition::Bottom => self.height.unwrap_or(px(360.)), - } - } - - fn set_size(&mut self, size: Option, _: &mut WindowContext) { - match self.position { - DockPosition::Left | DockPosition::Right => self.width = size, - DockPosition::Bottom => self.height = size, - } - } - - fn set_active(&mut self, active: bool, _: &mut WindowContext) { - self.active = active; - } - - fn starts_open(&self, _cx: &WindowContext) -> bool { - true - } -} - #[derive(Debug)] pub enum ContainerEvent { Close, @@ -172,6 +132,7 @@ impl StoryContainer { Self { focus_handle, + scroll_handle: ScrollHandle::new(), name: name.into(), description: description.into(), width: None, @@ -182,7 +143,7 @@ impl StoryContainer { } } - pub fn open( + pub fn add_pane( name: impl Into, description: impl Into, story: AnyView, @@ -203,6 +164,19 @@ impl StoryContainer { }) } + pub fn add_panel( + story: AnyView, + workspace: View, + position: DockPosition, + size: gpui::Pixels, + cx: &mut WindowContext, + ) { + workspace.update(cx, |workspace, cx| { + let panel = cx.new_view(|cx| MyPanel::new(story, position, size, cx)); + workspace.add_panel(panel, cx) + }); + } + pub fn width(mut self, width: gpui::Pixels) -> Self { self.width = Some(width); self @@ -226,21 +200,112 @@ impl StoryContainer { impl Render for StoryContainer { fn render(&mut self, _: &mut ViewContext) -> impl IntoElement { - v_flex() + div() + .id("story-container") + .overflow_y_scroll() + .track_scroll(&self.scroll_handle) .size_full() - .gap_6() - .p_2() .child( - div() - .flex() - .flex_col() - .gap_4() - .child(Label::new(self.name.clone()).text_size(px(24.0))) - .child(Label::new(self.description.clone()).text_size(px(16.0))), + v_flex() + .size_full() + .gap_6() + .p_4() + .child( + div() + .flex() + .flex_col() + .gap_4() + .child(Label::new(self.name.clone()).text_size(px(24.0))) + .child(Label::new(self.description.clone()).text_size(px(16.0))), + ) + .child(Divider::horizontal()) + .when_some(self.story.clone(), |this, story| { + this.child(v_flex().size_full().child(story)) + }), ) - .child(Divider::horizontal()) - .when_some(self.story.clone(), |this, story| { - this.child(v_flex().size_full().child(story)) - }) + } +} + +struct MyPanel { + focus_handle: gpui::FocusHandle, + view: AnyView, + _position: DockPosition, + width: Option, + height: Option, +} + +impl MyPanel { + fn new(view: AnyView, position: DockPosition, size: Pixels, cx: &mut WindowContext) -> Self { + let mut this = Self { + focus_handle: cx.focus_handle(), + view, + _position: position, + width: None, + height: None, + }; + this.update_size(size); + this + } + + fn update_size(&mut self, size: Pixels) { + match self._position { + DockPosition::Bottom => self.height = Some(size), + DockPosition::Left | DockPosition::Right => self.width = Some(size), + } + } +} + +impl Panel for MyPanel { + fn persistent_name() -> &'static str { + "my-panel" + } + + fn can_position(&self, position: DockPosition) -> bool { + match self._position { + DockPosition::Bottom => matches!(position, DockPosition::Bottom), + DockPosition::Left | DockPosition::Right => { + matches!(position, DockPosition::Left | DockPosition::Right) + } + } + } + + fn position(&self, _cx: &WindowContext) -> DockPosition { + self._position + } + + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + self._position = position; + cx.notify() + } + + fn size(&self, _cx: &WindowContext) -> gpui::Pixels { + match self._position { + DockPosition::Bottom => self.height.unwrap_or(px(100.)), + DockPosition::Left | DockPosition::Right => self.width.unwrap_or(px(100.)), + } + } + + fn set_size(&mut self, size: Option, cx: &mut ViewContext) { + dbg!("================ set_size", size); + if let Some(size) = size { + self.update_size(size) + } + cx.notify(); + } +} + +impl EventEmitter for MyPanel {} +impl FocusableView for MyPanel { + fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle { + self.focus_handle.clone() + } +} +impl Render for MyPanel { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + div() + .id("my-panel") + .track_focus(&self.focus_handle) + .size_full() + .child(self.view.clone()) } } diff --git a/crates/story/src/list_story.rs b/crates/story/src/list_story.rs index 07ea3871..91d93b27 100644 --- a/crates/story/src/list_story.rs +++ b/crates/story/src/list_story.rs @@ -2,9 +2,9 @@ use core::time; use fake::Fake; use gpui::{ - actions, div, prelude::FluentBuilder as _, px, ElementId, InteractiveElement, IntoElement, - ParentElement, Render, RenderOnce, Styled, Timer, View, ViewContext, VisualContext, - WindowContext, + actions, div, prelude::FluentBuilder as _, px, ElementId, FocusHandle, FocusableView, + InteractiveElement, IntoElement, ParentElement, Render, RenderOnce, Styled, Timer, View, + ViewContext, VisualContext, WindowContext, }; use ui::{ @@ -175,22 +175,11 @@ impl CompanyListDelegate { } pub struct ListStory { + focus_handle: FocusHandle, company_list: View>, selected_company: Option, } -fn random_company() -> Company { - let last_done = (0.0..999.0).fake::(); - let prev_close = last_done * (-0.1..0.1).fake::(); - Company { - name: fake::faker::company::en::CompanyName().fake(), - industry: fake::faker::company::en::Industry().fake(), - description: fake::faker::lorem::en::Paragraph(3..5).fake(), - last_done, - prev_close, - } -} - impl ListStory { pub fn view(cx: &mut WindowContext) -> View { cx.new_view(|cx| Self::new(cx)) @@ -235,6 +224,7 @@ impl ListStory { .detach(); Self { + focus_handle: cx.focus_handle(), company_list, selected_company: None, } @@ -248,48 +238,68 @@ impl ListStory { } } +fn random_company() -> Company { + let last_done = (0.0..999.0).fake::(); + let prev_close = last_done * (-0.1..0.1).fake::(); + Company { + name: fake::faker::company::en::CompanyName().fake(), + industry: fake::faker::company::en::Industry().fake(), + description: fake::faker::lorem::en::Paragraph(3..5).fake(), + last_done, + prev_close, + } +} + +impl FocusableView for ListStory { + fn focus_handle(&self, cx: &gpui::AppContext) -> FocusHandle { + self.focus_handle.clone() + } +} + impl Render for ListStory { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { h_flex() - .size_full() + .track_focus(&self.focus_handle) .on_action(cx.listener(Self::selected_company)) + .size_full() .gap_4() .mb_4() .child( v_flex() .h_full() + .w_full() .border_1() .border_color(cx.theme().border) .rounded_md() - .w(px(400.)) .occlude() .child(self.company_list.clone()), ) - .child( - div() - .flex_1() - .size_full() - .border_1() - .border_color(cx.theme().border) - .py_1() - .px_4() - .rounded_md() - .when_some(self.selected_company.clone(), |this, company| { - this.child( - div() - .flex_1() - .gap_2() - .child( - h_flex() - .items_start() - .justify_between() - .child(div().text_3xl().mb_6().child(company.name.clone())) - .child(format!("{:.2}", company.last_done)), - ) - .child(company.industry.clone()) - .child(company.description.clone()), - ) - }), - ) + // .child( + // div() + // .invisible() + // .flex_1() + // .size_full() + // .border_1() + // .border_color(cx.theme().border) + // .py_1() + // .px_4() + // .rounded_md() + // .when_some(self.selected_company.clone(), |this, company| { + // this.child( + // div() + // .flex_1() + // .gap_2() + // .child( + // h_flex() + // .items_start() + // .justify_between() + // .child(div().text_3xl().mb_6().child(company.name.clone())) + // .child(format!("{:.2}", company.last_done)), + // ) + // .child(company.industry.clone()) + // .child(company.description.clone()), + // ) + // }), + // ) } } diff --git a/crates/story/src/popover_story.rs b/crates/story/src/popover_story.rs index 0d5e160f..9ad1fe42 100644 --- a/crates/story/src/popover_story.rs +++ b/crates/story/src/popover_story.rs @@ -66,6 +66,7 @@ impl Render for PopoverStory { let form = self.form.clone(); v_flex() + .p_4() .size_full() .gap_6() .child( diff --git a/crates/story/src/tooltip_story.rs b/crates/story/src/tooltip_story.rs index 26288199..3ae98465 100644 --- a/crates/story/src/tooltip_story.rs +++ b/crates/story/src/tooltip_story.rs @@ -27,7 +27,7 @@ impl TooltipStory { impl Render for TooltipStory { fn render(&mut self, _cx: &mut gpui::ViewContext) -> impl gpui::IntoElement { v_flex() - .w(px(360.)) + .p_4() .gap_5() .child( div() diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index 72997182..6cf03dec 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -1,4 +1,4 @@ -use crate::theme::ActiveTheme; +use crate::theme::{hsl, ActiveTheme}; use gpui::{hsla, point, px, BoxShadow, Styled, WindowContext}; use smallvec::{smallvec, SmallVec}; @@ -76,6 +76,26 @@ pub trait StyledExt: Styled + Sized { fn elevation_3(self, cx: &WindowContext) -> Self { elevated(self, cx, ElevationIndex::ModalSurface) } + + /// Render a border with a width of 1px, color blue + fn debug_blue(self) -> Self { + self.border_1().border_color(gpui::blue()) + } + + /// Render a border with a width of 1px, color yellow + fn debug_yellow(self) -> Self { + self.border_1().border_color(gpui::yellow()) + } + + /// Render a border with a width of 1px, color green + fn debug_green(self) -> Self { + self.border_1().border_color(gpui::green()) + } + + /// Render a border with a width of 1px, color pink + fn debug_pink(self) -> Self { + self.border_1().border_color(hsl(300., 100., 47.)) + } } impl StyledExt for E {} diff --git a/crates/ui/src/tab/tab.rs b/crates/ui/src/tab/tab.rs index 0d70c202..56326c76 100644 --- a/crates/ui/src/tab/tab.rs +++ b/crates/ui/src/tab/tab.rs @@ -20,7 +20,7 @@ pub struct Tab { impl Tab { pub fn new(id: impl Into, label: impl Into) -> Self { Self { - base: div().id(id.into()), + base: div().id(id.into()).gap_1().py_1().px_3(), label: label.into(), disabled: false, selected: false, @@ -57,6 +57,12 @@ impl InteractiveElement for Tab { 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, cx: &mut WindowContext) -> impl IntoElement { let (text_color, bg_color) = match (self.selected, self.disabled) { @@ -68,14 +74,10 @@ impl RenderOnce for Tab { self.base .flex() .items_center() - .gap_2() .h_full() .flex_shrink_0() .overflow_scroll() .cursor_pointer() - .py_1() - .px_3() - .min_w_16() .text_color(text_color) .bg(bg_color) .when(self.selected, |this| this.rounded(px(6.))) diff --git a/crates/ui/src/tab/tab_bar.rs b/crates/ui/src/tab/tab_bar.rs index 840298c9..959d0e09 100644 --- a/crates/ui/src/tab/tab_bar.rs +++ b/crates/ui/src/tab/tab_bar.rs @@ -1,11 +1,11 @@ use crate::stock::h_flex; use crate::theme::ActiveTheme; use gpui::prelude::FluentBuilder as _; +use gpui::InteractiveElement; use gpui::{ div, AnyElement, Div, IntoElement, ParentElement, RenderOnce, ScrollHandle, SharedString, - StatefulInteractiveElement as _, WindowContext, + StatefulInteractiveElement as _, Styled, WindowContext, }; -use gpui::{InteractiveElement, Styled as _}; use smallvec::SmallVec; #[derive(IntoElement)] @@ -19,7 +19,7 @@ pub struct TabBar { impl TabBar { pub fn new(id: impl Into) -> Self { Self { - base: div(), + base: div().h_10().p_1(), id: id.into(), children: SmallVec::new(), scroll_handle: None, @@ -39,6 +39,12 @@ impl ParentElement for TabBar { } } +impl Styled for TabBar { + fn style(&mut self) -> &mut gpui::StyleRefinement { + self.base.style() + } +} + impl RenderOnce for TabBar { fn render(self, cx: &mut WindowContext) -> impl IntoElement { let theme = cx.theme(); @@ -50,8 +56,6 @@ impl RenderOnce for TabBar { .flex_none() .items_center() .w_full() - .h_10() - .p_1() .bg(theme.muted) .text_color(theme.muted_foreground) .relative() diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs index 92516f64..f59115f9 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme.rs @@ -170,7 +170,7 @@ impl Colors { ring: hsl(240.0, 5.9, 10.0), selection: hsl(211.0, 97.0, 85.0), scrollbar: Hsla::transparent_black(), - scrollbar_thumb: hsl(240.0, 5.9, 90.0), + scrollbar_thumb: hsl(240.0, 5.9, 90.0).opacity(0.7), panel: hsl(0.0, 0.0, 100.0), drop_target: hsl(240.0, 65., 44.0).opacity(0.15), } @@ -229,9 +229,9 @@ impl Colors { ring: hsl(240.0, 4.9, 83.9), selection: hsl(211.0, 97.0, 85.0), scrollbar: Hsla::transparent_black(), - scrollbar_thumb: hsl(240.0, 3.7, 15.9), + scrollbar_thumb: hsl(240.0, 3.7, 15.9).opacity(0.7), panel: hsl(299.0, 2., 9.), - drop_target: hsl(240.0, 65., 29.0).opacity(0.3), + drop_target: hsl(240.0, 89., 67.0).opacity(0.8), } } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index cd06765d..d808c30b 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -45,15 +45,17 @@ pub trait Panel: FocusableView + EventEmitter { /// Return the position of the panel. fn position(&self, cx: &WindowContext) -> DockPosition; /// Return true if the panel can be positioned at the given position. - fn can_position(&self, position: DockPosition, cx: &WindowContext) -> bool; + fn can_position(&self, position: DockPosition) -> bool { + true + } /// Set the position of the panel. - fn set_position(&mut self, position: DockPosition, cx: &mut WindowContext); + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) {} /// Return the size of the panel. fn size(&self, cx: &WindowContext) -> Pixels; /// Set the size of the panel. - fn set_size(&mut self, size: Option, cx: &mut WindowContext); + fn set_size(&mut self, size: Option, cx: &mut ViewContext) {} /// Set the active state of the panel. - fn set_active(&mut self, active: bool, cx: &mut WindowContext); + fn set_active(&mut self, active: bool, cx: &mut ViewContext) {} fn icon(&self, _cx: &WindowContext) -> Option { None } @@ -62,7 +64,7 @@ pub trait Panel: FocusableView + EventEmitter { } fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) {} fn starts_open(&self, _cx: &WindowContext) -> bool { - false + true } } @@ -99,7 +101,7 @@ where } fn can_position(&self, position: DockPosition, cx: &WindowContext) -> bool { - self.read(cx).can_position(position, cx) + self.read(cx).can_position(position) } fn set_position(&self, position: DockPosition, cx: &mut WindowContext) { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 6397e6b7..8c19f7c3 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -183,6 +183,13 @@ impl Pane { cx.notify(); } + pub fn set_should_display_tab_bar(&mut self, f: F) + where + F: 'static + Fn(&ViewContext) -> bool, + { + self.should_display_tab_bar = Rc::new(f); + } + pub fn set_custom_drop_handle(&mut self, cx: &mut ViewContext, handle: F) where F: 'static + Fn(&mut Pane, &dyn Any, &mut ViewContext) -> ControlFlow<(), ()>, @@ -651,24 +658,30 @@ impl Pane { }, cx, ); - // let indicator = render_item_indicator(item.boxed_clone(), cx); + let item_id = item.item_id(); 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); Tab::new(ix, label) + .group("tab") + .px(px(5.)) + .prefix(div().size_2().into_any_element()) + .gap_0p5() .suffix( div() .id("close-tab") - .p(px(1.5)) - .rounded_md() - .child(Icon::new(IconName::Close).size_3()) + .p(px(0.5)) + .rounded_lg() + .invisible() + .child(Icon::new(IconName::Close).size_2()) .hover(|this| this.bg(cx.theme().accent.darken(0.1))) .active(|this| this.bg(cx.theme().accent.darken(0.2))) .on_click(cx.listener(move |pane, _, cx| { pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); })) + .group_hover("tab", |this| this.visible()) .into_any(), ) .selected(is_active) @@ -685,7 +698,11 @@ impl Pane { }, |tab, cx| cx.new_view(|_| tab.clone()), ) - .drag_over::(|tab, _, cx| tab.bg(cx.theme().drop_target)) + .drag_over::(|tab, _, cx| { + tab.border_l_3() + .rounded_l_none() + .border_color(cx.theme().drop_target) + }) .drag_over::(|tab, _, cx| tab.bg(cx.theme().drop_target)) .when_some(self.can_drop_predicate.clone(), |this, p| { this.can_drop(move |a, cx| p(a, cx)) @@ -722,6 +739,7 @@ impl Pane { TabBar::new("tab-bar") .track_scroll(self.tab_bar_scroll_handle.clone()) + .gap(px(2.)) .when(self.has_focus(cx), |tab_bar| { tab_bar.child({ let render_tab_buttons = self.render_tab_bar_buttons.clone(); @@ -739,8 +757,6 @@ impl Pane { div() .id("tab-bar-drop-target") .min_w_6() - // HACK: This empty child is currently necessary to force the drop target to appear - // despite us setting a min width above. .child("") .h_full() .flex_grow()