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