toolbar.rs

  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).aligned();
 85                    if let Some((flex, expanded)) = flex {
 86                        primary_left_items.push(left_item.flex(flex, expanded).into_any());
 87                    } else {
 88                        primary_left_items.push(left_item.into_any());
 89                    }
 90                }
 91
 92                ToolbarItemLocation::PrimaryRight { flex } => {
 93                    primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
 94                    let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
 95                    if let Some((flex, expanded)) = flex {
 96                        primary_right_items.push(right_item.flex(flex, expanded).into_any());
 97                    } else {
 98                        primary_right_items.push(right_item.into_any());
 99                    }
100                }
101
102                ToolbarItemLocation::Secondary => {
103                    secondary_item = Some(
104                        ChildView::new(item.as_any(), cx)
105                            .constrained()
106                            .with_height(theme.height * item.row_count(cx) as f32)
107                            .into_any(),
108                    );
109                }
110            }
111        }
112
113        let container_style = theme.container;
114        let height = theme.height * primary_items_row_count as f32;
115
116        let mut primary_items = Flex::row().with_spacing(spacing);
117        primary_items.extend(primary_left_items);
118        primary_items.extend(primary_right_items);
119
120        let mut toolbar = Flex::column();
121        if !primary_items.is_empty() {
122            toolbar.add_child(primary_items.constrained().with_height(height));
123        }
124        if let Some(secondary_item) = secondary_item {
125            toolbar.add_child(secondary_item);
126        }
127
128        if toolbar.is_empty() {
129            toolbar.into_any_named("toolbar")
130        } else {
131            toolbar
132                .contained()
133                .with_style(container_style)
134                .into_any_named("toolbar")
135        }
136    }
137}
138
139// <<<<<<< HEAD
140// =======
141// #[allow(clippy::too_many_arguments)]
142// fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>)>(
143//     svg_path: &'static str,
144//     style: theme::Interactive<theme::IconButton>,
145//     nav_button_height: f32,
146//     tooltip_style: TooltipStyle,
147//     enabled: bool,
148//     spacing: f32,
149//     on_click: F,
150//     tooltip_action: A,
151//     action_name: &'static str,
152//     cx: &mut ViewContext<Toolbar>,
153// ) -> AnyElement<Toolbar> {
154//     MouseEventHandler::new::<A, _>(0, cx, |state, _| {
155//         let style = if enabled {
156//             style.style_for(state)
157//         } else {
158//             style.disabled_style()
159//         };
160//         Svg::new(svg_path)
161//             .with_color(style.color)
162//             .constrained()
163//             .with_width(style.icon_width)
164//             .aligned()
165//             .contained()
166//             .with_style(style.container)
167//             .constrained()
168//             .with_width(style.button_width)
169//             .with_height(nav_button_height)
170//             .aligned()
171//             .top()
172//     })
173//     .with_cursor_style(if enabled {
174//         CursorStyle::PointingHand
175//     } else {
176//         CursorStyle::default()
177//     })
178//     .on_click(MouseButton::Left, move |_, toolbar, cx| {
179//         on_click(toolbar, cx)
180//     })
181//     .with_tooltip::<A>(
182//         0,
183//         action_name,
184//         Some(Box::new(tooltip_action)),
185//         tooltip_style,
186//         cx,
187//     )
188//     .contained()
189//     .with_margin_right(spacing)
190//     .into_any_named("nav button")
191// }
192
193// >>>>>>> 139cbbfd3aebd0863a7d51b0c12d748764cf0b2e
194impl Toolbar {
195    pub fn new() -> Self {
196        Self {
197            active_item: None,
198            items: Default::default(),
199            hidden: false,
200            can_navigate: true,
201        }
202    }
203
204    pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
205        self.can_navigate = can_navigate;
206        cx.notify();
207    }
208
209    pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
210    where
211        T: 'static + ToolbarItemView,
212    {
213        let location = item.set_active_pane_item(self.active_item.as_deref(), cx);
214        cx.subscribe(&item, |this, item, event, cx| {
215            if let Some((_, current_location)) =
216                this.items.iter_mut().find(|(i, _)| i.id() == item.id())
217            {
218                let new_location = item
219                    .read(cx)
220                    .location_for_event(event, *current_location, cx);
221                if new_location != *current_location {
222                    *current_location = new_location;
223                    cx.notify();
224                }
225            }
226        })
227        .detach();
228        self.items.push((Box::new(item), location));
229        cx.notify();
230    }
231
232    pub fn set_active_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
233        self.active_item = item.map(|item| item.boxed_clone());
234        self.hidden = self
235            .active_item
236            .as_ref()
237            .map(|item| !item.show_toolbar(cx))
238            .unwrap_or(false);
239
240        for (toolbar_item, current_location) in self.items.iter_mut() {
241            let new_location = toolbar_item.set_active_pane_item(item, cx);
242            if new_location != *current_location {
243                *current_location = new_location;
244                cx.notify();
245            }
246        }
247    }
248
249    pub fn focus_changed(&mut self, focused: bool, cx: &mut ViewContext<Self>) {
250        for (toolbar_item, _) in self.items.iter_mut() {
251            toolbar_item.focus_changed(focused, cx);
252        }
253    }
254
255    pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
256        self.items
257            .iter()
258            .find_map(|(item, _)| item.as_any().clone().downcast())
259    }
260
261    pub fn hidden(&self) -> bool {
262        self.hidden
263    }
264}
265
266impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
267    fn id(&self) -> usize {
268        self.id()
269    }
270
271    fn as_any(&self) -> &AnyViewHandle {
272        self
273    }
274
275    fn set_active_pane_item(
276        &self,
277        active_pane_item: Option<&dyn ItemHandle>,
278        cx: &mut WindowContext,
279    ) -> ToolbarItemLocation {
280        self.update(cx, |this, cx| {
281            this.set_active_pane_item(active_pane_item, cx)
282        })
283    }
284
285    fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext) {
286        self.update(cx, |this, cx| {
287            this.pane_focus_update(pane_focused, cx);
288            cx.notify();
289        });
290    }
291
292    fn row_count(&self, cx: &WindowContext) -> usize {
293        self.read_with(cx, |this, cx| this.row_count(cx))
294    }
295}
296
297impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
298    fn from(val: &dyn ToolbarItemViewHandle) -> Self {
299        val.as_any().clone()
300    }
301}