1use super::*;
4
5pub fn menu_row(theme: &Theme, active: bool, fade: Option<Fade>) -> gpui::Div {
18 let row = div()
19 .flex()
20 .flex_row()
21 .items_center()
22 .gap(px(10.0))
23 .px(px(MENU_ROW_INSET))
24 .py(px(MENU_ROW_PAD_Y))
25 .rounded(px(Theme::inset_radius(Theme::surface_radius(), MENU_PAD)))
29 .text_style(TextStyle::Body)
30 .cursor_pointer();
31 match (active, fade) {
32 (true, _) => row
33 .bg(theme.card_selected_bg())
34 .shadow(theme::glass_selected_shadows())
35 .text_color(theme.text),
36 (false, None) => row.text_color(theme.text.opacity(0.9)),
37 (false, Some(fade)) => {
38 let mut row = row
39 .text_color(motion::hover_blend(
40 &fade,
41 theme.text.opacity(0.9),
42 theme.text,
43 ))
44 .bg(motion::hover_blend(
45 &fade,
46 theme::ink(0.0),
47 theme.element_hover,
48 ));
49 row.interactivity().on_hover(motion::hover_listener(fade));
52 row
53 }
54 }
55}
56
57pub fn menu_heading(theme: &Theme, label: impl Into<SharedString>) -> gpui::Div {
62 let label = label.into();
63 div()
64 .px(px(8.0))
65 .pb(px(4.0))
66 .pt(px(6.0))
67 .text_style(TextStyle::Caption2)
68 .text_color(theme.text_muted.opacity(0.6))
69 .child(tracked_upper(&label))
70}
71
72pub fn menu_note(theme: &Theme, text: impl Into<SharedString>) -> gpui::Div {
74 div()
75 .px(px(8.0))
76 .py(px(2.0))
77 .text_style(TextStyle::Caption)
78 .text_color(theme.text_muted)
79 .child(text.into())
80}
81
82pub fn tracked_upper(label: &str) -> String {
84 let upper = label.to_uppercase();
85 let mut out = String::with_capacity(upper.len() * 2);
86 let mut first = true;
87 for ch in upper.chars() {
88 if !first {
89 out.push('\u{200A}'); }
91 out.push(ch);
92 first = false;
93 }
94 out
95}
96
97pub fn divider(theme: &Theme) -> gpui::Div {
100 div()
103 .h(px(1.0))
104 .mx(px(-4.0))
105 .my(px(4.0))
106 .bg(theme.border_faint)
107}
108
109pub fn band() -> gpui::Hsla {
117 theme::band()
118}
119
120pub fn key_cap(_theme: &Theme) -> gpui::Div {
124 div()
125 .h(px(22.0))
126 .px(px(5.0))
127 .rounded(px(5.0))
128 .flex()
129 .flex_row()
130 .items_center()
131 .justify_center()
132 .gap(px(4.0))
133 .bg(ink(0.05))
134}
135
136pub(super) fn key_hint_label(theme: &Theme, label: &'static str) -> gpui::Div {
138 div()
139 .text_style(TextStyle::Caption)
140 .text_color(theme.text_muted.opacity(0.45))
141 .child(SharedString::from(label))
142}
143
144pub fn key_hint(theme: &Theme, icon: impl Into<Icon>, label: &'static str) -> gpui::Div {
147 div()
148 .flex()
149 .flex_row()
150 .items_center()
151 .gap(px(5.0))
152 .child(
153 key_cap(theme).child(
154 crate::icons::icon(icon)
155 .size(px(12.5))
156 .text_color(theme.text_muted.opacity(0.7)),
157 ),
158 )
159 .child(key_hint_label(theme, label))
160}
161
162pub fn key_hint_text(
165 theme: &Theme,
166 cap: impl Into<SharedString>,
167 label: &'static str,
168) -> gpui::Div {
169 div()
170 .flex()
171 .flex_row()
172 .items_center()
173 .gap(px(5.0))
174 .child(
175 key_cap(theme)
176 .text_style(TextStyle::Subheadline)
177 .font_family(theme.font_mono.clone())
178 .text_color(theme.text_muted.opacity(0.7))
179 .child(cap.into()),
180 )
181 .child(key_hint_label(theme, label))
182}
183
184pub fn key_hint_pair(
187 theme: &Theme,
188 first: impl Into<Icon>,
189 second: impl Into<Icon>,
190 label: &'static str,
191) -> gpui::Div {
192 div()
193 .flex()
194 .flex_row()
195 .items_center()
196 .gap(px(5.0))
197 .child(
198 key_cap(theme)
199 .child(
200 crate::icons::icon(first)
201 .size(px(12.5))
202 .text_color(theme.text_muted.opacity(0.7)),
203 )
204 .child(div().w(px(1.0)).h(px(11.0)).bg(theme.border))
205 .child(
206 crate::icons::icon(second)
207 .size(px(12.5))
208 .text_color(theme.text_muted.opacity(0.7)),
209 ),
210 )
211 .child(key_hint_label(theme, label))
212}
213
214pub fn kbd_hint(theme: &Theme, label: impl Into<SharedString>) -> gpui::Div {
216 div()
217 .flex_none()
218 .px(px(5.0))
219 .py(px(1.0))
220 .rounded(px(5.0))
221 .bg(ink(0.05))
222 .text_style(TextStyle::Caption)
223 .font_family(theme.font_mono.clone())
224 .text_color(theme.text_muted.opacity(0.6))
225 .child(label.into())
226}
227
228pub fn search_line(theme: &Theme, input: AnyElement) -> gpui::Div {
236 stack::row()
237 .mx(px(-MENU_PAD))
238 .px(px(MENU_PAD + MENU_ROW_INSET))
239 .py(px(7.0))
240 .mb(px(MENU_PAD))
241 .border_b_1()
242 .border_color(theme.border_faint)
243 .text_style(TextStyle::Body)
244 .child(
245 icons::icon(icons::glyph::Search)
246 .size(px(13.0))
247 .text_color(theme.text_faint),
248 )
249 .child(div().flex_1().child(input))
250}
251
252pub fn menu_section(theme: &Theme) -> gpui::Div {
257 div()
258 .mt(px(4.0))
259 .pt(px(4.0))
260 .border_t_1()
261 .border_color(theme.border_faint)
262 .flex()
263 .flex_col()
264 .gap(px(2.0))
265}
266
267pub fn dialog_card(theme: &Theme) -> gpui::Div {
274 div()
275 .w(px(360.0))
276 .p(px(20.0))
277 .rounded(px(DIALOG_RADIUS))
278 .bg(theme.surface_dialog)
279 .border_1()
280 .border_color(theme.border)
281 .shadow_lg()
282 .flex()
283 .flex_col()
284 .text_color(theme.text)
285}
286
287pub fn dialog_title(theme: &Theme, title: impl Into<SharedString>) -> gpui::Div {
289 div()
290 .text_style(TextStyle::Headline)
291 .text_color(theme.text)
292 .child(title.into())
293}
294
295pub fn dialog_body(theme: &Theme, copy: impl Into<SharedString>) -> gpui::Div {
297 div()
298 .text_style(TextStyle::Body)
299 .line_height(px(19.0))
300 .text_color(theme.text_muted)
301 .child(copy.into())
302}
303
304pub fn dialog_field(theme: &Theme, input: AnyElement) -> gpui::Div {
307 div()
308 .w_full()
309 .px(px(12.0))
310 .py(px(8.0))
311 .rounded(px(Theme::button_radius()))
312 .border_1()
313 .border_color(theme.border)
314 .bg(ink(0.04))
315 .text_style(TextStyle::Body)
316 .child(input)
317}
318
319pub fn redacted_rows(
323 _id: &'static str,
324 _theme: &Theme,
325 count: usize,
326 painter: Painter,
327 cx: &mut gpui::App,
328) -> AnyElement {
329 let wash = ink(0.04);
330 let delta = motion::pulse_delta(&PULSE, painter, cx);
331 div()
332 .flex()
333 .flex_col()
334 .gap(px(6.0))
335 .py(px(4.0))
336 .children((0..count).map(move |i| {
337 let phase = motion::staggered_phase(delta, i, 0.08);
338 div()
339 .h(px(28.0))
340 .rounded(px(Theme::control_radius()))
341 .bg(wash)
342 .opacity(0.35 + 0.4 * motion::pulse_wave(phase))
343 }))
344 .into_any_element()
345}
346
347pub fn error_row(theme: &Theme, message: impl Into<SharedString>) -> gpui::Div {
349 div()
350 .flex()
351 .flex_col()
352 .gap(px(6.0))
353 .p(px(Theme::SPACE))
354 .text_style(TextStyle::Callout)
355 .text_color(theme.danger)
356 .child(message.into())
357}