1use crate::{ItemHandle, Pane};
2use gpui::{
3 AnyView, App, Context, Decorations, Entity, IntoElement, ParentElement, Render, Styled,
4 Subscription, Window,
5};
6use std::any::TypeId;
7use theme::CLIENT_SIDE_DECORATION_ROUNDING;
8use ui::{h_flex, prelude::*};
9use util::ResultExt;
10
11pub trait StatusItemView: Render {
12 /// Event callback that is triggered when the active pane item changes.
13 fn set_active_pane_item(
14 &mut self,
15 active_pane_item: Option<&dyn crate::ItemHandle>,
16 window: &mut Window,
17 cx: &mut Context<Self>,
18 );
19}
20
21trait StatusItemViewHandle: Send {
22 fn to_any(&self) -> AnyView;
23 fn set_active_pane_item(
24 &self,
25 active_pane_item: Option<&dyn ItemHandle>,
26 window: &mut Window,
27 cx: &mut App,
28 );
29 fn item_type(&self) -> TypeId;
30}
31
32pub struct StatusBar {
33 left_items: Vec<Box<dyn StatusItemViewHandle>>,
34 right_items: Vec<Box<dyn StatusItemViewHandle>>,
35 active_pane: Entity<Pane>,
36 _observe_active_pane: Subscription,
37 workspace_sidebar_open: bool,
38}
39
40impl Render for StatusBar {
41 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
42 h_flex()
43 .w_full()
44 .justify_between()
45 .gap(DynamicSpacing::Base08.rems(cx))
46 .py(DynamicSpacing::Base04.rems(cx))
47 .px(DynamicSpacing::Base06.rems(cx))
48 .bg(cx.theme().colors().status_bar_background)
49 .map(|el| match window.window_decorations() {
50 Decorations::Server => el,
51 Decorations::Client { tiling, .. } => el
52 .when(!(tiling.bottom || tiling.right), |el| {
53 el.rounded_br(CLIENT_SIDE_DECORATION_ROUNDING)
54 })
55 .when(
56 !(tiling.bottom || tiling.left) && !self.workspace_sidebar_open,
57 |el| el.rounded_bl(CLIENT_SIDE_DECORATION_ROUNDING),
58 )
59 // This border is to avoid a transparent gap in the rounded corners
60 .mb(px(-1.))
61 .border_b(px(1.0))
62 .border_color(cx.theme().colors().status_bar_background),
63 })
64 .child(self.render_left_tools())
65 .child(self.render_right_tools())
66 }
67}
68
69impl StatusBar {
70 fn render_left_tools(&self) -> impl IntoElement {
71 h_flex()
72 .gap_1()
73 .min_w_0()
74 .overflow_x_hidden()
75 .children(self.left_items.iter().map(|item| item.to_any()))
76 }
77
78 fn render_right_tools(&self) -> impl IntoElement {
79 h_flex()
80 .flex_shrink_0()
81 .gap_1()
82 .overflow_x_hidden()
83 .children(self.right_items.iter().rev().map(|item| item.to_any()))
84 }
85}
86
87impl StatusBar {
88 pub fn new(active_pane: &Entity<Pane>, window: &mut Window, cx: &mut Context<Self>) -> Self {
89 let mut this = Self {
90 left_items: Default::default(),
91 right_items: Default::default(),
92 active_pane: active_pane.clone(),
93 _observe_active_pane: cx.observe_in(active_pane, window, |this, _, window, cx| {
94 this.update_active_pane_item(window, cx)
95 }),
96 workspace_sidebar_open: false,
97 };
98 this.update_active_pane_item(window, cx);
99 this
100 }
101
102 pub fn set_workspace_sidebar_open(&mut self, open: bool, cx: &mut Context<Self>) {
103 self.workspace_sidebar_open = open;
104 cx.notify();
105 }
106
107 pub fn add_left_item<T>(&mut self, item: Entity<T>, window: &mut Window, cx: &mut Context<Self>)
108 where
109 T: 'static + StatusItemView,
110 {
111 let active_pane_item = self.active_pane.read(cx).active_item();
112 item.set_active_pane_item(active_pane_item.as_deref(), window, cx);
113
114 self.left_items.push(Box::new(item));
115 cx.notify();
116 }
117
118 pub fn item_of_type<T: StatusItemView>(&self) -> Option<Entity<T>> {
119 self.left_items
120 .iter()
121 .chain(self.right_items.iter())
122 .find_map(|item| item.to_any().downcast().log_err())
123 }
124
125 pub fn position_of_item<T>(&self) -> Option<usize>
126 where
127 T: StatusItemView,
128 {
129 for (index, item) in self.left_items.iter().enumerate() {
130 if item.item_type() == TypeId::of::<T>() {
131 return Some(index);
132 }
133 }
134 for (index, item) in self.right_items.iter().enumerate() {
135 if item.item_type() == TypeId::of::<T>() {
136 return Some(index + self.left_items.len());
137 }
138 }
139 None
140 }
141
142 pub fn insert_item_after<T>(
143 &mut self,
144 position: usize,
145 item: Entity<T>,
146 window: &mut Window,
147 cx: &mut Context<Self>,
148 ) where
149 T: 'static + StatusItemView,
150 {
151 let active_pane_item = self.active_pane.read(cx).active_item();
152 item.set_active_pane_item(active_pane_item.as_deref(), window, cx);
153
154 if position < self.left_items.len() {
155 self.left_items.insert(position + 1, Box::new(item))
156 } else {
157 self.right_items
158 .insert(position + 1 - self.left_items.len(), Box::new(item))
159 }
160 cx.notify()
161 }
162
163 pub fn remove_item_at(&mut self, position: usize, cx: &mut Context<Self>) {
164 if position < self.left_items.len() {
165 self.left_items.remove(position);
166 } else {
167 self.right_items.remove(position - self.left_items.len());
168 }
169 cx.notify();
170 }
171
172 pub fn add_right_item<T>(
173 &mut self,
174 item: Entity<T>,
175 window: &mut Window,
176 cx: &mut Context<Self>,
177 ) where
178 T: 'static + StatusItemView,
179 {
180 let active_pane_item = self.active_pane.read(cx).active_item();
181 item.set_active_pane_item(active_pane_item.as_deref(), window, cx);
182
183 self.right_items.push(Box::new(item));
184 cx.notify();
185 }
186
187 pub fn set_active_pane(
188 &mut self,
189 active_pane: &Entity<Pane>,
190 window: &mut Window,
191 cx: &mut Context<Self>,
192 ) {
193 self.active_pane = active_pane.clone();
194 self._observe_active_pane = cx.observe_in(active_pane, window, |this, _, window, cx| {
195 this.update_active_pane_item(window, cx)
196 });
197 self.update_active_pane_item(window, cx);
198 }
199
200 fn update_active_pane_item(&mut self, window: &mut Window, cx: &mut Context<Self>) {
201 let active_pane_item = self.active_pane.read(cx).active_item();
202 for item in self.left_items.iter().chain(&self.right_items) {
203 item.set_active_pane_item(active_pane_item.as_deref(), window, cx);
204 }
205 }
206}
207
208impl<T: StatusItemView> StatusItemViewHandle for Entity<T> {
209 fn to_any(&self) -> AnyView {
210 self.clone().into()
211 }
212
213 fn set_active_pane_item(
214 &self,
215 active_pane_item: Option<&dyn ItemHandle>,
216 window: &mut Window,
217 cx: &mut App,
218 ) {
219 self.update(cx, |this, cx| {
220 this.set_active_pane_item(active_pane_item, window, cx)
221 });
222 }
223
224 fn item_type(&self) -> TypeId {
225 TypeId::of::<T>()
226 }
227}
228
229impl From<&dyn StatusItemViewHandle> for AnyView {
230 fn from(val: &dyn StatusItemViewHandle) -> Self {
231 val.to_any()
232 }
233}