use crate::{ItemHandle, Pane}; use gpui::{ AnyView, App, Context, Decorations, Entity, IntoElement, ParentElement, Render, Styled, Subscription, Window, }; use std::any::TypeId; use theme::CLIENT_SIDE_DECORATION_ROUNDING; use ui::{h_flex, prelude::*}; use util::ResultExt; pub trait StatusItemView: Render { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn crate::ItemHandle>, window: &mut Window, cx: &mut Context, ); } trait StatusItemViewHandle: Send { fn to_any(&self) -> AnyView; fn set_active_pane_item( &self, active_pane_item: Option<&dyn ItemHandle>, window: &mut Window, cx: &mut App, ); fn item_type(&self) -> TypeId; } pub struct StatusBar { left_items: Vec>, right_items: Vec>, active_pane: Entity, _observe_active_pane: Subscription, } impl Render for StatusBar { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { h_flex() .w_full() .justify_between() .gap(DynamicSpacing::Base08.rems(cx)) .py(DynamicSpacing::Base04.rems(cx)) .px(DynamicSpacing::Base06.rems(cx)) .bg(cx.theme().colors().status_bar_background) .map(|el| match window.window_decorations() { Decorations::Server => el, Decorations::Client { tiling, .. } => el .when(!(tiling.bottom || tiling.right), |el| { el.rounded_br(CLIENT_SIDE_DECORATION_ROUNDING) }) .when(!(tiling.bottom || tiling.left), |el| { el.rounded_bl(CLIENT_SIDE_DECORATION_ROUNDING) }) // This border is to avoid a transparent gap in the rounded corners .mb(px(-1.)) .border_b(px(1.0)) .border_color(cx.theme().colors().status_bar_background), }) .child(self.render_left_tools()) .child(self.render_right_tools()) } } impl StatusBar { fn render_left_tools(&self) -> impl IntoElement { h_flex() .gap_1() .overflow_x_hidden() .children(self.left_items.iter().map(|item| item.to_any())) } fn render_right_tools(&self) -> impl IntoElement { h_flex() .gap_1() .overflow_x_hidden() .children(self.right_items.iter().rev().map(|item| item.to_any())) } } impl StatusBar { pub fn new(active_pane: &Entity, window: &mut Window, cx: &mut Context) -> Self { let mut this = Self { left_items: Default::default(), right_items: Default::default(), active_pane: active_pane.clone(), _observe_active_pane: cx.observe_in(active_pane, window, |this, _, window, cx| { this.update_active_pane_item(window, cx) }), }; this.update_active_pane_item(window, cx); this } pub fn add_left_item(&mut self, item: Entity, window: &mut Window, cx: &mut Context) where T: 'static + StatusItemView, { let active_pane_item = self.active_pane.read(cx).active_item(); item.set_active_pane_item(active_pane_item.as_deref(), window, cx); self.left_items.push(Box::new(item)); cx.notify(); } pub fn item_of_type(&self) -> Option> { self.left_items .iter() .chain(self.right_items.iter()) .find_map(|item| item.to_any().downcast().log_err()) } pub fn position_of_item(&self) -> Option where T: StatusItemView, { for (index, item) in self.left_items.iter().enumerate() { if item.item_type() == TypeId::of::() { return Some(index); } } for (index, item) in self.right_items.iter().enumerate() { if item.item_type() == TypeId::of::() { return Some(index + self.left_items.len()); } } None } pub fn insert_item_after( &mut self, position: usize, item: Entity, window: &mut Window, cx: &mut Context, ) where T: 'static + StatusItemView, { let active_pane_item = self.active_pane.read(cx).active_item(); item.set_active_pane_item(active_pane_item.as_deref(), window, cx); if position < self.left_items.len() { self.left_items.insert(position + 1, Box::new(item)) } else { self.right_items .insert(position + 1 - self.left_items.len(), Box::new(item)) } cx.notify() } pub fn remove_item_at(&mut self, position: usize, cx: &mut Context) { if position < self.left_items.len() { self.left_items.remove(position); } else { self.right_items.remove(position - self.left_items.len()); } cx.notify(); } pub fn add_right_item( &mut self, item: Entity, window: &mut Window, cx: &mut Context, ) where T: 'static + StatusItemView, { let active_pane_item = self.active_pane.read(cx).active_item(); item.set_active_pane_item(active_pane_item.as_deref(), window, cx); self.right_items.push(Box::new(item)); cx.notify(); } pub fn set_active_pane( &mut self, active_pane: &Entity, window: &mut Window, cx: &mut Context, ) { self.active_pane = active_pane.clone(); self._observe_active_pane = cx.observe_in(active_pane, window, |this, _, window, cx| { this.update_active_pane_item(window, cx) }); self.update_active_pane_item(window, cx); } fn update_active_pane_item(&mut self, window: &mut Window, cx: &mut Context) { let active_pane_item = self.active_pane.read(cx).active_item(); for item in self.left_items.iter().chain(&self.right_items) { item.set_active_pane_item(active_pane_item.as_deref(), window, cx); } } } impl StatusItemViewHandle for Entity { fn to_any(&self) -> AnyView { self.clone().into() } fn set_active_pane_item( &self, active_pane_item: Option<&dyn ItemHandle>, window: &mut Window, cx: &mut App, ) { self.update(cx, |this, cx| { this.set_active_pane_item(active_pane_item, window, cx) }); } fn item_type(&self) -> TypeId { TypeId::of::() } } impl From<&dyn StatusItemViewHandle> for AnyView { fn from(val: &dyn StatusItemViewHandle) -> Self { val.to_any() } }