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}