status_bar.rs

  1use std::ops::Range;
  2
  3use crate::{ItemHandle, Pane};
  4use gpui::{
  5    elements::*,
  6    geometry::{
  7        rect::RectF,
  8        vector::{vec2f, Vector2F},
  9    },
 10    json::{json, ToJson},
 11    AnyViewHandle, DebugContext, ElementBox, Entity, LayoutContext, MeasurementContext,
 12    MutableAppContext, PaintContext, RenderContext, SizeConstraint, Subscription, View,
 13    ViewContext, ViewHandle,
 14};
 15use settings::Settings;
 16
 17pub trait StatusItemView: View {
 18    fn set_active_pane_item(
 19        &mut self,
 20        active_pane_item: Option<&dyn crate::ItemHandle>,
 21        cx: &mut ViewContext<Self>,
 22    );
 23}
 24
 25trait StatusItemViewHandle {
 26    fn as_any(&self) -> &AnyViewHandle;
 27    fn set_active_pane_item(
 28        &self,
 29        active_pane_item: Option<&dyn ItemHandle>,
 30        cx: &mut MutableAppContext,
 31    );
 32}
 33
 34pub struct StatusBar {
 35    left_items: Vec<Box<dyn StatusItemViewHandle>>,
 36    right_items: Vec<Box<dyn StatusItemViewHandle>>,
 37    active_pane: ViewHandle<Pane>,
 38    _observe_active_pane: Subscription,
 39}
 40
 41impl Entity for StatusBar {
 42    type Event = ();
 43}
 44
 45impl View for StatusBar {
 46    fn ui_name() -> &'static str {
 47        "StatusBar"
 48    }
 49
 50    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
 51        let theme = &cx.global::<Settings>().theme.workspace.status_bar;
 52
 53        StatusBarElement {
 54            left: Flex::row()
 55                .with_children(self.left_items.iter().map(|i| {
 56                    ChildView::new(i.as_any(), cx)
 57                        .aligned()
 58                        .contained()
 59                        .with_margin_right(theme.item_spacing)
 60                        .boxed()
 61                }))
 62                .boxed(),
 63
 64            right: Flex::row()
 65                .with_children(self.right_items.iter().rev().map(|i| {
 66                    ChildView::new(i.as_any(), cx)
 67                        .aligned()
 68                        .contained()
 69                        .with_margin_left(theme.item_spacing)
 70                        .boxed()
 71                }))
 72                .boxed(),
 73        }
 74        .contained()
 75        .with_style(theme.container)
 76        .constrained()
 77        .with_height(theme.height)
 78        .boxed()
 79    }
 80}
 81
 82impl StatusBar {
 83    pub fn new(active_pane: &ViewHandle<Pane>, cx: &mut ViewContext<Self>) -> Self {
 84        let mut this = Self {
 85            left_items: Default::default(),
 86            right_items: Default::default(),
 87            active_pane: active_pane.clone(),
 88            _observe_active_pane: cx
 89                .observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)),
 90        };
 91        this.update_active_pane_item(cx);
 92        this
 93    }
 94
 95    pub fn add_left_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
 96    where
 97        T: 'static + StatusItemView,
 98    {
 99        cx.reparent(item.as_any());
100        self.left_items.push(Box::new(item));
101        cx.notify();
102    }
103
104    pub fn add_right_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
105    where
106        T: 'static + StatusItemView,
107    {
108        cx.reparent(item.as_any());
109        self.right_items.push(Box::new(item));
110        cx.notify();
111    }
112
113    pub fn set_active_pane(&mut self, active_pane: &ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
114        self.active_pane = active_pane.clone();
115        self._observe_active_pane =
116            cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
117        self.update_active_pane_item(cx);
118    }
119
120    fn update_active_pane_item(&mut self, cx: &mut ViewContext<Self>) {
121        let active_pane_item = self.active_pane.read(cx).active_item();
122        for item in self.left_items.iter().chain(&self.right_items) {
123            item.set_active_pane_item(active_pane_item.as_deref(), cx);
124        }
125    }
126}
127
128impl<T: StatusItemView> StatusItemViewHandle for ViewHandle<T> {
129    fn as_any(&self) -> &AnyViewHandle {
130        self
131    }
132
133    fn set_active_pane_item(
134        &self,
135        active_pane_item: Option<&dyn ItemHandle>,
136        cx: &mut MutableAppContext,
137    ) {
138        self.update(cx, |this, cx| {
139            this.set_active_pane_item(active_pane_item, cx)
140        });
141    }
142}
143
144impl From<&dyn StatusItemViewHandle> for AnyViewHandle {
145    fn from(val: &dyn StatusItemViewHandle) -> Self {
146        val.as_any().clone()
147    }
148}
149
150struct StatusBarElement {
151    left: ElementBox,
152    right: ElementBox,
153}
154
155impl Element for StatusBarElement {
156    type LayoutState = ();
157    type PaintState = ();
158
159    fn layout(
160        &mut self,
161        mut constraint: SizeConstraint,
162        cx: &mut LayoutContext,
163    ) -> (Vector2F, Self::LayoutState) {
164        let max_width = constraint.max.x();
165        constraint.min = vec2f(0., constraint.min.y());
166
167        let right_size = self.right.layout(constraint, cx);
168        let constraint = SizeConstraint::new(
169            vec2f(0., constraint.min.y()),
170            vec2f(max_width - right_size.x(), constraint.max.y()),
171        );
172
173        self.left.layout(constraint, cx);
174
175        (vec2f(max_width, right_size.y()), ())
176    }
177
178    fn paint(
179        &mut self,
180        bounds: RectF,
181        visible_bounds: RectF,
182        _: &mut Self::LayoutState,
183        cx: &mut PaintContext,
184    ) -> Self::PaintState {
185        let origin_y = bounds.upper_right().y();
186        let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
187
188        let left_origin = vec2f(bounds.lower_left().x(), origin_y);
189        self.left.paint(left_origin, visible_bounds, cx);
190
191        let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y);
192        self.right.paint(right_origin, visible_bounds, cx);
193    }
194
195    fn rect_for_text_range(
196        &self,
197        _: Range<usize>,
198        _: RectF,
199        _: RectF,
200        _: &Self::LayoutState,
201        _: &Self::PaintState,
202        _: &MeasurementContext,
203    ) -> Option<RectF> {
204        None
205    }
206
207    fn debug(
208        &self,
209        bounds: RectF,
210        _: &Self::LayoutState,
211        _: &Self::PaintState,
212        _: &DebugContext,
213    ) -> serde_json::Value {
214        json!({
215            "type": "StatusBarElement",
216            "bounds": bounds.to_json()
217        })
218    }
219}