1use std::{
2 borrow::Cow,
3 cell::{
4 Ref,
5 RefCell,
6 },
7 rc::Rc,
8};
9
10use freya_core::prelude::*;
11use freya_edit::*;
12use torin::{
13 prelude::{
14 Alignment,
15 Area,
16 Direction,
17 },
18 size::Size,
19};
20
21use crate::{
22 cursor_blink::use_cursor_blink,
23 get_theme,
24 scrollviews::ScrollView,
25 theming::component_themes::InputThemePartial,
26};
27
28#[derive(Default, Clone, PartialEq)]
29pub enum InputMode {
30 #[default]
31 Shown,
32 Hidden(char),
33}
34
35impl InputMode {
36 pub fn new_password() -> Self {
37 Self::Hidden('*')
38 }
39}
40
41#[derive(Debug, Default, PartialEq, Clone, Copy)]
42pub enum InputStatus {
43 #[default]
45 Idle,
46 Hovering,
48}
49
50#[derive(Clone)]
51pub struct InputValidator {
52 valid: Rc<RefCell<bool>>,
53 text: Rc<RefCell<String>>,
54}
55
56impl InputValidator {
57 pub fn new(text: String) -> Self {
58 Self {
59 valid: Rc::new(RefCell::new(true)),
60 text: Rc::new(RefCell::new(text)),
61 }
62 }
63 pub fn text(&'_ self) -> Ref<'_, String> {
64 self.text.borrow()
65 }
66 pub fn set_valid(&self, is_valid: bool) {
67 *self.valid.borrow_mut() = is_valid;
68 }
69 pub fn is_valid(&self) -> bool {
70 *self.valid.borrow()
71 }
72}
73
74#[cfg_attr(feature = "docs",
107 doc = embed_doc_image::embed_image!("input", "images/gallery_input.png")
108)]
109#[derive(Clone, PartialEq)]
110pub struct Input {
111 pub(crate) theme: Option<InputThemePartial>,
112 value: Cow<'static, str>,
113 placeholder: Option<Cow<'static, str>>,
114 on_change: Option<EventHandler<String>>,
115 on_validate: Option<EventHandler<InputValidator>>,
116 on_submit: Option<EventHandler<String>>,
117 mode: InputMode,
118 auto_focus: bool,
119 width: Size,
120 enabled: bool,
121 key: DiffKey,
122}
123
124impl KeyExt for Input {
125 fn write_key(&mut self) -> &mut DiffKey {
126 &mut self.key
127 }
128}
129
130impl Default for Input {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl Input {
137 pub fn new() -> Self {
138 Input {
139 theme: None,
140 value: Cow::default(),
141 placeholder: None,
142 on_change: None,
143 on_validate: None,
144 on_submit: None,
145 mode: InputMode::default(),
146 auto_focus: false,
147 width: Size::px(150.),
148 enabled: true,
149 key: DiffKey::default(),
150 }
151 }
152
153 pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
154 self.enabled = enabled.into();
155 self
156 }
157
158 pub fn value(mut self, value: impl Into<Cow<'static, str>>) -> Self {
159 self.value = value.into();
160 self
161 }
162
163 pub fn placeholder(mut self, placeholder: impl Into<Cow<'static, str>>) -> Self {
164 self.placeholder = Some(placeholder.into());
165 self
166 }
167
168 pub fn on_change(mut self, on_change: impl Into<EventHandler<String>>) -> Self {
169 self.on_change = Some(on_change.into());
170 self
171 }
172
173 pub fn on_validate(mut self, on_validate: impl Into<EventHandler<InputValidator>>) -> Self {
174 self.on_validate = Some(on_validate.into());
175 self
176 }
177
178 pub fn on_submit(mut self, on_submit: impl Into<EventHandler<String>>) -> Self {
179 self.on_submit = Some(on_submit.into());
180 self
181 }
182
183 pub fn mode(mut self, mode: InputMode) -> Self {
184 self.mode = mode;
185 self
186 }
187
188 pub fn auto_focus(mut self, auto_focus: impl Into<bool>) -> Self {
189 self.auto_focus = auto_focus.into();
190 self
191 }
192
193 pub fn width(mut self, width: impl Into<Size>) -> Self {
194 self.width = width.into();
195 self
196 }
197
198 pub fn theme(mut self, theme: InputThemePartial) -> Self {
199 self.theme = Some(theme);
200 self
201 }
202
203 pub fn key(mut self, key: impl Into<DiffKey>) -> Self {
204 self.key = key.into();
205 self
206 }
207}
208
209impl Component for Input {
210 fn render(&self) -> impl IntoElement {
211 let focus = use_focus();
212 let focus_status = use_focus_status(focus);
213 let holder = use_state(ParagraphHolder::default);
214 let mut area = use_state(Area::default);
215 let mut status = use_state(InputStatus::default);
216 let mut editable = use_editable(|| self.value.to_string(), EditableConfig::new);
217 let mut is_dragging = use_state(|| false);
218 let mut ime_preedit = use_state(|| None);
219 let theme = get_theme!(&self.theme, input);
220 let (mut movement_timeout, cursor_color) =
221 use_cursor_blink(focus_status() != FocusStatus::Not, theme.color);
222
223 let enabled = use_reactive(&self.enabled);
224 use_drop(move || {
225 if status() == InputStatus::Hovering && enabled() {
226 Cursor::set(CursorIcon::default());
227 }
228 });
229
230 let display_placeholder = self.value.is_empty() && self.placeholder.is_some();
231 let on_change = self.on_change.clone();
232 let on_validate = self.on_validate.clone();
233 let on_submit = self.on_submit.clone();
234
235 if &*self.value != editable.editor().read().rope() {
236 editable.editor_mut().write().set(&self.value);
237 editable.editor_mut().write().editor_history().clear();
238 }
239
240 let on_ime_preedit = move |e: Event<ImePreeditEventData>| {
241 ime_preedit.set(Some(e.data().text.clone()));
242 };
243
244 let on_key_down = move |e: Event<KeyboardEventData>| {
245 match &e.key {
246 Key::Named(NamedKey::Enter) => {
248 if let Some(on_submit) = &on_submit {
249 let text = editable.editor().peek().to_string();
250 on_submit.call(text);
251 }
252 }
253 key => {
255 if *key != Key::Named(NamedKey::Enter) && *key != Key::Named(NamedKey::Tab) {
256 e.stop_propagation();
257 movement_timeout.reset();
258 editable.process_event(EditableEvent::KeyDown {
259 key: &e.key,
260 modifiers: e.modifiers,
261 });
262 let text = editable.editor().read().rope().to_string();
263
264 let apply_change = match &on_validate {
265 Some(on_validate) => {
266 let mut editor = editable.editor_mut().write();
267 let validator = InputValidator::new(text.clone());
268 on_validate.call(validator.clone());
269 if !validator.is_valid() {
270 if let Some(idx) = editor.undo() {
271 editor.move_cursor_to(idx);
272 }
273 editor.editor_history().clear_redos();
274 }
275 validator.is_valid()
276 }
277 None => true,
278 };
279
280 if apply_change && let Some(on_change) = &on_change {
281 on_change.call(text);
282 }
283 }
284 }
285 }
286 };
287
288 let on_key_up = move |e: Event<KeyboardEventData>| {
289 e.stop_propagation();
290 editable.process_event(EditableEvent::KeyUp { key: &e.key });
291 };
292
293 let on_input_pointer_down = move |e: Event<PointerEventData>| {
294 e.stop_propagation();
295 is_dragging.set(true);
296 movement_timeout.reset();
297 if !display_placeholder {
298 let area = area.read().to_f64();
299 let global_location = e.global_location().clamp(area.min(), area.max());
300 let location = (global_location - area.min()).to_point();
301 editable.process_event(EditableEvent::Down {
302 location,
303 editor_line: EditorLine::SingleParagraph,
304 holder: &holder.read(),
305 });
306 }
307 focus.request_focus();
308 };
309
310 let on_pointer_down = move |e: Event<PointerEventData>| {
311 e.stop_propagation();
312 is_dragging.set(true);
313 movement_timeout.reset();
314 if !display_placeholder {
315 editable.process_event(EditableEvent::Down {
316 location: e.element_location(),
317 editor_line: EditorLine::SingleParagraph,
318 holder: &holder.read(),
319 });
320 }
321 focus.request_focus();
322 };
323
324 let on_global_mouse_move = move |e: Event<MouseEventData>| {
325 if focus.is_focused() && *is_dragging.read() {
326 let mut location = e.global_location;
327 location.x -= area.read().min_x() as f64;
328 location.y -= area.read().min_y() as f64;
329 editable.process_event(EditableEvent::Move {
330 location,
331 editor_line: EditorLine::SingleParagraph,
332 holder: &holder.read(),
333 });
334 }
335 };
336
337 let on_pointer_enter = move |_| {
338 *status.write() = InputStatus::Hovering;
339 if enabled() {
340 Cursor::set(CursorIcon::Text);
341 } else {
342 Cursor::set(CursorIcon::NotAllowed);
343 }
344 };
345
346 let on_pointer_leave = move |_| {
347 if status() == InputStatus::Hovering {
348 Cursor::set(CursorIcon::default());
349 *status.write() = InputStatus::default();
350 }
351 };
352
353 let on_global_mouse_up = move |_| {
354 match *status.read() {
355 InputStatus::Idle if focus.is_focused() => {
356 editable.process_event(EditableEvent::Release);
357 }
358 InputStatus::Hovering => {
359 editable.process_event(EditableEvent::Release);
360 }
361 _ => {}
362 };
363
364 if focus.is_focused() {
365 if *is_dragging.read() {
366 is_dragging.set(false);
368 } else {
369 focus.request_unfocus();
371 }
372 }
373 };
374
375 let a11y_id = focus.a11y_id();
376
377 let (background, cursor_index, text_selection) =
378 if enabled() && focus_status() != FocusStatus::Not {
379 (
380 theme.hover_background,
381 Some(editable.editor().read().cursor_pos()),
382 editable
383 .editor()
384 .read()
385 .get_visible_selection(EditorLine::SingleParagraph),
386 )
387 } else {
388 (theme.background, None, None)
389 };
390
391 let border = if focus_status() == FocusStatus::Keyboard {
392 Border::new()
393 .fill(theme.focus_border_fill)
394 .width(2.)
395 .alignment(BorderAlignment::Inner)
396 } else {
397 Border::new()
398 .fill(theme.border_fill.mul_if(!self.enabled, 0.85))
399 .width(1.)
400 .alignment(BorderAlignment::Inner)
401 };
402
403 let color = if display_placeholder {
404 theme.placeholder_color
405 } else {
406 theme.color
407 };
408
409 let text = match (self.mode.clone(), &self.placeholder) {
410 (_, Some(ph)) if display_placeholder => Cow::Borrowed(ph.as_ref()),
411 (InputMode::Hidden(ch), _) => Cow::Owned(ch.to_string().repeat(self.value.len())),
412 (InputMode::Shown, _) => Cow::Borrowed(self.value.as_ref()),
413 };
414
415 let preedit_text = (!display_placeholder)
416 .then(|| ime_preedit.read().clone())
417 .flatten();
418
419 let a11_role = match self.mode {
420 InputMode::Hidden(_) => AccessibilityRole::PasswordInput,
421 _ => AccessibilityRole::TextInput,
422 };
423
424 rect()
425 .a11y_id(a11y_id)
426 .a11y_focusable(self.enabled)
427 .a11y_auto_focus(self.auto_focus)
428 .a11y_alt(text.clone())
429 .a11y_role(a11_role)
430 .maybe(self.enabled, |rect| {
431 rect.on_key_up(on_key_up)
432 .on_key_down(on_key_down)
433 .on_pointer_down(on_input_pointer_down)
434 .on_ime_preedit(on_ime_preedit)
435 })
436 .on_pointer_enter(on_pointer_enter)
437 .on_pointer_leave(on_pointer_leave)
438 .width(self.width.clone())
439 .background(background.mul_if(!self.enabled, 0.85))
440 .border(border)
441 .corner_radius(theme.corner_radius)
442 .main_align(Alignment::center())
443 .cross_align(Alignment::center())
444 .child(
445 ScrollView::new()
446 .height(Size::Inner)
447 .direction(Direction::Horizontal)
448 .show_scrollbar(false)
449 .child(
450 paragraph()
451 .holder(holder.read().clone())
452 .on_sized(move |e: Event<SizedEventData>| area.set(e.visible_area))
453 .min_width(Size::func(move |context| {
454 Some(context.parent + theme.inner_margin.horizontal())
455 }))
456 .maybe(self.enabled, |rect| {
457 rect.on_pointer_down(on_pointer_down)
458 .on_global_mouse_up(on_global_mouse_up)
459 .on_global_mouse_move(on_global_mouse_move)
460 })
461 .margin(theme.inner_margin)
462 .cursor_index(cursor_index)
463 .cursor_color(cursor_color)
464 .color(color)
465 .max_lines(1)
466 .highlights(text_selection.map(|h| vec![h]))
467 .span(text.to_string())
468 .map(preedit_text, |el, preedit_text| el.span(preedit_text)),
469 ),
470 )
471 }
472
473 fn render_key(&self) -> DiffKey {
474 self.key.clone().or(self.default_key())
475 }
476}