toolbar.rs

  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}