1use apace_traits::{
2 events::{ApaceImeEvent, ApaceKeyEvent},
3 node_id::NodeId,
4 shell::ShellProvider,
5};
6use keyboard_types::{Key, Modifiers, NamedKey};
7use parley::{ContentWidths, FontContext, LayoutContext};
8
9use super::password::Password;
10use super::placeholder::Placeholder;
11use crate::util::ACTION_MOD;
12
13#[derive(Debug, Clone, Copy, Default, PartialEq)]
14pub struct TextBrush {
16 pub id: NodeId,
18}
19
20impl TextBrush {
21 pub(crate) fn from_id(id: NodeId) -> Self {
22 Self { id }
23 }
24}
25
26#[derive(Clone, Default)]
27pub struct TextLayout {
28 pub text: String,
29 pub content_widths: Option<ContentWidths>,
30 pub layout: parley::layout::Layout<TextBrush>,
31 pub block_offset: f32,
34 pub span_cb_flags: stylo_takia::StyleFlags,
38}
39
40impl TextLayout {
41 pub fn new() -> Self {
42 Default::default()
43 }
44
45 pub fn content_widths(&mut self) -> ContentWidths {
46 *self
47 .content_widths
48 .get_or_insert_with(|| self.layout.calculate_content_widths())
49 }
50}
51
52impl std::fmt::Debug for TextLayout {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 write!(f, "TextLayout")
55 }
56}
57
58pub enum GeneratedTextInputEvent {
60 Input,
61 Select,
62 PreEditChange,
63 Submit,
64}
65
66pub struct TextInputData {
67 pub editor: Box<parley::PlainEditor<TextBrush>>,
69 password: Option<Password>,
70 placeholder: Option<Placeholder>,
71 placeholder_width: Option<f32>,
72 width: Option<f32>,
73 alignment: parley::layout::Alignment,
74 pub is_multiline: bool,
76 pub scroll_offset: f32,
82}
83
84impl Clone for TextInputData {
86 fn clone(&self) -> Self {
87 let mut data = TextInputData::new(self.is_multiline);
88 data.set_password(self.is_password());
89 data.set_placeholder(self.placeholder.as_ref().map(|hint| hint.text.as_str()));
90 data
91 }
92}
93
94impl TextInputData {
95 pub(crate) fn sync_ime_context(&self, provider: &dyn ShellProvider) {
99 if !provider.supports_ime_surrounding_text() || self.editor.is_composing() {
100 return;
101 }
102 let text = self.editor.raw_text();
103 let selection = self.editor.raw_selection();
104 let anchor = selection.anchor().index();
105 let cursor = selection.focus().index();
106 let low = anchor.min(cursor);
107 let high = anchor.max(cursor);
108 const BUDGET: usize = 3999;
109 if self.is_password() || high - low > BUDGET {
110 provider.set_ime_surrounding_text("", 0, 0);
111 return;
112 }
113 let mut start = low.saturating_sub((BUDGET - (high - low)) / 2);
114 while !text.is_char_boundary(start) {
115 start += 1;
116 }
117 let mut end = text.len().min(start + BUDGET);
118 while !text.is_char_boundary(end) {
119 end -= 1;
120 }
121 provider.set_ime_surrounding_text(&text[start..end], cursor - start, anchor - start);
122 }
123 pub fn new(is_multiline: bool) -> Self {
124 let editor = Box::new(parley::PlainEditor::new(16.0));
125 Self {
126 editor,
127 password: None,
128 placeholder: None,
129 placeholder_width: None,
130 width: None,
131 alignment: parley::layout::Alignment::Start,
132 is_multiline,
133 scroll_offset: 0.0,
134 }
135 }
136
137 pub fn set_text(
138 &mut self,
139 font_ctx: &mut FontContext,
140 layout_ctx: &mut LayoutContext<TextBrush>,
141 text: &str,
142 ) {
143 if self.editor.text() != text {
144 self.editor.set_text(text);
145 self.editor.driver(font_ctx, layout_ctx).refresh_layout();
146 self.refresh_display(font_ctx, layout_ctx);
147 }
148 }
149
150 pub fn is_password(&self) -> bool {
151 self.password.is_some()
152 }
153
154 pub(crate) fn set_placeholder(&mut self, text: Option<&str>) {
155 let text = text.filter(|text| text.len() <= 4096);
156 if self.placeholder.as_ref().map(|hint| hint.text.as_str()) == text {
157 return;
158 }
159 self.placeholder = text.map(Placeholder::new);
160 }
161 pub fn placeholder_shown(&self) -> bool {
162 self.placeholder.is_some()
163 && self.editor.raw_text().is_empty()
164 && !self.editor.is_composing()
165 }
166 pub fn placeholder_layout(&self) -> Option<&parley::Layout<TextBrush>> {
168 if !self.placeholder_shown() {
169 return None;
170 }
171 self.placeholder.as_ref()?.editor.as_ref()?.try_layout()
172 }
173 pub(crate) fn set_placeholder_width(&mut self, width: Option<f32>) {
174 if self.placeholder_width == width {
175 return;
176 }
177 self.placeholder_width = width;
178 if let Some(hint) = &mut self.placeholder {
179 hint.dirty = true;
180 }
181 }
182
183 pub(crate) fn set_password(&mut self, value: bool) {
184 if value && self.password.is_none() {
185 let mut password = Password::new(self.editor.get_font_size());
186 password.editor.set_width(self.width);
187 password.editor.set_alignment(self.alignment);
188 self.password = Some(password);
189 } else if !value {
190 self.password = None;
191 }
192 }
193
194 pub(crate) fn set_width(&mut self, width: Option<f32>) {
195 self.set_placeholder_width(width);
196 self.width = width;
197 self.editor.set_width(width);
198 if let Some(password) = &mut self.password {
199 password.editor.set_width(width);
200 }
201 }
202
203 pub(crate) fn set_alignment(&mut self, alignment: parley::layout::Alignment) {
204 if self.alignment != alignment
205 && let Some(hint) = &mut self.placeholder
206 {
207 hint.dirty = true;
208 }
209 self.alignment = alignment;
210 self.editor.set_alignment(alignment);
211 if let Some(password) = &mut self.password {
212 password.editor.set_alignment(alignment);
213 }
214 }
215
216 pub fn display_editor(&self) -> &parley::PlainEditor<TextBrush> {
219 self.password
220 .as_ref()
221 .map_or(&self.editor, |password| &password.editor)
222 }
223
224 pub fn cursor_geometry(&self, size: f32) -> Option<parley::BoundingBox> {
225 self.editor
226 .is_cursor_visible()
227 .then(|| {
228 let display = self.display_editor();
229 display
230 .try_layout()
231 .map(|layout| display.raw_selection().focus().geometry(layout, size))
232 })
233 .flatten()
234 }
235
236 pub fn ime_cursor_area(&self) -> parley::BoundingBox {
237 let display = self.display_editor();
238 if let (Some(password), Some(compose), Some(layout)) = (
239 &self.password,
240 self.editor.raw_compose(),
241 display.try_layout(),
242 ) {
243 use parley::editing::{Cursor, Selection};
244 use parley::layout::Affinity;
245 let selection = Selection::new(
246 Cursor::from_byte_index(
247 layout,
248 password.display_index(compose.start, false),
249 Affinity::Downstream,
250 ),
251 Cursor::from_byte_index(
252 layout,
253 password.display_index(compose.end, true),
254 Affinity::Upstream,
255 ),
256 );
257 let mut bounds = display.ime_cursor_area();
258 for (rect, _) in selection.geometry(layout) {
259 bounds.x0 = bounds.x0.min(rect.x0);
260 bounds.y0 = bounds.y0.min(rect.y0);
261 bounds.x1 = bounds.x1.max(rect.x1);
262 bounds.y1 = bounds.y1.max(rect.y1);
263 }
264 bounds
265 } else {
266 display.ime_cursor_area()
267 }
268 }
269
270 pub(crate) fn refresh_display(
271 &mut self,
272 fonts: &mut FontContext,
273 layouts: &mut LayoutContext<TextBrush>,
274 ) {
275 if let Some(password) = &mut self.password {
276 password.sync(&self.editor, fonts, layouts);
277 }
278 if self.editor.raw_text().is_empty() {
279 self.scroll_offset = 0.;
280 }
281 if let Some(hint) = &mut self.placeholder {
282 hint.sync(
283 &self.editor,
284 self.placeholder_width,
285 self.alignment,
286 self.is_multiline,
287 fonts,
288 layouts,
289 );
290 }
291 }
292
293 pub(crate) fn refresh_display_config(
294 &mut self,
295 fonts: &mut FontContext,
296 layouts: &mut LayoutContext<TextBrush>,
297 ) {
298 if let Some(password) = &mut self.password {
299 password.invalidate_config();
300 }
301 if let Some(hint) = &mut self.placeholder {
302 hint.dirty = true;
303 }
304 self.refresh_display(fonts, layouts);
305 }
306
307 pub(crate) fn point_selection(
310 &mut self,
311 fonts: &mut FontContext,
312 layouts: &mut LayoutContext<TextBrush>,
313 apply: impl FnOnce(&mut parley::PlainEditorDriver<TextBrush>),
314 ) {
315 self.refresh_display(fonts, layouts);
316 if let Some(password) = &mut self.password {
317 apply(&mut password.editor.driver(fonts, layouts));
318 let selection = password.editor.raw_selection();
319 let anchor = password.raw_index(selection.anchor().index());
320 let focus = password.raw_index(selection.focus().index());
321 self.editor
322 .driver(fonts, layouts)
323 .select_byte_range(anchor, focus);
324 password.sync_selection(&self.editor, fonts, layouts);
325 } else {
326 apply(&mut self.editor.driver(fonts, layouts));
327 }
328 }
329
330 fn password_delete(
331 &mut self,
332 fonts: &mut FontContext,
333 layouts: &mut LayoutContext<TextBrush>,
334 backwards: bool,
335 whole: bool,
336 ) {
337 self.refresh_display(fonts, layouts);
338 let range =
339 self.password
340 .as_ref()
341 .expect("password")
342 .deletion(&self.editor, backwards, whole);
343 let mut driver = self.editor.driver(fonts, layouts);
344 driver.select_byte_range(range.start, range.end);
345 driver.delete_selection();
346 self.refresh_display(fonts, layouts);
347 }
348
349 pub fn clamp_scroll_offset(&mut self, content_box_width: f32, content_box_height: f32) {
355 let Some(layout) = self.display_editor().try_layout() else {
356 return;
357 };
358 let scale = layout.scale();
361
362 let Some(caret) = self.cursor_geometry(1.5) else {
364 return;
365 };
366
367 let (caret_start, caret_end, content, viewport) = if self.is_multiline {
369 (
370 caret.y0 as f32 / scale,
371 caret.y1 as f32 / scale,
372 layout.height() / scale,
373 content_box_height,
374 )
375 } else {
376 (
377 caret.x0 as f32 / scale,
378 caret.x1 as f32 / scale,
379 layout.full_width() / scale,
380 content_box_width,
381 )
382 };
383
384 let mut offset = self.scroll_offset;
385
386 if caret_end > offset + viewport {
388 offset = caret_end - viewport;
389 }
390 if caret_start < offset {
391 offset = caret_start;
392 }
393
394 let max_offset = (content.max(caret_end) - viewport).max(0.0);
398 self.scroll_offset = offset.clamp(0.0, max_offset);
399 }
400
401 pub fn max_scroll_offset(&self, content_box_width: f32, content_box_height: f32) -> f32 {
408 let Some(layout) = self.display_editor().try_layout() else {
409 return 0.0;
410 };
411 let scale = layout.scale();
412 let (content, viewport) = if self.is_multiline {
413 (layout.height() / scale, content_box_height)
414 } else {
415 (layout.full_width() / scale, content_box_width)
416 };
417 (content - viewport).max(0.0)
418 }
419
420 pub fn scroll_by(
427 &mut self,
428 delta: f32,
429 content_box_width: f32,
430 content_box_height: f32,
431 ) -> f32 {
432 let max_offset = self.max_scroll_offset(content_box_width, content_box_height);
433 if max_offset <= 0.0 {
434 return delta;
435 }
436
437 let new_offset = (self.scroll_offset - delta).clamp(0.0, max_offset);
440 let consumed = self.scroll_offset - new_offset;
441 self.scroll_offset = new_offset;
442 delta - consumed
443 }
444
445 pub(crate) fn apply_keypress_event(
446 &mut self,
447 font_ctx: &mut FontContext,
448 layout_ctx: &mut LayoutContext<TextBrush>,
449 shell_provider: &dyn ShellProvider,
450 mut event: ApaceKeyEvent,
451 ) -> Option<GeneratedTextInputEvent> {
452 if self.is_password() && event.state.is_pressed() {
453 let action = event.modifiers.contains(ACTION_MOD);
454 if action
455 && matches!(&event.key, Key::Character(c) if c.eq_ignore_ascii_case("c") || c.eq_ignore_ascii_case("x"))
456 {
457 return None;
458 }
459 if matches!(
460 event.key,
461 Key::Named(NamedKey::Delete | NamedKey::Backspace)
462 ) {
463 self.password_delete(
464 font_ctx,
465 layout_ctx,
466 event.key == Key::Named(NamedKey::Backspace),
467 action,
468 );
469 return Some(GeneratedTextInputEvent::Input);
470 }
471 let navigation = matches!(
472 event.key,
473 Key::Named(
474 NamedKey::ArrowLeft
475 | NamedKey::ArrowRight
476 | NamedKey::ArrowUp
477 | NamedKey::ArrowDown
478 | NamedKey::Home
479 | NamedKey::End
480 )
481 ) || action
482 && matches!(&event.key, Key::Character(c) if c.eq_ignore_ascii_case("a"));
483 if navigation {
484 if action && event.key == Key::Named(NamedKey::ArrowLeft) {
485 event.key = Key::Named(NamedKey::Home);
486 } else if action && event.key == Key::Named(NamedKey::ArrowRight) {
487 event.key = Key::Named(NamedKey::End);
488 }
489 self.refresh_display(font_ctx, layout_ctx);
490 let mut password = self.password.take().expect("password");
491 std::mem::swap(&mut self.editor, &mut password.editor);
492 let result =
493 self.apply_plain_keypress_event(font_ctx, layout_ctx, shell_provider, event);
494 std::mem::swap(&mut self.editor, &mut password.editor);
495 let selection = password.editor.raw_selection();
496 let a = password.raw_index(selection.anchor().index());
497 let f = password.raw_index(selection.focus().index());
498 self.editor
499 .driver(font_ctx, layout_ctx)
500 .select_byte_range(a, f);
501 password.sync_selection(&self.editor, font_ctx, layout_ctx);
502 self.password = Some(password);
503 return result;
504 }
505 }
506 let result = self.apply_plain_keypress_event(font_ctx, layout_ctx, shell_provider, event);
507 self.refresh_display(font_ctx, layout_ctx);
508 result
509 }
510
511 fn apply_plain_keypress_event(
512 &mut self,
513 font_ctx: &mut FontContext,
514 layout_ctx: &mut LayoutContext<TextBrush>,
515 shell_provider: &dyn ShellProvider,
516 event: ApaceKeyEvent,
517 ) -> Option<GeneratedTextInputEvent> {
518 if !event.state.is_pressed() {
520 return None;
521 }
522
523 let mods = event.modifiers;
524 let shift = mods.contains(Modifiers::SHIFT);
525 let action_mod = mods.contains(ACTION_MOD);
526
527 let is_multiline = self.is_multiline;
528 let editor = &mut self.editor;
529 let mut driver = editor.driver(font_ctx, layout_ctx);
530 match event.key {
531 Key::Character(c) if action_mod && matches!(c.as_str(), "c" | "x" | "v") => {
532 match c.to_lowercase().as_str() {
533 "c" => {
534 if let Some(text) = driver.editor.selected_text() {
535 let _ = shell_provider.set_clipboard_text(text.to_owned());
536 }
537 }
538 "x" => {
539 if let Some(text) = driver.editor.selected_text() {
540 let _ = shell_provider.set_clipboard_text(text.to_owned());
541 driver.delete_selection()
542 }
543 }
544 "v" => {
545 let text = shell_provider.get_clipboard_text().unwrap_or_default();
546 driver.insert_or_replace_selection(&text)
547 }
548 _ => unreachable!(),
549 }
550
551 return Some(GeneratedTextInputEvent::Input);
552 }
553 Key::Character(c) if action_mod && matches!(c.to_lowercase().as_str(), "a") => {
554 if shift {
555 driver.collapse_selection()
556 } else {
557 driver.select_all()
558 }
559 return Some(GeneratedTextInputEvent::Select);
560 }
561 Key::Named(NamedKey::ArrowLeft) => {
562 if action_mod {
563 if shift {
564 driver.select_word_left()
565 } else {
566 driver.move_word_left()
567 }
568 } else if shift {
569 driver.select_left()
570 } else {
571 driver.move_left()
572 }
573 return Some(GeneratedTextInputEvent::Select);
574 }
575 Key::Named(NamedKey::ArrowRight) => {
576 if action_mod {
577 if shift {
578 driver.select_word_right()
579 } else {
580 driver.move_word_right()
581 }
582 } else if shift {
583 driver.select_right()
584 } else {
585 driver.move_right()
586 }
587 return Some(GeneratedTextInputEvent::Select);
588 }
589 Key::Named(NamedKey::ArrowUp) => {
590 if shift {
591 driver.select_up()
592 } else {
593 driver.move_up()
594 }
595 return Some(GeneratedTextInputEvent::Select);
596 }
597 Key::Named(NamedKey::ArrowDown) => {
598 if shift {
599 driver.select_down()
600 } else {
601 driver.move_down()
602 }
603 return Some(GeneratedTextInputEvent::Select);
604 }
605 Key::Named(NamedKey::Home) => {
606 if action_mod {
607 if shift {
608 driver.select_to_text_start()
609 } else {
610 driver.move_to_text_start()
611 }
612 } else if shift {
613 driver.select_to_line_start()
614 } else {
615 driver.move_to_line_start()
616 }
617 return Some(GeneratedTextInputEvent::Select);
618 }
619 Key::Named(NamedKey::End) => {
620 if action_mod {
621 if shift {
622 driver.select_to_text_end()
623 } else {
624 driver.move_to_text_end()
625 }
626 } else if shift {
627 driver.select_to_line_end()
628 } else {
629 driver.move_to_line_end()
630 }
631 return Some(GeneratedTextInputEvent::Select);
632 }
633 Key::Named(NamedKey::Delete) => {
634 if action_mod {
635 driver.delete_word()
636 } else {
637 driver.delete()
638 }
639 return Some(GeneratedTextInputEvent::Input);
640 }
641
642 #[cfg(not(target_os = "macos"))]
644 Key::Named(NamedKey::Backspace) => {
645 if action_mod {
646 driver.backdelete_word()
647 } else {
648 driver.backdelete()
649 }
650 return Some(GeneratedTextInputEvent::Input);
651 }
652
653 Key::Character(c) if c == "\n" => {
654 if is_multiline {
655 driver.insert_or_replace_selection("\n");
656 return Some(GeneratedTextInputEvent::Input);
657 } else {
658 return Some(GeneratedTextInputEvent::Submit);
659 }
660 }
661 Key::Named(NamedKey::Enter) => {
662 if is_multiline {
663 driver.insert_or_replace_selection("\n");
664 return Some(GeneratedTextInputEvent::Input);
665 } else {
666 return Some(GeneratedTextInputEvent::Submit);
667 }
668 }
669 Key::Character(s)
670 if !mods.contains(Modifiers::CONTROL) && !mods.contains(Modifiers::META) =>
671 {
672 driver.insert_or_replace_selection(&s);
673 return Some(GeneratedTextInputEvent::Input);
674 }
675 _ => {}
676 };
677
678 None
679 }
680
681 pub(crate) fn apply_apple_standard_keybinding(
682 &mut self,
683 font_ctx: &mut FontContext,
684 layout_ctx: &mut LayoutContext<TextBrush>,
685 shell_provider: &dyn ShellProvider,
686 command: &str,
687 ) -> Option<GeneratedTextInputEvent> {
688 if self.is_password() {
689 if matches!(command, "copy:" | "cut:") {
690 return None;
691 }
692 let deletion = match command {
693 "deleteBackward:" | "deleteBackwardByDecomposingPreviousCharacter:" => {
694 Some((true, false))
695 }
696 "deleteForward:" => Some((false, false)),
697 "deleteWordBackward:"
698 | "deleteToBeginningOfLine:"
699 | "deleteToBeginningOfParagraph:" => Some((true, true)),
700 "deleteWordForward:" | "deleteToEndOfLine:" | "deleteToEndOfParagraph:" => {
701 Some((false, true))
702 }
703 _ => None,
704 };
705 if let Some((backwards, whole)) = deletion {
706 self.password_delete(font_ctx, layout_ctx, backwards, whole);
707 return Some(GeneratedTextInputEvent::Input);
708 }
709 if command.starts_with("move") || command.starts_with("select") {
710 if command.contains("Word") {
711 self.point_selection(font_ctx, layout_ctx, |driver| {
712 if command.starts_with("select") {
713 driver.select_all();
714 } else if command.contains("Backward") || command.contains("Left") {
715 if command.contains("ModifySelection") {
716 driver.select_to_text_start();
717 } else {
718 driver.move_to_text_start();
719 }
720 } else if command.contains("ModifySelection") {
721 driver.select_to_text_end();
722 } else {
723 driver.move_to_text_end();
724 }
725 });
726 return Some(GeneratedTextInputEvent::Select);
727 }
728 self.refresh_display(font_ctx, layout_ctx);
729 let mut password = self.password.take().expect("password");
730 std::mem::swap(&mut self.editor, &mut password.editor);
731 let result = self.apply_plain_apple_standard_keybinding(
732 font_ctx,
733 layout_ctx,
734 shell_provider,
735 command,
736 );
737 std::mem::swap(&mut self.editor, &mut password.editor);
738 let selection = password.editor.raw_selection();
739 let a = password.raw_index(selection.anchor().index());
740 let f = password.raw_index(selection.focus().index());
741 self.editor
742 .driver(font_ctx, layout_ctx)
743 .select_byte_range(a, f);
744 password.sync_selection(&self.editor, font_ctx, layout_ctx);
745 self.password = Some(password);
746 return result;
747 }
748 }
749 let result = self.apply_plain_apple_standard_keybinding(
750 font_ctx,
751 layout_ctx,
752 shell_provider,
753 command,
754 );
755 self.refresh_display(font_ctx, layout_ctx);
756 result
757 }
758
759 fn apply_plain_apple_standard_keybinding(
760 &mut self,
761 font_ctx: &mut FontContext,
762 layout_ctx: &mut LayoutContext<TextBrush>,
763 shell_provider: &dyn ShellProvider,
764 command: &str,
765 ) -> Option<GeneratedTextInputEvent> {
766 let editor = &mut self.editor;
767 let mut driver = editor.driver(font_ctx, layout_ctx);
768 let is_multiline = self.is_multiline;
769
770 match command {
771 "insertBacktab:" => {}
775 "insertContainerBreak:" => {}
777 "insertDoubleQuoteIgnoringSubstitution:" => {
779 driver.insert_or_replace_selection("\"");
780 return Some(GeneratedTextInputEvent::Input);
781 }
782 "insertLineBreak:" => {
784 driver.insert_or_replace_selection("\n");
785 return Some(GeneratedTextInputEvent::Input);
786 }
787 "insertNewline:" => {
789 if is_multiline {
790 driver.insert_or_replace_selection("\n");
791 return Some(GeneratedTextInputEvent::Input);
792 } else {
793 return Some(GeneratedTextInputEvent::Submit);
794 }
795 }
796 "insertNewlineIgnoringFieldEditor:" => {
798 driver.insert_or_replace_selection("\n");
799 return Some(GeneratedTextInputEvent::Input);
800 }
801 "insertParagraphSeparator:" => {
803 driver.insert_or_replace_selection("\n");
804 return Some(GeneratedTextInputEvent::Input);
805 }
806 "insertSingleQuoteIgnoringSubstitution:" => {
807 driver.insert_or_replace_selection("'");
808 return Some(GeneratedTextInputEvent::Input);
809 }
810 "insertTab:" | "insertTabIgnoringFieldEditor:" => {
812 }
814 "insertText:" => {}
816
817 "deleteBackward:" | "deleteBackwardByDecomposingPreviousCharacter:" => {
822 driver.backdelete();
823 return Some(GeneratedTextInputEvent::Input);
824 }
825 "deleteForward:" => {
826 driver.delete();
827 return Some(GeneratedTextInputEvent::Input);
828 }
829 "deleteToBeginningOfLine:" => {
831 if driver.editor.raw_selection().is_collapsed() {
832 driver.select_to_line_start();
833 }
834 driver.delete_selection();
835 return Some(GeneratedTextInputEvent::Input);
836 }
837 "deleteToEndOfLine:" => {
839 if driver.editor.raw_selection().is_collapsed() {
840 driver.select_to_line_end();
841 }
842 driver.delete_selection();
843 return Some(GeneratedTextInputEvent::Input);
844 }
845 "deleteToBeginningOfParagraph:" => {
846 if driver.editor.raw_selection().is_collapsed() {
847 driver.select_to_hard_line_start();
848 }
849 driver.delete_selection();
850 return Some(GeneratedTextInputEvent::Input);
851 }
852
853 "deleteToEndOfParagraph:" => {
855 if driver.editor.raw_selection().is_collapsed() {
856 driver.select_to_hard_line_end();
857 }
858 driver.delete_selection();
859 return Some(GeneratedTextInputEvent::Input);
860 }
861 "deleteWordBackward:" => {
863 driver.backdelete_word();
864 return Some(GeneratedTextInputEvent::Input);
865 }
866 "deleteWordForward:" => {
868 driver.delete_word();
869 return Some(GeneratedTextInputEvent::Input);
870 }
871 "yank:" => {
873 if let Some(text) = driver.editor.selected_text() {
874 let _ = shell_provider.set_clipboard_text(text.to_owned());
875 driver.delete_selection();
876 return Some(GeneratedTextInputEvent::Input);
877 }
878 }
879
880 "moveBackward:" => {
884 driver.move_left(); return Some(GeneratedTextInputEvent::Select);
886 }
887
888 "moveDown:" => {
890 driver.move_down();
891 return Some(GeneratedTextInputEvent::Select);
892 }
893 "moveForward:" => {
895 driver.move_right();
896 return Some(GeneratedTextInputEvent::Select);
897 } "moveLeft:" => {
901 driver.move_left();
902 return Some(GeneratedTextInputEvent::Select);
903 }
904 "moveRight:" => {
906 driver.move_right();
907 return Some(GeneratedTextInputEvent::Select);
908 }
909 "moveUp:" => {
911 driver.move_up();
912 return Some(GeneratedTextInputEvent::Select);
913 }
914
915 "moveBackwardAndModifySelection:" => {
919 driver.select_left(); return Some(GeneratedTextInputEvent::Select);
921 }
922 "moveDownAndModifySelection:" => {
924 driver.select_down();
925 return Some(GeneratedTextInputEvent::Select);
926 }
927 "moveForwardAndModifySelection:" => {
929 driver.select_right(); return Some(GeneratedTextInputEvent::Select);
931 }
932 "moveLeftAndModifySelection:" => {
934 driver.select_left();
935 return Some(GeneratedTextInputEvent::Select);
936 }
937 "moveRightAndModifySelection:" => {
939 driver.select_right();
940 return Some(GeneratedTextInputEvent::Select);
941 }
942 "moveUpAndModifySelection:" => {
944 driver.select_up();
945 return Some(GeneratedTextInputEvent::Select);
946 }
947
948 "selectAll:" => {
950 driver.select_all();
951 return Some(GeneratedTextInputEvent::Select);
952 }
953 "selectLine:" => {
954 driver.move_to_line_start();
955 driver.select_to_line_end();
956 return Some(GeneratedTextInputEvent::Select);
957 }
958 "selectParagraph:" => {
959 driver.move_to_hard_line_start();
960 driver.select_to_hard_line_end();
961 return Some(GeneratedTextInputEvent::Select);
962 }
963 "selectWord:" => {
964 }
966
967 "moveToBeginningOfDocument:" => {
969 driver.move_to_text_start();
970 return Some(GeneratedTextInputEvent::Select);
971 }
972 "moveToBeginningOfDocumentAndModifySelection:" => {
973 driver.select_to_text_start();
974 return Some(GeneratedTextInputEvent::Select);
975 }
976 "moveToEndOfDocument:" => {
977 driver.move_to_text_end();
978 return Some(GeneratedTextInputEvent::Select);
979 }
980 "moveToEndOfDocumentAndModifySelection:" => {
981 driver.move_to_text_end();
982 return Some(GeneratedTextInputEvent::Select);
983 }
984
985 "moveParagraphBackwardAndModifySelection:" => {}
987 "moveParagraphForwardAndModifySelection:" => {}
988 "moveToBeginningOfParagraph:" => {
989 driver.move_to_hard_line_start();
990 return Some(GeneratedTextInputEvent::Select);
991 }
992 "moveToBeginningOfParagraphAndModifySelection:" => {
993 driver.select_to_hard_line_start();
994 return Some(GeneratedTextInputEvent::Select);
995 }
996 "moveToEndOfParagraph:" => {
997 driver.move_to_hard_line_end();
998 return Some(GeneratedTextInputEvent::Select);
999 }
1000 "moveToEndOfParagraphAndModifySelection:" => {
1001 driver.select_to_hard_line_end();
1002 return Some(GeneratedTextInputEvent::Select);
1003 }
1004
1005 "moveToBeginningOfLine:" => {
1007 driver.move_to_line_start();
1008 return Some(GeneratedTextInputEvent::Select);
1009 }
1010 "moveToBeginningOfLineAndModifySelection:" => {
1011 driver.select_to_line_start();
1012 return Some(GeneratedTextInputEvent::Select);
1013 }
1014 "moveToEndOfLine:" => {
1015 driver.move_to_line_end();
1016 return Some(GeneratedTextInputEvent::Select);
1017 }
1018 "moveToEndOfLineAndModifySelection:" => {
1019 driver.select_to_line_end();
1020 return Some(GeneratedTextInputEvent::Select);
1021 }
1022 "moveToLeftEndOfLine:" => {
1023 driver.move_to_text_start();
1024 return Some(GeneratedTextInputEvent::Select);
1025 }
1026 "moveToLeftEndOfLineAndModifySelection:" => {
1027 driver.select_to_line_start();
1028 return Some(GeneratedTextInputEvent::Select);
1029 }
1030 "moveToRightEndOfLine:" => {
1031 driver.move_to_line_end();
1032 return Some(GeneratedTextInputEvent::Select);
1033 }
1034 "moveToRightEndOfLineAndModifySelection:" => {
1035 driver.select_to_line_end();
1036 return Some(GeneratedTextInputEvent::Select);
1037 }
1038
1039 "moveWordBackward:" => {
1041 driver.move_word_left();
1042 return Some(GeneratedTextInputEvent::Select);
1043 }
1044 "moveWordBackwardAndModifySelection:" => {
1045 driver.select_word_left();
1046 return Some(GeneratedTextInputEvent::Select);
1047 }
1048 "moveWordForward:" => {
1049 driver.move_word_right();
1050 return Some(GeneratedTextInputEvent::Select);
1051 }
1052 "moveWordForwardAndModifySelection:" => {
1053 driver.select_word_right();
1054 return Some(GeneratedTextInputEvent::Select);
1055 }
1056 "moveWordLeft:" => {
1057 driver.move_word_left();
1058 return Some(GeneratedTextInputEvent::Select);
1059 }
1060 "moveWordLeftAndModifySelection:" => {
1061 driver.select_word_left();
1062 return Some(GeneratedTextInputEvent::Select);
1063 }
1064 "moveWordRight:" => {
1065 driver.move_word_right();
1066 return Some(GeneratedTextInputEvent::Select);
1067 }
1068 "moveWordRightAndModifySelection:" => {
1069 driver.select_word_right();
1070 return Some(GeneratedTextInputEvent::Select);
1071 }
1072
1073 "scrollPageDown:" => {}
1077 "scrollPageUp:" => {}
1079 "scrollLineDown:" => {}
1081 "scrollLineUp:" => {}
1083 "scrollToBeginningOfDocument:" => {}
1085 "scrollToEndOfDocument:" => {}
1087 "pageDown:" => {}
1089 "pageUp:" => {}
1091 "pageDownAndModifySelection:" => {}
1093 "pageUpAndModifySelection:" => {}
1095 "centerSelectionInVisibleArea:" => {}
1097
1098 "transpose:" => {}
1102 "transposeWords:" => {}
1104
1105 "indent:" => {}
1108
1109 "cancelOperation:" => {}
1112
1113 "quickLookPreviewItems:" => {}
1116
1117 "makeBaseWritingDirectionLeftToRight:" => {}
1119 "makeBaseWritingDirectionNatural:" => {}
1120 "makeBaseWritingDirectionRightToLeft:" => {}
1121 "makeTextWritingDirectionLeftToRight:" => {}
1122 "makeTextWritingDirectionNatural:" => {}
1123 "makeTextWritingDirectionRightToLeft:" => {}
1124
1125 "capitalizeWord:" => {}
1127 "changeCaseOfLetter:" => {}
1128 "lowercaseWord:" => {}
1129 "uppercaseWord:" => {}
1130
1131 "setMark:" => {}
1133 "selectToMark:" => {}
1134 "deleteToMark:" => {}
1135 "swapWithMark:" => {}
1136
1137 "complete:" => {}
1139
1140 "showContextMenuForSelection:" => {}
1142
1143 _ => {}
1145 };
1146
1147 None
1148 }
1149
1150 pub(crate) fn apply_ime_event(
1151 &mut self,
1152 font_ctx: &mut FontContext,
1153 layout_ctx: &mut LayoutContext<TextBrush>,
1154 event: ApaceImeEvent,
1155 ) -> Option<GeneratedTextInputEvent> {
1156 let result = self.apply_plain_ime_event(font_ctx, layout_ctx, event);
1157 self.refresh_display(font_ctx, layout_ctx);
1158 result
1159 }
1160
1161 fn apply_plain_ime_event(
1162 &mut self,
1163 font_ctx: &mut FontContext,
1164 layout_ctx: &mut LayoutContext<TextBrush>,
1165 event: ApaceImeEvent,
1166 ) -> Option<GeneratedTextInputEvent> {
1167 let editor = &mut self.editor;
1168 let mut driver = editor.driver(font_ctx, layout_ctx);
1169
1170 match event {
1171 ApaceImeEvent::Enabled => {
1172 None
1174 }
1175 ApaceImeEvent::Disabled => {
1176 driver.clear_compose();
1177 Some(GeneratedTextInputEvent::PreEditChange)
1178 }
1179 ApaceImeEvent::Commit(text) => {
1180 driver.clear_compose();
1181 driver.insert_or_replace_selection(&text);
1182 Some(GeneratedTextInputEvent::Input)
1183 }
1184 ApaceImeEvent::Preedit(text, cursor) => {
1185 if text.is_empty() {
1186 driver.clear_compose();
1187 } else {
1188 driver.set_compose(&text, cursor);
1189 }
1190 Some(GeneratedTextInputEvent::PreEditChange)
1191 }
1192 ApaceImeEvent::DeleteSurrounding {
1193 before_bytes,
1194 after_bytes,
1195 } => {
1196 let _ = before_bytes;
1197 let _ = after_bytes;
1198 None
1200 }
1201 }
1202 }
1203}
1204
1205#[cfg(test)]
1206#[path = "ime_context_tests.rs"]
1207mod ime_context_tests;