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:
Olivier Goffart 2021-01-26 12:54:24 +01:00
parent c2982d9ab3
commit 802383cd6b
8 changed files with 133 additions and 15 deletions

View file

@ -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) \

View file

@ -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.

View file

@ -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.

View file

@ -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();

View file

@ -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();
} }
} }

View 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");
```
*/

View file

@ -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");
``` ```

View file

@ -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(), "");