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::{
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}