mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-30 13:51:13 +00:00
Fix cursor navigation when using combining characters
The cursor navigation left/right (and subsequently text selection) needs to respect grapheme boundaries. Since we already depend on the unicode-segmentation crate through femtovg, we might as well use the functionality for determining grapheme boundaries from there. The only place where the cursor navigation is allowed to break that is when using backspace, as that allows the user to break glyph clusters.
This commit is contained in:
parent
35541cffd9
commit
17b3fbc7cf
3 changed files with 68 additions and 8 deletions
|
@ -40,6 +40,7 @@ auto_enums = "0.7"
|
|||
weak-table = "0.3"
|
||||
scopeguard = "1.1.0"
|
||||
cfg-if = "1"
|
||||
unicode-segmentation = "1.8.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
instant = { version = "0.1", features = [ "wasm-bindgen", "now" ] }
|
||||
|
|
|
@ -432,6 +432,7 @@ impl ItemConsts for TextInput {
|
|||
enum TextCursorDirection {
|
||||
Forward,
|
||||
Backward,
|
||||
PreviousCharacter, // breaks grapheme boundaries, so only used by delete-previous-char
|
||||
StartOfLine,
|
||||
EndOfLine,
|
||||
}
|
||||
|
@ -487,17 +488,17 @@ impl TextInput {
|
|||
|
||||
let last_cursor_pos = (self.cursor_position() as usize).max(0).min(text.len());
|
||||
|
||||
let mut grapheme_cursor =
|
||||
unicode_segmentation::GraphemeCursor::new(last_cursor_pos, text.len(), true);
|
||||
|
||||
let new_cursor_pos = match direction {
|
||||
TextCursorDirection::Forward => {
|
||||
let mut i = last_cursor_pos;
|
||||
loop {
|
||||
i = i.checked_add(1).unwrap_or_default().min(text.len());
|
||||
if text.is_char_boundary(i) {
|
||||
break i;
|
||||
}
|
||||
}
|
||||
grapheme_cursor.next_boundary(&text, 0).ok().flatten().unwrap_or_else(|| text.len())
|
||||
}
|
||||
TextCursorDirection::Backward => {
|
||||
grapheme_cursor.prev_boundary(&text, 0).ok().flatten().unwrap_or(0)
|
||||
}
|
||||
TextCursorDirection::PreviousCharacter => {
|
||||
let mut i = last_cursor_pos;
|
||||
loop {
|
||||
i = i.checked_sub(1).unwrap_or_default();
|
||||
|
@ -538,7 +539,8 @@ impl TextInput {
|
|||
self.delete_selection();
|
||||
return;
|
||||
}
|
||||
if self.move_cursor(TextCursorDirection::Backward, AnchorMode::MoveAnchor, window) {
|
||||
if self.move_cursor(TextCursorDirection::PreviousCharacter, AnchorMode::MoveAnchor, window)
|
||||
{
|
||||
self.delete_char(window);
|
||||
}
|
||||
}
|
||||
|
|
57
tests/cases/text/cursor_move_grapheme.60
Normal file
57
tests/cases/text/cursor_move_grapheme.60
Normal file
|
@ -0,0 +1,57 @@
|
|||
/* LICENSE BEGIN
|
||||
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||
Copyright (c) 2021 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||
Copyright (c) 2021 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
This file is also available under commercial licensing terms.
|
||||
Please contact info@sixtyfps.io for more information.
|
||||
LICENSE END */
|
||||
TestCase := TextInput {
|
||||
width: 100phx;
|
||||
height: 100phx;
|
||||
property<string> test_text: self.text;
|
||||
property<int> test_cursor_pos: self.cursor_position;
|
||||
property<int> test_anchor_pos: self.anchor_position;
|
||||
property<bool> has_selection: self.cursor_position != self.anchor_position;
|
||||
property<bool> input_focused: self.has_focus;
|
||||
}
|
||||
|
||||
/*
|
||||
```rust
|
||||
|
||||
// from input.rs
|
||||
const LEFT_CODE: char = '\u{000E}'; // shift out
|
||||
const BACK_CODE: char = '\u{0007}'; // backspace \b
|
||||
|
||||
let shift_modifier = sixtyfps::re_exports::KeyboardModifiers {
|
||||
shift: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let instance = TestCase::new();
|
||||
sixtyfps::testing::send_mouse_click(&instance, 50., 50.);
|
||||
assert!(instance.get_input_focused());
|
||||
assert_eq!(instance.get_test_text(), "");
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, "e\u{0301}");
|
||||
assert_eq!(instance.get_test_text(), "e\u{0301}");
|
||||
assert!(!instance.get_has_selection());
|
||||
|
||||
// Test that selecting the grapheme works
|
||||
sixtyfps::testing::set_current_keyboard_modifiers(&instance, shift_modifier);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
|
||||
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::KeyboardModifiers::default());
|
||||
assert!(instance.get_has_selection());
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
|
||||
|
||||
assert_eq!(instance.get_test_text(), "");
|
||||
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, "e\u{0301}");
|
||||
|
||||
// Test that backspace does not operate on the grapheme and just removes the
|
||||
// diacritic.
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
|
||||
assert_eq!(instance.get_test_cursor_pos(), 1);
|
||||
assert_eq!(instance.get_test_text(), "e");
|
||||
```
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue