popover.rs

  1use crate::prelude::*;
  2use crate::v_flex;
  3use gpui::{
  4    AnyElement, App, Element, InteractiveElement, IntoElement, ParentElement, Pixels, RenderOnce,
  5    Role, SharedString, Styled, Window, div,
  6};
  7use smallvec::SmallVec;
  8
  9/// Y height added beyond the size of the contents.
 10pub const POPOVER_Y_PADDING: Pixels = px(8.);
 11
 12/// A popover is used to display a menu or show some options.
 13///
 14/// Clicking the element that launches the popover should not change the current view,
 15/// and the popover should be statically positioned relative to that element (not the
 16/// user's mouse.)
 17///
 18/// Example: A "new" menu with options like "new file", "new folder", etc,
 19/// Linear's "Display" menu, a profile menu that appears when you click your avatar.
 20///
 21/// Related elements:
 22///
 23/// [`ContextMenu`](crate::ContextMenu):
 24///
 25/// Used to display a popover menu that only contains a list of items. Context menus are always
 26/// launched by secondary clicking on an element. The menu is positioned relative to the user's cursor.
 27///
 28/// Example: Right clicking a file in the file tree to get a list of actions, right clicking
 29/// a tab to in the tab bar to get a list of actions.
 30///
 31/// `Dropdown`:
 32///
 33/// Used to display a list of options when the user clicks an element. The menu is
 34/// positioned relative the element that was clicked, and clicking an item in the
 35/// dropdown should change the value of the element that was clicked.
 36///
 37/// Example: A theme select control. Displays "One Dark", clicking it opens a list of themes.
 38/// When one is selected, the theme select control displays the selected theme.
 39#[derive(IntoElement)]
 40pub struct Popover {
 41    id: Option<ElementId>,
 42    a11y_label: Option<SharedString>,
 43    children: SmallVec<[AnyElement; 2]>,
 44    aside: Option<AnyElement>,
 45}
 46
 47impl RenderOnce for Popover {
 48    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
 49        let main_content = v_flex()
 50            .elevation_2(cx)
 51            .py(POPOVER_Y_PADDING / 2.)
 52            .child(div().children(self.children));
 53
 54        let aside_content = self.aside.map(|aside| {
 55            v_flex()
 56                .elevation_2(cx)
 57                .bg(cx.theme().colors().surface_background)
 58                .px_1()
 59                .child(aside)
 60        });
 61
 62        if let Some(id) = self.id {
 63            div()
 64                .flex()
 65                .gap_1()
 66                .id(id)
 67                .role(Role::Dialog)
 68                .when_some(self.a11y_label, |this, label| this.aria_label(label))
 69                .child(main_content)
 70                .when_some(aside_content, |this, aside| this.child(aside))
 71                .into_any_element()
 72        } else {
 73            div()
 74                .flex()
 75                .gap_1()
 76                .child(main_content)
 77                .when_some(aside_content, |this, aside| this.child(aside))
 78                .into_any_element()
 79        }
 80    }
 81}
 82
 83impl Default for Popover {
 84    fn default() -> Self {
 85        Self::new()
 86    }
 87}
 88
 89impl Popover {
 90    pub fn new() -> Self {
 91        Self {
 92            id: None,
 93            a11y_label: None,
 94            children: SmallVec::new(),
 95            aside: None,
 96        }
 97    }
 98
 99    pub fn id(mut self, id: impl Into<ElementId>) -> Self {
100        self.id = Some(id.into());
101        self
102    }
103
104    pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
105        self.a11y_label = Some(label.into());
106        self
107    }
108
109    pub fn aside(mut self, aside: impl IntoElement) -> Self
110    where
111        Self: Sized,
112    {
113        self.aside = Some(aside.into_element().into_any());
114        self
115    }
116}
117
118impl ParentElement for Popover {
119    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
120        self.children.extend(elements)
121    }
122}