input_field.rs

 1use editor::Editor;
 2use gpui::{Focusable, div};
 3use ui::{
 4    ActiveTheme as _, App, FluentBuilder as _, InteractiveElement as _, IntoElement,
 5    ParentElement as _, RenderOnce, Styled as _, Window,
 6};
 7
 8#[derive(IntoElement)]
 9pub struct SettingsInputField {
10    initial_text: Option<String>,
11    placeholder: Option<&'static str>,
12    confirm: Option<Box<dyn Fn(Option<String>, &mut App)>>,
13    tab_index: Option<isize>,
14}
15
16// TODO: Update the `ui_input::InputField` to use `window.use_state` and `RenceOnce` and remove this component
17impl SettingsInputField {
18    pub fn new() -> Self {
19        Self {
20            initial_text: None,
21            placeholder: None,
22            confirm: None,
23            tab_index: None,
24        }
25    }
26
27    pub fn with_initial_text(mut self, initial_text: String) -> Self {
28        self.initial_text = Some(initial_text);
29        self
30    }
31
32    pub fn with_placeholder(mut self, placeholder: &'static str) -> Self {
33        self.placeholder = Some(placeholder);
34        self
35    }
36
37    pub fn on_confirm(mut self, confirm: impl Fn(Option<String>, &mut App) + 'static) -> Self {
38        self.confirm = Some(Box::new(confirm));
39        self
40    }
41
42    pub(crate) fn tab_index(mut self, arg: isize) -> Self {
43        self.tab_index = Some(arg);
44        self
45    }
46}
47
48impl RenderOnce for SettingsInputField {
49    fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
50        let editor = window.use_state(cx, {
51            move |window, cx| {
52                let mut editor = Editor::single_line(window, cx);
53                if let Some(text) = self.initial_text {
54                    editor.set_text(text, window, cx);
55                }
56
57                if let Some(placeholder) = self.placeholder {
58                    editor.set_placeholder_text(placeholder, window, cx);
59                }
60                // todo(settings_ui): We should have an observe global use for settings store
61                // so whenever a settings file is updated, the settings ui updates too
62                editor
63            }
64        });
65
66        let weak_editor = editor.downgrade();
67
68        let theme_colors = cx.theme().colors();
69
70        div()
71            .py_1()
72            .px_2()
73            .min_w_64()
74            .rounded_md()
75            .border_1()
76            .border_color(theme_colors.border)
77            .bg(theme_colors.editor_background)
78            .when_some(self.tab_index, |this, tab_index| {
79                let focus_handle = editor.focus_handle(cx).tab_index(tab_index).tab_stop(true);
80                this.track_focus(&focus_handle)
81                    .focus(|s| s.border_color(theme_colors.border_focused))
82            })
83            .child(editor)
84            .when_some(self.confirm, |this, confirm| {
85                this.on_action::<menu::Confirm>({
86                    move |_, _, cx| {
87                        let Some(editor) = weak_editor.upgrade() else {
88                            return;
89                        };
90                        let new_value = editor.read_with(cx, |editor, cx| editor.text(cx));
91                        let new_value = (!new_value.is_empty()).then_some(new_value);
92                        confirm(new_value, cx);
93                    }
94                })
95            })
96    }
97}