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}