sidebar.rs

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