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