toolbar.rs

  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}