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 Window, &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(
 38        mut self,
 39        confirm: impl Fn(Option<String>, &mut Window, &mut App) + 'static,
 40    ) -> Self {
 41        self.confirm = Some(Box::new(confirm));
 42        self
 43    }
 44
 45    pub(crate) fn tab_index(mut self, arg: isize) -> Self {
 46        self.tab_index = Some(arg);
 47        self
 48    }
 49}
 50
 51impl RenderOnce for SettingsInputField {
 52    fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
 53        let editor = window.use_state(cx, {
 54            move |window, cx| {
 55                let mut editor = Editor::single_line(window, cx);
 56                if let Some(text) = self.initial_text {
 57                    editor.set_text(text, window, cx);
 58                }
 59
 60                if let Some(placeholder) = self.placeholder {
 61                    editor.set_placeholder_text(placeholder, window, cx);
 62                }
 63                // todo(settings_ui): We should have an observe global use for settings store
 64                // so whenever a settings file is updated, the settings ui updates too
 65                editor
 66            }
 67        });
 68
 69        let weak_editor = editor.downgrade();
 70
 71        let theme_colors = cx.theme().colors();
 72
 73        div()
 74            .py_1()
 75            .px_2()
 76            .min_w_64()
 77            .rounded_md()
 78            .border_1()
 79            .border_color(theme_colors.border)
 80            .bg(theme_colors.editor_background)
 81            .when_some(self.tab_index, |this, tab_index| {
 82                let focus_handle = editor.focus_handle(cx).tab_index(tab_index).tab_stop(true);
 83                this.track_focus(&focus_handle)
 84                    .focus(|s| s.border_color(theme_colors.border_focused))
 85            })
 86            .child(editor)
 87            .when_some(self.confirm, |this, confirm| {
 88                this.on_action::<menu::Confirm>({
 89                    move |_, window, cx| {
 90                        let Some(editor) = weak_editor.upgrade() else {
 91                            return;
 92                        };
 93                        let new_value = editor.read_with(cx, |editor, cx| editor.text(cx));
 94                        let new_value = (!new_value.is_empty()).then_some(new_value);
 95                        confirm(new_value, window, cx);
 96                    }
 97                })
 98            })
 99    }
100}