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