1use std::rc::Rc;
9
10use gpui::prelude::*;
11use gpui::{
12 deferred, div, px, AnyElement, App, ClickEvent, FontWeight, IntoElement, SharedString, Window,
13};
14
15use crate::devtools::ProbedAny;
16use crate::theme::{theme, Size};
17
18type CloseHandler = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Side {
23 Left,
24 Right,
25 Top,
26 Bottom,
27}
28
29#[derive(IntoElement)]
31pub struct Drawer {
32 title: Option<SharedString>,
33 children: Vec<AnyElement>,
34 side: Side,
35 size: f32,
36 padding: Size,
37 on_close: Option<CloseHandler>,
38}
39
40impl Drawer {
41 pub fn new() -> Self {
42 Drawer {
43 title: None,
44 children: Vec::new(),
45 side: Side::Right,
46 size: 360.0,
47 padding: Size::Lg,
48 on_close: None,
49 }
50 }
51
52 pub fn title(mut self, title: impl Into<SharedString>) -> Self {
53 self.title = Some(title.into());
54 self
55 }
56
57 pub fn side(mut self, side: Side) -> Self {
58 self.side = side;
59 self
60 }
61
62 pub fn size(mut self, size: f32) -> Self {
64 self.size = size;
65 self
66 }
67
68 pub fn padding(mut self, padding: Size) -> Self {
69 self.padding = padding;
70 self
71 }
72
73 pub fn on_close(
74 mut self,
75 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
76 ) -> Self {
77 self.on_close = Some(Rc::new(handler));
78 self
79 }
80}
81
82impl Default for Drawer {
83 fn default() -> Self {
84 Drawer::new()
85 }
86}
87
88impl ParentElement for Drawer {
89 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
90 self.children.extend(elements);
91 }
92}
93
94impl RenderOnce for Drawer {
95 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
96 let t = theme(cx);
97 let surface = t.surface().hsla();
98 let text = t.text().hsla();
99 let dimmed = t.dimmed().hsla();
100 let scrim = t.black.alpha(0.55);
101 let padding = t.spacing(self.padding);
102 let gap = t.spacing(Size::Md);
103 let viewport = window.viewport_size();
104 let close = self.on_close.clone();
105
106 let mut panel = div()
107 .id("guise-drawer-panel")
108 .occlude()
109 .flex()
110 .flex_col()
111 .gap(px(gap))
112 .bg(surface)
113 .p(px(padding))
114 .shadow_xl()
115 .on_click(|_ev, _window, cx| cx.stop_propagation());
116 panel = match self.side {
117 Side::Left | Side::Right => panel.h(viewport.height).w(px(self.size)),
118 Side::Top | Side::Bottom => panel.w(viewport.width).h(px(self.size)),
119 };
120
121 if self.title.is_some() || close.is_some() {
122 let mut header = div().flex().items_center().justify_between();
123 header = header.child(match self.title {
124 Some(title) => div()
125 .text_size(px(18.0))
126 .font_weight(FontWeight::BOLD)
127 .text_color(text)
128 .child(title),
129 None => div(),
130 });
131 if let Some(handler) = close.clone() {
132 header = header.child(
133 div()
134 .id("guise-drawer-close")
135 .px(px(6.0))
136 .rounded(px(4.0))
137 .text_size(px(18.0))
138 .text_color(dimmed)
139 .hover(move |s| s.text_color(text))
140 .child(SharedString::new_static("\u{00d7}"))
141 .on_click(move |ev, window, cx| {
142 handler(ev, window, cx);
143 cx.stop_propagation();
144 }),
145 );
146 }
147 panel = panel.child(header);
148 }
149 panel = panel.children(self.children);
150
151 let mut backdrop = div()
152 .id("guise-drawer-backdrop")
153 .occlude()
154 .absolute()
155 .top(px(0.0))
156 .left(px(0.0))
157 .w(viewport.width)
158 .h(viewport.height)
159 .flex()
160 .bg(scrim);
161 backdrop = match self.side {
162 Side::Left => backdrop.flex_row().justify_start(),
163 Side::Right => backdrop.flex_row().justify_end(),
164 Side::Top => backdrop.flex_col().justify_start(),
165 Side::Bottom => backdrop.flex_col().justify_end(),
166 };
167 backdrop = backdrop.child(panel);
168
169 if let Some(handler) = close {
170 backdrop = backdrop.on_click(move |ev, window, cx| handler(ev, window, cx));
171 }
172
173 deferred(backdrop).probe_any("Drawer")
174 }
175}