1use crate::ItemHandle;
2use gpui::{
3 elements::*, AnyElement, AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle,
4 WindowContext,
5};
6
7pub trait ToolbarItemView: View {
8 fn set_active_pane_item(
9 &mut self,
10 active_pane_item: Option<&dyn crate::ItemHandle>,
11 cx: &mut ViewContext<Self>,
12 ) -> ToolbarItemLocation;
13
14 fn location_for_event(
15 &self,
16 _event: &Self::Event,
17 current_location: ToolbarItemLocation,
18 _cx: &AppContext,
19 ) -> ToolbarItemLocation {
20 current_location
21 }
22
23 fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
24
25 /// Number of times toolbar's height will be repeated to get the effective height.
26 /// Useful when multiple rows one under each other are needed.
27 /// The rows have the same width and act as a whole when reacting to resizes and similar events.
28 fn row_count(&self, _cx: &ViewContext<Self>) -> usize {
29 1
30 }
31}
32
33trait ToolbarItemViewHandle {
34 fn id(&self) -> usize;
35 fn as_any(&self) -> &AnyViewHandle;
36 fn set_active_pane_item(
37 &self,
38 active_pane_item: Option<&dyn ItemHandle>,
39 cx: &mut WindowContext,
40 ) -> ToolbarItemLocation;
41 fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext);
42 fn row_count(&self, cx: &WindowContext) -> usize;
43}
44
45#[derive(Copy, Clone, Debug, PartialEq)]
46pub enum ToolbarItemLocation {
47 Hidden,
48 PrimaryLeft { flex: Option<(f32, bool)> },
49 PrimaryRight { flex: Option<(f32, bool)> },
50 Secondary,
51}
52
53pub struct Toolbar {
54 active_item: Option<Box<dyn ItemHandle>>,
55 hidden: bool,
56 can_navigate: bool,
57 items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
58}
59
60impl Entity for Toolbar {
61 type Event = ();
62}
63
64impl View for Toolbar {
65 fn ui_name() -> &'static str {
66 "Toolbar"
67 }
68
69 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
70 let theme = &theme::current(cx).workspace.toolbar;
71
72 let mut primary_left_items = Vec::new();
73 let mut primary_right_items = Vec::new();
74 let mut secondary_item = None;
75 let spacing = theme.item_spacing;
76 let mut primary_items_row_count = 1;
77
78 for (item, position) in &self.items {
79 match *position {
80 ToolbarItemLocation::Hidden => {}
81
82 ToolbarItemLocation::PrimaryLeft { flex } => {
83 primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
84 let left_item = ChildView::new(item.as_any(), cx)
85 .aligned()
86 .contained()
87 .with_margin_right(spacing);
88 if let Some((flex, expanded)) = flex {
89 primary_left_items.push(left_item.flex(flex, expanded).into_any());
90 } else {
91 primary_left_items.push(left_item.into_any());
92 }
93 }
94
95 ToolbarItemLocation::PrimaryRight { flex } => {
96 primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
97 let right_item = ChildView::new(item.as_any(), cx)
98 .aligned()
99 .contained()
100 .with_margin_left(spacing)
101 .flex_float();
102 if let Some((flex, expanded)) = flex {
103 primary_right_items.push(right_item.flex(flex, expanded).into_any());
104 } else {
105 primary_right_items.push(right_item.into_any());
106 }
107 }
108
109 ToolbarItemLocation::Secondary => {
110 secondary_item = Some(
111 ChildView::new(item.as_any(), cx)
112 .constrained()
113 .with_height(theme.height * item.row_count(cx) as f32)
114 .into_any(),
115 );
116 }
117 }
118 }
119
120 let container_style = theme.container;
121 let height = theme.height * primary_items_row_count as f32;
122
123 let mut primary_items = Flex::row();
124 primary_items.extend(primary_left_items);
125 primary_items.extend(primary_right_items);
126
127 let mut toolbar = Flex::column();
128 if !primary_items.is_empty() {
129 toolbar.add_child(primary_items.constrained().with_height(height));
130 }
131 if let Some(secondary_item) = secondary_item {
132 toolbar.add_child(secondary_item);
133 }
134
135 if toolbar.is_empty() {
136 toolbar.into_any_named("toolbar")
137 } else {
138 toolbar
139 .contained()
140 .with_style(container_style)
141 .into_any_named("toolbar")
142 }
143 }
144}
145
146impl Toolbar {
147 pub fn new() -> Self {
148 Self {
149 active_item: None,
150 items: Default::default(),
151 hidden: false,
152 can_navigate: true,
153 }
154 }
155
156 pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
157 self.can_navigate = can_navigate;
158 cx.notify();
159 }
160
161 pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
162 where
163 T: 'static + ToolbarItemView,
164 {
165 let location = item.set_active_pane_item(self.active_item.as_deref(), cx);
166 cx.subscribe(&item, |this, item, event, cx| {
167 if let Some((_, current_location)) =
168 this.items.iter_mut().find(|(i, _)| i.id() == item.id())
169 {
170 let new_location = item
171 .read(cx)
172 .location_for_event(event, *current_location, cx);
173 if new_location != *current_location {
174 *current_location = new_location;
175 cx.notify();
176 }
177 }
178 })
179 .detach();
180 self.items.push((Box::new(item), location));
181 cx.notify();
182 }
183
184 pub fn set_active_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
185 self.active_item = item.map(|item| item.boxed_clone());
186 self.hidden = self
187 .active_item
188 .as_ref()
189 .map(|item| !item.show_toolbar(cx))
190 .unwrap_or(false);
191
192 for (toolbar_item, current_location) in self.items.iter_mut() {
193 let new_location = toolbar_item.set_active_pane_item(item, cx);
194 if new_location != *current_location {
195 *current_location = new_location;
196 cx.notify();
197 }
198 }
199 }
200
201 pub fn focus_changed(&mut self, focused: bool, cx: &mut ViewContext<Self>) {
202 for (toolbar_item, _) in self.items.iter_mut() {
203 toolbar_item.focus_changed(focused, cx);
204 }
205 }
206
207 pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
208 self.items
209 .iter()
210 .find_map(|(item, _)| item.as_any().clone().downcast())
211 }
212
213 pub fn hidden(&self) -> bool {
214 self.hidden
215 }
216}
217
218impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
219 fn id(&self) -> usize {
220 self.id()
221 }
222
223 fn as_any(&self) -> &AnyViewHandle {
224 self
225 }
226
227 fn set_active_pane_item(
228 &self,
229 active_pane_item: Option<&dyn ItemHandle>,
230 cx: &mut WindowContext,
231 ) -> ToolbarItemLocation {
232 self.update(cx, |this, cx| {
233 this.set_active_pane_item(active_pane_item, cx)
234 })
235 }
236
237 fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext) {
238 self.update(cx, |this, cx| {
239 this.pane_focus_update(pane_focused, cx);
240 cx.notify();
241 });
242 }
243
244 fn row_count(&self, cx: &WindowContext) -> usize {
245 self.read_with(cx, |this, cx| this.row_count(cx))
246 }
247}
248
249impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
250 fn from(val: &dyn ToolbarItemViewHandle) -> Self {
251 val.as_any().clone()
252 }
253}