Skip to main content

iced_core/text/
input.rs

1#![allow(missing_docs)] // TODO
2use crate::alignment;
3use crate::clipboard;
4use crate::layout;
5use crate::mouse;
6use crate::text::editor;
7use crate::text::paragraph;
8use crate::text::{self, Alignment, Editor, LineHeight, Position, Text, Wrapping};
9use crate::widget::operation::{Focusable, TextInput};
10use crate::{
11    Color, Event, Font, InputMethod, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
12};
13
14use unicode_segmentation::UnicodeSegmentation;
15
16use std::sync::Arc;
17
18const SECURE_CHAR: char = '•';
19
20pub struct Input<R: text::Renderer> {
21    editor: R::Editor,
22    secure: Option<R::Editor>,
23    state: editor::State,
24    placeholder: paragraph::Plain<R::Paragraph>,
25    padding: Padding,
26    multiline: Option<Wrapping>,
27}
28
29pub struct Layout<'a> {
30    pub width: Length,
31    pub height: Length,
32    pub padding: Padding,
33    pub placeholder: &'a str,
34    pub font: Option<Font>,
35    pub size: Option<Pixels>,
36    pub line_height: Option<LineHeight>,
37    pub alignment: Alignment,
38    pub multiline: Option<Wrapping>,
39    pub is_secure: bool,
40}
41
42impl<R: text::Renderer> Input<R> {
43    pub fn new() -> Self {
44        Self {
45            editor: R::Editor::with_text(""),
46            secure: None,
47            state: editor::State::new(),
48            placeholder: paragraph::Plain::default(),
49            padding: Padding::default(),
50            multiline: None,
51        }
52    }
53
54    pub fn is_empty(&self) -> bool {
55        self.editor.is_empty()
56    }
57
58    pub fn value(&self) -> String {
59        Editor::text(&self.editor)
60    }
61
62    pub fn placeholder(&self) -> &str {
63        self.placeholder.content()
64    }
65
66    pub fn overwrite(&mut self, value: &str) {
67        self.editor.overwrite(value);
68
69        if let Some(secure) = &mut self.secure {
70            let secured = protect(value, self.multiline.is_some());
71            secure.overwrite(&secured);
72        }
73    }
74
75    pub fn layout(&mut self, renderer: &R, limits: &layout::Limits, layout: Layout<'_>) -> Size {
76        self.padding = layout.padding;
77        self.multiline = layout.multiline;
78
79        let limits = limits
80            .width(layout.width)
81            .height(layout.height)
82            .shrink(layout.padding);
83
84        let font = layout.font.unwrap_or_else(|| renderer.font());
85        let size = layout.size.unwrap_or_else(|| renderer.text_size());
86        let line_height = layout.line_height.unwrap_or_else(|| renderer.line_height());
87        let hint_factor = renderer.hint_factor();
88
89        if layout.is_secure {
90            if self.secure.is_none() {
91                let value = self.value();
92                let secured = protect(&value, layout.multiline.is_some());
93
94                self.secure = Some(text::Editor::with_text(&secured));
95            }
96        } else {
97            self.secure = None;
98        }
99
100        let editor = self.secure.as_mut().unwrap_or(&mut self.editor);
101
102        editor.update(
103            limits.bounds(),
104            font,
105            size,
106            line_height,
107            layout.multiline.unwrap_or(text::Wrapping::None),
108            layout.alignment,
109            hint_factor,
110            &mut text::parser::PlainText,
111        );
112
113        let bounds = limits.resolve(layout.width, layout.height, editor.min_bounds());
114
115        let _ = self.placeholder.update(Text {
116            content: layout.placeholder,
117            font,
118            line_height,
119            bounds,
120            size,
121            align_x: layout.alignment,
122            align_y: alignment::Vertical::Top,
123            shaping: text::Shaping::Advanced,
124            wrapping: text::Wrapping::None,
125            ellipsis: text::Ellipsis::None,
126            hint_factor,
127        });
128
129        bounds.expand(layout.padding)
130    }
131
132    pub fn update<Message>(
133        &mut self,
134        event: &Event,
135        bounds: Rectangle,
136        cursor: mouse::Cursor,
137        shell: &mut Shell<'_, Message>,
138        key_binding: impl Fn(editor::KeyPress) -> Option<editor::Binding<Message>>,
139    ) -> Option<Edit> {
140        fn apply<Message>(
141            editor: &mut impl Editor,
142            shell: &mut Shell<'_, Message>,
143            update: editor::Update<Message>,
144            is_multiline: bool,
145        ) -> Option<Edit> {
146            match update {
147                editor::Update::Action(action) => {
148                    let (action, is_paste) = match action {
149                        editor::Action::Edit(editor::Edit::Enter)
150                        | editor::Action::Move(editor::Motion::Up)
151                        | editor::Action::Move(editor::Motion::Down)
152                            if !is_multiline =>
153                        {
154                            return None;
155                        }
156                        editor::Action::Edit(editor::Edit::Paste(text)) => (
157                            editor::Action::Edit(editor::Edit::Paste(if !is_multiline {
158                                Arc::new(text.lines().collect())
159                            } else {
160                                text
161                            })),
162                            true,
163                        ),
164                        _ => (action, false),
165                    };
166
167                    let is_edit = action.is_edit();
168
169                    editor.perform(action);
170                    shell.capture_event();
171
172                    if is_edit && is_multiline {
173                        shell.invalidate_layout();
174                    } else {
175                        shell.request_redraw();
176                    }
177
178                    return is_edit.then_some(Edit {
179                        has_pasted: is_paste,
180                    });
181                }
182                editor::Update::Focus | editor::Update::InputMethod => {
183                    shell.request_redraw();
184                    shell.capture_event();
185                }
186                editor::Update::Unfocus => {
187                    shell.request_redraw();
188                }
189                editor::Update::Release => {}
190                editor::Update::Copy(text) => {
191                    shell.write_clipboard(text);
192                    shell.capture_event();
193                }
194                editor::Update::Paste => {
195                    shell.read_clipboard(clipboard::Kind::Text);
196                    shell.capture_event();
197                }
198                editor::Update::RedrawAt(at) => {
199                    shell.request_redraw_at(at);
200                }
201                editor::Update::Custom(message) => {
202                    shell.publish(message);
203                    shell.capture_event();
204                }
205                editor::Update::Sequence(updates) => {
206                    let mut edit: Option<Edit> = None;
207
208                    for update in updates {
209                        if let Some(new_edit) = apply(editor, shell, update, is_multiline) {
210                            edit = Some(Edit {
211                                has_pasted: edit.unwrap_or_default().has_pasted
212                                    || new_edit.has_pasted,
213                            });
214                        }
215                    }
216
217                    return edit;
218                }
219            }
220
221            None
222        }
223
224        let editor = self.secure.as_ref().unwrap_or(&self.editor);
225
226        let update = self
227            .state
228            .update(editor, event, bounds, self.padding, cursor, key_binding)?;
229
230        if let Some(secure) = &mut self.secure {
231            fn apply_secure<Message>(
232                editor: &mut impl Editor,
233                update: &editor::Update<Message>,
234                is_multiline: bool,
235            ) {
236                match update {
237                    editor::Update::Action(action) => {
238                        let action = match action {
239                            editor::Action::Edit(editor::Edit::Insert(_)) => {
240                                editor::Action::Edit(editor::Edit::Insert(SECURE_CHAR))
241                            }
242                            editor::Action::Edit(editor::Edit::Paste(text)) => {
243                                let text = protect(text, is_multiline);
244
245                                editor::Action::Edit(editor::Edit::Paste(Arc::new(text)))
246                            }
247                            action => action.clone(),
248                        };
249
250                        editor.perform(action);
251                    }
252                    editor::Update::Sequence(updates) => {
253                        for update in updates {
254                            apply_secure(editor, update, is_multiline);
255                        }
256                    }
257                    _ => {}
258                }
259            }
260
261            apply_secure(secure, &update, self.multiline.is_some());
262
263            match &update {
264                editor::Update::Action(action) if !action.is_edit() => {
265                    fn translate(
266                        editor: &impl text::Editor,
267                        position: text::Position,
268                    ) -> text::Position {
269                        let Some(line) = editor.line(position.line) else {
270                            return text::Position { line: 0, index: 0 };
271                        };
272
273                        let grapheme = position.index / SECURE_CHAR.len_utf8();
274                        let index = line.text.graphemes(true).take(grapheme).map(str::len).sum();
275
276                        text::Position {
277                            line: position.line,
278                            index,
279                        }
280                    }
281
282                    let cursor = secure.cursor();
283
284                    self.editor.move_to(editor::Cursor {
285                        position: translate(&self.editor, cursor.position),
286                        selection: cursor
287                            .selection
288                            .map(|selection| translate(&self.editor, selection)),
289                    });
290
291                    shell.request_redraw();
292
293                    None
294                }
295                _ => apply(&mut self.editor, shell, update, self.multiline.is_some()),
296            }
297        } else {
298            apply(&mut self.editor, shell, update, self.multiline.is_some())
299        }
300    }
301
302    pub fn draw(&self, renderer: &mut R, bounds: Rectangle, viewport: Rectangle, style: Style) {
303        let text_bounds = bounds.shrink(self.padding);
304
305        let Some(clip_bounds) = text_bounds.intersection(&viewport) else {
306            return;
307        };
308
309        let editor = self.secure.as_ref().unwrap_or(&self.editor);
310
311        if editor.is_empty() {
312            let anchor = text_bounds.anchor(
313                self.placeholder.min_bounds(),
314                self.placeholder.align_x(),
315                self.placeholder.align_y(),
316            );
317
318            renderer.fill_paragraph(
319                self.placeholder.raw(),
320                anchor,
321                style.placeholder,
322                clip_bounds,
323            );
324        }
325
326        self.state.draw(
327            editor,
328            renderer,
329            text_bounds.position(),
330            clip_bounds,
331            editor::Style {
332                value: style.value,
333                selection: style.selection,
334            },
335        );
336    }
337
338    pub fn input_method(&self, position: Point) -> InputMethod<&str> {
339        self.state.input_method(&self.editor, position)
340    }
341}
342
343impl<R: text::Renderer> Default for Input<R> {
344    fn default() -> Self {
345        Self::new()
346    }
347}
348
349pub struct Style {
350    pub value: Color,
351    pub selection: Color,
352    pub placeholder: Color,
353}
354
355impl<R: text::Renderer> Focusable for Input<R> {
356    fn is_focused(&self) -> bool {
357        self.state.is_focused()
358    }
359
360    fn focus(&mut self) {
361        self.state.focus();
362    }
363
364    fn unfocus(&mut self) {
365        self.state.unfocus();
366    }
367}
368
369impl<R: text::Renderer> TextInput for Input<R> {
370    fn text(&self) -> text::Fragment<'_> {
371        if self.editor.is_empty() {
372            text::Fragment::Borrowed(self.placeholder.content())
373        } else {
374            TextInput::text(&self.editor)
375        }
376    }
377
378    fn move_cursor_to(&mut self, position: Position) {
379        self.editor.move_cursor_to(position);
380    }
381
382    fn move_cursor_to_front(&mut self) {
383        self.editor.move_cursor_to_front();
384    }
385
386    fn move_cursor_to_end(&mut self) {
387        self.editor.move_cursor_to_end();
388    }
389
390    fn select_all(&mut self) {
391        self.editor.select_all();
392    }
393
394    fn select_range(&mut self, start: Position, end: Position) {
395        self.editor.select_range(start, end);
396    }
397}
398
399#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
400pub struct Edit {
401    pub has_pasted: bool,
402}
403
404fn protect(text: &str, is_multiline: bool) -> String {
405    if is_multiline {
406        text.lines()
407            .map(|line| line.graphemes(true).map(|_| SECURE_CHAR).collect())
408            .collect::<Vec<String>>()
409            .join("\n")
410    } else {
411        text.graphemes(true).map(|_| SECURE_CHAR).collect()
412    }
413}