sidebar.rs

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