mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
Propagate key event to parent if the item don't handle it
Does not work for C++ because binary compatibility issue
This commit is contained in:
parent
c2982d9ab3
commit
802383cd6b
8 changed files with 133 additions and 15 deletions
|
@ -25,10 +25,11 @@ inline void send_mouse_click(const ComponentHandle<Component> *component, float
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Component>
|
template<typename Component>
|
||||||
inline void send_keyboard_string_sequence(const Component &component,
|
inline void send_keyboard_string_sequence(const Component *component,
|
||||||
const sixtyfps::SharedString &str)
|
const sixtyfps::SharedString &str,
|
||||||
|
KeyboardModifiers modifiers = {})
|
||||||
{
|
{
|
||||||
cbindgen_private::send_keyboard_string_sequence(&str, {}, &component.window);
|
cbindgen_private::send_keyboard_string_sequence(&str, modifiers, &component->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define assert_eq(A, B) \
|
#define assert_eq(A, B) \
|
||||||
|
|
|
@ -194,6 +194,7 @@ pub struct KeyEvent {
|
||||||
/// Represents how an item's key_event handler dealt with a key event.
|
/// Represents how an item's key_event handler dealt with a key event.
|
||||||
/// An accepted event results in no further event propagation.
|
/// An accepted event results in no further event propagation.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum KeyEventResult {
|
pub enum KeyEventResult {
|
||||||
/// The event was handled.
|
/// The event was handled.
|
||||||
EventAccepted,
|
EventAccepted,
|
||||||
|
@ -203,7 +204,7 @@ pub enum KeyEventResult {
|
||||||
|
|
||||||
/// This event is sent to a component and items when they receive or loose
|
/// This event is sent to a component and items when they receive or loose
|
||||||
/// the keyboard focus.
|
/// the keyboard focus.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub enum FocusEvent {
|
pub enum FocusEvent {
|
||||||
/// This event is sent when an item receives the focus.
|
/// This event is sent when an item receives the focus.
|
||||||
|
|
|
@ -128,6 +128,10 @@ impl ItemRc {
|
||||||
pub fn downgrade(&self) -> ItemWeak {
|
pub fn downgrade(&self) -> ItemWeak {
|
||||||
ItemWeak { component: VRc::downgrade(&self.component), index: self.index }
|
ItemWeak { component: VRc::downgrade(&self.component), index: self.index }
|
||||||
}
|
}
|
||||||
|
pub fn parent_item(&self) -> ItemWeak {
|
||||||
|
let comp_ref_pin = vtable::VRc::borrow_pin(&self.component);
|
||||||
|
comp_ref_pin.as_ref().parent_item(self.index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Weak reference to an item that can be constructed from an ItemRc.
|
/// A Weak reference to an item that can be constructed from an ItemRc.
|
||||||
|
|
|
@ -292,12 +292,11 @@ impl Item for TextInput {
|
||||||
return KeyEventResult::EventAccepted;
|
return KeyEventResult::EventAccepted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyEventResult::EventIgnored
|
|
||||||
}
|
|
||||||
KeyEventType::KeyReleased
|
|
||||||
// Only insert/interpreter non-control character strings
|
// Only insert/interpreter non-control character strings
|
||||||
if !event.text.is_empty() && event.text.as_str().chars().all(|ch| !ch.is_control()) =>
|
if event.text.is_empty() || event.text.as_str().chars().any(|ch| ch.is_control()) {
|
||||||
{
|
return KeyEventResult::EventIgnored;
|
||||||
|
}
|
||||||
if event.modifiers.control {
|
if event.modifiers.control {
|
||||||
if event.text == "c" {
|
if event.text == "c" {
|
||||||
self.copy();
|
self.copy();
|
||||||
|
@ -306,6 +305,7 @@ impl Item for TextInput {
|
||||||
self.paste();
|
self.paste();
|
||||||
return KeyEventResult::EventAccepted;
|
return KeyEventResult::EventAccepted;
|
||||||
}
|
}
|
||||||
|
return KeyEventResult::EventIgnored;
|
||||||
}
|
}
|
||||||
self.delete_selection();
|
self.delete_selection();
|
||||||
|
|
||||||
|
|
|
@ -135,9 +135,15 @@ impl Window {
|
||||||
/// * `event`: The key event received by the windowing system.
|
/// * `event`: The key event received by the windowing system.
|
||||||
/// * `component`: The SixtyFPS compiled component that provides the tree of items.
|
/// * `component`: The SixtyFPS compiled component that provides the tree of items.
|
||||||
pub fn process_key_input(self: Rc<Self>, event: &KeyEvent) {
|
pub fn process_key_input(self: Rc<Self>, event: &KeyEvent) {
|
||||||
if let Some(focus_item) = self.focus_item.borrow().upgrade() {
|
let mut item = self.focus_item.borrow().clone();
|
||||||
|
while let Some(focus_item) = item.upgrade() {
|
||||||
let window = &ComponentWindow::new(self.clone());
|
let window = &ComponentWindow::new(self.clone());
|
||||||
focus_item.borrow().as_ref().key_event(event, &window);
|
if focus_item.borrow().as_ref().key_event(event, &window)
|
||||||
|
== crate::input::KeyEventResult::EventAccepted
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item = focus_item.parent_item();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
106
tests/cases/focus/event_propagation.60
Normal file
106
tests/cases/focus/event_propagation.60
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/* LICENSE BEGIN
|
||||||
|
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||||
|
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||||
|
Copyright (c) 2020 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 := Rectangle {
|
||||||
|
width: 400phx;
|
||||||
|
height: 400phx;
|
||||||
|
forward-focus: input2;
|
||||||
|
|
||||||
|
input1 := TextInput {
|
||||||
|
width: parent.width;
|
||||||
|
height: 200phx;
|
||||||
|
Rectangle {
|
||||||
|
FocusScope {
|
||||||
|
width: 75%;
|
||||||
|
key-pressed(event) => {
|
||||||
|
recieved += event.text
|
||||||
|
}
|
||||||
|
|
||||||
|
input2 := TextInput {
|
||||||
|
width: 75%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property<bool> input1_focused: input1.has_focus;
|
||||||
|
property<string> input1_text: input1.text;
|
||||||
|
property<bool> input2_focused: input2.has_focus;
|
||||||
|
property<string> input2_text: input2.text;
|
||||||
|
property<string> recieved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
```rust
|
||||||
|
let ctrl_modifier = sixtyfps::re_exports::KeyboardModifiers {
|
||||||
|
control: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let instance = TestCase::new();
|
||||||
|
|
||||||
|
assert!(!instance.get_input1_focused());
|
||||||
|
assert!(instance.get_input2_focused());
|
||||||
|
|
||||||
|
sixtyfps::testing::send_keyboard_string_sequence(&instance, "Hello");
|
||||||
|
assert_eq!(instance.get_input2_text(), "Hello");
|
||||||
|
assert_eq!(instance.get_input1_text(), "");
|
||||||
|
assert_eq!(instance.get_recieved(), "");
|
||||||
|
|
||||||
|
sixtyfps::testing::set_current_keyboard_modifiers(&instance, ctrl_modifier);
|
||||||
|
sixtyfps::testing::send_keyboard_string_sequence(&instance, "ß");
|
||||||
|
assert_eq!(instance.get_input2_text(), "Hello");
|
||||||
|
assert_eq!(instance.get_input1_text(), "");
|
||||||
|
assert_eq!(instance.get_recieved(), "ß");
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
sixtyfps::cbindgen_private::KeyboardModifiers ctrl_modifier{};
|
||||||
|
ctrl_modifier.control = true;
|
||||||
|
|
||||||
|
auto handle = TestCase::create();
|
||||||
|
const TestCase &instance = *handle;
|
||||||
|
|
||||||
|
assert(!instance.get_input1_focused());
|
||||||
|
assert(instance.get_input2_focused());
|
||||||
|
|
||||||
|
sixtyfps::testing::send_keyboard_string_sequence(&instance, "Hello");
|
||||||
|
assert_eq(instance.get_input2_text(), "Hello");
|
||||||
|
assert_eq(instance.get_input1_text(), "");
|
||||||
|
assert_eq(instance.get_recieved(), "");
|
||||||
|
|
||||||
|
sixtyfps::testing::send_keyboard_string_sequence(&instance, "ß", ctrl_modifier);
|
||||||
|
assert_eq(instance.get_input2_text(), "Hello");
|
||||||
|
assert_eq(instance.get_input1_text(), "");
|
||||||
|
assert_eq(instance.get_recieved(), "ß");
|
||||||
|
```
|
||||||
|
|
||||||
|
```j*s
|
||||||
|
var instance = new sixtyfps.TestCase();
|
||||||
|
assert(!instance.input1_focused);
|
||||||
|
assert(!instance.input2_focused);
|
||||||
|
|
||||||
|
instance.send_mouse_click(150., 100.);
|
||||||
|
assert(instance.input1_focused);
|
||||||
|
assert(!instance.input2_focused);
|
||||||
|
|
||||||
|
instance.send_keyboard_string_sequence("Only for field 1");
|
||||||
|
assert.equal(instance.input1_text, "Only for field 1");
|
||||||
|
assert.equal(instance.input2_text, "");
|
||||||
|
|
||||||
|
instance.send_mouse_click(150., 300.);
|
||||||
|
assert(!instance.input1_focused);
|
||||||
|
assert(instance.input2_focused);
|
||||||
|
|
||||||
|
instance.send_keyboard_string_sequence("Only for field 2");
|
||||||
|
assert.equal(instance.input1_text, "Only for field 1");
|
||||||
|
assert.equal(instance.input2_text, "Only for field 2");
|
||||||
|
```
|
||||||
|
*/
|
|
@ -61,7 +61,7 @@ sixtyfps::testing::send_mouse_click(&handle, 150., 100.);
|
||||||
assert(instance.get_input1_focused());
|
assert(instance.get_input1_focused());
|
||||||
assert(!instance.get_input2_focused());
|
assert(!instance.get_input2_focused());
|
||||||
|
|
||||||
sixtyfps::testing::send_keyboard_string_sequence(instance, "Only for field 1");
|
sixtyfps::testing::send_keyboard_string_sequence(&instance, "Only for field 1");
|
||||||
assert_eq(instance.get_input1_text(), "Only for field 1");
|
assert_eq(instance.get_input1_text(), "Only for field 1");
|
||||||
assert_eq(instance.get_input2_text(), "");
|
assert_eq(instance.get_input2_text(), "");
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ sixtyfps::testing::send_mouse_click(&handle, 150., 300.);
|
||||||
assert(!instance.get_input1_focused());
|
assert(!instance.get_input1_focused());
|
||||||
assert(instance.get_input2_focused());
|
assert(instance.get_input2_focused());
|
||||||
|
|
||||||
sixtyfps::testing::send_keyboard_string_sequence(instance, "Only for field 2");
|
sixtyfps::testing::send_keyboard_string_sequence(&instance, "Only for field 2");
|
||||||
assert_eq(instance.get_input1_text(), "Only for field 1");
|
assert_eq(instance.get_input1_text(), "Only for field 1");
|
||||||
assert_eq(instance.get_input2_text(), "Only for field 2");
|
assert_eq(instance.get_input2_text(), "Only for field 2");
|
||||||
```
|
```
|
||||||
|
|
|
@ -54,7 +54,7 @@ const TestCase &instance = *handle;
|
||||||
assert(instance.get_input1_focused());
|
assert(instance.get_input1_focused());
|
||||||
assert(!instance.get_input2_focused());
|
assert(!instance.get_input2_focused());
|
||||||
|
|
||||||
sixtyfps::testing::send_keyboard_string_sequence(instance, "Only for field 1");
|
sixtyfps::testing::send_keyboard_string_sequence(&instance, "Only for field 1");
|
||||||
assert_eq(instance.get_input1_text(), "Only for field 1");
|
assert_eq(instance.get_input1_text(), "Only for field 1");
|
||||||
assert_eq(instance.get_input2_text(), "");
|
assert_eq(instance.get_input2_text(), "");
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue