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(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
73 self.on_close = Some(Rc::new(handler));
74 self
75 }
76}
77
78impl Default for Drawer {
79 fn default() -> Self {
80 Drawer::new()
81 }
82}
83
84impl ParentElement for Drawer {
85 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
86 self.children.extend(elements);
87 }
88}
89
90impl RenderOnce for Drawer {
91 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
92 let t = theme(cx);
93 let surface = t.surface().hsla();
94 let text = t.text().hsla();
95 let dimmed = t.dimmed().hsla();
96 let scrim = t.black.alpha(0.55);
97 let padding = t.spacing(self.padding);
98 let gap = t.spacing(Size::Md);
99 let viewport = window.viewport_size();
100 let close = self.on_close.clone();
101
102 let mut panel = div()
103 .id("guise-drawer-panel")
104 .occlude()
105 .flex()
106 .flex_col()
107 .gap(px(gap))
108 .bg(surface)
109 .p(px(padding))
110 .shadow_xl()
111 .on_click(|_ev, _window, cx| cx.stop_propagation());
112 panel = match self.side {
113 Side::Left | Side::Right => panel.h(viewport.height).w(px(self.size)),
114 Side::Top | Side::Bottom => panel.w(viewport.width).h(px(self.size)),
115 };
116
117 if self.title.is_some() || close.is_some() {
118 let mut header = div().flex().items_center().justify_between();
119 header = header.child(match self.title {
120 Some(title) => div()
121 .text_size(px(18.0))
122 .font_weight(FontWeight::BOLD)
123 .text_color(text)
124 .child(title),
125 None => div(),
126 });
127 if let Some(handler) = close.clone() {
128 header = header.child(
129 div()
130 .id("guise-drawer-close")
131 .px(px(6.0))
132 .rounded(px(4.0))
133 .text_size(px(18.0))
134 .text_color(dimmed)
135 .hover(move |s| s.text_color(text))
136 .child(SharedString::new_static("\u{00d7}"))
137 .on_click(move |ev, window, cx| {
138 handler(ev, window, cx);
139 cx.stop_propagation();
140 }),
141 );
142 }
143 panel = panel.child(header);
144 }
145 panel = panel.children(self.children);
146
147 let mut backdrop = div()
148 .id("guise-drawer-backdrop")
149 .occlude()
150 .absolute()
151 .top(px(0.0))
152 .left(px(0.0))
153 .w(viewport.width)
154 .h(viewport.height)
155 .flex()
156 .bg(scrim);
157 backdrop = match self.side {
158 Side::Left => backdrop.flex_row().justify_start(),
159 Side::Right => backdrop.flex_row().justify_end(),
160 Side::Top => backdrop.flex_col().justify_start(),
161 Side::Bottom => backdrop.flex_col().justify_end(),
162 };
163 backdrop = backdrop.child(panel);
164
165 if let Some(handler) = close {
166 backdrop = backdrop.on_click(move |ev, window, cx| handler(ev, window, cx));
167 }
168
169 deferred(backdrop)
170 }
171}