1#![allow(missing_docs)] use 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}