use super::Workspace; use gpui::{action, elements::*, platform::CursorStyle, AnyViewHandle, RenderContext}; use std::{cell::RefCell, rc::Rc}; use theme::Theme; pub struct Sidebar { side: Side, items: Vec, active_item_ix: Option, width: Rc>, } #[derive(Clone, Copy)] pub enum Side { Left, Right, } struct Item { icon_path: &'static str, view: AnyViewHandle, } action!(ToggleSidebarItem, SidebarItemId); action!(ToggleSidebarItemFocus, SidebarItemId); #[derive(Clone)] pub struct SidebarItemId { pub side: Side, pub item_index: usize, } impl Sidebar { pub fn new(side: Side) -> Self { Self { side, items: Default::default(), active_item_ix: None, width: Rc::new(RefCell::new(260.)), } } pub fn add_item(&mut self, icon_path: &'static str, view: AnyViewHandle) { self.items.push(Item { icon_path, view }); } pub fn activate_item(&mut self, item_ix: usize) { self.active_item_ix = Some(item_ix); } pub fn toggle_item(&mut self, item_ix: usize) { if self.active_item_ix == Some(item_ix) { self.active_item_ix = None; } else { self.active_item_ix = Some(item_ix); } } pub fn active_item(&self) -> Option<&AnyViewHandle> { self.active_item_ix .and_then(|ix| self.items.get(ix)) .map(|item| &item.view) } fn theme<'a>(&self, theme: &'a Theme) -> &'a theme::Sidebar { match self.side { Side::Left => &theme.workspace.left_sidebar, Side::Right => &theme.workspace.right_sidebar, } } pub fn render(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { let side = self.side; let theme = self.theme(theme); ConstrainedBox::new( Container::new( Flex::column() .with_children(self.items.iter().enumerate().map(|(item_index, item)| { let theme = if Some(item_index) == self.active_item_ix { &theme.active_item } else { &theme.item }; enum SidebarButton {} MouseEventHandler::new::(item.view.id(), cx, |_, _| { ConstrainedBox::new( Align::new( ConstrainedBox::new( Svg::new(item.icon_path) .with_color(theme.icon_color) .boxed(), ) .with_height(theme.icon_size) .boxed(), ) .boxed(), ) .with_height(theme.height) .boxed() }) .with_cursor_style(CursorStyle::PointingHand) .on_mouse_down(move |cx| { cx.dispatch_action(ToggleSidebarItem(SidebarItemId { side, item_index, })) }) .boxed() })) .boxed(), ) .with_style(theme.container) .boxed(), ) .with_width(theme.width) .boxed() } pub fn render_active_item( &self, theme: &Theme, cx: &mut RenderContext, ) -> Option { if let Some(active_item) = self.active_item() { let mut container = Flex::row(); if matches!(self.side, Side::Right) { container.add_child(self.render_resize_handle(theme, cx)); } container.add_child( Hook::new( ConstrainedBox::new(ChildView::new(active_item).boxed()) .with_max_width(*self.width.borrow()) .boxed(), ) .on_after_layout({ let width = self.width.clone(); move |size, _| *width.borrow_mut() = size.x() }) .flexible(1., false) .boxed(), ); if matches!(self.side, Side::Left) { container.add_child(self.render_resize_handle(theme, cx)); } Some(container.boxed()) } else { None } } fn render_resize_handle(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { let width = self.width.clone(); let side = self.side; MouseEventHandler::new::(side as usize, cx, |_, _| { Container::new(Empty::new().boxed()) .with_style(self.theme(theme).resize_handle) .boxed() }) .with_padding(Padding { left: 4., right: 4., ..Default::default() }) .with_cursor_style(CursorStyle::ResizeLeftRight) .on_drag(move |delta, cx| { let prev_width = *width.borrow(); match side { Side::Left => *width.borrow_mut() = 0f32.max(prev_width + delta.x()), Side::Right => *width.borrow_mut() = 0f32.max(prev_width - delta.x()), } cx.notify(); }) .boxed() } }