1use std::any::TypeId;
2
3use crate::{ItemHandle, Pane};
4use gpui::{
5 div, AnyView, Div, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
6 WindowContext,
7};
8use ui::prelude::*;
9use ui::{h_stack, Button, Icon, IconButton};
10use util::ResultExt;
11
12pub trait StatusItemView: Render {
13 fn set_active_pane_item(
14 &mut self,
15 active_pane_item: Option<&dyn crate::ItemHandle>,
16 cx: &mut ViewContext<Self>,
17 );
18}
19
20trait StatusItemViewHandle: Send {
21 fn to_any(&self) -> AnyView;
22 fn set_active_pane_item(
23 &self,
24 active_pane_item: Option<&dyn ItemHandle>,
25 cx: &mut WindowContext,
26 );
27 fn item_type(&self) -> TypeId;
28}
29
30pub struct StatusBar {
31 left_items: Vec<Box<dyn StatusItemViewHandle>>,
32 right_items: Vec<Box<dyn StatusItemViewHandle>>,
33 active_pane: View<Pane>,
34 _observe_active_pane: Subscription,
35}
36
37impl Render for StatusBar {
38 type Element = Div;
39
40 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
41 div()
42 .py_0p5()
43 .px_1()
44 .flex()
45 .items_center()
46 .justify_between()
47 .w_full()
48 .h_8()
49 .bg(cx.theme().colors().status_bar_background)
50 .child(h_stack().gap_1().child(self.render_left_tools(cx)))
51 .child(
52 h_stack()
53 .gap_4()
54 .child(
55 h_stack().gap_1().child(
56 // TODO: Language picker
57 div()
58 .border()
59 .border_color(gpui::red())
60 .child(Button::new("status_buffer_language", "Rust")),
61 ),
62 )
63 .child(
64 h_stack()
65 .gap_1()
66 .child(
67 // Github tool
68 div()
69 .border()
70 .border_color(gpui::red())
71 .child(IconButton::new("status-copilot", Icon::Copilot)),
72 )
73 .child(
74 // Feedback Tool
75 div()
76 .border()
77 .border_color(gpui::red())
78 .child(IconButton::new("status-feedback", Icon::Envelope)),
79 ),
80 )
81 .child(
82 // Bottom Dock
83 h_stack().gap_1().child(
84 // Terminal
85 div()
86 .border()
87 .border_color(gpui::red())
88 .child(IconButton::new("status-terminal", Icon::Terminal)),
89 ),
90 )
91 .child(
92 // Right Dock
93 h_stack()
94 .gap_1()
95 .child(
96 // Terminal
97 div()
98 .border()
99 .border_color(gpui::red())
100 .child(IconButton::new("status-assistant", Icon::Ai)),
101 )
102 .child(
103 // Terminal
104 div()
105 .border()
106 .border_color(gpui::red())
107 .child(IconButton::new("status-chat", Icon::MessageBubbles)),
108 ),
109 )
110 .child(self.render_right_tools(cx)),
111 )
112 }
113}
114
115impl StatusBar {
116 fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
117 h_stack()
118 .items_center()
119 .gap_2()
120 .children(self.left_items.iter().map(|item| item.to_any()))
121 }
122
123 fn render_right_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
124 h_stack()
125 .items_center()
126 .gap_2()
127 .children(self.right_items.iter().rev().map(|item| item.to_any()))
128 }
129}
130
131impl StatusBar {
132 pub fn new(active_pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Self {
133 let mut this = Self {
134 left_items: Default::default(),
135 right_items: Default::default(),
136 active_pane: active_pane.clone(),
137 _observe_active_pane: cx
138 .observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)),
139 };
140 this.update_active_pane_item(cx);
141 this
142 }
143
144 pub fn add_left_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
145 where
146 T: 'static + StatusItemView,
147 {
148 self.left_items.push(Box::new(item));
149 cx.notify();
150 }
151
152 pub fn item_of_type<T: StatusItemView>(&self) -> Option<View<T>> {
153 self.left_items
154 .iter()
155 .chain(self.right_items.iter())
156 .find_map(|item| item.to_any().clone().downcast().log_err())
157 }
158
159 pub fn position_of_item<T>(&self) -> Option<usize>
160 where
161 T: StatusItemView,
162 {
163 for (index, item) in self.left_items.iter().enumerate() {
164 if item.item_type() == TypeId::of::<T>() {
165 return Some(index);
166 }
167 }
168 for (index, item) in self.right_items.iter().enumerate() {
169 if item.item_type() == TypeId::of::<T>() {
170 return Some(index + self.left_items.len());
171 }
172 }
173 return None;
174 }
175
176 pub fn insert_item_after<T>(
177 &mut self,
178 position: usize,
179 item: View<T>,
180 cx: &mut ViewContext<Self>,
181 ) where
182 T: 'static + StatusItemView,
183 {
184 if position < self.left_items.len() {
185 self.left_items.insert(position + 1, Box::new(item))
186 } else {
187 self.right_items
188 .insert(position + 1 - self.left_items.len(), Box::new(item))
189 }
190 cx.notify()
191 }
192
193 pub fn remove_item_at(&mut self, position: usize, cx: &mut ViewContext<Self>) {
194 if position < self.left_items.len() {
195 self.left_items.remove(position);
196 } else {
197 self.right_items.remove(position - self.left_items.len());
198 }
199 cx.notify();
200 }
201
202 pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
203 where
204 T: 'static + StatusItemView,
205 {
206 self.right_items.push(Box::new(item));
207 cx.notify();
208 }
209
210 pub fn set_active_pane(&mut self, active_pane: &View<Pane>, cx: &mut ViewContext<Self>) {
211 self.active_pane = active_pane.clone();
212 self._observe_active_pane =
213 cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
214 self.update_active_pane_item(cx);
215 }
216
217 fn update_active_pane_item(&mut self, cx: &mut ViewContext<Self>) {
218 let active_pane_item = self.active_pane.read(cx).active_item();
219 for item in self.left_items.iter().chain(&self.right_items) {
220 item.set_active_pane_item(active_pane_item.as_deref(), cx);
221 }
222 }
223}
224
225impl<T: StatusItemView> StatusItemViewHandle for View<T> {
226 fn to_any(&self) -> AnyView {
227 self.clone().into()
228 }
229
230 fn set_active_pane_item(
231 &self,
232 active_pane_item: Option<&dyn ItemHandle>,
233 cx: &mut WindowContext,
234 ) {
235 self.update(cx, |this, cx| {
236 this.set_active_pane_item(active_pane_item, cx)
237 });
238 }
239
240 fn item_type(&self) -> TypeId {
241 TypeId::of::<T>()
242 }
243}
244
245impl From<&dyn StatusItemViewHandle> for AnyView {
246 fn from(val: &dyn StatusItemViewHandle) -> Self {
247 val.to_any().clone()
248 }
249}