1use super::*;
9use kas::event::components::TextInput;
10use kas::event::{FocusSource, ImeSurroundingText, Scroll};
11use kas::geom::Vec2;
12use kas::prelude::*;
13use kas::text::{CursorRange, NotReady, SelectionHelper};
14use kas::theme::{Text, TextClass};
15use kas::util::UndoStack;
16use std::borrow::Cow;
17use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
18
19#[autoimpl(Debug)]
21pub struct Editor {
22 pub(super) id: Id,
24 pub(super) pos: Coord,
25 pub(super) editable: bool,
26 pub(super) text: Text<String>,
27 pub(super) selection: SelectionHelper,
28 pub(super) edit_x_coord: Option<f32>,
29 last_edit: Option<EditOp>,
30 undo_stack: UndoStack<(String, CursorRange)>,
31 pub(super) has_key_focus: bool,
32 pub(super) current: CurrentAction,
33 error_state: bool,
34 error_message: Option<Cow<'static, str>>,
35 pub(super) input_handler: TextInput,
36}
37
38impl Editor {
40 #[inline]
42 pub(super) fn new() -> Self {
43 Editor {
44 id: Id::default(),
45 pos: Coord::ZERO,
46 editable: true,
47 text: Text::new(String::new(), TextClass::Editor, false),
48 selection: Default::default(),
49 edit_x_coord: None,
50 last_edit: Some(EditOp::Initial),
51 undo_stack: UndoStack::new(),
52 has_key_focus: false,
53 current: CurrentAction::None,
54 error_state: false,
55 error_message: None,
56 input_handler: Default::default(),
57 }
58 }
59
60 #[inline]
62 pub(super) fn from<S: ToString>(text: S) -> Self {
63 let text = text.to_string();
64 let len = text.len();
65 Editor {
66 text: Text::new(text, TextClass::Editor, false),
67 selection: SelectionHelper::from(len),
68 ..Editor::new()
69 }
70 }
71
72 pub(super) fn cancel_selection_and_ime(&mut self, cx: &mut EventState) {
77 if self.current == CurrentAction::Selection {
78 self.input_handler.stop_selecting();
79 self.current = CurrentAction::None;
80 } else if self.current.is_ime_enabled() {
81 self.clear_ime();
82 cx.cancel_ime_focus(&self.id);
83 }
84 }
85
86 pub(super) fn clear_ime(&mut self) {
91 if self.current.is_ime_enabled() {
92 let action = std::mem::replace(&mut self.current, CurrentAction::None);
93 if let CurrentAction::ImePreedit { edit_range } = action {
94 self.selection.set_cursor(edit_range.start.cast());
95 self.text.replace_range(edit_range.cast(), "");
96 }
97 }
98 }
99
100 pub(super) fn ime_surrounding_text(&self) -> Option<ImeSurroundingText> {
101 const MAX_TEXT_BYTES: usize = ImeSurroundingText::MAX_TEXT_BYTES;
102
103 let sel_range = self.selection.range();
104 let edit_range = match self.current.clone() {
105 CurrentAction::ImePreedit { edit_range } => Some(edit_range.cast()),
106 _ => None,
107 };
108 let mut range = edit_range.clone().unwrap_or(sel_range);
109 let initial_range = range.clone();
110 let edit_len = edit_range.clone().map(|r| r.len()).unwrap_or(0);
111
112 if let Ok(Some((_, line_range))) = self.text.find_line(range.start) {
113 range.start = line_range.start;
114 }
115 if let Ok(Some((_, line_range))) = self.text.find_line(range.end) {
116 range.end = line_range.end;
117 }
118
119 if range.len() - edit_len > MAX_TEXT_BYTES {
120 range.end = range.end.min(initial_range.end + MAX_TEXT_BYTES / 2);
121 while !self.text.as_str().is_char_boundary(range.end) {
122 range.end -= 1;
123 }
124
125 if range.len() - edit_len > MAX_TEXT_BYTES {
126 range.start = range.start.max(initial_range.start - MAX_TEXT_BYTES / 2);
127 while !self.text.as_str().is_char_boundary(range.start) {
128 range.start += 1;
129 }
130 }
131 }
132
133 let start = range.start;
134 let mut text = String::with_capacity(range.len() - edit_len);
135 if let Some(er) = edit_range {
136 text.push_str(&self.text.as_str()[range.start..er.start]);
137 text.push_str(&self.text.as_str()[er.end..range.end]);
138 } else {
139 text = self.text.as_str()[range].to_string();
140 }
141
142 let cursor = self.selection.edit_index().saturating_sub(start);
143 let sel_index = self.selection.sel_index().saturating_sub(start);
146 ImeSurroundingText::new(text, cursor, sel_index)
147 .inspect_err(|err| {
148 log::warn!("Editor::ime_surrounding_text failed: {err:?}")
150 })
151 .ok()
152 }
153
154 pub(super) fn set_ime_cursor_area(&self, cx: &mut EventState) {
156 if let Ok(text) = self.text.display() {
157 let range = match self.current.clone() {
158 CurrentAction::ImeStart => self.selection.range(),
159 CurrentAction::ImePreedit { edit_range } => edit_range.cast(),
160 _ => return,
161 };
162
163 let (m1, m2);
164 if range.is_empty() {
165 let mut iter = text.text_glyph_pos(range.start);
166 m1 = iter.next();
167 m2 = iter.next();
168 } else {
169 m1 = text.text_glyph_pos(range.start).next_back();
170 m2 = text.text_glyph_pos(range.end).next();
171 }
172
173 let rect = if let Some((c1, c2)) = m1.zip(m2) {
174 let left = c1.pos.0.min(c2.pos.0);
175 let right = c1.pos.0.max(c2.pos.0);
176 let top = (c1.pos.1 - c1.ascent).min(c2.pos.1 - c2.ascent);
177 let bottom = (c1.pos.1 - c1.descent).max(c2.pos.1 - c2.ascent);
178 let p1 = Vec2(left, top).cast_floor();
179 let p2 = Vec2(right, bottom).cast_ceil();
180 Rect::from_coords(p1, p2)
181 } else if let Some(c) = m1.or(m2) {
182 let p1 = Vec2(c.pos.0, c.pos.1 - c.ascent).cast_floor();
183 let p2 = Vec2(c.pos.0, c.pos.1 - c.descent).cast_ceil();
184 Rect::from_coords(p1, p2)
185 } else {
186 return;
187 };
188
189 cx.set_ime_cursor_area(&self.id, rect + Offset::conv(self.pos));
190 }
191 }
192
193 pub(super) fn clear_error(&mut self) {
194 self.error_state = false;
195 self.error_message = None;
196 }
197
198 pub(super) fn tooltip(&self) -> Option<&str> {
199 self.error_message.as_deref()
200 }
201
202 pub(super) fn save_undo_state(&mut self, edit: Option<EditOp>) {
206 if let Some(op) = edit
207 && op.try_merge(&mut self.last_edit)
208 {
209 return;
210 }
211
212 self.last_edit = edit;
213 self.undo_stack
214 .try_push((self.clone_string(), self.cursor_range()));
215 }
216
217 pub(super) fn prepare_and_scroll(&mut self, cx: &mut EventCx, force_set_offset: bool) {
222 let bb = self.text.bounding_box();
223 if self.text.prepare() {
224 self.text.ensure_no_left_overhang();
225 cx.redraw();
226 }
227
228 let mut set_offset = force_set_offset;
229 if bb != self.text.bounding_box() {
230 cx.resize();
231 set_offset = true;
232 }
233 if set_offset {
234 self.set_view_offset_from_cursor(cx);
235 }
236 }
237
238 pub(super) fn received_text(&mut self, cx: &mut EventCx, text: &str) -> IsUsed {
242 if !self.editable {
243 return Unused;
244 }
245 self.cancel_selection_and_ime(cx);
246
247 let index = self.selection.edit_index();
248 let selection = self.selection.range();
249 let have_sel = selection.start < selection.end;
250 if have_sel {
251 self.text.replace_range(selection.clone(), text);
252 self.selection.set_cursor(selection.start + text.len());
253 } else {
254 self.text.insert_str(index, text);
255 self.selection.set_cursor(index + text.len());
256 }
257 self.edit_x_coord = None;
258
259 self.prepare_and_scroll(cx, false);
260 Used
261 }
262
263 pub(super) fn request_key_focus(&self, cx: &mut EventCx, source: FocusSource) {
265 if !self.has_key_focus && !self.current.is_ime_enabled() {
266 cx.request_key_focus(self.id(), source);
267 }
268 }
269
270 pub(super) fn trim_paste(&self, text: &str) -> Range<usize> {
271 let mut end = text.len();
272 if !self.multi_line() {
273 for (i, c) in text.char_indices() {
277 if c < '\u{20}' || ('\u{7f}'..='\u{9f}').contains(&c) {
278 end = i;
279 break;
280 }
281 }
282 }
283 0..end
284 }
285
286 pub(super) fn cmd_action(
288 &mut self,
289 cx: &mut EventCx,
290 cmd: Command,
291 ) -> Result<CmdAction, NotReady> {
292 let editable = self.editable;
293 let mut shift = cx.modifiers().shift_key();
294 let mut buf = [0u8; 4];
295 let cursor = self.selection.edit_index();
296 let len = self.text.str_len();
297 let multi_line = self.multi_line();
298 let selection = self.selection.range();
299 let have_sel = selection.end > selection.start;
300 let string;
301
302 enum Action<'a> {
303 None,
304 Deselect,
305 Activate,
306 Insert(&'a str, EditOp),
307 Delete(Range<usize>, EditOp),
308 Move(usize, Option<f32>),
309 UndoRedo(bool),
310 }
311
312 let action = match cmd {
313 Command::Escape | Command::Deselect if !selection.is_empty() => Action::Deselect,
314 Command::Activate => Action::Activate,
315 Command::Enter if shift || !multi_line => Action::Activate,
316 Command::Enter if editable && multi_line => {
317 Action::Insert('\n'.encode_utf8(&mut buf), EditOp::KeyInput)
318 }
319 Command::Left | Command::Home if !shift && have_sel => {
323 Action::Move(selection.start, None)
324 }
325 Command::Left if cursor > 0 => GraphemeCursor::new(cursor, len, true)
326 .prev_boundary(self.text.text(), 0)
327 .unwrap()
328 .map(|index| Action::Move(index, None))
329 .unwrap_or(Action::None),
330 Command::Right | Command::End if !shift && have_sel => {
331 Action::Move(selection.end, None)
332 }
333 Command::Right if cursor < len => GraphemeCursor::new(cursor, len, true)
334 .next_boundary(self.text.text(), 0)
335 .unwrap()
336 .map(|index| Action::Move(index, None))
337 .unwrap_or(Action::None),
338 Command::WordLeft if cursor > 0 => {
339 let mut iter = self.text.text()[0..cursor].split_word_bound_indices();
340 let mut p = iter.next_back().map(|(index, _)| index).unwrap_or(0);
341 while self.text.text()[p..]
342 .chars()
343 .next()
344 .map(|c| c.is_whitespace())
345 .unwrap_or(false)
346 {
347 if let Some((index, _)) = iter.next_back() {
348 p = index;
349 } else {
350 break;
351 }
352 }
353 Action::Move(p, None)
354 }
355 Command::WordRight if cursor < len => {
356 let mut iter = self.text.text()[cursor..]
357 .split_word_bound_indices()
358 .skip(1);
359 let mut p = iter.next().map(|(index, _)| cursor + index).unwrap_or(len);
360 while self.text.text()[p..]
361 .chars()
362 .next()
363 .map(|c| c.is_whitespace())
364 .unwrap_or(false)
365 {
366 if let Some((index, _)) = iter.next() {
367 p = cursor + index;
368 } else {
369 break;
370 }
371 }
372 Action::Move(p, None)
373 }
374 Command::Left | Command::Right | Command::WordLeft | Command::WordRight => Action::None,
376 Command::Up | Command::Down if multi_line => {
377 let x = match self.edit_x_coord {
378 Some(x) => x,
379 None => self
380 .text
381 .text_glyph_pos(cursor)?
382 .next_back()
383 .map(|r| r.pos.0)
384 .unwrap_or(0.0),
385 };
386 let mut line = self.text.find_line(cursor)?.map(|r| r.0).unwrap_or(0);
387 line = match cmd {
389 Command::Up => line.wrapping_sub(1),
390 Command::Down => line.wrapping_add(1),
391 _ => unreachable!(),
392 };
393 const HALF: usize = usize::MAX / 2;
394 let nearest_end = match line {
395 0..=HALF => len,
396 _ => 0,
397 };
398 self.text
399 .line_index_nearest(line, x)?
400 .map(|index| Action::Move(index, Some(x)))
401 .unwrap_or(Action::Move(nearest_end, None))
402 }
403 Command::Home if cursor > 0 => {
404 let index = self.text.find_line(cursor)?.map(|r| r.1.start).unwrap_or(0);
405 Action::Move(index, None)
406 }
407 Command::End if cursor < len => {
408 let index = self.text.find_line(cursor)?.map(|r| r.1.end).unwrap_or(len);
409 Action::Move(index, None)
410 }
411 Command::DocHome if cursor > 0 => Action::Move(0, None),
412 Command::DocEnd if cursor < len => Action::Move(len, None),
413 Command::Home | Command::End | Command::DocHome | Command::DocEnd => Action::None,
415 Command::PageUp | Command::PageDown if multi_line => {
416 let mut v = self
417 .text
418 .text_glyph_pos(cursor)?
419 .next_back()
420 .map(|r| r.pos.into())
421 .unwrap_or(Vec2::ZERO);
422 if let Some(x) = self.edit_x_coord {
423 v.0 = x;
424 }
425 const FACTOR: f32 = 2.0 / 3.0;
426 let mut h_dist = f32::conv(self.text.rect().size.1) * FACTOR;
427 if cmd == Command::PageUp {
428 h_dist *= -1.0;
429 }
430 v.1 += h_dist;
431 Action::Move(self.text.text_index_nearest(v)?, Some(v.0))
432 }
433 Command::Delete | Command::DelBack if editable && have_sel => {
434 Action::Delete(selection.clone(), EditOp::Delete)
435 }
436 Command::Delete if editable => GraphemeCursor::new(cursor, len, true)
437 .next_boundary(self.text.text(), 0)
438 .unwrap()
439 .map(|next| Action::Delete(cursor..next, EditOp::Delete))
440 .unwrap_or(Action::None),
441 Command::DelBack if editable => GraphemeCursor::new(cursor, len, true)
442 .prev_boundary(self.text.text(), 0)
443 .unwrap()
444 .map(|prev| Action::Delete(prev..cursor, EditOp::Delete))
445 .unwrap_or(Action::None),
446 Command::DelWord if editable => {
447 let next = self.text.text()[cursor..]
448 .split_word_bound_indices()
449 .nth(1)
450 .map(|(index, _)| cursor + index)
451 .unwrap_or(len);
452 Action::Delete(cursor..next, EditOp::Delete)
453 }
454 Command::DelWordBack if editable => {
455 let prev = self.text.text()[0..cursor]
456 .split_word_bound_indices()
457 .next_back()
458 .map(|(index, _)| index)
459 .unwrap_or(0);
460 Action::Delete(prev..cursor, EditOp::Delete)
461 }
462 Command::SelectAll => {
463 self.selection.set_sel_index(0);
464 shift = true; Action::Move(len, None)
466 }
467 Command::Cut if editable && have_sel => {
468 cx.set_clipboard((self.text.text()[selection.clone()]).into());
469 Action::Delete(selection.clone(), EditOp::Clipboard)
470 }
471 Command::Copy if have_sel => {
472 cx.set_clipboard((self.text.text()[selection.clone()]).into());
473 Action::None
474 }
475 Command::Paste if editable => {
476 if let Some(content) = cx.get_clipboard() {
477 let range = self.trim_paste(&content);
478 string = content;
479 Action::Insert(&string[range], EditOp::Clipboard)
480 } else {
481 Action::None
482 }
483 }
484 Command::Undo | Command::Redo if editable => Action::UndoRedo(cmd == Command::Redo),
485 _ => return Ok(CmdAction::Unused),
486 };
487
488 if !matches!(action, Action::None | Action::Deselect) {
491 self.request_key_focus(cx, FocusSource::Synthetic);
492 }
493
494 if !matches!(action, Action::None) {
495 self.cancel_selection_and_ime(cx);
496 }
497
498 let edit_op = match action {
499 Action::None => return Ok(CmdAction::Used),
500 Action::Deselect | Action::Move(_, _) => Some(EditOp::Cursor),
501 Action::Activate | Action::UndoRedo(_) => None,
502 Action::Insert(_, edit) | Action::Delete(_, edit) => Some(edit),
503 };
504 self.save_undo_state(edit_op);
505
506 Ok(match action {
507 Action::None => unreachable!(),
508 Action::Deselect => {
509 self.selection.set_empty();
510 cx.redraw();
511 CmdAction::Cursor
512 }
513 Action::Activate => CmdAction::Activate,
514 Action::Insert(s, _) => {
515 let mut index = cursor;
516 let range = if have_sel {
517 index = selection.start;
518 selection.clone()
519 } else {
520 index..index
521 };
522 self.text.replace_range(range, s);
523 self.selection.set_cursor(index + s.len());
524 self.edit_x_coord = None;
525 CmdAction::Edit
526 }
527 Action::Delete(sel, _) => {
528 self.text.replace_range(sel.clone(), "");
529 self.selection.set_cursor(sel.start);
530 self.edit_x_coord = None;
531 CmdAction::Edit
532 }
533 Action::Move(index, x_coord) => {
534 self.selection.set_edit_index(index);
535 if !shift {
536 self.selection.set_empty();
537 } else {
538 self.set_primary(cx);
539 }
540 self.edit_x_coord = x_coord;
541 cx.redraw();
542 CmdAction::Cursor
543 }
544 Action::UndoRedo(redo) => {
545 if let Some((text, cursor)) = self.undo_stack.undo_or_redo(redo) {
546 if self.text.set_str(text) {
547 self.edit_x_coord = None;
548 }
549 self.selection = (*cursor).into();
550 CmdAction::Edit
551 } else {
552 CmdAction::Used
553 }
554 }
555 })
556 }
557
558 pub(super) fn set_cursor_from_coord(&mut self, cx: &mut EventCx, coord: Coord) {
562 let rel_pos = (coord - self.pos).cast();
563 if let Ok(index) = self.text.text_index_nearest(rel_pos) {
564 if index != self.selection.edit_index() {
565 self.selection.set_edit_index(index);
566 self.set_view_offset_from_cursor(cx);
567 self.edit_x_coord = None;
568 cx.redraw();
569 }
570 }
571 }
572
573 pub(super) fn set_primary(&self, cx: &mut EventCx) {
575 if self.has_key_focus && !self.selection.is_empty() && cx.has_primary() {
576 let range = self.selection.range();
577 cx.set_primary(String::from(&self.text.as_str()[range]));
578 }
579 }
580
581 pub(super) fn set_view_offset_from_cursor(&mut self, cx: &mut EventCx) {
587 let cursor = self.selection.edit_index();
588 if let Some(marker) = self
589 .text
590 .text_glyph_pos(cursor)
591 .ok()
592 .and_then(|mut m| m.next_back())
593 {
594 let y0 = (marker.pos.1 - marker.ascent).cast_floor();
595 let pos = self.pos + Offset(marker.pos.0.cast_nearest(), y0);
596 let size = Size(0, i32::conv_ceil(marker.pos.1 - marker.descent) - y0);
597 cx.set_scroll(Scroll::Rect(Rect { pos, size }));
598 }
599 }
600}
601
602impl Editor {
604 #[inline]
606 pub fn id_ref(&self) -> &Id {
607 &self.id
608 }
609
610 #[inline]
612 pub fn id(&self) -> Id {
613 self.id.clone()
614 }
615
616 #[inline]
618 pub fn as_str(&self) -> &str {
619 self.text.as_str()
620 }
621
622 #[inline]
624 pub fn clone_string(&self) -> String {
625 self.text.clone_string()
626 }
627
628 #[inline]
633 pub fn pre_commit(&mut self) {
634 self.save_undo_state(Some(EditOp::Synthetic));
635 }
636
637 #[inline]
642 pub fn clear(&mut self, cx: &mut EventState) {
643 self.last_edit = Some(EditOp::Initial);
644 self.undo_stack.clear();
645 self.set_string(cx, String::new());
646 }
647
648 #[inline]
658 pub fn set_str(&mut self, cx: &mut EventState, text: &str) -> bool {
659 if self.text.as_str() != text {
660 self.set_string(cx, text.to_string());
661 true
662 } else {
663 false
664 }
665 }
666
667 pub fn set_string(&mut self, cx: &mut EventState, string: String) -> bool {
677 self.cancel_selection_and_ime(cx);
678
679 if !self.text.set_string(string) {
680 return false;
681 }
682
683 let len = self.text.str_len();
684 self.selection.set_max_len(len);
685 self.edit_x_coord = None;
686 self.clear_error();
687 self.text.prepare()
688 }
689
690 #[inline]
700 pub fn replace_selected_text(&mut self, cx: &mut EventState, text: &str) -> bool {
701 self.cancel_selection_and_ime(cx);
702
703 let index = self.selection.edit_index();
704 let selection = self.selection.range();
705 let have_sel = selection.start < selection.end;
706 if have_sel {
707 self.text.replace_range(selection.clone(), text);
708 self.selection.set_cursor(selection.start + text.len());
709 } else {
710 self.text.insert_str(index, text);
711 self.selection.set_cursor(index + text.len());
712 }
713 self.edit_x_coord = None;
714 self.clear_error();
715 self.text.prepare()
716 }
717
718 #[inline]
720 pub fn cursor_range(&self) -> CursorRange {
721 *self.selection
722 }
723
724 #[inline]
729 pub fn set_cursor_range(&mut self, range: impl Into<CursorRange>) {
730 self.edit_x_coord = None;
731 self.selection = range.into().into();
732 }
733
734 #[inline]
736 pub fn is_editable(&self) -> bool {
737 self.editable
738 }
739
740 #[inline]
742 pub fn set_editable(&mut self, editable: bool) {
743 self.editable = editable;
744 }
745
746 #[inline]
748 pub fn multi_line(&self) -> bool {
749 self.text.wrap()
750 }
751
752 #[inline]
754 pub fn class(&self) -> TextClass {
755 self.text.class()
756 }
757
758 #[inline]
762 pub fn has_input_focus(&self) -> bool {
763 self.has_key_focus || self.current.is_ime_enabled()
764 }
765
766 #[inline]
768 pub fn has_error(&self) -> bool {
769 self.error_state
770 }
771
772 pub fn set_error(&mut self, cx: &mut EventState, message: Option<Cow<'static, str>>) {
781 self.error_state = true;
782 self.error_message = message;
783 cx.redraw(&self.id);
784 }
785}