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