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 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}