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