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 Hook::new(
139 ConstrainedBox::new(ChildView::new(active_item.id()).boxed())
140 .with_max_width(*self.width.borrow())
141 .boxed(),
142 )
143 .on_after_layout({
144 let width = self.width.clone();
145 move |size, _| *width.borrow_mut() = size.x()
146 })
147 .flexible(1., false)
148 .boxed(),
149 );
150 if matches!(self.side, Side::Left) {
151 container.add_child(self.render_resize_handle(settings, cx));
152 }
153 Some(container.boxed())
154 } else {
155 None
156 }
157 }
158
159 fn render_resize_handle(
160 &self,
161 settings: &Settings,
162 mut cx: &mut MutableAppContext,
163 ) -> ElementBox {
164 let width = self.width.clone();
165 let side = self.side;
166 MouseEventHandler::new::<Self, _, _, _>(self.side.id(), &mut cx, |_, _| {
167 Container::new(Empty::new().boxed())
168 .with_style(self.theme(settings).resize_handle)
169 .boxed()
170 })
171 .with_padding(Padding {
172 left: 4.,
173 right: 4.,
174 ..Default::default()
175 })
176 .with_cursor_style(CursorStyle::ResizeLeftRight)
177 .on_drag(move |delta, cx| {
178 let prev_width = *width.borrow();
179 match side {
180 Side::Left => *width.borrow_mut() = 0f32.max(prev_width + delta.x()),
181 Side::Right => *width.borrow_mut() = 0f32.max(prev_width - delta.x()),
182 }
183
184 cx.notify();
185 })
186 .boxed()
187 }
188}
189
190impl Side {
191 fn id(self) -> usize {
192 match self {
193 Side::Left => 0,
194 Side::Right => 1,
195 }
196 }
197}