diff --git a/crates/story/src/sidebar_story.rs b/crates/story/src/sidebar_story.rs index d28c5f3d..7a245860 100644 --- a/crates/story/src/sidebar_story.rs +++ b/crates/story/src/sidebar_story.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use gpui::{ div, impl_internal_actions, prelude::FluentBuilder, relative, App, AppContext, ClickEvent, Context, Entity, Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Window, @@ -14,7 +16,7 @@ use gpui_component::{ SidebarToggleButton, }, switch::Switch, - v_flex, white, ActiveTheme, Collapsible, Icon, IconName, Side, + v_flex, white, ActiveTheme, Icon, IconName, Side, }; use serde::Deserialize; @@ -24,7 +26,8 @@ pub struct SelectCompany(SharedString); impl_internal_actions!(sidebar_story, [SelectCompany]); pub struct SidebarStory { - active_item: Item, + active_items: HashMap, + last_active_item: Item, active_subitem: Option, collapsed: bool, side: Side, @@ -37,8 +40,12 @@ impl SidebarStory { } fn new(_: &mut Window, cx: &mut Context) -> Self { + let mut active_items = HashMap::new(); + active_items.insert(Item::Playground, true); + Self { - active_item: Item::Playground, + active_items, + last_active_item: Item::Playground, active_subitem: None, collapsed: false, side: Side::Left, @@ -61,7 +68,7 @@ impl SidebarStory { } } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] enum Item { Playground, Models, @@ -121,8 +128,13 @@ impl Item { { let item = *self; move |this, _, _, cx| { - this.active_item = item; - this.active_subitem = None; + if this.active_items.contains_key(&item) { + this.active_items.remove(&item); + } else { + this.active_items.insert(item, true); + } + + this.last_active_item = item; cx.notify(); } } @@ -176,7 +188,13 @@ impl SubItem { let item = *item; let subitem = *self; move |this, _, _, cx| { - this.active_item = item; + println!( + "Clicked on item: {}, child: {}", + item.label(), + subitem.label() + ); + this.active_items.insert(item, true); + this.last_active_item = item; this.active_subitem = Some(subitem); cx.notify(); } @@ -219,12 +237,6 @@ impl Render for SidebarStory { ], ]; - let sidebar = if self.side.is_left() { - Sidebar::left(&cx.entity()) - } else { - Sidebar::right(&cx.entity()) - }; - h_flex() .rounded(cx.theme().radius) .border_1() @@ -232,11 +244,10 @@ impl Render for SidebarStory { .h_full() .when(self.side.is_right(), |this| this.flex_row_reverse()) .child( - sidebar + Sidebar::new(self.side) .collapsed(self.collapsed) .header( SidebarHeader::new() - .collapsed(self.collapsed) .w_full() .child( div() @@ -249,13 +260,13 @@ impl Render for SidebarStory { .size_8() .flex_shrink_0() .when(!self.collapsed, |this| { - this.child(Icon::new(IconName::GalleryVerticalEnd).size_4()) + this.child(Icon::new(IconName::GalleryVerticalEnd)) }) .when(self.collapsed, |this| { this.size_4() .bg(cx.theme().transparent) .text_color(cx.theme().foreground) - .child(Icon::new(IconName::GalleryVerticalEnd).size_5()) + .child(Icon::new(IconName::GalleryVerticalEnd)) }), ) .when(!self.collapsed, |this| { @@ -291,9 +302,33 @@ impl Render for SidebarStory { ) }), ) + .child( + SidebarGroup::new("Platform").child(SidebarMenu::new().children( + groups[0].iter().map(|item| { + SidebarMenuItem::new(item.label()) + .icon(item.icon()) + .active(self.active_items.contains_key(item)) + .children(item.items().into_iter().map(|sub_item| { + SidebarMenuItem::new(sub_item.label()) + .active(self.active_subitem == Some(sub_item)) + .on_click(cx.listener(sub_item.handler(&item))) + })) + .on_click(cx.listener(item.handler())) + }), + )), + ) + .child( + SidebarGroup::new("Projects").child(SidebarMenu::new().children( + groups[1].iter().map(|item| { + SidebarMenuItem::new(item.label()) + .icon(item.icon()) + .active(self.last_active_item == *item) + .on_click(cx.listener(item.handler())) + }), + )), + ) .footer( SidebarFooter::new() - .collapsed(self.collapsed) .justify_between() .child( h_flex() @@ -302,55 +337,8 @@ impl Render for SidebarStory { .when(!self.collapsed, |this| this.child("Jason Lee")), ) .when(!self.collapsed, |this| { - this.child( - Icon::new(IconName::ChevronsUpDown).size_4().flex_shrink_0(), - ) + this.child(Icon::new(IconName::ChevronsUpDown).size_4()) }), - ) - .child( - SidebarGroup::new("Platform").child(SidebarMenu::new().children({ - let mut items = Vec::with_capacity(groups[0].len()); - for item in groups[0].iter() { - let item = *item; - items.push( - SidebarMenuItem::new(item.label()) - .icon(item.icon().into()) - .active(self.active_item == item) - .children({ - let mut sub_items = - Vec::with_capacity(item.items().len()); - for sub_item in item.items() { - sub_items.push( - SidebarMenuItem::new(sub_item.label()) - .active( - self.active_subitem == Some(sub_item), - ) - .on_click( - cx.listener(sub_item.handler(&item)), - ), - ); - } - sub_items - }) - .on_click(cx.listener(item.handler())), - ); - } - items - })), - ) - .child( - SidebarGroup::new("Projects").child(SidebarMenu::new().children({ - let mut items = Vec::with_capacity(groups[1].len()); - for item in groups[1].iter() { - items.push( - SidebarMenuItem::new(item.label()) - .icon(item.icon().into()) - .active(self.active_item == *item) - .on_click(cx.listener(item.handler())), - ); - } - items - })), ), ) .child( @@ -379,12 +367,12 @@ impl Render for SidebarStory { Breadcrumb::new() .item(BreadcrumbItem::new("0", "Home").on_click(cx.listener( |this, _, _, cx| { - this.active_item = Item::Playground; + this.last_active_item = Item::Playground; cx.notify(); }, ))) .item( - BreadcrumbItem::new("1", self.active_item.label()) + BreadcrumbItem::new("1", self.last_active_item.label()) .on_click(cx.listener(|this, _, _, cx| { this.active_subitem = None; cx.notify(); diff --git a/crates/ui/src/icon.rs b/crates/ui/src/icon.rs index 33d733ee..a51e389f 100644 --- a/crates/ui/src/icon.rs +++ b/crates/ui/src/icon.rs @@ -290,7 +290,8 @@ impl RenderOnce for Icon { let mut base = self.base; *base.style() = self.style; - base.text_color(text_color) + base.flex_shrink_0() + .text_color(text_color) .when(!has_base_size, |this| this.size(text_size)) .when_some(self.size, |this, size| match size { Size::Size(px) => this.size(px), @@ -318,7 +319,8 @@ impl Render for Icon { let mut base = svg().flex_none(); *base.style() = self.style.clone(); - base.text_color(text_color) + base.flex_shrink_0() + .text_color(text_color) .when(!has_base_size, |this| this.size(text_size)) .when_some(self.size, |this, size| match size { Size::Size(px) => this.size(px), diff --git a/crates/ui/src/sidebar/header.rs b/crates/ui/src/sidebar/header.rs index e82154c6..de40539a 100644 --- a/crates/ui/src/sidebar/header.rs +++ b/crates/ui/src/sidebar/header.rs @@ -33,6 +33,7 @@ impl Selectable for SidebarHeader { &self.id } } + impl Collapsible for SidebarHeader { fn is_collapsed(&self) -> bool { self.collapsed diff --git a/crates/ui/src/sidebar/menu.rs b/crates/ui/src/sidebar/menu.rs index 50d5a62d..76ea9f30 100644 --- a/crates/ui/src/sidebar/menu.rs +++ b/crates/ui/src/sidebar/menu.rs @@ -81,8 +81,8 @@ impl SidebarMenuItem { } /// Set the icon for the menu item - pub fn icon(mut self, icon: Icon) -> Self { - self.icon = Some(icon); + pub fn icon(mut self, icon: impl Into) -> Self { + self.icon = Some(icon.into()); self } @@ -138,7 +138,7 @@ impl SidebarMenuItem { let is_submenu = self.is_submenu(); h_flex() - .id(self.id.clone()) + .id("item") .overflow_hidden() .flex_shrink_0() .p_2() @@ -155,9 +155,12 @@ impl SidebarMenuItem { .bg(cx.theme().sidebar_accent) .text_color(cx.theme().sidebar_accent_foreground) }) - .when_some(self.icon.clone(), |this, icon| this.child(icon.size_4())) + .when_some(self.icon.clone(), |this, icon| this.child(icon)) .when(is_collapsed, |this| { - this.justify_center().size_7().mx_auto() + this.justify_center().when(is_active, |this| { + this.bg(cx.theme().sidebar_accent) + .text_color(cx.theme().sidebar_accent_foreground) + }) }) .when(!is_collapsed, |this| { this.h_7() @@ -181,6 +184,7 @@ impl RenderOnce for SidebarMenuItem { let is_collapsed = self.collapsed; div() + .id(self.id.clone()) .w_full() .child(self.render_menu_item(window, cx)) .when(is_submenu && is_open && !is_collapsed, |this| { diff --git a/crates/ui/src/sidebar/mod.rs b/crates/ui/src/sidebar/mod.rs index a4cb4606..3e6f1a1a 100644 --- a/crates/ui/src/sidebar/mod.rs +++ b/crates/ui/src/sidebar/mod.rs @@ -5,9 +5,8 @@ use crate::{ v_flex, ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt, }; use gpui::{ - div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, Entity, EntityId, - InteractiveElement as _, IntoElement, ParentElement, Pixels, Render, RenderOnce, Styled, - Window, + div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, InteractiveElement as _, + IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window, }; use std::rc::Rc; @@ -26,8 +25,6 @@ const COLLAPSED_WIDTH: Pixels = px(48.); /// A sidebar #[derive(IntoElement)] pub struct Sidebar { - /// The parent view id - view_id: EntityId, content: Vec, /// header view header: Option, @@ -41,9 +38,8 @@ pub struct Sidebar { } impl Sidebar { - fn new(view_id: EntityId, side: Side) -> Self { + pub fn new(side: Side) -> Self { Self { - view_id, content: vec![], header: None, footer: None, @@ -54,12 +50,12 @@ impl Sidebar { } } - pub fn left(view: &Entity) -> Self { - Self::new(view.entity_id(), Side::Left) + pub fn left() -> Self { + Self::new(Side::Left) } - pub fn right(view: &Entity) -> Self { - Self::new(view.entity_id(), Side::Right) + pub fn right() -> Self { + Self::new(Side::Right) } /// Set the width of the sidebar @@ -117,7 +113,7 @@ pub struct SidebarToggleButton { impl SidebarToggleButton { fn new(side: Side) -> Self { Self { - btn: Button::new("sidebar-collapse").ghost().small(), + btn: Button::new("collapse").ghost().small(), collapsed: false, side, on_click: None, @@ -181,7 +177,8 @@ impl RenderOnce for SidebarToggleButton { } impl RenderOnce for Sidebar { - fn render(mut self, _: &mut Window, cx: &mut App) -> impl IntoElement { + fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let view_id = window.current_view(); v_flex() .id("sidebar") .w(self.width) @@ -210,7 +207,7 @@ impl RenderOnce for Sidebar { .map(|(ix, c)| div().id(ix).child(c.collapsed(self.collapsed))), ) .gap_2() - .scrollable(self.view_id, ScrollbarAxis::Vertical), + .scrollable(view_id, ScrollbarAxis::Vertical), ), ) .when_some(self.footer.take(), |this, footer| {