Update dock layout

This commit is contained in:
Jason Lee 2024-07-04 01:05:32 +08:00
parent 19930e46db
commit f78d50b5e9
13 changed files with 279 additions and 159 deletions

View file

@ -4,7 +4,7 @@ use story::{
ButtonStory, CheckboxStory, DropdownStory, ImageStory, InputStory, ListStory, PickerStory, ButtonStory, CheckboxStory, DropdownStory, ImageStory, InputStory, ListStory, PickerStory,
PopoverStory, StoryContainer, SwitchStory, TooltipStory, PopoverStory, StoryContainer, SwitchStory, TooltipStory,
}; };
use workspace::{TitleBar, Workspace}; use workspace::{dock::DockPosition, TitleBar, Workspace};
use std::sync::Arc; use std::sync::Arc;
use ui::{ use ui::{
@ -40,7 +40,7 @@ impl StoryWorkspace {
}) })
.detach(); .detach();
StoryContainer::open( StoryContainer::add_pane(
"Buttons", "Buttons",
"Displays a button or a component that looks like a button.", "Displays a button or a component that looks like a button.",
ButtonStory::view(cx).into(), ButtonStory::view(cx).into(),
@ -49,25 +49,23 @@ impl StoryWorkspace {
) )
.detach(); .detach();
StoryContainer::open( StoryContainer::add_panel(
"Inputs",
"Displays a text input field.",
InputStory::view(cx).into(), InputStory::view(cx).into(),
workspace.clone(), workspace.clone(),
DockPosition::Right,
px(500.0),
cx, cx,
) );
.detach();
StoryContainer::open( StoryContainer::add_panel(
"Checkbox",
"A control that allows the user to toggle between checked and not checked.",
CheckboxStory::view(cx).into(), CheckboxStory::view(cx).into(),
workspace.clone(), workspace.clone(),
DockPosition::Bottom,
px(300.),
cx, cx,
) );
.detach();
StoryContainer::open( StoryContainer::add_pane(
"Switch", "Switch",
"A control that allows the user to toggle between two states.", "A control that allows the user to toggle between two states.",
SwitchStory::view(cx).into(), SwitchStory::view(cx).into(),
@ -76,7 +74,7 @@ impl StoryWorkspace {
) )
.detach(); .detach();
StoryContainer::open( StoryContainer::add_pane(
"Dropdowns", "Dropdowns",
"Displays a list of options for the user to pick from—triggered by a button.", "Displays a list of options for the user to pick from—triggered by a button.",
DropdownStory::new(cx).into(), DropdownStory::new(cx).into(),
@ -85,7 +83,7 @@ impl StoryWorkspace {
) )
.detach(); .detach();
StoryContainer::open( StoryContainer::add_pane(
"Picker", "Picker",
"Picker is a component that allows the user to select an item from a list of options.", "Picker is a component that allows the user to select an item from a list of options.",
PickerStory::view(cx).into(), PickerStory::view(cx).into(),
@ -94,7 +92,7 @@ impl StoryWorkspace {
) )
.detach(); .detach();
StoryContainer::open( StoryContainer::add_pane(
"Popover", "Popover",
"Displays rich content in a portal, triggered by a button.", "Displays rich content in a portal, triggered by a button.",
PopoverStory::view(cx).into(), PopoverStory::view(cx).into(),
@ -103,24 +101,24 @@ impl StoryWorkspace {
) )
.detach(); .detach();
StoryContainer::open( StoryContainer::add_pane(
"Tooltip", "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(), TooltipStory::view(cx).into(),
workspace.clone(), workspace.clone(),
cx, cx,
).detach();
StoryContainer::open(
"List",
"A complex list example, includes use Picker, Scrollbar, and more.",
ListStory::view(cx).into(),
workspace.clone(),
cx,
) )
.detach(); .detach();
StoryContainer::open( StoryContainer::add_panel(
ListStory::view(cx).into(),
workspace.clone(),
DockPosition::Left,
px(360.),
cx,
);
StoryContainer::add_pane(
"Image", "Image",
"Render SVG image and Chart", "Render SVG image and Chart",
ImageStory::view(cx).into(), ImageStory::view(cx).into(),
@ -136,7 +134,7 @@ impl StoryWorkspace {
app_state: Arc<AppState>, app_state: Arc<AppState>,
cx: &mut AppContext, cx: &mut AppContext,
) -> Task<anyhow::Result<WindowHandle<Self>>> { ) -> Task<anyhow::Result<WindowHandle<Self>>> {
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 { cx.spawn(|mut cx| async move {
let options = WindowOptions { let options = WindowOptions {

View file

@ -33,6 +33,7 @@ impl CheckboxStory {
impl Render for CheckboxStory { impl Render for CheckboxStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex() v_flex()
.p_4()
.gap_6() .gap_6()
.child( .child(
v_flex().items_start().justify_start().gap_6().child( v_flex().items_start().justify_start().gap_6().child(

View file

@ -83,6 +83,7 @@ impl Render for InputStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex() v_flex()
.size_full() .size_full()
.p_4()
.justify_start() .justify_start()
.gap_3() .gap_3()
.child( .child(

View file

@ -21,9 +21,10 @@ pub use switch_story::SwitchStory;
pub use tooltip_story::TooltipStory; pub use tooltip_story::TooltipStory;
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, EventEmitter, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, Element,
FocusableView, IntoElement, ParentElement, Render, SharedString, Styled as _, Task, View, EventEmitter, FocusableView, InteractiveElement, IntoElement, ParentElement, Pixels, Render,
ViewContext, VisualContext, WindowContext, ScrollHandle, SharedString, StatefulInteractiveElement, Styled as _, Task, View, ViewContext,
VisualContext, WindowContext,
}; };
use workspace::{ use workspace::{
dock::{DockPosition, Panel, PanelEvent}, dock::{DockPosition, Panel, PanelEvent},
@ -32,7 +33,7 @@ use workspace::{
}; };
use anyhow::Result; 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( pub fn story_case(
name: &'static str, name: &'static str,
@ -61,6 +62,7 @@ pub fn section(title: impl Into<SharedString>, cx: &WindowContext) -> Div {
pub struct StoryContainer { pub struct StoryContainer {
focus_handle: gpui::FocusHandle, focus_handle: gpui::FocusHandle,
scroll_handle: gpui::ScrollHandle,
name: SharedString, name: SharedString,
description: SharedString, description: SharedString,
position: DockPosition, position: DockPosition,
@ -76,48 +78,6 @@ impl FocusableView for StoryContainer {
} }
} }
impl EventEmitter<PanelEvent> 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<gpui::Pixels>, _: &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)] #[derive(Debug)]
pub enum ContainerEvent { pub enum ContainerEvent {
Close, Close,
@ -172,6 +132,7 @@ impl StoryContainer {
Self { Self {
focus_handle, focus_handle,
scroll_handle: ScrollHandle::new(),
name: name.into(), name: name.into(),
description: description.into(), description: description.into(),
width: None, width: None,
@ -182,7 +143,7 @@ impl StoryContainer {
} }
} }
pub fn open( pub fn add_pane(
name: impl Into<SharedString>, name: impl Into<SharedString>,
description: impl Into<SharedString>, description: impl Into<SharedString>,
story: AnyView, story: AnyView,
@ -203,6 +164,19 @@ impl StoryContainer {
}) })
} }
pub fn add_panel(
story: AnyView,
workspace: View<Workspace>,
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 { pub fn width(mut self, width: gpui::Pixels) -> Self {
self.width = Some(width); self.width = Some(width);
self self
@ -226,21 +200,112 @@ impl StoryContainer {
impl Render for StoryContainer { impl Render for StoryContainer {
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
v_flex() div()
.id("story-container")
.overflow_y_scroll()
.track_scroll(&self.scroll_handle)
.size_full() .size_full()
.gap_6()
.p_2()
.child( .child(
div() v_flex()
.flex() .size_full()
.flex_col() .gap_6()
.gap_4() .p_4()
.child(Label::new(self.name.clone()).text_size(px(24.0))) .child(
.child(Label::new(self.description.clone()).text_size(px(16.0))), 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<Pixels>,
height: Option<Pixels>,
}
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>) {
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<gpui::Pixels>, cx: &mut ViewContext<Self>) {
dbg!("================ set_size", size);
if let Some(size) = size {
self.update_size(size)
}
cx.notify();
}
}
impl EventEmitter<PanelEvent> 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<Self>) -> impl IntoElement {
div()
.id("my-panel")
.track_focus(&self.focus_handle)
.size_full()
.child(self.view.clone())
} }
} }

View file

@ -2,9 +2,9 @@ use core::time;
use fake::Fake; use fake::Fake;
use gpui::{ use gpui::{
actions, div, prelude::FluentBuilder as _, px, ElementId, InteractiveElement, IntoElement, actions, div, prelude::FluentBuilder as _, px, ElementId, FocusHandle, FocusableView,
ParentElement, Render, RenderOnce, Styled, Timer, View, ViewContext, VisualContext, InteractiveElement, IntoElement, ParentElement, Render, RenderOnce, Styled, Timer, View,
WindowContext, ViewContext, VisualContext, WindowContext,
}; };
use ui::{ use ui::{
@ -175,22 +175,11 @@ impl CompanyListDelegate {
} }
pub struct ListStory { pub struct ListStory {
focus_handle: FocusHandle,
company_list: View<Picker<CompanyListDelegate>>, company_list: View<Picker<CompanyListDelegate>>,
selected_company: Option<Company>, selected_company: Option<Company>,
} }
fn random_company() -> Company {
let last_done = (0.0..999.0).fake::<f64>();
let prev_close = last_done * (-0.1..0.1).fake::<f64>();
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 { impl ListStory {
pub fn view(cx: &mut WindowContext) -> View<Self> { pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(|cx| Self::new(cx)) cx.new_view(|cx| Self::new(cx))
@ -235,6 +224,7 @@ impl ListStory {
.detach(); .detach();
Self { Self {
focus_handle: cx.focus_handle(),
company_list, company_list,
selected_company: None, selected_company: None,
} }
@ -248,48 +238,68 @@ impl ListStory {
} }
} }
fn random_company() -> Company {
let last_done = (0.0..999.0).fake::<f64>();
let prev_close = last_done * (-0.1..0.1).fake::<f64>();
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 { impl Render for ListStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
h_flex() h_flex()
.size_full() .track_focus(&self.focus_handle)
.on_action(cx.listener(Self::selected_company)) .on_action(cx.listener(Self::selected_company))
.size_full()
.gap_4() .gap_4()
.mb_4() .mb_4()
.child( .child(
v_flex() v_flex()
.h_full() .h_full()
.w_full()
.border_1() .border_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.rounded_md() .rounded_md()
.w(px(400.))
.occlude() .occlude()
.child(self.company_list.clone()), .child(self.company_list.clone()),
) )
.child( // .child(
div() // div()
.flex_1() // .invisible()
.size_full() // .flex_1()
.border_1() // .size_full()
.border_color(cx.theme().border) // .border_1()
.py_1() // .border_color(cx.theme().border)
.px_4() // .py_1()
.rounded_md() // .px_4()
.when_some(self.selected_company.clone(), |this, company| { // .rounded_md()
this.child( // .when_some(self.selected_company.clone(), |this, company| {
div() // this.child(
.flex_1() // div()
.gap_2() // .flex_1()
.child( // .gap_2()
h_flex() // .child(
.items_start() // h_flex()
.justify_between() // .items_start()
.child(div().text_3xl().mb_6().child(company.name.clone())) // .justify_between()
.child(format!("{:.2}", company.last_done)), // .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(company.industry.clone())
) // .child(company.description.clone()),
}), // )
) // }),
// )
} }
} }

View file

@ -66,6 +66,7 @@ impl Render for PopoverStory {
let form = self.form.clone(); let form = self.form.clone();
v_flex() v_flex()
.p_4()
.size_full() .size_full()
.gap_6() .gap_6()
.child( .child(

View file

@ -27,7 +27,7 @@ impl TooltipStory {
impl Render for TooltipStory { impl Render for TooltipStory {
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement { fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
v_flex() v_flex()
.w(px(360.)) .p_4()
.gap_5() .gap_5()
.child( .child(
div() div()

View file

@ -1,4 +1,4 @@
use crate::theme::ActiveTheme; use crate::theme::{hsl, ActiveTheme};
use gpui::{hsla, point, px, BoxShadow, Styled, WindowContext}; use gpui::{hsla, point, px, BoxShadow, Styled, WindowContext};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
@ -76,6 +76,26 @@ pub trait StyledExt: Styled + Sized {
fn elevation_3(self, cx: &WindowContext) -> Self { fn elevation_3(self, cx: &WindowContext) -> Self {
elevated(self, cx, ElevationIndex::ModalSurface) 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<E: Styled> StyledExt for E {} impl<E: Styled> StyledExt for E {}

View file

@ -20,7 +20,7 @@ pub struct Tab {
impl Tab { impl Tab {
pub fn new(id: impl Into<ElementId>, label: impl Into<AnyElement>) -> Self { pub fn new(id: impl Into<ElementId>, label: impl Into<AnyElement>) -> Self {
Self { Self {
base: div().id(id.into()), base: div().id(id.into()).gap_1().py_1().px_3(),
label: label.into(), label: label.into(),
disabled: false, disabled: false,
selected: false, selected: false,
@ -57,6 +57,12 @@ impl InteractiveElement for Tab {
impl StatefulInteractiveElement for Tab {} impl StatefulInteractiveElement for Tab {}
impl Styled for Tab {
fn style(&mut self) -> &mut gpui::StyleRefinement {
self.base.style()
}
}
impl RenderOnce for Tab { impl RenderOnce for Tab {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let (text_color, bg_color) = match (self.selected, self.disabled) { let (text_color, bg_color) = match (self.selected, self.disabled) {
@ -68,14 +74,10 @@ impl RenderOnce for Tab {
self.base self.base
.flex() .flex()
.items_center() .items_center()
.gap_2()
.h_full() .h_full()
.flex_shrink_0() .flex_shrink_0()
.overflow_scroll() .overflow_scroll()
.cursor_pointer() .cursor_pointer()
.py_1()
.px_3()
.min_w_16()
.text_color(text_color) .text_color(text_color)
.bg(bg_color) .bg(bg_color)
.when(self.selected, |this| this.rounded(px(6.))) .when(self.selected, |this| this.rounded(px(6.)))

View file

@ -1,11 +1,11 @@
use crate::stock::h_flex; use crate::stock::h_flex;
use crate::theme::ActiveTheme; use crate::theme::ActiveTheme;
use gpui::prelude::FluentBuilder as _; use gpui::prelude::FluentBuilder as _;
use gpui::InteractiveElement;
use gpui::{ use gpui::{
div, AnyElement, Div, IntoElement, ParentElement, RenderOnce, ScrollHandle, SharedString, div, AnyElement, Div, IntoElement, ParentElement, RenderOnce, ScrollHandle, SharedString,
StatefulInteractiveElement as _, WindowContext, StatefulInteractiveElement as _, Styled, WindowContext,
}; };
use gpui::{InteractiveElement, Styled as _};
use smallvec::SmallVec; use smallvec::SmallVec;
#[derive(IntoElement)] #[derive(IntoElement)]
@ -19,7 +19,7 @@ pub struct TabBar {
impl TabBar { impl TabBar {
pub fn new(id: impl Into<SharedString>) -> Self { pub fn new(id: impl Into<SharedString>) -> Self {
Self { Self {
base: div(), base: div().h_10().p_1(),
id: id.into(), id: id.into(),
children: SmallVec::new(), children: SmallVec::new(),
scroll_handle: None, 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 { impl RenderOnce for TabBar {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let theme = cx.theme(); let theme = cx.theme();
@ -50,8 +56,6 @@ impl RenderOnce for TabBar {
.flex_none() .flex_none()
.items_center() .items_center()
.w_full() .w_full()
.h_10()
.p_1()
.bg(theme.muted) .bg(theme.muted)
.text_color(theme.muted_foreground) .text_color(theme.muted_foreground)
.relative() .relative()

View file

@ -170,7 +170,7 @@ impl Colors {
ring: hsl(240.0, 5.9, 10.0), ring: hsl(240.0, 5.9, 10.0),
selection: hsl(211.0, 97.0, 85.0), selection: hsl(211.0, 97.0, 85.0),
scrollbar: Hsla::transparent_black(), 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), panel: hsl(0.0, 0.0, 100.0),
drop_target: hsl(240.0, 65., 44.0).opacity(0.15), 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), ring: hsl(240.0, 4.9, 83.9),
selection: hsl(211.0, 97.0, 85.0), selection: hsl(211.0, 97.0, 85.0),
scrollbar: Hsla::transparent_black(), 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.), 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),
} }
} }
} }

View file

@ -45,15 +45,17 @@ pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
/// Return the position of the panel. /// Return the position of the panel.
fn position(&self, cx: &WindowContext) -> DockPosition; fn position(&self, cx: &WindowContext) -> DockPosition;
/// Return true if the panel can be positioned at the given position. /// 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. /// 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<Self>) {}
/// Return the size of the panel. /// Return the size of the panel.
fn size(&self, cx: &WindowContext) -> Pixels; fn size(&self, cx: &WindowContext) -> Pixels;
/// Set the size of the panel. /// Set the size of the panel.
fn set_size(&mut self, size: Option<Pixels>, cx: &mut WindowContext); fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {}
/// Set the active state of the panel. /// 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<Self>) {}
fn icon(&self, _cx: &WindowContext) -> Option<IconName> { fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
None None
} }
@ -62,7 +64,7 @@ pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
} }
fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {} fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
fn starts_open(&self, _cx: &WindowContext) -> bool { fn starts_open(&self, _cx: &WindowContext) -> bool {
false true
} }
} }
@ -99,7 +101,7 @@ where
} }
fn can_position(&self, position: DockPosition, cx: &WindowContext) -> bool { 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) { fn set_position(&self, position: DockPosition, cx: &mut WindowContext) {

View file

@ -183,6 +183,13 @@ impl Pane {
cx.notify(); cx.notify();
} }
pub fn set_should_display_tab_bar<F>(&mut self, f: F)
where
F: 'static + Fn(&ViewContext<Pane>) -> bool,
{
self.should_display_tab_bar = Rc::new(f);
}
pub fn set_custom_drop_handle<F>(&mut self, cx: &mut ViewContext<Self>, handle: F) pub fn set_custom_drop_handle<F>(&mut self, cx: &mut ViewContext<Self>, handle: F)
where where
F: 'static + Fn(&mut Pane, &dyn Any, &mut ViewContext<Pane>) -> ControlFlow<(), ()>, F: 'static + Fn(&mut Pane, &dyn Any, &mut ViewContext<Pane>) -> ControlFlow<(), ()>,
@ -651,24 +658,30 @@ impl Pane {
}, },
cx, cx,
); );
// let indicator = render_item_indicator(item.boxed_clone(), cx);
let item_id = item.item_id(); let item_id = item.item_id();
let _is_first_item = ix == 0; let _is_first_item = ix == 0;
let _is_last_item = ix == self.items.len() - 1; let _is_last_item = ix == self.items.len() - 1;
let _position_relative_to_active_item = ix.cmp(&self.active_item_index); let _position_relative_to_active_item = ix.cmp(&self.active_item_index);
Tab::new(ix, label) Tab::new(ix, label)
.group("tab")
.px(px(5.))
.prefix(div().size_2().into_any_element())
.gap_0p5()
.suffix( .suffix(
div() div()
.id("close-tab") .id("close-tab")
.p(px(1.5)) .p(px(0.5))
.rounded_md() .rounded_lg()
.child(Icon::new(IconName::Close).size_3()) .invisible()
.child(Icon::new(IconName::Close).size_2())
.hover(|this| this.bg(cx.theme().accent.darken(0.1))) .hover(|this| this.bg(cx.theme().accent.darken(0.1)))
.active(|this| this.bg(cx.theme().accent.darken(0.2))) .active(|this| this.bg(cx.theme().accent.darken(0.2)))
.on_click(cx.listener(move |pane, _, cx| { .on_click(cx.listener(move |pane, _, cx| {
pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); pane.close_item_by_id(item_id, cx).detach_and_log_err(cx);
})) }))
.group_hover("tab", |this| this.visible())
.into_any(), .into_any(),
) )
.selected(is_active) .selected(is_active)
@ -685,7 +698,11 @@ impl Pane {
}, },
|tab, cx| cx.new_view(|_| tab.clone()), |tab, cx| cx.new_view(|_| tab.clone()),
) )
.drag_over::<DraggedTab>(|tab, _, cx| tab.bg(cx.theme().drop_target)) .drag_over::<DraggedTab>(|tab, _, cx| {
tab.border_l_3()
.rounded_l_none()
.border_color(cx.theme().drop_target)
})
.drag_over::<DraggedSelection>(|tab, _, cx| tab.bg(cx.theme().drop_target)) .drag_over::<DraggedSelection>(|tab, _, cx| tab.bg(cx.theme().drop_target))
.when_some(self.can_drop_predicate.clone(), |this, p| { .when_some(self.can_drop_predicate.clone(), |this, p| {
this.can_drop(move |a, cx| p(a, cx)) this.can_drop(move |a, cx| p(a, cx))
@ -722,6 +739,7 @@ impl Pane {
TabBar::new("tab-bar") TabBar::new("tab-bar")
.track_scroll(self.tab_bar_scroll_handle.clone()) .track_scroll(self.tab_bar_scroll_handle.clone())
.gap(px(2.))
.when(self.has_focus(cx), |tab_bar| { .when(self.has_focus(cx), |tab_bar| {
tab_bar.child({ tab_bar.child({
let render_tab_buttons = self.render_tab_bar_buttons.clone(); let render_tab_buttons = self.render_tab_bar_buttons.clone();
@ -739,8 +757,6 @@ impl Pane {
div() div()
.id("tab-bar-drop-target") .id("tab-bar-drop-target")
.min_w_6() .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("") .child("")
.h_full() .h_full()
.flex_grow() .flex_grow()