widgets: added key-pressed and key-released callbacks to all text input components (#7081)

* widgets: removed rejected event
* widgets: added key-pressed, key-released callbacks to text input components
* Update docs/astro/src/content/docs/reference/std-widgets/lineedit.mdx
Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>

---------

Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>
This commit is contained in:
FloVanGH 2024-12-12 17:20:45 +01:00 committed by GitHub
parent c0f346fdf7
commit e0ad561c86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 147 additions and 53 deletions

View file

@ -151,9 +151,6 @@ described by the `Point` argument.
### edited()
Invoked when the text has changed because the user modified it.
### rejected()
Invoked when the escape key is pressed.
## TextInputInterface
The `TextInputInterface.text-input-focused` property can be used to find out if a `TextInput` element has the focus.

View file

@ -119,17 +119,6 @@ LineEdit {
}
```
### rejected(string)
Invoked when the escape key is pressed.
```slint no-test {2-4}
LineEdit {
rejected(text) => {
debug("Rejected: ", text);
}
}
```
### edited(string)
Emitted when the text has changed because the user modified it
@ -140,3 +129,12 @@ LineEdit {
}
}
```
### key-pressed(KeyEvent) -> EventResult
Invoked when a key is pressed, the argument is a <Link type="KeyEvent" /> struct. Use this callback to
handle keys before `LineEdit` does. Return `accept` to indicate that you've handled the event, or return
`reject` to let `LineEdit` handle it.
### key-released(KeyEvent) -> EventResult
Invoked when a key is released, the argument is a <Link type="KeyEvent" /> struct. The returned `EventResult`
indicates whether to accept or ignore the event. Ignored events are forwarded to the parent element.

View file

@ -97,14 +97,10 @@ TextEdit {
}
```
### rejected(string)
Invoked when the escape key is pressed.
```slint no-test {2-4}
TextEdit {
rejected(text) => {
debug("Rejected: ", text);
}
}
```
### key-pressed(KeyEvent) -> EventResult
Invoked when a key is pressed, the argument is a <Link type="KeyEvent" /> struct. The returned `EventResult`
indicates whether to accept or ignore the event. Ignored events are forwarded to the parent element.
### key-released(KeyEvent) -> EventResult
Invoked when a key is released, the argument is a <Link type="KeyEvent" /> struct. The returned `EventResult`
indicates whether to accept or ignore the event. Ignored events are forwarded to the parent element.

View file

@ -260,9 +260,10 @@ export component TextInput {
out property <int> anchor-position-byte-offset;
out property <bool> has-focus;
callback accepted;
callback rejected;
callback edited;
callback cursor_position_changed(position: Point);
callback key_pressed(event: KeyEvent) -> EventResult;
callback key_released(event: KeyEvent) -> EventResult;
in property <bool> enabled: true;
in property <bool> single-line: true;
in property <bool> read-only: false;

View file

@ -18,8 +18,9 @@ export component LineEditBase inherits Rectangle {
in property <length> margin;
callback accepted(text: string);
callback rejected(text: string);
callback edited(text: string);
callback key-pressed(event: KeyEvent) -> EventResult;
callback key-released(event: KeyEvent) -> EventResult;
public function set-selection-offsets(start: int, end: int) {
text-input.set-selection-offsets(start, end);
@ -88,12 +89,16 @@ export component LineEditBase inherits Rectangle {
root.accepted(self.text);
}
rejected => {
root.rejected(self.text);
}
edited => {
root.edited(self.text);
}
key-pressed(event) => {
root.key-pressed(event)
}
key-released(event) => {
root.key-released(event)
}
}
}

View file

@ -29,7 +29,8 @@ export component TextEditBase inherits Rectangle {
in property <brush> placeholder-color;
callback edited(text: string);
callback rejected(text: string);
callback key-pressed(event: KeyEvent) -> EventResult;
callback key-released(event: KeyEvent) -> EventResult;
public function set-selection-offsets(start: int, end: int) {
text-input.set-selection-offsets(start, end);
@ -76,8 +77,12 @@ export component TextEditBase inherits Rectangle {
root.edited(self.text);
}
rejected => {
root.rejected(self.text);
key-pressed(event) => {
root.key-pressed(event)
}
key-released(event) => {
root.key-released(event)
}
cursor-position-changed(cpos) => {

View file

@ -15,8 +15,9 @@ export component LineEdit {
in-out property <string> text <=> base.text;
callback accepted <=> base.accepted;
callback rejected <=> base.rejected;
callback edited <=> base.edited;
callback key-pressed <=> base.key-pressed;
callback key-released <=> base.key-released;
accessible-role: text-input;
accessible-enabled: root.enabled;
accessible-value <=> text;

View file

@ -22,7 +22,8 @@ export component TextEdit {
in-out property <length> viewport-height <=> base.viewport-height;
callback edited <=> base.edited;
callback rejected <=> base.rejected;
callback key-pressed <=> base.key-pressed;
callback key-released <=> base.key-released;
accessible-role: AccessibleRole.text-input;
accessible-enabled: root.enabled;

View file

@ -16,8 +16,9 @@ export component LineEdit {
in-out property <string> text <=> base.text;
callback accepted <=> base.accepted;
callback rejected <=> base.rejected;
callback edited <=> base.edited;
callback key-pressed <=> base.key-pressed;
callback key-released <=> base.key-released;
accessible-role: text-input;
accessible-enabled: root.enabled;
accessible-value <=> text;

View file

@ -78,7 +78,8 @@ export component TextEdit {
in property <string> placeholder-text;
callback edited(text: string);
callback rejected(text: string);
callback key-pressed(event: KeyEvent) -> EventResult;
callback key-released(event: KeyEvent) -> EventResult;
accessible-role: AccessibleRole.text-input;
accessible-enabled: root.enabled;
@ -158,9 +159,13 @@ export component TextEdit {
edited => {
root.edited(self.text);
}
key-pressed(event) => {
root.key-pressed(event)
}
rejected => {
root.rejected(self.text);
key-released(event) => {
root.key-released(event)
}
cursor-position-changed(cpos) => {

View file

@ -15,8 +15,9 @@ export component LineEdit {
in-out property <string> text <=> base.text;
callback accepted <=> base.accepted;
callback rejected <=> base.rejected;
callback edited <=> base.edited;
callback key-pressed <=> base.key-pressed;
callback key-released <=> base.key-released;
accessible-role: text-input;
accessible-enabled: root.enabled;
accessible-value <=> text;

View file

@ -22,7 +22,8 @@ export component TextEdit {
in-out property <length> viewport-height <=> base.viewport-height;
callback edited <=> base.edited;
callback rejected <=> base.rejected;
callback key-pressed <=> base.key-pressed;
callback key-released <=> base.key-released;
accessible-role: AccessibleRole.text-input;
accessible-enabled: root.enabled;

View file

@ -16,8 +16,9 @@ export component LineEdit {
in-out property <string> text <=> base.text;
callback accepted <=> base.accepted;
callback rejected <=> base.rejected;
callback edited <=> base.edited;
callback key-pressed <=> base.key-pressed;
callback key-released <=> base.key-released;
accessible-role: text-input;
accessible-enabled: root.enabled;
accessible-value <=> text;

View file

@ -22,7 +22,8 @@ export component TextEdit {
in-out property <length> viewport-height <=> base.viewport-height;
callback edited <=> base.edited;
callback rejected <=> base.rejected;
callback key-pressed <=> base.key-pressed;
callback key-released <=> base.key-released;
accessible-role: AccessibleRole.text-input;
accessible-enabled: root.enabled;

View file

@ -14,8 +14,9 @@ export component LineEdit {
in-out property <string> text <=> inner.text;
callback accepted <=> inner.accepted;
callback rejected <=> inner.rejected;
callback edited <=> inner.edited;
callback key-pressed <=> inner.key-pressed;
callback key-released <=> inner.key-released;
accessible-role: text-input;
accessible-enabled: root.enabled;
accessible-value <=> text;

View file

@ -21,7 +21,8 @@ export component TextEdit {
in-out property <length> viewport-height <=> base.viewport-height;
callback edited <=> base.edited;
callback rejected <=> base.rejected;
callback key-pressed <=> base.key-pressed;
callback key-released <=> base.key-released;
accessible-role: AccessibleRole.text-input;
accessible-enabled: root.enabled;

View file

@ -8,9 +8,10 @@ When adding an item or a property, it needs to be kept in sync with different pl
Lookup the [`crate::items`] module documentation.
*/
use super::{
FontMetrics, InputType, Item, ItemConsts, ItemRc, ItemRef, KeyEventResult, KeyEventType,
PointArg, PointerEventButton, RenderingResult, TextHorizontalAlignment, TextOverflow,
TextStrokeStyle, TextVerticalAlignment, TextWrap, VoidArg,
EventResult, FontMetrics, InputType, Item, ItemConsts, ItemRc, ItemRef, KeyEventArg,
KeyEventResult, KeyEventType, PointArg, PointerEventButton, RenderingResult,
TextHorizontalAlignment, TextOverflow, TextStrokeStyle, TextVerticalAlignment, TextWrap,
VoidArg,
};
use crate::graphics::{Brush, Color, FontRequest};
use crate::input::{
@ -490,9 +491,10 @@ pub struct TextInput {
pub has_focus: Property<bool>,
pub enabled: Property<bool>,
pub accepted: Callback<VoidArg>,
pub rejected: Callback<VoidArg>,
pub cursor_position_changed: Callback<PointArg>,
pub edited: Callback<VoidArg>,
pub key_pressed: Callback<KeyEventArg, EventResult>,
pub key_released: Callback<KeyEventArg, EventResult>,
pub single_line: Property<bool>,
pub read_only: Property<bool>,
pub preedit_text: Property<SharedString>,
@ -685,6 +687,13 @@ impl Item for TextInput {
}
match event.event_type {
KeyEventType::KeyPressed => {
// invoke first key_pressed callback to give the developer/designer the possibility to implement a custom behaviour
if Self::FIELD_OFFSETS.key_pressed.apply_pin(self).call(&(event.clone(),))
== EventResult::Accept
{
return KeyEventResult::EventAccepted;
}
match event.text_shortcut() {
Some(text_shortcut) if !self.read_only() => match text_shortcut {
TextShortcut::Move(direction) => {
@ -746,9 +755,6 @@ impl Item for TextInput {
if keycode == key_codes::Return && !self.read_only() && self.single_line() {
Self::FIELD_OFFSETS.accepted.apply_pin(self).call(&());
return KeyEventResult::EventAccepted;
} else if keycode == key_codes::Escape && !self.read_only() {
Self::FIELD_OFFSETS.rejected.apply_pin(self).call(&());
return KeyEventResult::EventAccepted;
}
}
@ -854,6 +860,16 @@ impl Item for TextInput {
KeyEventResult::EventAccepted
}
KeyEventType::KeyReleased => {
return match Self::FIELD_OFFSETS
.key_released
.apply_pin(self)
.call(&(event.clone(),))
{
EventResult::Accept => KeyEventResult::EventAccepted,
EventResult::Reject => KeyEventResult::EventIgnored,
};
}
KeyEventType::UpdateComposition | KeyEventType::CommitComposition => {
let cursor = self.cursor_position(&self.text()) as i32;
self.preedit_text.set(event.preedit_text.clone());
@ -889,7 +905,6 @@ impl Item for TextInput {
}
KeyEventResult::EventAccepted
}
_ => KeyEventResult::EventIgnored,
}
}

View file

@ -0,0 +1,63 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
export component TestCase inherits TextInput {
out property <string> pressed-text;
out property <string> released-text;
out property <string> test-text: self.text;
width: 100phx;
height: 100phx;
key-pressed(event) => {
if event.text == "a" || event.text == "c" {
root.pressed-text += event.text;
return accept;
}
reject
}
key-released(event) => {
root.released-text += event.text;
accept
}
}
/*
```rust
use slint::platform::{WindowEvent};
let instance = TestCase::new().unwrap();
instance.window().dispatch_event(WindowEvent::KeyPressed { text: 'a'.into() });
assert_eq!(instance.get_pressed_text(), "a");
assert_eq!(instance.get_released_text(), "");
assert_eq!(instance.get_test_text(), "");
instance.window().dispatch_event(WindowEvent::KeyReleased { text: 'a'.into() });
assert_eq!(instance.get_pressed_text(), "a");
assert_eq!(instance.get_released_text(), "a");
assert_eq!(instance.get_test_text(), "");
instance.window().dispatch_event(WindowEvent::KeyPressed { text: 'b'.into() });
assert_eq!(instance.get_pressed_text(), "a");
assert_eq!(instance.get_released_text(), "a");
assert_eq!(instance.get_test_text(), "b");
instance.window().dispatch_event(WindowEvent::KeyReleased { text: 'b'.into() });
assert_eq!(instance.get_pressed_text(), "a");
assert_eq!(instance.get_released_text(), "ab");
assert_eq!(instance.get_test_text(), "b");
instance.window().dispatch_event(WindowEvent::KeyPressed { text: 'c'.into() });
assert_eq!(instance.get_pressed_text(), "ac");
assert_eq!(instance.get_released_text(), "ab");
assert_eq!(instance.get_test_text(), "b");
instance.window().dispatch_event(WindowEvent::KeyReleased { text: 'c'.into() });
assert_eq!(instance.get_pressed_text(), "ac");
assert_eq!(instance.get_released_text(), "abc");
assert_eq!(instance.get_test_text(), "b");
```
*/