sidebar.rs

  1use super::Workspace;
  2use gpui::{
  3    elements::*, impl_internal_actions, platform::CursorStyle, AnyViewHandle, RenderContext,
  4};
  5use std::{cell::RefCell, rc::Rc};
  6use theme::Theme;
  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
 26#[derive(Clone)]
 27pub struct ToggleSidebarItem(pub SidebarItemId);
 28
 29#[derive(Clone)]
 30pub struct ToggleSidebarItemFocus(pub SidebarItemId);
 31
 32impl_internal_actions!(workspace, [ToggleSidebarItem, ToggleSidebarItemFocus]);
 33
 34#[derive(Clone)]
 35pub struct SidebarItemId {
 36    pub side: Side,
 37    pub item_index: usize,
 38}
 39
 40impl Sidebar {
 41    pub fn new(side: Side) -> Self {
 42        Self {
 43            side,
 44            items: Default::default(),
 45            active_item_ix: None,
 46            width: Rc::new(RefCell::new(260.)),
 47        }
 48    }
 49
 50    pub fn add_item(&mut self, icon_path: &'static str, view: AnyViewHandle) {
 51        self.items.push(Item { icon_path, view });
 52    }
 53
 54    pub fn activate_item(&mut self, item_ix: usize) {
 55        self.active_item_ix = Some(item_ix);
 56    }
 57
 58    pub fn toggle_item(&mut self, item_ix: usize) {
 59        if self.active_item_ix == Some(item_ix) {
 60            self.active_item_ix = None;
 61        } else {
 62            self.active_item_ix = Some(item_ix);
 63        }
 64    }
 65
 66    pub fn active_item(&self) -> Option<&AnyViewHandle> {
 67        self.active_item_ix
 68            .and_then(|ix| self.items.get(ix))
 69            .map(|item| &item.view)
 70    }
 71
 72    fn theme<'a>(&self, theme: &'a Theme) -> &'a theme::Sidebar {
 73        match self.side {
 74            Side::Left => &theme.workspace.left_sidebar,
 75            Side::Right => &theme.workspace.right_sidebar,
 76        }
 77    }
 78
 79    pub fn render(&self, theme: &Theme, cx: &mut RenderContext<Workspace>) -> ElementBox {
 80        let side = self.side;
 81        let theme = self.theme(theme);
 82
 83        ConstrainedBox::new(
 84            Container::new(
 85                Flex::column()
 86                    .with_children(self.items.iter().enumerate().map(|(item_index, item)| {
 87                        let theme = if Some(item_index) == self.active_item_ix {
 88                            &theme.active_item
 89                        } else {
 90                            &theme.item
 91                        };
 92                        enum SidebarButton {}
 93                        MouseEventHandler::new::<SidebarButton, _, _>(item.view.id(), cx, |_, _| {
 94                            ConstrainedBox::new(
 95                                Align::new(
 96                                    ConstrainedBox::new(
 97                                        Svg::new(item.icon_path)
 98                                            .with_color(theme.icon_color)
 99                                            .boxed(),
100                                    )
101                                    .with_height(theme.icon_size)
102                                    .boxed(),
103                                )
104                                .boxed(),
105                            )
106                            .with_height(theme.height)
107                            .boxed()
108                        })
109                        .with_cursor_style(CursorStyle::PointingHand)
110                        .on_mouse_down(move |cx| {
111                            cx.dispatch_action(ToggleSidebarItem(SidebarItemId {
112                                side,
113                                item_index,
114                            }))
115                        })
116                        .boxed()
117                    }))
118                    .boxed(),
119            )
120            .with_style(theme.container)
121            .boxed(),
122        )
123        .with_width(theme.width)
124        .boxed()
125    }
126
127    pub fn render_active_item(
128        &self,
129        theme: &Theme,
130        cx: &mut RenderContext<Workspace>,
131    ) -> Option<ElementBox> {
132        if let Some(active_item) = self.active_item() {
133            let mut container = Flex::row();
134            if matches!(self.side, Side::Right) {
135                container.add_child(self.render_resize_handle(theme, cx));
136            }
137
138            container.add_child(
139                Hook::new(
140                    ConstrainedBox::new(ChildView::new(active_item).boxed())
141                        .with_max_width(*self.width.borrow())
142                        .boxed(),
143                )
144                .on_after_layout({
145                    let width = self.width.clone();
146                    move |size, _| *width.borrow_mut() = size.x()
147                })
148                .flex(1., false)
149                .boxed(),
150            );
151            if matches!(self.side, Side::Left) {
152                container.add_child(self.render_resize_handle(theme, cx));
153            }
154            Some(container.boxed())
155        } else {
156            None
157        }
158    }
159
160    fn render_resize_handle(&self, theme: &Theme, cx: &mut RenderContext<Workspace>) -> ElementBox {
161        let width = self.width.clone();
162        let side = self.side;
163        MouseEventHandler::new::<Self, _, _>(side as usize, cx, |_, _| {
164            Container::new(Empty::new().boxed())
165                .with_style(self.theme(theme).resize_handle)
166                .boxed()
167        })
168        .with_padding(Padding {
169            left: 4.,
170            right: 4.,
171            ..Default::default()
172        })
173        .with_cursor_style(CursorStyle::ResizeLeftRight)
174        .on_drag(move |delta, cx| {
175            let prev_width = *width.borrow();
176            match side {
177                Side::Left => *width.borrow_mut() = 0f32.max(prev_width + delta.x()),
178                Side::Right => *width.borrow_mut() = 0f32.max(prev_width - delta.x()),
179            }
180
181            cx.notify();
182        })
183        .boxed()
184    }
185}