status_bar.rs

  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 theme2::ActiveTheme;
  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            // Nate: I know this isn't how we render status bar tools
 51            // We can move these to the correct place once we port their tools
 52            .child(
 53                h_stack().gap_1().child(self.render_left_tools(cx)).child(
 54                    h_stack().gap_4().child(
 55                        // TODO: Language Server status
 56                        div()
 57                            .border()
 58                            .border_color(gpui::red())
 59                            .child("Checking..."),
 60                    ),
 61                ),
 62            )
 63            .child(
 64                h_stack()
 65                    .gap_4()
 66                    .child(
 67                        h_stack()
 68                            .gap_1()
 69                            .child(
 70                                // TODO: Line / column numbers
 71                                div()
 72                                    .border()
 73                                    .border_color(gpui::red())
 74                                    .child(Button::new("15:22")),
 75                            )
 76                            .child(
 77                                // TODO: Language picker
 78                                div()
 79                                    .border()
 80                                    .border_color(gpui::red())
 81                                    .child(Button::new("Rust")),
 82                            ),
 83                    )
 84                    .child(
 85                        h_stack()
 86                            .gap_1()
 87                            .child(
 88                                // Github tool
 89                                div()
 90                                    .border()
 91                                    .border_color(gpui::red())
 92                                    .child(IconButton::new("status-copilot", Icon::Copilot)),
 93                            )
 94                            .child(
 95                                // Feedback Tool
 96                                div()
 97                                    .border()
 98                                    .border_color(gpui::red())
 99                                    .child(IconButton::new("status-feedback", Icon::Envelope)),
100                            ),
101                    )
102                    .child(
103                        // Bottom Dock
104                        h_stack().gap_1().child(
105                            // Terminal
106                            div()
107                                .border()
108                                .border_color(gpui::red())
109                                .child(IconButton::new("status-terminal", Icon::Terminal)),
110                        ),
111                    )
112                    .child(
113                        // Right Dock
114                        h_stack()
115                            .gap_1()
116                            .child(
117                                // Terminal
118                                div()
119                                    .border()
120                                    .border_color(gpui::red())
121                                    .child(IconButton::new("status-assistant", Icon::Ai)),
122                            )
123                            .child(
124                                // Terminal
125                                div()
126                                    .border()
127                                    .border_color(gpui::red())
128                                    .child(IconButton::new("status-chat", Icon::MessageBubbles)),
129                            ),
130                    )
131                    .child(self.render_right_tools(cx)),
132            )
133    }
134}
135
136impl StatusBar {
137    fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
138        h_stack()
139            .items_center()
140            .gap_2()
141            .children(self.left_items.iter().map(|item| item.to_any()))
142    }
143
144    fn render_right_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
145        h_stack()
146            .items_center()
147            .gap_2()
148            .children(self.right_items.iter().map(|item| item.to_any()))
149    }
150}
151
152impl StatusBar {
153    pub fn new(active_pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Self {
154        let mut this = Self {
155            left_items: Default::default(),
156            right_items: Default::default(),
157            active_pane: active_pane.clone(),
158            _observe_active_pane: cx
159                .observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)),
160        };
161        this.update_active_pane_item(cx);
162        this
163    }
164
165    pub fn add_left_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
166    where
167        T: 'static + StatusItemView,
168    {
169        self.left_items.push(Box::new(item));
170        cx.notify();
171    }
172
173    pub fn item_of_type<T: StatusItemView>(&self) -> Option<View<T>> {
174        self.left_items
175            .iter()
176            .chain(self.right_items.iter())
177            .find_map(|item| item.to_any().clone().downcast().log_err())
178    }
179
180    pub fn position_of_item<T>(&self) -> Option<usize>
181    where
182        T: StatusItemView,
183    {
184        for (index, item) in self.left_items.iter().enumerate() {
185            if item.item_type() == TypeId::of::<T>() {
186                return Some(index);
187            }
188        }
189        for (index, item) in self.right_items.iter().enumerate() {
190            if item.item_type() == TypeId::of::<T>() {
191                return Some(index + self.left_items.len());
192            }
193        }
194        return None;
195    }
196
197    pub fn insert_item_after<T>(
198        &mut self,
199        position: usize,
200        item: View<T>,
201        cx: &mut ViewContext<Self>,
202    ) where
203        T: 'static + StatusItemView,
204    {
205        if position < self.left_items.len() {
206            self.left_items.insert(position + 1, Box::new(item))
207        } else {
208            self.right_items
209                .insert(position + 1 - self.left_items.len(), Box::new(item))
210        }
211        cx.notify()
212    }
213
214    pub fn remove_item_at(&mut self, position: usize, cx: &mut ViewContext<Self>) {
215        if position < self.left_items.len() {
216            self.left_items.remove(position);
217        } else {
218            self.right_items.remove(position - self.left_items.len());
219        }
220        cx.notify();
221    }
222
223    pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
224    where
225        T: 'static + StatusItemView,
226    {
227        self.right_items.push(Box::new(item));
228        cx.notify();
229    }
230
231    pub fn set_active_pane(&mut self, active_pane: &View<Pane>, cx: &mut ViewContext<Self>) {
232        self.active_pane = active_pane.clone();
233        self._observe_active_pane =
234            cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
235        self.update_active_pane_item(cx);
236    }
237
238    fn update_active_pane_item(&mut self, cx: &mut ViewContext<Self>) {
239        let active_pane_item = self.active_pane.read(cx).active_item();
240        for item in self.left_items.iter().chain(&self.right_items) {
241            item.set_active_pane_item(active_pane_item.as_deref(), cx);
242        }
243    }
244}
245
246impl<T: StatusItemView> StatusItemViewHandle for View<T> {
247    fn to_any(&self) -> AnyView {
248        self.clone().into()
249    }
250
251    fn set_active_pane_item(
252        &self,
253        active_pane_item: Option<&dyn ItemHandle>,
254        cx: &mut WindowContext,
255    ) {
256        self.update(cx, |this, cx| {
257            this.set_active_pane_item(active_pane_item, cx)
258        });
259    }
260
261    fn item_type(&self) -> TypeId {
262        TypeId::of::<T>()
263    }
264}
265
266impl From<&dyn StatusItemViewHandle> for AnyView {
267    fn from(val: &dyn StatusItemViewHandle) -> Self {
268        val.to_any().clone()
269    }
270}