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