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, SidebarItemId);
 27action!(ToggleSidebarItemFocus, SidebarItemId);
 28
 29#[derive(Clone)]
 30pub struct SidebarItemId {
 31    pub side: Side,
 32    pub item_index: usize,
 33}
 34
 35impl Sidebar {
 36    pub fn new(side: Side) -> Self {
 37        Self {
 38            side,
 39            items: Default::default(),
 40            active_item_ix: None,
 41            width: Rc::new(RefCell::new(260.)),
 42        }
 43    }
 44
 45    pub fn add_item(&mut self, icon_path: &'static str, view: AnyViewHandle) {
 46        self.items.push(Item { icon_path, view });
 47    }
 48
 49    pub fn activate_item(&mut self, item_ix: usize) {
 50        self.active_item_ix = Some(item_ix);
 51    }
 52
 53    pub fn toggle_item(&mut self, item_ix: usize) {
 54        if self.active_item_ix == Some(item_ix) {
 55            self.active_item_ix = None;
 56        } else {
 57            self.active_item_ix = Some(item_ix);
 58        }
 59    }
 60
 61    pub fn active_item(&self) -> Option<&AnyViewHandle> {
 62        self.active_item_ix
 63            .and_then(|ix| self.items.get(ix))
 64            .map(|item| &item.view)
 65    }
 66
 67    fn theme<'a>(&self, settings: &'a Settings) -> &'a theme::Sidebar {
 68        match self.side {
 69            Side::Left => &settings.theme.workspace.left_sidebar,
 70            Side::Right => &settings.theme.workspace.right_sidebar,
 71        }
 72    }
 73
 74    pub fn render(&self, settings: &Settings, cx: &mut RenderContext<Workspace>) -> ElementBox {
 75        let side = self.side;
 76        let theme = self.theme(settings);
 77
 78        ConstrainedBox::new(
 79            Container::new(
 80                Flex::column()
 81                    .with_children(self.items.iter().enumerate().map(|(item_index, item)| {
 82                        let theme = if Some(item_index) == self.active_item_ix {
 83                            &theme.active_item
 84                        } else {
 85                            &theme.item
 86                        };
 87                        enum SidebarButton {}
 88                        MouseEventHandler::new::<SidebarButton, _, _, _>(
 89                            item.view.id(),
 90                            cx,
 91                            |_, _| {
 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                        )
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        settings: &Settings,
129        cx: &mut MutableAppContext,
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(settings, cx));
135            }
136
137            container.add_child(
138                Flexible::new(
139                    1.,
140                    Hook::new(
141                        ConstrainedBox::new(ChildView::new(active_item.id()).boxed())
142                            .with_max_width(*self.width.borrow())
143                            .boxed(),
144                    )
145                    .on_after_layout({
146                        let width = self.width.clone();
147                        move |size, _| *width.borrow_mut() = size.x()
148                    })
149                    .boxed(),
150                )
151                .boxed(),
152            );
153            if matches!(self.side, Side::Left) {
154                container.add_child(self.render_resize_handle(settings, cx));
155            }
156            Some(container.boxed())
157        } else {
158            None
159        }
160    }
161
162    fn render_resize_handle(
163        &self,
164        settings: &Settings,
165        mut cx: &mut MutableAppContext,
166    ) -> ElementBox {
167        let width = self.width.clone();
168        let side = self.side;
169        MouseEventHandler::new::<Self, _, _, _>(self.side.id(), &mut cx, |_, _| {
170            Container::new(Empty::new().boxed())
171                .with_style(self.theme(settings).resize_handle)
172                .boxed()
173        })
174        .with_padding(Padding {
175            left: 4.,
176            right: 4.,
177            ..Default::default()
178        })
179        .with_cursor_style(CursorStyle::ResizeLeftRight)
180        .on_drag(move |delta, cx| {
181            let prev_width = *width.borrow();
182            match side {
183                Side::Left => *width.borrow_mut() = 0f32.max(prev_width + delta.x()),
184                Side::Right => *width.borrow_mut() = 0f32.max(prev_width - delta.x()),
185            }
186
187            cx.notify();
188        })
189        .boxed()
190    }
191}
192
193impl Side {
194    fn id(self) -> usize {
195        match self {
196            Side::Left => 0,
197            Side::Right => 1,
198        }
199    }
200}