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, 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}