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, 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 // Feedback Tool
57 div()
58 .border()
59 .border_color(gpui::red())
60 .child(IconButton::new("status-feedback", Icon::Envelope)),
61 ),
62 )
63 .child(
64 // Right Dock
65 h_stack()
66 .gap_1()
67 .child(
68 // Terminal
69 div()
70 .border()
71 .border_color(gpui::red())
72 .child(IconButton::new("status-assistant", Icon::Ai)),
73 )
74 .child(
75 // Terminal
76 div()
77 .border()
78 .border_color(gpui::red())
79 .child(IconButton::new("status-chat", Icon::MessageBubbles)),
80 ),
81 )
82 .child(self.render_right_tools(cx)),
83 )
84 }
85}
86
87impl StatusBar {
88 fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
89 h_stack()
90 .items_center()
91 .gap_2()
92 .children(self.left_items.iter().map(|item| item.to_any()))
93 }
94
95 fn render_right_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
96 h_stack()
97 .items_center()
98 .gap_2()
99 .children(self.right_items.iter().rev().map(|item| item.to_any()))
100 }
101}
102
103impl StatusBar {
104 pub fn new(active_pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Self {
105 let mut this = Self {
106 left_items: Default::default(),
107 right_items: Default::default(),
108 active_pane: active_pane.clone(),
109 _observe_active_pane: cx
110 .observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)),
111 };
112 this.update_active_pane_item(cx);
113 this
114 }
115
116 pub fn add_left_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
117 where
118 T: 'static + StatusItemView,
119 {
120 self.left_items.push(Box::new(item));
121 cx.notify();
122 }
123
124 pub fn item_of_type<T: StatusItemView>(&self) -> Option<View<T>> {
125 self.left_items
126 .iter()
127 .chain(self.right_items.iter())
128 .find_map(|item| item.to_any().clone().downcast().log_err())
129 }
130
131 pub fn position_of_item<T>(&self) -> Option<usize>
132 where
133 T: StatusItemView,
134 {
135 for (index, item) in self.left_items.iter().enumerate() {
136 if item.item_type() == TypeId::of::<T>() {
137 return Some(index);
138 }
139 }
140 for (index, item) in self.right_items.iter().enumerate() {
141 if item.item_type() == TypeId::of::<T>() {
142 return Some(index + self.left_items.len());
143 }
144 }
145 return None;
146 }
147
148 pub fn insert_item_after<T>(
149 &mut self,
150 position: usize,
151 item: View<T>,
152 cx: &mut ViewContext<Self>,
153 ) where
154 T: 'static + StatusItemView,
155 {
156 if position < self.left_items.len() {
157 self.left_items.insert(position + 1, Box::new(item))
158 } else {
159 self.right_items
160 .insert(position + 1 - self.left_items.len(), Box::new(item))
161 }
162 cx.notify()
163 }
164
165 pub fn remove_item_at(&mut self, position: usize, cx: &mut ViewContext<Self>) {
166 if position < self.left_items.len() {
167 self.left_items.remove(position);
168 } else {
169 self.right_items.remove(position - self.left_items.len());
170 }
171 cx.notify();
172 }
173
174 pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
175 where
176 T: 'static + StatusItemView,
177 {
178 self.right_items.push(Box::new(item));
179 cx.notify();
180 }
181
182 pub fn set_active_pane(&mut self, active_pane: &View<Pane>, cx: &mut ViewContext<Self>) {
183 self.active_pane = active_pane.clone();
184 self._observe_active_pane =
185 cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
186 self.update_active_pane_item(cx);
187 }
188
189 fn update_active_pane_item(&mut self, cx: &mut ViewContext<Self>) {
190 let active_pane_item = self.active_pane.read(cx).active_item();
191 for item in self.left_items.iter().chain(&self.right_items) {
192 item.set_active_pane_item(active_pane_item.as_deref(), cx);
193 }
194 }
195}
196
197impl<T: StatusItemView> StatusItemViewHandle for View<T> {
198 fn to_any(&self) -> AnyView {
199 self.clone().into()
200 }
201
202 fn set_active_pane_item(
203 &self,
204 active_pane_item: Option<&dyn ItemHandle>,
205 cx: &mut WindowContext,
206 ) {
207 self.update(cx, |this, cx| {
208 this.set_active_pane_item(active_pane_item, cx)
209 });
210 }
211
212 fn item_type(&self) -> TypeId {
213 TypeId::of::<T>()
214 }
215}
216
217impl From<&dyn StatusItemViewHandle> for AnyView {
218 fn from(val: &dyn StatusItemViewHandle) -> Self {
219 val.to_any().clone()
220 }
221}