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