1use crate::ItemHandle;
2use gpui::{
3 elements::*, AnyViewHandle, AppContext, ElementBox, Entity, MutableAppContext, RenderContext,
4 View, ViewContext, ViewHandle,
5};
6use settings::Settings;
7
8pub trait ToolbarItemView: View {
9 fn set_active_pane_item(
10 &mut self,
11 active_pane_item: Option<&dyn crate::ItemHandle>,
12 cx: &mut ViewContext<Self>,
13 ) -> ToolbarItemLocation;
14
15 fn location_for_event(
16 &self,
17 _event: &Self::Event,
18 current_location: ToolbarItemLocation,
19 _cx: &AppContext,
20 ) -> ToolbarItemLocation {
21 current_location
22 }
23}
24
25trait ToolbarItemViewHandle {
26 fn id(&self) -> usize;
27 fn to_any(&self) -> AnyViewHandle;
28 fn set_active_pane_item(
29 &self,
30 active_pane_item: Option<&dyn ItemHandle>,
31 cx: &mut MutableAppContext,
32 ) -> ToolbarItemLocation;
33}
34
35#[derive(Copy, Clone, Debug, PartialEq)]
36pub enum ToolbarItemLocation {
37 Hidden,
38 PrimaryLeft { flex: Option<(f32, bool)> },
39 PrimaryRight { flex: Option<(f32, bool)> },
40 Secondary,
41}
42
43pub struct Toolbar {
44 active_pane_item: Option<Box<dyn ItemHandle>>,
45 items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
46}
47
48impl Entity for Toolbar {
49 type Event = ();
50}
51
52impl View for Toolbar {
53 fn ui_name() -> &'static str {
54 "Toolbar"
55 }
56
57 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
58 let theme = &cx.global::<Settings>().theme.workspace.toolbar;
59
60 let mut primary_left_items = Vec::new();
61 let mut primary_right_items = Vec::new();
62 let mut secondary_item = None;
63
64 for (item, position) in &self.items {
65 match *position {
66 ToolbarItemLocation::Hidden => {}
67 ToolbarItemLocation::PrimaryLeft { flex } => {
68 let left_item = ChildView::new(item.as_ref())
69 .aligned()
70 .contained()
71 .with_margin_right(theme.item_spacing);
72 if let Some((flex, expanded)) = flex {
73 primary_left_items.push(left_item.flex(flex, expanded).boxed());
74 } else {
75 primary_left_items.push(left_item.boxed());
76 }
77 }
78 ToolbarItemLocation::PrimaryRight { flex } => {
79 let right_item = ChildView::new(item.as_ref())
80 .aligned()
81 .contained()
82 .with_margin_left(theme.item_spacing)
83 .flex_float();
84 if let Some((flex, expanded)) = flex {
85 primary_right_items.push(right_item.flex(flex, expanded).boxed());
86 } else {
87 primary_right_items.push(right_item.boxed());
88 }
89 }
90 ToolbarItemLocation::Secondary => {
91 secondary_item = Some(
92 ChildView::new(item.as_ref())
93 .constrained()
94 .with_height(theme.height)
95 .boxed(),
96 );
97 }
98 }
99 }
100
101 Flex::column()
102 .with_child(
103 Flex::row()
104 .with_children(primary_left_items)
105 .with_children(primary_right_items)
106 .constrained()
107 .with_height(theme.height)
108 .boxed(),
109 )
110 .with_children(secondary_item)
111 .contained()
112 .with_style(theme.container)
113 .boxed()
114 }
115}
116
117impl Toolbar {
118 pub fn new() -> Self {
119 Self {
120 active_pane_item: None,
121 items: Default::default(),
122 }
123 }
124
125 pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
126 where
127 T: 'static + ToolbarItemView,
128 {
129 let location = item.set_active_pane_item(self.active_pane_item.as_deref(), cx);
130 cx.subscribe(&item, |this, item, event, cx| {
131 if let Some((_, current_location)) =
132 this.items.iter_mut().find(|(i, _)| i.id() == item.id())
133 {
134 let new_location = item
135 .read(cx)
136 .location_for_event(event, *current_location, cx);
137 if new_location != *current_location {
138 *current_location = new_location;
139 cx.notify();
140 }
141 }
142 })
143 .detach();
144 self.items.push((Box::new(item), location));
145 cx.notify();
146 }
147
148 pub fn set_active_pane_item(
149 &mut self,
150 pane_item: Option<&dyn ItemHandle>,
151 cx: &mut ViewContext<Self>,
152 ) {
153 self.active_pane_item = pane_item.map(|item| item.boxed_clone());
154 for (toolbar_item, current_location) in self.items.iter_mut() {
155 let new_location = toolbar_item.set_active_pane_item(pane_item, cx);
156 if new_location != *current_location {
157 *current_location = new_location;
158 cx.notify();
159 }
160 }
161 }
162
163 pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
164 self.items
165 .iter()
166 .find_map(|(item, _)| item.to_any().downcast())
167 }
168}
169
170impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
171 fn id(&self) -> usize {
172 self.id()
173 }
174
175 fn to_any(&self) -> AnyViewHandle {
176 self.into()
177 }
178
179 fn set_active_pane_item(
180 &self,
181 active_pane_item: Option<&dyn ItemHandle>,
182 cx: &mut MutableAppContext,
183 ) -> ToolbarItemLocation {
184 self.update(cx, |this, cx| {
185 this.set_active_pane_item(active_pane_item, cx)
186 })
187 }
188}
189
190impl Into<AnyViewHandle> for &dyn ToolbarItemViewHandle {
191 fn into(self) -> AnyViewHandle {
192 self.to_any()
193 }
194}