Skip to content

Commit dd2a4c8

Browse files
masonry: adopt ui-events text input events
Switch Masonry to the `ui-events` text input model and remove the local IME event copy. * carry `TextInputEvent` through `masonry_core` and `masonry_winit` * translate `TextArea` to the new text input/event model * synthesize composition end on Masonry-side IME teardown * add tests for replacement, selection, composition, actions, and current unsupported cases
1 parent 05db616 commit dd2a4c8

15 files changed

Lines changed: 461 additions & 183 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ tracing = { version = "0.1.44", default-features = false }
6565
tracing-wasm = "0.2.1"
6666
console_error_panic_hook = "0.1.7"
6767
# Remove "std" when that's fixed upstream
68-
ui-events = { version = "0.3.0", default-features = false, features = ["kurbo"] }
69-
ui-events-winit = { version = "0.3.0", default-features = false }
68+
ui-events = { git = "https://github.com/endoli/ui-events.git", rev = "5e840f0e39b412573aaec8debadd4be81c17d823", package = "ui-events", default-features = false, features = ["kurbo"] }
69+
ui-events-winit = { git = "https://github.com/endoli/ui-events.git", rev = "5e840f0e39b412573aaec8debadd4be81c17d823", package = "ui-events-winit", default-features = false }
7070
smallvec = "1.15.1"
7171
hashbrown = { version = "0.16.1", default-features = false, features = ["default-hasher"] }
7272
dpi = "0.1.2"

masonry/src/tests/update.rs

Lines changed: 180 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ use masonry_testing::{
1111

1212
use crate::core::pointer::{PointerButton, PointerEvent};
1313
use crate::core::{
14-
CursorIcon, Ime, NewWidget, PropertySet, TextEvent, Update, Widget, WidgetId, WidgetPod,
15-
WidgetTag,
14+
CursorIcon, Handled, NewWidget, PropertySet, TextCursorAnchor, TextCursorOffset,
15+
TextCursorPlacement, TextDeleteSurroundingEvent, TextEvent, TextInputAction, TextInputEvent,
16+
TextInsertEvent, TextTargetRange, Update, Widget, WidgetId, WidgetPod, WidgetTag,
1617
};
1718
use crate::layout::Length;
1819
use crate::theme::test_property_set;
19-
use crate::widgets::{Button, Flex, Label, SizedBox, TextArea};
20+
use crate::widgets::{Button, Flex, InsertNewline, Label, SizedBox, TextAction, TextArea};
2021

2122
// TREE
2223

@@ -496,16 +497,186 @@ fn ime_commit() {
496497

497498
harness.focus_on(Some(textbox_id));
498499

499-
harness.process_text_event(TextEvent::Ime(Ime::Commit("New Text".to_string())));
500+
harness.process_text_event(TextEvent::TextInput(TextInputEvent::insert("New Text")));
500501
assert_eq!(harness.get_widget(textbox_tag).text(), "New Text");
501502

502-
harness.process_text_event(TextEvent::Ime(Ime::Commit(" and more".to_string())));
503+
harness.process_text_event(TextEvent::TextInput(TextInputEvent::insert(" and more")));
503504
assert_eq!(harness.get_widget(textbox_tag).text(), "New Text and more");
504505

505506
let ime_area_size = harness.ime_rect().1;
506507
assert!(ime_area_size.width > 0. && ime_area_size.height > 0.);
507508
}
508509

510+
#[test]
511+
fn text_input_insert_replacement_range() {
512+
let textbox_tag = WidgetTag::named("textbox");
513+
let textbox = NewWidget::new_with_tag(TextArea::new_editable("hello"), textbox_tag);
514+
515+
let mut harness = TestHarness::create(test_property_set(), textbox);
516+
let textbox_id = harness.get_widget(textbox_tag).id();
517+
harness.focus_on(Some(textbox_id));
518+
519+
let handled = harness.process_text_event(TextEvent::TextInput(TextInputEvent::replace(
520+
"i",
521+
TextTargetRange::utf8_bytes(1, 4),
522+
)));
523+
524+
assert_eq!(handled, Handled::Yes);
525+
assert_eq!(harness.get_widget(textbox_tag).text(), "hio");
526+
}
527+
528+
#[test]
529+
fn text_input_delete_surrounding_utf8() {
530+
let textbox_tag = WidgetTag::named("textbox");
531+
let textbox = NewWidget::new_with_tag(TextArea::new_editable("abcdef"), textbox_tag);
532+
533+
let mut harness = TestHarness::create(test_property_set(), textbox);
534+
let textbox_id = harness.get_widget(textbox_tag).id();
535+
harness.focus_on(Some(textbox_id));
536+
537+
assert_eq!(
538+
harness.process_text_event(TextEvent::TextInput(TextInputEvent::set_selection(
539+
TextTargetRange::utf8_bytes(3, 3),
540+
))),
541+
Handled::Yes
542+
);
543+
assert_eq!(
544+
harness.process_text_event(TextEvent::TextInput(
545+
TextInputEvent::delete_surrounding_utf8(1, 2),
546+
)),
547+
Handled::Yes
548+
);
549+
550+
assert_eq!(harness.get_widget(textbox_tag).text(), "abf");
551+
}
552+
553+
#[test]
554+
fn text_input_set_selection_replaces_selected_text() {
555+
let textbox_tag = WidgetTag::named("textbox");
556+
let textbox = NewWidget::new_with_tag(TextArea::new_editable("hello"), textbox_tag);
557+
558+
let mut harness = TestHarness::create(test_property_set(), textbox);
559+
let textbox_id = harness.get_widget(textbox_tag).id();
560+
harness.focus_on(Some(textbox_id));
561+
562+
assert_eq!(
563+
harness.process_text_event(TextEvent::TextInput(TextInputEvent::set_selection(
564+
TextTargetRange::utf8_bytes(1, 4),
565+
))),
566+
Handled::Yes
567+
);
568+
assert_eq!(
569+
harness.process_text_event(TextEvent::TextInput(TextInputEvent::insert("i"))),
570+
Handled::Yes
571+
);
572+
573+
assert_eq!(harness.get_widget(textbox_tag).text(), "hio");
574+
}
575+
576+
#[test]
577+
fn text_input_action_newline_inserts_when_enabled() {
578+
let textbox_tag = WidgetTag::named("textbox");
579+
let textbox = NewWidget::new_with_tag(
580+
TextArea::new_editable("hello").with_insert_newline(InsertNewline::OnEnter),
581+
textbox_tag,
582+
);
583+
584+
let mut harness = TestHarness::create(test_property_set(), textbox);
585+
let textbox_id = harness.get_widget(textbox_tag).id();
586+
harness.focus_on(Some(textbox_id));
587+
588+
assert_eq!(
589+
harness.process_text_event(TextEvent::TextInput(TextInputEvent::action(
590+
TextInputAction::Newline,
591+
))),
592+
Handled::Yes
593+
);
594+
595+
let (action, widget_id) = harness.pop_action::<TextAction>().unwrap();
596+
assert_eq!(widget_id, textbox_id);
597+
assert_eq!(action, TextAction::Changed("\nhello".to_string()));
598+
assert_eq!(harness.get_widget(textbox_tag).text(), "\nhello");
599+
}
600+
601+
#[test]
602+
fn text_input_actions_submit_entered() {
603+
let textbox_tag = WidgetTag::named("textbox");
604+
let textbox = NewWidget::new_with_tag(TextArea::new_editable("query"), textbox_tag);
605+
606+
let mut harness = TestHarness::create(test_property_set(), textbox);
607+
let textbox_id = harness.get_widget(textbox_tag).id();
608+
harness.focus_on(Some(textbox_id));
609+
610+
for action in [
611+
TextInputAction::Done,
612+
TextInputAction::Search,
613+
TextInputAction::Send,
614+
] {
615+
assert_eq!(
616+
harness.process_text_event(TextEvent::TextInput(TextInputEvent::action(action))),
617+
Handled::Yes
618+
);
619+
let (emitted, widget_id) = harness.pop_action::<TextAction>().unwrap();
620+
assert_eq!(widget_id, textbox_id);
621+
assert_eq!(emitted, TextAction::Entered("query".to_string()));
622+
}
623+
}
624+
625+
#[test]
626+
fn text_input_non_utf8_delete_surrounding_is_unhandled() {
627+
let textbox_tag = WidgetTag::named("textbox");
628+
let textbox = NewWidget::new_with_tag(TextArea::new_editable("abcdef"), textbox_tag);
629+
630+
let mut harness = TestHarness::create(test_property_set(), textbox);
631+
let textbox_id = harness.get_widget(textbox_tag).id();
632+
harness.focus_on(Some(textbox_id));
633+
634+
assert_eq!(
635+
harness.process_text_event(TextEvent::TextInput(TextInputEvent::set_selection(
636+
TextTargetRange::utf8_bytes(3, 3),
637+
))),
638+
Handled::Yes
639+
);
640+
641+
let handled = harness.process_text_event(TextEvent::TextInput(
642+
TextInputEvent::DeleteSurrounding(TextDeleteSurroundingEvent::utf16_code_units(1, 1)),
643+
));
644+
645+
assert_eq!(handled, Handled::No);
646+
assert_eq!(harness.get_widget(textbox_tag).text(), "abcdef");
647+
}
648+
649+
#[test]
650+
fn text_input_cursor_placement_is_currently_ignored() {
651+
let textbox_tag = WidgetTag::named("textbox");
652+
let textbox = NewWidget::new_with_tag(TextArea::new_editable(""), textbox_tag);
653+
654+
let mut harness = TestHarness::create(test_property_set(), textbox);
655+
let textbox_id = harness.get_widget(textbox_tag).id();
656+
harness.focus_on(Some(textbox_id));
657+
658+
let placement = TextCursorPlacement::Offset(TextCursorOffset::utf8_bytes(
659+
0,
660+
TextCursorAnchor::InsertedTextStart,
661+
));
662+
663+
assert_eq!(
664+
harness.process_text_event(TextEvent::TextInput(TextInputEvent::Insert(
665+
TextInsertEvent::new("ab").with_cursor_placement(placement),
666+
))),
667+
Handled::Yes
668+
);
669+
assert_eq!(
670+
harness.process_text_event(TextEvent::TextInput(TextInputEvent::Insert(
671+
TextInsertEvent::new("X"),
672+
))),
673+
Handled::Yes
674+
);
675+
676+
// If cursor placement were applied, the follow-up insert would land before "ab".
677+
assert_eq!(harness.get_widget(textbox_tag).text(), "abX");
678+
}
679+
509680
#[test]
510681
fn ime_removed() {
511682
let textbox_tag = WidgetTag::named("textbox");
@@ -543,7 +714,10 @@ fn ime_start_stop() {
543714

544715
let records = harness.take_records_of(textbox_tag);
545716
assert_any(records, |r| {
546-
matches!(r, Record::TextEvent(TextEvent::Ime(Ime::Disabled)))
717+
matches!(
718+
r,
719+
Record::TextEvent(TextEvent::TextInput(TextInputEvent::CompositionEnd))
720+
)
547721
});
548722

549723
assert!(!harness.has_ime_session());

0 commit comments

Comments
 (0)