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