sidebar.rs

  1use super::Workspace;
  2use crate::Settings;
  3use gpui::{
  4    action, elements::*, platform::CursorStyle, AnyViewHandle, MutableAppContext, RenderContext,
  5};
  6use std::{cell::RefCell, rc::Rc};
  7
  8pub struct Sidebar {
  9    side: Side,
 10    items: Vec<Item>,
 11    active_item_ix: Option<usize>,
 12    width: Rc<RefCell<f32>>,
 13}
 14
 15#[derive(Clone, Copy)]
 16pub enum Side {
 17    Left,
 18    Right,
 19}
 20
 21struct Item {
 22    icon_path: &'static str,
 23    view: AnyViewHandle,
 24}
 25
 26action!(ToggleSidebarItem, ToggleArg);
 27
 28#[derive(Clone)]
 29pub struct ToggleArg {
 30    pub side: Side,
 31    pub item_index: usize,
 32}
 33
 34impl Sidebar {
 35    pub fn new(side: Side) -> Self {
 36        Self {
 37            side,
 38            items: Default::default(),
 39            active_item_ix: None,
 40            width: Rc::new(RefCell::new(100.)),
 41        }
 42    }
 43
 44    pub fn add_item(&mut self, icon_path: &'static str, view: AnyViewHandle) {
 45        self.items.push(Item { icon_path, view });
 46    }
 47
 48    pub fn toggle_item(&mut self, item_ix: usize) {
 49        if self.active_item_ix == Some(item_ix) {
 50            self.active_item_ix = None;
 51        } else {
 52            self.active_item_ix = Some(item_ix);
 53        }
 54    }
 55
 56    pub fn active_item(&self) -> Option<&AnyViewHandle> {
 57        self.active_item_ix
 58            .and_then(|ix| self.items.get(ix))
 59            .map(|item| &item.view)
 60    }
 61
 62    pub fn render(&self, settings: &Settings, cx: &mut RenderContext<Workspace>) -> ElementBox {
 63        let side = self.side;
 64        let theme = &settings.theme;
 65        let line_height = cx.font_cache().line_height(
 66            theme.workspace.tab.label.text.font_id,
 67            theme.workspace.tab.label.text.font_size,
 68        );
 69
 70        Container::new(
 71            Flex::column()
 72                .with_children(self.items.iter().enumerate().map(|(item_index, item)| {
 73                    let theme = if Some(item_index) == self.active_item_ix {
 74                        &settings.theme.workspace.active_sidebar_icon
 75                    } else {
 76                        &settings.theme.workspace.sidebar_icon
 77                    };
 78                    enum SidebarButton {}
 79                    MouseEventHandler::new::<SidebarButton, _, _>(item.view.id(), cx, |_, _| {
 80                        ConstrainedBox::new(
 81                            Align::new(
 82                                ConstrainedBox::new(
 83                                    Svg::new(item.icon_path).with_color(theme.color).boxed(),
 84                                )
 85                                .with_height(line_height)
 86                                .boxed(),
 87                            )
 88                            .boxed(),
 89                        )
 90                        .with_height(line_height + 16.0)
 91                        .boxed()
 92                    })
 93                    .with_cursor_style(CursorStyle::PointingHand)
 94                    .on_click(move |cx| {
 95                        cx.dispatch_action(ToggleSidebarItem(ToggleArg { side, item_index }))
 96                    })
 97                    .boxed()
 98                }))
 99                .boxed(),
100        )
101        .with_style(&settings.theme.workspace.sidebar.icons)
102        .boxed()
103    }
104
105    pub fn render_active_item(
106        &self,
107        settings: &Settings,
108        cx: &mut MutableAppContext,
109    ) -> Option<ElementBox> {
110        if let Some(active_item) = self.active_item() {
111            let mut container = Flex::row();
112            if matches!(self.side, Side::Right) {
113                container.add_child(self.render_resize_handle(settings, cx));
114            }
115            container.add_child(
116                ConstrainedBox::new(ChildView::new(active_item.id()).boxed())
117                    .with_width(*self.width.borrow())
118                    .boxed(),
119            );
120            if matches!(self.side, Side::Left) {
121                container.add_child(self.render_resize_handle(settings, cx));
122            }
123            Some(container.boxed())
124        } else {
125            None
126        }
127    }
128
129    fn render_resize_handle(
130        &self,
131        settings: &Settings,
132        mut cx: &mut MutableAppContext,
133    ) -> ElementBox {
134        let width = self.width.clone();
135        let side = self.side;
136        MouseEventHandler::new::<Self, _, _>(self.side.id(), &mut cx, |_, _| {
137            Container::new(Empty::new().boxed())
138                .with_style(&settings.theme.workspace.sidebar.resize_handle)
139                .boxed()
140        })
141        .with_cursor_style(CursorStyle::ResizeLeftRight)
142        .on_drag(move |delta, cx| {
143            let prev_width = *width.borrow();
144            match side {
145                Side::Left => *width.borrow_mut() = 0f32.max(prev_width + delta.x()),
146                Side::Right => *width.borrow_mut() = 0f32.max(prev_width - delta.x()),
147            }
148
149            cx.notify();
150        })
151        .boxed()
152    }
153}
154
155impl Side {
156    fn id(self) -> usize {
157        match self {
158            Side::Left => 0,
159            Side::Right => 1,
160        }
161    }
162}