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