sidebar: Fix Sidebar details, something like icon size, remove view_id argument. (#774)
Also updated the Sidebar example to support expand multiple root items. ## Break Change - Sidebar create not need assign parent view_id now.
This commit is contained in:
parent
b016d9fff4
commit
d186bf7c9e
5 changed files with 81 additions and 89 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, impl_internal_actions, prelude::FluentBuilder, relative, App, AppContext, ClickEvent,
|
div, impl_internal_actions, prelude::FluentBuilder, relative, App, AppContext, ClickEvent,
|
||||||
Context, Entity, Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Window,
|
Context, Entity, Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Window,
|
||||||
|
|
@ -14,7 +16,7 @@ use gpui_component::{
|
||||||
SidebarToggleButton,
|
SidebarToggleButton,
|
||||||
},
|
},
|
||||||
switch::Switch,
|
switch::Switch,
|
||||||
v_flex, white, ActiveTheme, Collapsible, Icon, IconName, Side,
|
v_flex, white, ActiveTheme, Icon, IconName, Side,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
|
@ -24,7 +26,8 @@ pub struct SelectCompany(SharedString);
|
||||||
impl_internal_actions!(sidebar_story, [SelectCompany]);
|
impl_internal_actions!(sidebar_story, [SelectCompany]);
|
||||||
|
|
||||||
pub struct SidebarStory {
|
pub struct SidebarStory {
|
||||||
active_item: Item,
|
active_items: HashMap<Item, bool>,
|
||||||
|
last_active_item: Item,
|
||||||
active_subitem: Option<SubItem>,
|
active_subitem: Option<SubItem>,
|
||||||
collapsed: bool,
|
collapsed: bool,
|
||||||
side: Side,
|
side: Side,
|
||||||
|
|
@ -37,8 +40,12 @@ impl SidebarStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
let mut active_items = HashMap::new();
|
||||||
|
active_items.insert(Item::Playground, true);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
active_item: Item::Playground,
|
active_items,
|
||||||
|
last_active_item: Item::Playground,
|
||||||
active_subitem: None,
|
active_subitem: None,
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
side: Side::Left,
|
side: Side::Left,
|
||||||
|
|
@ -61,7 +68,7 @@ impl SidebarStory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
enum Item {
|
enum Item {
|
||||||
Playground,
|
Playground,
|
||||||
Models,
|
Models,
|
||||||
|
|
@ -121,8 +128,13 @@ impl Item {
|
||||||
{
|
{
|
||||||
let item = *self;
|
let item = *self;
|
||||||
move |this, _, _, cx| {
|
move |this, _, _, cx| {
|
||||||
this.active_item = item;
|
if this.active_items.contains_key(&item) {
|
||||||
this.active_subitem = None;
|
this.active_items.remove(&item);
|
||||||
|
} else {
|
||||||
|
this.active_items.insert(item, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.last_active_item = item;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +188,13 @@ impl SubItem {
|
||||||
let item = *item;
|
let item = *item;
|
||||||
let subitem = *self;
|
let subitem = *self;
|
||||||
move |this, _, _, cx| {
|
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);
|
this.active_subitem = Some(subitem);
|
||||||
cx.notify();
|
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()
|
h_flex()
|
||||||
.rounded(cx.theme().radius)
|
.rounded(cx.theme().radius)
|
||||||
.border_1()
|
.border_1()
|
||||||
|
|
@ -232,11 +244,10 @@ impl Render for SidebarStory {
|
||||||
.h_full()
|
.h_full()
|
||||||
.when(self.side.is_right(), |this| this.flex_row_reverse())
|
.when(self.side.is_right(), |this| this.flex_row_reverse())
|
||||||
.child(
|
.child(
|
||||||
sidebar
|
Sidebar::new(self.side)
|
||||||
.collapsed(self.collapsed)
|
.collapsed(self.collapsed)
|
||||||
.header(
|
.header(
|
||||||
SidebarHeader::new()
|
SidebarHeader::new()
|
||||||
.collapsed(self.collapsed)
|
|
||||||
.w_full()
|
.w_full()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
|
@ -249,13 +260,13 @@ impl Render for SidebarStory {
|
||||||
.size_8()
|
.size_8()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.when(!self.collapsed, |this| {
|
.when(!self.collapsed, |this| {
|
||||||
this.child(Icon::new(IconName::GalleryVerticalEnd).size_4())
|
this.child(Icon::new(IconName::GalleryVerticalEnd))
|
||||||
})
|
})
|
||||||
.when(self.collapsed, |this| {
|
.when(self.collapsed, |this| {
|
||||||
this.size_4()
|
this.size_4()
|
||||||
.bg(cx.theme().transparent)
|
.bg(cx.theme().transparent)
|
||||||
.text_color(cx.theme().foreground)
|
.text_color(cx.theme().foreground)
|
||||||
.child(Icon::new(IconName::GalleryVerticalEnd).size_5())
|
.child(Icon::new(IconName::GalleryVerticalEnd))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.when(!self.collapsed, |this| {
|
.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(
|
.footer(
|
||||||
SidebarFooter::new()
|
SidebarFooter::new()
|
||||||
.collapsed(self.collapsed)
|
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|
@ -302,55 +337,8 @@ impl Render for SidebarStory {
|
||||||
.when(!self.collapsed, |this| this.child("Jason Lee")),
|
.when(!self.collapsed, |this| this.child("Jason Lee")),
|
||||||
)
|
)
|
||||||
.when(!self.collapsed, |this| {
|
.when(!self.collapsed, |this| {
|
||||||
this.child(
|
this.child(Icon::new(IconName::ChevronsUpDown).size_4())
|
||||||
Icon::new(IconName::ChevronsUpDown).size_4().flex_shrink_0(),
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
)
|
|
||||||
.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(
|
.child(
|
||||||
|
|
@ -379,12 +367,12 @@ impl Render for SidebarStory {
|
||||||
Breadcrumb::new()
|
Breadcrumb::new()
|
||||||
.item(BreadcrumbItem::new("0", "Home").on_click(cx.listener(
|
.item(BreadcrumbItem::new("0", "Home").on_click(cx.listener(
|
||||||
|this, _, _, cx| {
|
|this, _, _, cx| {
|
||||||
this.active_item = Item::Playground;
|
this.last_active_item = Item::Playground;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
},
|
},
|
||||||
)))
|
)))
|
||||||
.item(
|
.item(
|
||||||
BreadcrumbItem::new("1", self.active_item.label())
|
BreadcrumbItem::new("1", self.last_active_item.label())
|
||||||
.on_click(cx.listener(|this, _, _, cx| {
|
.on_click(cx.listener(|this, _, _, cx| {
|
||||||
this.active_subitem = None;
|
this.active_subitem = None;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,8 @@ impl RenderOnce for Icon {
|
||||||
let mut base = self.base;
|
let mut base = self.base;
|
||||||
*base.style() = self.style;
|
*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(!has_base_size, |this| this.size(text_size))
|
||||||
.when_some(self.size, |this, size| match size {
|
.when_some(self.size, |this, size| match size {
|
||||||
Size::Size(px) => this.size(px),
|
Size::Size(px) => this.size(px),
|
||||||
|
|
@ -318,7 +319,8 @@ impl Render for Icon {
|
||||||
let mut base = svg().flex_none();
|
let mut base = svg().flex_none();
|
||||||
*base.style() = self.style.clone();
|
*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(!has_base_size, |this| this.size(text_size))
|
||||||
.when_some(self.size, |this, size| match size {
|
.when_some(self.size, |this, size| match size {
|
||||||
Size::Size(px) => this.size(px),
|
Size::Size(px) => this.size(px),
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ impl Selectable for SidebarHeader {
|
||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collapsible for SidebarHeader {
|
impl Collapsible for SidebarHeader {
|
||||||
fn is_collapsed(&self) -> bool {
|
fn is_collapsed(&self) -> bool {
|
||||||
self.collapsed
|
self.collapsed
|
||||||
|
|
|
||||||
|
|
@ -81,8 +81,8 @@ impl SidebarMenuItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the icon for the menu item
|
/// Set the icon for the menu item
|
||||||
pub fn icon(mut self, icon: Icon) -> Self {
|
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
|
||||||
self.icon = Some(icon);
|
self.icon = Some(icon.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,7 +138,7 @@ impl SidebarMenuItem {
|
||||||
let is_submenu = self.is_submenu();
|
let is_submenu = self.is_submenu();
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id.clone())
|
.id("item")
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.p_2()
|
.p_2()
|
||||||
|
|
@ -155,9 +155,12 @@ impl SidebarMenuItem {
|
||||||
.bg(cx.theme().sidebar_accent)
|
.bg(cx.theme().sidebar_accent)
|
||||||
.text_color(cx.theme().sidebar_accent_foreground)
|
.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| {
|
.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| {
|
.when(!is_collapsed, |this| {
|
||||||
this.h_7()
|
this.h_7()
|
||||||
|
|
@ -181,6 +184,7 @@ impl RenderOnce for SidebarMenuItem {
|
||||||
let is_collapsed = self.collapsed;
|
let is_collapsed = self.collapsed;
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.id(self.id.clone())
|
||||||
.w_full()
|
.w_full()
|
||||||
.child(self.render_menu_item(window, cx))
|
.child(self.render_menu_item(window, cx))
|
||||||
.when(is_submenu && is_open && !is_collapsed, |this| {
|
.when(is_submenu && is_open && !is_collapsed, |this| {
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,8 @@ use crate::{
|
||||||
v_flex, ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt,
|
v_flex, ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, Entity, EntityId,
|
div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, InteractiveElement as _,
|
||||||
InteractiveElement as _, IntoElement, ParentElement, Pixels, Render, RenderOnce, Styled,
|
IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window,
|
||||||
Window,
|
|
||||||
};
|
};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
@ -26,8 +25,6 @@ const COLLAPSED_WIDTH: Pixels = px(48.);
|
||||||
/// A sidebar
|
/// A sidebar
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Sidebar<E: Collapsible + IntoElement + 'static> {
|
pub struct Sidebar<E: Collapsible + IntoElement + 'static> {
|
||||||
/// The parent view id
|
|
||||||
view_id: EntityId,
|
|
||||||
content: Vec<E>,
|
content: Vec<E>,
|
||||||
/// header view
|
/// header view
|
||||||
header: Option<AnyElement>,
|
header: Option<AnyElement>,
|
||||||
|
|
@ -41,9 +38,8 @@ pub struct Sidebar<E: Collapsible + IntoElement + 'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Collapsible + IntoElement> Sidebar<E> {
|
impl<E: Collapsible + IntoElement> Sidebar<E> {
|
||||||
fn new(view_id: EntityId, side: Side) -> Self {
|
pub fn new(side: Side) -> Self {
|
||||||
Self {
|
Self {
|
||||||
view_id,
|
|
||||||
content: vec![],
|
content: vec![],
|
||||||
header: None,
|
header: None,
|
||||||
footer: None,
|
footer: None,
|
||||||
|
|
@ -54,12 +50,12 @@ impl<E: Collapsible + IntoElement> Sidebar<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn left<V: Render + 'static>(view: &Entity<V>) -> Self {
|
pub fn left() -> Self {
|
||||||
Self::new(view.entity_id(), Side::Left)
|
Self::new(Side::Left)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn right<V: Render + 'static>(view: &Entity<V>) -> Self {
|
pub fn right() -> Self {
|
||||||
Self::new(view.entity_id(), Side::Right)
|
Self::new(Side::Right)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the width of the sidebar
|
/// Set the width of the sidebar
|
||||||
|
|
@ -117,7 +113,7 @@ pub struct SidebarToggleButton {
|
||||||
impl SidebarToggleButton {
|
impl SidebarToggleButton {
|
||||||
fn new(side: Side) -> Self {
|
fn new(side: Side) -> Self {
|
||||||
Self {
|
Self {
|
||||||
btn: Button::new("sidebar-collapse").ghost().small(),
|
btn: Button::new("collapse").ghost().small(),
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
side,
|
side,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
|
|
@ -181,7 +177,8 @@ impl RenderOnce for SidebarToggleButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
|
impl<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
|
||||||
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()
|
v_flex()
|
||||||
.id("sidebar")
|
.id("sidebar")
|
||||||
.w(self.width)
|
.w(self.width)
|
||||||
|
|
@ -210,7 +207,7 @@ impl<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
|
||||||
.map(|(ix, c)| div().id(ix).child(c.collapsed(self.collapsed))),
|
.map(|(ix, c)| div().id(ix).child(c.collapsed(self.collapsed))),
|
||||||
)
|
)
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.scrollable(self.view_id, ScrollbarAxis::Vertical),
|
.scrollable(view_id, ScrollbarAxis::Vertical),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.when_some(self.footer.take(), |this, footer| {
|
.when_some(self.footer.take(), |this, footer| {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue