sidebar.rs

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