Skip to main content

gpui_macos/
fields.rs

1//! Familiar macOS text-field configurations on the shared editor behavior.
2use crate::{IconName, Input, icon, input};
3use gpui_kit::base::input::InputState;
4use gpui_kit::{App, ElementId, Entity, Styled, Window, rems};
5
6/// A rounded search field with a leading search glyph. The app owns the query.
7pub fn search_field(
8    id: impl Into<ElementId>,
9    state: &Entity<InputState>,
10    window: &Window,
11    cx: &mut App,
12) -> Input {
13    input(id, state, window, cx)
14        .rounded_full()
15        .prefix(icon(IconName::Search).size(rems(0.875)))
16}
17
18/// A masked editor. Initialize a dedicated state for the secret value.
19/// Base supplies secure clipboard and accessibility behavior while masked.
20pub fn secure_field(
21    id: impl Into<ElementId>,
22    state: &Entity<InputState>,
23    window: &mut Window,
24    cx: &mut App,
25) -> Input {
26    if !state.read(cx).presentation().is_masked() {
27        state.update(cx, |state, cx| state.set_masked(true, window, cx));
28    }
29    input(id, state, window, cx)
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35    use gpui_kit::{AppContext, Context, IntoElement, Render, TestAppContext};
36    use std::{cell::Cell, rc::Rc};
37
38    struct SecureField(Entity<InputState>);
39
40    impl Render for SecureField {
41        fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
42            secure_field("password", &self.0, window, cx)
43        }
44    }
45
46    #[gpui_kit::test]
47    fn secure_field_does_not_notify_its_editor_on_unchanged_redraws(cx: &mut TestAppContext) {
48        cx.update(crate::init);
49        let notifications = Rc::new(Cell::new(0));
50        let observed = notifications.clone();
51        let (view, cx) = cx.add_window_view(move |window, cx| {
52            let state = cx.new(|cx| InputState::new(window, cx).default_value("secret"));
53            cx.observe(&state, move |_, _, _| observed.set(observed.get() + 1))
54                .detach();
55            SecureField(state)
56        });
57        cx.update(|window, cx| window.draw(cx).clear(cx));
58        cx.run_until_parked();
59        notifications.set(0);
60        for _ in 0..3 {
61            cx.update(|window, cx| {
62                window.refresh();
63                window.draw(cx).clear(cx);
64                assert!(view.read(cx).0.read(cx).presentation().is_masked());
65            });
66            cx.run_until_parked();
67        }
68        assert_eq!(
69            notifications.get(),
70            0,
71            "Rendering a secure field must not schedule another redraw"
72        );
73    }
74}