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