1use super::Workspace;
2use gpui::{action, elements::*, 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
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, theme: &'a Theme) -> &'a theme::Sidebar {
66 match self.side {
67 Side::Left => &theme.workspace.left_sidebar,
68 Side::Right => &theme.workspace.right_sidebar,
69 }
70 }
71
72 pub fn render(&self, theme: &Theme, cx: &mut RenderContext<Workspace>) -> ElementBox {
73 let side = self.side;
74 let theme = self.theme(theme);
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, _, _>(item.view.id(), cx, |_, _| {
87 ConstrainedBox::new(
88 Align::new(
89 ConstrainedBox::new(
90 Svg::new(item.icon_path)
91 .with_color(theme.icon_color)
92 .boxed(),
93 )
94 .with_height(theme.icon_size)
95 .boxed(),
96 )
97 .boxed(),
98 )
99 .with_height(theme.height)
100 .boxed()
101 })
102 .with_cursor_style(CursorStyle::PointingHand)
103 .on_mouse_down(move |cx| {
104 cx.dispatch_action(ToggleSidebarItem(SidebarItemId {
105 side,
106 item_index,
107 }))
108 })
109 .boxed()
110 }))
111 .boxed(),
112 )
113 .with_style(theme.container)
114 .boxed(),
115 )
116 .with_width(theme.width)
117 .boxed()
118 }
119
120 pub fn render_active_item(
121 &self,
122 theme: &Theme,
123 cx: &mut RenderContext<Workspace>,
124 ) -> Option<ElementBox> {
125 if let Some(active_item) = self.active_item() {
126 let mut container = Flex::row();
127 if matches!(self.side, Side::Right) {
128 container.add_child(self.render_resize_handle(theme, cx));
129 }
130
131 container.add_child(
132 Hook::new(
133 ConstrainedBox::new(ChildView::new(active_item).boxed())
134 .with_max_width(*self.width.borrow())
135 .boxed(),
136 )
137 .on_after_layout({
138 let width = self.width.clone();
139 move |size, _| *width.borrow_mut() = size.x()
140 })
141 .flex(1., false)
142 .boxed(),
143 );
144 if matches!(self.side, Side::Left) {
145 container.add_child(self.render_resize_handle(theme, cx));
146 }
147 Some(container.boxed())
148 } else {
149 None
150 }
151 }
152
153 fn render_resize_handle(&self, theme: &Theme, cx: &mut RenderContext<Workspace>) -> ElementBox {
154 let width = self.width.clone();
155 let side = self.side;
156 MouseEventHandler::new::<Self, _, _>(side as usize, cx, |_, _| {
157 Container::new(Empty::new().boxed())
158 .with_style(self.theme(theme).resize_handle)
159 .boxed()
160 })
161 .with_padding(Padding {
162 left: 4.,
163 right: 4.,
164 ..Default::default()
165 })
166 .with_cursor_style(CursorStyle::ResizeLeftRight)
167 .on_drag(move |delta, cx| {
168 let prev_width = *width.borrow();
169 match side {
170 Side::Left => *width.borrow_mut() = 0f32.max(prev_width + delta.x()),
171 Side::Right => *width.borrow_mut() = 0f32.max(prev_width - delta.x()),
172 }
173
174 cx.notify();
175 })
176 .boxed()
177 }
178}