input_field.rs

  1use std::rc::Rc;
  2
  3use editor::Editor;
  4use gpui::{AnyElement, ElementId, Focusable, TextStyleRefinement};
  5use settings::Settings as _;
  6use theme::ThemeSettings;
  7use ui::{Tooltip, prelude::*, rems};
  8
  9#[derive(IntoElement)]
 10pub struct SettingsInputField {
 11    id: Option<ElementId>,
 12    initial_text: Option<String>,
 13    placeholder: Option<&'static str>,
 14    confirm: Option<Rc<dyn Fn(Option<String>, &mut Window, &mut App)>>,
 15    tab_index: Option<isize>,
 16    use_buffer_font: bool,
 17    display_confirm_button: bool,
 18    display_clear_button: bool,
 19    action_slot: Option<AnyElement>,
 20    color: Option<Color>,
 21}
 22
 23impl SettingsInputField {
 24    pub fn new() -> Self {
 25        Self {
 26            id: None,
 27            initial_text: None,
 28            placeholder: None,
 29            confirm: None,
 30            tab_index: None,
 31            use_buffer_font: false,
 32            display_confirm_button: false,
 33            display_clear_button: false,
 34            action_slot: None,
 35            color: None,
 36        }
 37    }
 38
 39    pub fn with_id(mut self, id: impl Into<ElementId>) -> Self {
 40        self.id = Some(id.into());
 41        self
 42    }
 43
 44    pub fn with_initial_text(mut self, initial_text: String) -> Self {
 45        self.initial_text = Some(initial_text);
 46        self
 47    }
 48
 49    pub fn with_placeholder(mut self, placeholder: &'static str) -> Self {
 50        self.placeholder = Some(placeholder);
 51        self
 52    }
 53
 54    pub fn on_confirm(
 55        mut self,
 56        confirm: impl Fn(Option<String>, &mut Window, &mut App) + 'static,
 57    ) -> Self {
 58        self.confirm = Some(Rc::new(confirm));
 59        self
 60    }
 61
 62    pub fn display_confirm_button(mut self) -> Self {
 63        self.display_confirm_button = true;
 64        self
 65    }
 66
 67    pub fn display_clear_button(mut self) -> Self {
 68        self.display_clear_button = true;
 69        self
 70    }
 71
 72    pub fn action_slot(mut self, action: impl IntoElement) -> Self {
 73        self.action_slot = Some(action.into_any_element());
 74        self
 75    }
 76
 77    pub(crate) fn tab_index(mut self, arg: isize) -> Self {
 78        self.tab_index = Some(arg);
 79        self
 80    }
 81
 82    pub fn with_buffer_font(mut self) -> Self {
 83        self.use_buffer_font = true;
 84        self
 85    }
 86
 87    pub fn color(mut self, color: Color) -> Self {
 88        self.color = Some(color);
 89        self
 90    }
 91}
 92
 93impl RenderOnce for SettingsInputField {
 94    fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
 95        let settings = ThemeSettings::get_global(cx);
 96        let use_buffer_font = self.use_buffer_font;
 97        let color = self.color.map(|c| c.color(cx));
 98        let styles = TextStyleRefinement {
 99            font_family: use_buffer_font.then(|| settings.buffer_font.family.clone()),
100            font_size: use_buffer_font.then(|| rems(0.75).into()),
101            color,
102            ..Default::default()
103        };
104
105        let editor = if let Some(id) = self.id {
106            window.use_keyed_state(id, cx, {
107                let initial_text = self.initial_text.clone();
108                let placeholder = self.placeholder;
109                move |window, cx| {
110                    let mut editor = Editor::single_line(window, cx);
111                    if let Some(text) = initial_text {
112                        editor.set_text(text, window, cx);
113                    }
114
115                    if let Some(placeholder) = placeholder {
116                        editor.set_placeholder_text(placeholder, window, cx);
117                    }
118                    editor.set_text_style_refinement(styles);
119                    editor
120                }
121            })
122        } else {
123            window.use_state(cx, {
124                let initial_text = self.initial_text.clone();
125                let placeholder = self.placeholder;
126                move |window, cx| {
127                    let mut editor = Editor::single_line(window, cx);
128                    if let Some(text) = initial_text {
129                        editor.set_text(text, window, cx);
130                    }
131
132                    if let Some(placeholder) = placeholder {
133                        editor.set_placeholder_text(placeholder, window, cx);
134                    }
135                    editor.set_text_style_refinement(styles);
136                    editor
137                }
138            })
139        };
140
141        let weak_editor = editor.downgrade();
142        let weak_editor_for_button = editor.downgrade();
143        let weak_editor_for_clear = editor.downgrade();
144
145        let theme_colors = cx.theme().colors();
146
147        let display_confirm_button = self.display_confirm_button;
148        let display_clear_button = self.display_clear_button;
149        let confirm_for_button = self.confirm.clone();
150        let is_editor_empty = editor.read(cx).text(cx).trim().is_empty();
151        let is_editor_focused = editor.read(cx).is_focused(window);
152
153        h_flex()
154            .group("settings-input-field-editor")
155            .relative()
156            .py_1()
157            .px_2()
158            .h_8()
159            .min_w_64()
160            .rounded_md()
161            .border_1()
162            .border_color(theme_colors.border)
163            .bg(theme_colors.editor_background)
164            .when_some(self.tab_index, |this, tab_index| {
165                let focus_handle = editor.focus_handle(cx).tab_index(tab_index).tab_stop(true);
166                this.track_focus(&focus_handle)
167                    .focus(|s| s.border_color(theme_colors.border_focused))
168            })
169            .child(editor)
170            .child(
171                h_flex()
172                    .absolute()
173                    .top_1()
174                    .right_1()
175                    .invisible()
176                    .when(is_editor_focused, |this| this.visible())
177                    .group_hover("settings-input-field-editor", |this| this.visible())
178                    .when(
179                        display_clear_button && !is_editor_empty && is_editor_focused,
180                        |this| {
181                            this.child(
182                                IconButton::new("clear-button", IconName::Close)
183                                    .icon_size(IconSize::Small)
184                                    .icon_color(Color::Muted)
185                                    .tooltip(Tooltip::text("Clear"))
186                                    .on_click(move |_, window, cx| {
187                                        let Some(editor) = weak_editor_for_clear.upgrade() else {
188                                            return;
189                                        };
190                                        editor.update(cx, |editor, cx| {
191                                            editor.set_text("", window, cx);
192                                        });
193                                    }),
194                            )
195                        },
196                    )
197                    .when(
198                        display_confirm_button && !is_editor_empty && is_editor_focused,
199                        |this| {
200                            this.child(
201                                IconButton::new("confirm-button", IconName::Check)
202                                    .icon_size(IconSize::Small)
203                                    .icon_color(Color::Success)
204                                    .tooltip(Tooltip::text("Enter to Confirm"))
205                                    .on_click(move |_, window, cx| {
206                                        let Some(confirm) = confirm_for_button.as_ref() else {
207                                            return;
208                                        };
209                                        let Some(editor) = weak_editor_for_button.upgrade() else {
210                                            return;
211                                        };
212                                        let new_value =
213                                            editor.read_with(cx, |editor, cx| editor.text(cx));
214                                        let new_value =
215                                            (!new_value.is_empty()).then_some(new_value);
216                                        confirm(new_value, window, cx);
217                                    }),
218                            )
219                        },
220                    )
221                    .when_some(self.action_slot, |this, action| this.child(action)),
222            )
223            .when_some(self.confirm, |this, confirm| {
224                this.on_action::<menu::Confirm>({
225                    move |_, window, cx| {
226                        let Some(editor) = weak_editor.upgrade() else {
227                            return;
228                        };
229                        let new_value = editor.read_with(cx, |editor, cx| editor.text(cx));
230                        let new_value = (!new_value.is_empty()).then_some(new_value);
231                        confirm(new_value, window, cx);
232                    }
233                })
234            })
235    }
236}