mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 18:58:36 +00:00
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:
parent
c0f346fdf7
commit
e0ad561c86
18 changed files with 147 additions and 53 deletions
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
63
tests/cases/text/textinput_keyevents.slint
Normal file
63
tests/cases/text/textinput_keyevents.slint
Normal 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");
|
||||
|
||||
```
|
||||
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue