status_bar.rs

  1use crate::{ItemHandle, Pane};
  2use gpui::{
  3    AnyView, Decorations, IntoElement, ParentElement, Render, Styled, Subscription, View,
  4    ViewContext, WindowContext,
  5};
  6use std::any::TypeId;
  7use theme::CLIENT_SIDE_DECORATION_ROUNDING;
  8use ui::{h_flex, prelude::*};
  9use util::ResultExt;
 10
 11pub trait StatusItemView: Render {
 12    fn set_active_pane_item(
 13        &mut self,
 14        active_pane_item: Option<&dyn crate::ItemHandle>,
 15        cx: &mut ViewContext<Self>,
 16    );
 17}
 18
 19trait StatusItemViewHandle: Send {
 20    fn to_any(&self) -> AnyView;
 21    fn set_active_pane_item(
 22        &self,
 23        active_pane_item: Option<&dyn ItemHandle>,
 24        cx: &mut WindowContext,
 25    );
 26    fn item_type(&self) -> TypeId;
 27}
 28
 29pub struct StatusBar {
 30    left_items: Vec<Box<dyn StatusItemViewHandle>>,
 31    right_items: Vec<Box<dyn StatusItemViewHandle>>,
 32    active_pane: View<Pane>,
 33    _observe_active_pane: Subscription,
 34}
 35
 36impl Render for StatusBar {
 37    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 38        h_flex()
 39            .w_full()
 40            .justify_between()
 41            .gap(Spacing::Large.rems(cx))
 42            .py(Spacing::Small.rems(cx))
 43            .px(Spacing::Large.rems(cx))
 44            .bg(cx.theme().colors().status_bar_background)
 45            .map(|el| match cx.window_decorations() {
 46                Decorations::Server => el,
 47                Decorations::Client { tiling, .. } => el
 48                    .when(!(tiling.bottom || tiling.right), |el| {
 49                        el.rounded_br(CLIENT_SIDE_DECORATION_ROUNDING)
 50                    })
 51                    .when(!(tiling.bottom || tiling.left), |el| {
 52                        el.rounded_bl(CLIENT_SIDE_DECORATION_ROUNDING)
 53                    }),
 54            })
 55            .child(self.render_left_tools(cx))
 56            .child(self.render_right_tools(cx))
 57    }
 58}
 59
 60impl StatusBar {
 61    fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 62        h_flex()
 63            .gap(Spacing::Large.rems(cx))
 64            .overflow_x_hidden()
 65            .children(self.left_items.iter().map(|item| item.to_any()))
 66    }
 67
 68    fn render_right_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 69        h_flex()
 70            .gap(Spacing::Large.rems(cx))
 71            .children(self.right_items.iter().rev().map(|item| item.to_any()))
 72    }
 73}
 74
 75impl StatusBar {
 76    pub fn new(active_pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Self {
 77        let mut this = Self {
 78            left_items: Default::default(),
 79            right_items: Default::default(),
 80            active_pane: active_pane.clone(),
 81            _observe_active_pane: cx
 82                .observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)),
 83        };
 84        this.update_active_pane_item(cx);
 85        this
 86    }
 87
 88    pub fn add_left_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
 89    where
 90        T: 'static + StatusItemView,
 91    {
 92        let active_pane_item = self.active_pane.read(cx).active_item();
 93        item.set_active_pane_item(active_pane_item.as_deref(), cx);
 94
 95        self.left_items.push(Box::new(item));
 96        cx.notify();
 97    }
 98
 99    pub fn item_of_type<T: StatusItemView>(&self) -> Option<View<T>> {
100        self.left_items
101            .iter()
102            .chain(self.right_items.iter())
103            .find_map(|item| item.to_any().clone().downcast().log_err())
104    }
105
106    pub fn position_of_item<T>(&self) -> Option<usize>
107    where
108        T: StatusItemView,
109    {
110        for (index, item) in self.left_items.iter().enumerate() {
111            if item.item_type() == TypeId::of::<T>() {
112                return Some(index);
113            }
114        }
115        for (index, item) in self.right_items.iter().enumerate() {
116            if item.item_type() == TypeId::of::<T>() {
117                return Some(index + self.left_items.len());
118            }
119        }
120        return None;
121    }
122
123    pub fn insert_item_after<T>(
124        &mut self,
125        position: usize,
126        item: View<T>,
127        cx: &mut ViewContext<Self>,
128    ) where
129        T: 'static + StatusItemView,
130    {
131        let active_pane_item = self.active_pane.read(cx).active_item();
132        item.set_active_pane_item(active_pane_item.as_deref(), cx);
133
134        if position < self.left_items.len() {
135            self.left_items.insert(position + 1, Box::new(item))
136        } else {
137            self.right_items
138                .insert(position + 1 - self.left_items.len(), Box::new(item))
139        }
140        cx.notify()
141    }
142
143    pub fn remove_item_at(&mut self, position: usize, cx: &mut ViewContext<Self>) {
144        if position < self.left_items.len() {
145            self.left_items.remove(position);
146        } else {
147            self.right_items.remove(position - self.left_items.len());
148        }
149        cx.notify();
150    }
151
152    pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
153    where
154        T: 'static + StatusItemView,
155    {
156        let active_pane_item = self.active_pane.read(cx).active_item();
157        item.set_active_pane_item(active_pane_item.as_deref(), cx);
158
159        self.right_items.push(Box::new(item));
160        cx.notify();
161    }
162
163    pub fn set_active_pane(&mut self, active_pane: &View<Pane>, cx: &mut ViewContext<Self>) {
164        self.active_pane = active_pane.clone();
165        self._observe_active_pane =
166            cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
167        self.update_active_pane_item(cx);
168    }
169
170    fn update_active_pane_item(&mut self, cx: &mut ViewContext<Self>) {
171        let active_pane_item = self.active_pane.read(cx).active_item();
172        for item in self.left_items.iter().chain(&self.right_items) {
173            item.set_active_pane_item(active_pane_item.as_deref(), cx);
174        }
175    }
176}
177
178impl<T: StatusItemView> StatusItemViewHandle for View<T> {
179    fn to_any(&self) -> AnyView {
180        self.clone().into()
181    }
182
183    fn set_active_pane_item(
184        &self,
185        active_pane_item: Option<&dyn ItemHandle>,
186        cx: &mut WindowContext,
187    ) {
188        self.update(cx, |this, cx| {
189            this.set_active_pane_item(active_pane_item, cx)
190        });
191    }
192
193    fn item_type(&self) -> TypeId {
194        TypeId::of::<T>()
195    }
196}
197
198impl From<&dyn StatusItemViewHandle> for AnyView {
199    fn from(val: &dyn StatusItemViewHandle) -> Self {
200        val.to_any().clone()
201    }
202}