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().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}