1use super::*;
9use kas::event::components::TextInputAction;
10use kas::event::{CursorIcon, ElementState, FocusSource, PhysicalKey};
11use kas::event::{Ime, ImePurpose};
12use kas::messages::{ReplaceSelectedText, SetValueText};
13use kas::prelude::*;
14use kas::text::{Effect, EffectFlags, NotReady};
15use kas::theme::TextClass;
16use std::fmt::{Debug, Display};
17use std::str::FromStr;
18
19#[impl_self]
20mod EditField {
21 #[autoimpl(Debug where G: trait)]
66 #[autoimpl(Deref, DerefMut using self.editor)]
67 #[widget]
68 pub struct EditField<G: EditGuard = DefaultGuard<()>> {
69 core: widget_core!(),
70 width: (f32, f32),
71 lines: (f32, f32),
72 editor: Editor,
73 pub guard: G,
75 }
76
77 impl Layout for Self {
78 #[inline]
79 fn rect(&self) -> Rect {
80 self.text.rect()
81 }
82
83 fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
84 let (min, mut ideal): (i32, i32);
85 if axis.is_horizontal() {
86 let dpem = cx.dpem(self.text.class());
87 min = (self.width.0 * dpem).cast_ceil();
88 ideal = (self.width.1 * dpem).cast_ceil();
89 } else if let Some(width) = axis.other() {
90 let height = self
92 .text
93 .measure_height(width.cast(), std::num::NonZero::new(1));
94 min = (self.lines.0 * height).cast_ceil();
95 ideal = (self.lines.1 * height).cast_ceil();
96 } else {
97 unreachable!()
98 };
99
100 let rules = self.text.size_rules(cx, axis);
101 ideal = ideal.max(rules.ideal_size());
102
103 let stretch = if axis.is_horizontal() || self.multi_line() {
104 Stretch::High
105 } else {
106 Stretch::None
107 };
108 SizeRules::new(min, ideal, stretch).with_margins(cx.text_margins().extract(axis))
109 }
110
111 fn set_rect(&mut self, cx: &mut SizeCx, rect: Rect, mut hints: AlignHints) {
112 self.editor.pos = rect.pos;
113 hints.vert = Some(if self.multi_line() {
114 Align::Default
115 } else {
116 Align::Center
117 });
118 self.text.set_rect(cx, rect, hints);
119 self.text.ensure_no_left_overhang();
120 if self.current.is_ime_enabled() {
121 self.set_ime_cursor_area(cx);
122 }
123 }
124 }
125
126 impl Viewport for Self {
127 #[inline]
128 fn content_size(&self) -> Size {
129 if let Ok((tl, br)) = self.text.bounding_box() {
130 (br - tl).cast_ceil()
131 } else {
132 Size::ZERO
133 }
134 }
135
136 fn draw_with_offset(&self, mut draw: DrawCx, rect: Rect, offset: Offset) {
137 let pos = self.rect().pos - offset;
138
139 if let CurrentAction::ImePreedit { edit_range } = self.current.clone() {
140 let effects = [
142 Effect {
143 start: 0,
144 e: 0,
145 flags: Default::default(),
146 },
147 Effect {
148 start: edit_range.start,
149 e: 0,
150 flags: EffectFlags::UNDERLINE,
151 },
152 Effect {
153 start: edit_range.end,
154 e: 0,
155 flags: Default::default(),
156 },
157 ];
158 draw.text_with_effects(pos, rect, &self.text, &[], &effects);
159 } else {
160 draw.text_with_selection(pos, rect, &self.text, self.selection.range());
161 }
162
163 if self.editable && draw.ev_state().has_input_focus(self.id_ref()) == Some(true) {
164 draw.text_cursor(pos, rect, &self.text, self.selection.edit_index());
165 }
166 }
167 }
168
169 impl Tile for Self {
170 fn navigable(&self) -> bool {
171 true
172 }
173
174 #[inline]
175 fn tooltip(&self) -> Option<&str> {
176 self.editor.tooltip()
177 }
178
179 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
180 Role::TextInput {
181 text: self.text.as_str(),
182 multi_line: self.multi_line(),
183 cursor: self.cursor_range(),
184 }
185 }
186 }
187
188 impl Events for Self {
189 const REDRAW_ON_MOUSE_OVER: bool = true;
190
191 type Data = G::Data;
192
193 fn probe(&self, _: Coord) -> Id {
194 self.id()
195 }
196
197 #[inline]
198 fn mouse_over_icon(&self) -> Option<CursorIcon> {
199 Some(CursorIcon::Text)
200 }
201
202 fn configure(&mut self, cx: &mut ConfigCx) {
203 self.editor.id = self.id();
204 self.text.configure(&mut cx.size_cx());
205 self.guard.configure(&mut self.editor, cx);
206 }
207
208 fn update(&mut self, cx: &mut ConfigCx, data: &G::Data) {
209 let size = self.content_size();
210 if !self.has_input_focus() {
211 self.guard.update(&mut self.editor, cx, data);
212 }
213 if size != self.content_size() {
214 cx.resize();
215 }
216 }
217
218 fn handle_event(&mut self, cx: &mut EventCx, data: &G::Data, event: Event) -> IsUsed {
219 match event {
220 Event::NavFocus(source) if source == FocusSource::Key => {
221 if !self.input_handler.is_selecting() {
222 self.request_key_focus(cx, source);
223 }
224 Used
225 }
226 Event::NavFocus(_) => Used,
227 Event::LostNavFocus => Used,
228 Event::SelFocus(source) => {
229 self.has_key_focus = true;
232 if source == FocusSource::Pointer {
233 self.set_primary(cx);
234 }
235
236 Used
237 }
238 Event::KeyFocus => {
239 self.has_key_focus = true;
240 self.set_view_offset_from_cursor(cx);
241 if !self.current.is_ime_enabled() {
242 self.guard.focus_gained(&mut self.editor, cx, data);
243 }
244
245 if self.current.is_none() {
246 let hint = Default::default();
247 let purpose = ImePurpose::Normal;
248 let surrounding_text = self.ime_surrounding_text();
249 cx.replace_ime_focus(self.id.clone(), hint, purpose, surrounding_text);
250 }
251 Used
252 }
253 Event::LostKeyFocus => {
254 self.has_key_focus = false;
255 cx.redraw();
256 if !self.current.is_ime_enabled() {
257 self.guard.focus_lost(&mut self.editor, cx, data);
258 }
259 Used
260 }
261 Event::LostSelFocus => {
262 if !self.selection.is_empty() {
264 self.save_undo_state(None);
265 self.selection.set_empty();
266 }
267 self.input_handler.stop_selecting();
268 cx.redraw();
269 Used
270 }
271 Event::Command(cmd, code) => match self.control_key(cx, data, cmd, code) {
272 Ok(r) => r,
273 Err(NotReady) => Used,
274 },
275 Event::Key(event, false) if event.state == ElementState::Pressed => {
276 if let Some(text) = &event.text {
277 self.save_undo_state(Some(EditOp::KeyInput));
278 let used = self.received_text(cx, text);
279 self.call_guard_edit(cx, data);
280 used
281 } else {
282 let opt_cmd = cx
283 .config()
284 .shortcuts()
285 .try_match(cx.modifiers(), &event.key_without_modifiers);
286 if let Some(cmd) = opt_cmd {
287 match self.control_key(cx, data, cmd, Some(event.physical_key)) {
288 Ok(r) => r,
289 Err(NotReady) => Used,
290 }
291 } else {
292 Unused
293 }
294 }
295 }
296 Event::Ime(ime) => match ime {
297 Ime::Enabled => {
298 if !self.has_key_focus {
299 self.guard.focus_gained(&mut self.editor, cx, data);
300 }
301 match self.current {
302 CurrentAction::None => {
303 self.current = CurrentAction::ImeStart;
304 self.set_ime_cursor_area(cx);
305 }
306 CurrentAction::ImeStart | CurrentAction::ImePreedit { .. } => {
307 }
309 CurrentAction::Selection => {
310 cx.cancel_ime_focus(self.id_ref());
312 }
313 }
314 Used
315 }
316 Ime::Disabled => {
317 self.clear_ime();
318 if !self.has_key_focus {
319 self.guard.focus_lost(&mut self.editor, cx, data);
320 }
321 Used
322 }
323 Ime::Preedit { text, cursor } => {
324 self.save_undo_state(None);
325 let mut edit_range = match self.current.clone() {
326 CurrentAction::ImeStart if cursor.is_some() => self.selection.range(),
327 CurrentAction::ImeStart => return Used,
328 CurrentAction::ImePreedit { edit_range } => edit_range.cast(),
329 _ => return Used,
330 };
331
332 self.text.replace_range(edit_range.clone(), text);
333 edit_range.end = edit_range.start + text.len();
334 if let Some((start, end)) = cursor {
335 self.selection.set_sel_index_only(edit_range.start + start);
336 self.selection.set_edit_index(edit_range.start + end);
337 } else {
338 self.selection.set_cursor(edit_range.start + text.len());
339 }
340
341 self.current = CurrentAction::ImePreedit {
342 edit_range: edit_range.cast(),
343 };
344 self.edit_x_coord = None;
345 self.prepare_and_scroll(cx, false);
346 Used
347 }
348 Ime::Commit { text } => {
349 self.save_undo_state(Some(EditOp::Ime));
350 let edit_range = match self.current.clone() {
351 CurrentAction::ImeStart => self.selection.range(),
352 CurrentAction::ImePreedit { edit_range } => edit_range.cast(),
353 _ => return Used,
354 };
355
356 self.text.replace_range(edit_range.clone(), text);
357 self.selection.set_cursor(edit_range.start + text.len());
358
359 self.current = CurrentAction::ImePreedit {
360 edit_range: self.selection.range().cast(),
361 };
362 self.edit_x_coord = None;
363 self.prepare_and_scroll(cx, false);
364 self.call_guard_edit(cx, data);
365 Used
366 }
367 Ime::DeleteSurrounding {
368 before_bytes,
369 after_bytes,
370 } => {
371 self.save_undo_state(None);
372 let edit_range = match self.current.clone() {
373 CurrentAction::ImeStart => self.selection.range(),
374 CurrentAction::ImePreedit { edit_range } => edit_range.cast(),
375 _ => return Used,
376 };
377
378 if before_bytes > 0 {
379 let end = edit_range.start;
380 let start = end - before_bytes;
381 if self.as_str().is_char_boundary(start) {
382 self.text.replace_range(start..end, "");
383 self.selection.delete_range(start..end);
384 } else {
385 log::warn!("buggy IME tried to delete range not at char boundary");
386 }
387 }
388
389 if after_bytes > 0 {
390 let start = edit_range.end;
391 let end = start + after_bytes;
392 if self.as_str().is_char_boundary(end) {
393 self.text.replace_range(start..end, "");
394 } else {
395 log::warn!("buggy IME tried to delete range not at char boundary");
396 }
397 }
398
399 if let Some(text) = self.ime_surrounding_text() {
400 cx.update_ime_surrounding_text(self.id_ref(), text);
401 }
402
403 Used
404 }
405 },
406 Event::PressStart(press) if press.is_tertiary() => {
407 press.grab_click(self.id()).complete(cx)
408 }
409 Event::PressEnd { press, .. } if press.is_tertiary() => {
410 self.set_cursor_from_coord(cx, press.coord);
411 self.cancel_selection_and_ime(cx);
412
413 if let Some(content) = cx.get_primary() {
414 self.save_undo_state(Some(EditOp::Clipboard));
415
416 let index = self.selection.edit_index();
417 let range = self.trim_paste(&content);
418
419 self.text
420 .replace_range(index..index, &content[range.clone()]);
421 self.selection.set_cursor(index + range.len());
422 self.edit_x_coord = None;
423 self.prepare_and_scroll(cx, false);
424
425 self.call_guard_edit(cx, data);
426 }
427
428 self.request_key_focus(cx, FocusSource::Pointer);
429 Used
430 }
431 event => match self.editor.input_handler.handle(cx, self.core.id(), event) {
432 TextInputAction::Used => Used,
433 TextInputAction::Unused => Unused,
434 TextInputAction::PressStart {
435 coord,
436 clear,
437 repeats,
438 } => {
439 if self.current.is_ime_enabled() {
440 self.clear_ime();
441 cx.cancel_ime_focus(self.id_ref());
442 }
443 self.save_undo_state(Some(EditOp::Cursor));
444 self.current = CurrentAction::Selection;
445
446 self.set_cursor_from_coord(cx, coord);
447 self.selection.set_anchor(clear);
448 if repeats > 1 {
449 self.editor
450 .selection
451 .expand(&self.editor.text, repeats >= 3);
452 }
453
454 self.request_key_focus(cx, FocusSource::Pointer);
455 Used
456 }
457 TextInputAction::PressMove { coord, repeats } => {
458 if self.current == CurrentAction::Selection {
459 self.set_cursor_from_coord(cx, coord);
460 if repeats > 1 {
461 self.editor
462 .selection
463 .expand(&self.editor.text, repeats >= 3);
464 }
465 }
466
467 Used
468 }
469 TextInputAction::PressEnd { coord } => {
470 if self.current.is_ime_enabled() {
471 self.clear_ime();
472 cx.cancel_ime_focus(self.id_ref());
473 }
474 self.save_undo_state(Some(EditOp::Cursor));
475 if self.current == CurrentAction::Selection {
476 self.set_primary(cx);
477 } else {
478 self.set_cursor_from_coord(cx, coord);
479 self.selection.set_empty();
480 }
481 self.current = CurrentAction::None;
482
483 self.request_key_focus(cx, FocusSource::Pointer);
484 Used
485 }
486 },
487 }
488 }
489
490 fn handle_messages(&mut self, cx: &mut EventCx, data: &G::Data) {
491 if !self.editable {
492 return;
493 }
494
495 if let Some(SetValueText(string)) = cx.try_pop() {
496 self.pre_commit();
497 self.set_string(cx, string);
498 self.call_guard_edit(cx, data);
499 } else if let Some(ReplaceSelectedText(text)) = cx.try_pop() {
500 self.pre_commit();
501 self.replace_selected_text(cx, &text);
502 self.call_guard_edit(cx, data);
503 }
504 }
505 }
506
507 impl Default for Self
508 where
509 G: Default,
510 {
511 #[inline]
512 fn default() -> Self {
513 EditField::new(G::default())
514 }
515 }
516
517 impl Self {
518 #[inline]
520 pub fn new(guard: G) -> EditField<G> {
521 EditField {
522 core: Default::default(),
523 width: (8.0, 16.0),
524 lines: (1.0, 1.0),
525 editor: Editor::new(),
526 guard,
527 }
528 }
529
530 #[inline]
532 pub fn call_guard_activate(&mut self, cx: &mut EventCx, data: &G::Data) {
533 self.guard.activate(&mut self.editor, cx, data);
534 }
535
536 #[inline]
540 pub fn call_guard_edit(&mut self, cx: &mut EventCx, data: &G::Data) {
541 self.clear_error();
542 self.guard.edit(&mut self.editor, cx, data);
543 }
544 }
545}
546
547impl<A: 'static> EditField<DefaultGuard<A>> {
548 #[inline]
550 pub fn text<S: ToString>(text: S) -> Self {
551 EditField {
552 editor: Editor::from(text),
553 ..Default::default()
554 }
555 }
556
557 #[inline]
559 pub fn string(value_fn: impl Fn(&A) -> String + 'static) -> EditField<StringGuard<A>> {
560 EditField::new(StringGuard::new(value_fn)).with_editable(false)
561 }
562
563 #[inline]
577 pub fn parser<T: Debug + Display + FromStr, M: Debug + 'static>(
578 value_fn: impl Fn(&A) -> T + 'static,
579 msg_fn: impl Fn(T) -> M + 'static,
580 ) -> EditField<ParseGuard<A, T>> {
581 EditField::new(ParseGuard::new(value_fn, msg_fn))
582 }
583
584 pub fn instant_parser<T: Debug + Display + FromStr, M: Debug + 'static>(
594 value_fn: impl Fn(&A) -> T + 'static,
595 msg_fn: impl Fn(T) -> M + 'static,
596 ) -> EditField<InstantParseGuard<A, T>> {
597 EditField::new(InstantParseGuard::new(value_fn, msg_fn))
598 }
599}
600
601impl<A: 'static> EditField<StringGuard<A>> {
602 #[must_use]
609 pub fn with_msg<M>(mut self, msg_fn: impl Fn(&str) -> M + 'static) -> Self
610 where
611 M: Debug + 'static,
612 {
613 self.guard = self.guard.with_msg(msg_fn);
614 self.editable = true;
615 self
616 }
617}
618
619impl<G: EditGuard> EditField<G> {
620 #[inline]
624 #[must_use]
625 pub fn with_text(mut self, text: impl ToString) -> Self {
626 debug_assert!(self.current == CurrentAction::None && !self.input_handler.is_selecting());
627 let text = text.to_string();
628 let len = text.len();
629 self.text.set_string(text);
630 self.selection.set_cursor(len);
631 self
632 }
633
634 #[inline]
636 #[must_use]
637 pub fn with_editable(mut self, editable: bool) -> Self {
638 self.editable = editable;
639 self
640 }
641
642 #[inline]
649 #[must_use]
650 pub fn with_multi_line(mut self, multi_line: bool) -> Self {
651 self.text.set_wrap(multi_line);
652 self.lines = match multi_line {
653 false => (1.0, 1.0),
654 true => (4.0, 7.0),
655 };
656 self
657 }
658
659 #[inline]
661 #[must_use]
662 pub fn with_class(mut self, class: TextClass) -> Self {
663 self.text.set_class(class);
664 self
665 }
666
667 #[inline]
669 pub fn set_lines(&mut self, min_lines: f32, ideal_lines: f32) {
670 self.lines = (min_lines, ideal_lines);
671 }
672
673 #[inline]
675 #[must_use]
676 pub fn with_lines(mut self, min_lines: f32, ideal_lines: f32) -> Self {
677 self.set_lines(min_lines, ideal_lines);
678 self
679 }
680
681 #[inline]
683 pub fn set_width_em(&mut self, min_em: f32, ideal_em: f32) {
684 self.width = (min_em, ideal_em);
685 }
686
687 #[inline]
689 #[must_use]
690 pub fn with_width_em(mut self, min_em: f32, ideal_em: f32) -> Self {
691 self.set_width_em(min_em, ideal_em);
692 self
693 }
694
695 fn control_key(
696 &mut self,
697 cx: &mut EventCx,
698 data: &G::Data,
699 cmd: Command,
700 code: Option<PhysicalKey>,
701 ) -> Result<IsUsed, NotReady> {
702 let action = self.editor.cmd_action(cx, cmd)?;
703 if matches!(action, CmdAction::Unused) {
704 return Ok(Unused);
705 }
706
707 self.prepare_and_scroll(cx, true);
708
709 Ok(match action {
710 CmdAction::Unused => Unused,
711 CmdAction::Used | CmdAction::Cursor => Used,
712 CmdAction::Activate => {
713 cx.depress_with_key(&self, code);
714 self.guard.activate(&mut self.editor, cx, data)
715 }
716 CmdAction::Edit => {
717 self.call_guard_edit(cx, data);
718 Used
719 }
720 })
721 }
722}