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