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