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, ToggleArg);
27
28#[derive(Clone)]
29pub struct ToggleArg {
30 pub side: Side,
31 pub item_index: usize,
32}
33
34impl Sidebar {
35 pub fn new(side: Side) -> Self {
36 Self {
37 side,
38 items: Default::default(),
39 active_item_ix: None,
40 width: Rc::new(RefCell::new(100.)),
41 }
42 }
43
44 pub fn add_item(&mut self, icon_path: &'static str, view: AnyViewHandle) {
45 self.items.push(Item { icon_path, view });
46 }
47
48 pub fn toggle_item(&mut self, item_ix: usize) {
49 if self.active_item_ix == Some(item_ix) {
50 self.active_item_ix = None;
51 } else {
52 self.active_item_ix = Some(item_ix);
53 }
54 }
55
56 pub fn active_item(&self) -> Option<&AnyViewHandle> {
57 self.active_item_ix
58 .and_then(|ix| self.items.get(ix))
59 .map(|item| &item.view)
60 }
61
62 pub fn render(&self, settings: &Settings, cx: &mut RenderContext<Workspace>) -> ElementBox {
63 let side = self.side;
64 let theme = &settings.theme;
65 let line_height = cx.font_cache().line_height(
66 theme.workspace.tab.label.text.font_id,
67 theme.workspace.tab.label.text.font_size,
68 );
69
70 Container::new(
71 Flex::column()
72 .with_children(self.items.iter().enumerate().map(|(item_index, item)| {
73 let theme = if Some(item_index) == self.active_item_ix {
74 &settings.theme.workspace.active_sidebar_icon
75 } else {
76 &settings.theme.workspace.sidebar_icon
77 };
78 enum SidebarButton {}
79 MouseEventHandler::new::<SidebarButton, _, _>(item.view.id(), cx, |_, _| {
80 ConstrainedBox::new(
81 Align::new(
82 ConstrainedBox::new(
83 Svg::new(item.icon_path).with_color(theme.color).boxed(),
84 )
85 .with_height(line_height)
86 .boxed(),
87 )
88 .boxed(),
89 )
90 .with_height(line_height + 16.0)
91 .boxed()
92 })
93 .with_cursor_style(CursorStyle::PointingHand)
94 .on_click(move |cx| {
95 cx.dispatch_action(ToggleSidebarItem(ToggleArg { side, item_index }))
96 })
97 .boxed()
98 }))
99 .boxed(),
100 )
101 .with_style(&settings.theme.workspace.sidebar.icons)
102 .boxed()
103 }
104
105 pub fn render_active_item(
106 &self,
107 settings: &Settings,
108 cx: &mut MutableAppContext,
109 ) -> Option<ElementBox> {
110 if let Some(active_item) = self.active_item() {
111 let mut container = Flex::row();
112 if matches!(self.side, Side::Right) {
113 container.add_child(self.render_resize_handle(settings, cx));
114 }
115 container.add_child(
116 ConstrainedBox::new(ChildView::new(active_item.id()).boxed())
117 .with_width(*self.width.borrow())
118 .boxed(),
119 );
120 if matches!(self.side, Side::Left) {
121 container.add_child(self.render_resize_handle(settings, cx));
122 }
123 Some(container.boxed())
124 } else {
125 None
126 }
127 }
128
129 fn render_resize_handle(
130 &self,
131 settings: &Settings,
132 mut cx: &mut MutableAppContext,
133 ) -> ElementBox {
134 let width = self.width.clone();
135 let side = self.side;
136 MouseEventHandler::new::<Self, _, _>(self.side.id(), &mut cx, |_, _| {
137 Container::new(Empty::new().boxed())
138 .with_style(&settings.theme.workspace.sidebar.resize_handle)
139 .boxed()
140 })
141 .with_cursor_style(CursorStyle::ResizeLeftRight)
142 .on_drag(move |delta, cx| {
143 let prev_width = *width.borrow();
144 match side {
145 Side::Left => *width.borrow_mut() = 0f32.max(prev_width + delta.x()),
146 Side::Right => *width.borrow_mut() = 0f32.max(prev_width - delta.x()),
147 }
148
149 cx.notify();
150 })
151 .boxed()
152 }
153}
154
155impl Side {
156 fn id(self) -> usize {
157 match self {
158 Side::Left => 0,
159 Side::Right => 1,
160 }
161 }
162}