mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
Allow FocusScope to reject the events
This commit is contained in:
parent
2e23b5bbdd
commit
9de5af75ce
10 changed files with 140 additions and 38 deletions
|
@ -77,6 +77,7 @@ using cbindgen_private::TextVerticalAlignment;
|
||||||
using cbindgen_private::TraversalOrder;
|
using cbindgen_private::TraversalOrder;
|
||||||
using cbindgen_private::ImageFit;
|
using cbindgen_private::ImageFit;
|
||||||
using cbindgen_private::KeyEvent;
|
using cbindgen_private::KeyEvent;
|
||||||
|
using cbindgen_private::EventResult;
|
||||||
using cbindgen_private::KeyboardModifiers;
|
using cbindgen_private::KeyboardModifiers;
|
||||||
|
|
||||||
namespace private_api {
|
namespace private_api {
|
||||||
|
|
|
@ -104,8 +104,8 @@ export FocusScope := _ {
|
||||||
property <length> width;
|
property <length> width;
|
||||||
property <length> height;
|
property <length> height;
|
||||||
property <bool> has_focus;
|
property <bool> has_focus;
|
||||||
callback key_pressed(KeyEvent);
|
callback key_pressed(KeyEvent) -> EventResult;
|
||||||
callback key_released(KeyEvent);
|
callback key_released(KeyEvent) -> EventResult;
|
||||||
//-default_size_binding:expands_to_parent_geometry
|
//-default_size_binding:expands_to_parent_geometry
|
||||||
//-accepts_focus
|
//-accepts_focus
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,6 +160,14 @@ impl<'a> LookupCtx<'a> {
|
||||||
type_loader: None,
|
type_loader: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn return_type(&self) -> &Type {
|
||||||
|
if let Type::Callback { return_type, .. } = &self.property_type {
|
||||||
|
return_type.as_ref().map_or(&Type::Void, |b| &(**b))
|
||||||
|
} else {
|
||||||
|
&self.property_type
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_element_by_id(roots: &[ElementRc], name: &str) -> Option<ElementRc> {
|
fn find_element_by_id(roots: &[ElementRc], name: &str) -> Option<ElementRc> {
|
||||||
|
@ -310,11 +318,7 @@ impl Expression {
|
||||||
node: syntax_nodes::ReturnStatement,
|
node: syntax_nodes::ReturnStatement,
|
||||||
ctx: &mut LookupCtx,
|
ctx: &mut LookupCtx,
|
||||||
) -> Expression {
|
) -> Expression {
|
||||||
let return_type = if let Type::Callback { return_type, .. } = &ctx.property_type {
|
let return_type = ctx.return_type().clone();
|
||||||
return_type.as_ref().map_or(Type::Void, |b| (**b).clone())
|
|
||||||
} else {
|
|
||||||
ctx.property_type.clone()
|
|
||||||
};
|
|
||||||
Expression::ReturnStatement(node.Expression().map(|n| {
|
Expression::ReturnStatement(node.Expression().map(|n| {
|
||||||
Box::new(Self::from_expression_node(n.into(), ctx).maybe_convert_to(
|
Box::new(Self::from_expression_node(n.into(), ctx).maybe_convert_to(
|
||||||
return_type,
|
return_type,
|
||||||
|
@ -555,7 +559,7 @@ impl Expression {
|
||||||
return Expression::Invalid;
|
return Expression::Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
match &ctx.property_type {
|
match ctx.return_type() {
|
||||||
Type::Color => {
|
Type::Color => {
|
||||||
if let Some(c) = css_color_parser2::NAMED_COLORS.get(first_str.as_str()) {
|
if let Some(c) = css_color_parser2::NAMED_COLORS.get(first_str.as_str()) {
|
||||||
let value = ((c.a as u32 * 255) << 24)
|
let value = ((c.a as u32 * 255) << 24)
|
||||||
|
|
|
@ -122,6 +122,7 @@ impl TypeRegister {
|
||||||
&["stretch", "center", "start", "end", "space_between", "space_around"],
|
&["stretch", "center", "start", "end", "space_between", "space_around"],
|
||||||
);
|
);
|
||||||
declare_enum("ImageFit", &["fill", "contain"]);
|
declare_enum("ImageFit", &["fill", "contain"]);
|
||||||
|
declare_enum("EventResult", &["reject", "accept"]);
|
||||||
|
|
||||||
register.supported_property_animation_types.insert(Type::Float32.to_string());
|
register.supported_property_animation_types.insert(Type::Float32.to_string());
|
||||||
register.supported_property_animation_types.insert(Type::Int32.to_string());
|
register.supported_property_animation_types.insert(Type::Int32.to_string());
|
||||||
|
|
|
@ -386,6 +386,21 @@ ItemVTable_static! {
|
||||||
pub static TouchAreaVTable for TouchArea
|
pub static TouchAreaVTable for TouchArea
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, strum_macros::EnumString, strum_macros::Display)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
/// What is returned from the event handler
|
||||||
|
pub enum EventResult {
|
||||||
|
reject,
|
||||||
|
accept,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EventResult {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::reject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A runtime item that exposes key
|
/// A runtime item that exposes key
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
||||||
|
@ -396,8 +411,8 @@ pub struct FocusScope {
|
||||||
pub width: Property<f32>,
|
pub width: Property<f32>,
|
||||||
pub height: Property<f32>,
|
pub height: Property<f32>,
|
||||||
pub has_focus: Property<bool>,
|
pub has_focus: Property<bool>,
|
||||||
pub key_pressed: Callback<KeyEventArg>,
|
pub key_pressed: Callback<KeyEventArg, EventResult>,
|
||||||
pub key_released: Callback<KeyEventArg>,
|
pub key_released: Callback<KeyEventArg, EventResult>,
|
||||||
/// FIXME: remove this
|
/// FIXME: remove this
|
||||||
pub cached_rendering_data: CachedRenderingData,
|
pub cached_rendering_data: CachedRenderingData,
|
||||||
}
|
}
|
||||||
|
@ -431,19 +446,22 @@ impl Item for FocusScope {
|
||||||
window.set_focus_item(self_rc);
|
window.set_focus_item(self_rc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEventResult::EventAccepted
|
InputEventResult::EventIgnored
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_event(self: Pin<&Self>, event: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
fn key_event(self: Pin<&Self>, event: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
||||||
match event.event_type {
|
let r = match event.event_type {
|
||||||
KeyEventType::KeyPressed => {
|
KeyEventType::KeyPressed => {
|
||||||
Self::FIELD_OFFSETS.key_pressed.apply_pin(self).call(&(event.clone(),));
|
Self::FIELD_OFFSETS.key_pressed.apply_pin(self).call(&(event.clone(),))
|
||||||
}
|
}
|
||||||
KeyEventType::KeyReleased => {
|
KeyEventType::KeyReleased => {
|
||||||
Self::FIELD_OFFSETS.key_released.apply_pin(self).call(&(event.clone(),));
|
Self::FIELD_OFFSETS.key_released.apply_pin(self).call(&(event.clone(),))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
KeyEventResult::EventAccepted
|
match r {
|
||||||
|
EventResult::accept => KeyEventResult::EventAccepted,
|
||||||
|
EventResult::reject => KeyEventResult::EventIgnored,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_event(self: Pin<&Self>, event: &FocusEvent, _window: &ComponentWindow) {
|
fn focus_event(self: Pin<&Self>, event: &FocusEvent, _window: &ComponentWindow) {
|
||||||
|
|
|
@ -41,6 +41,7 @@ declare_ValueType![
|
||||||
crate::model::StandardListViewItem,
|
crate::model::StandardListViewItem,
|
||||||
crate::items::ImageFit,
|
crate::items::ImageFit,
|
||||||
crate::input::KeyEvent,
|
crate::input::KeyEvent,
|
||||||
|
crate::items::EventResult,
|
||||||
];
|
];
|
||||||
|
|
||||||
/// What kind of animation is on a binding
|
/// What kind of animation is on a binding
|
||||||
|
|
|
@ -247,6 +247,7 @@ declare_value_enum_conversion!(corelib::items::TextVerticalAlignment, TextVertic
|
||||||
declare_value_enum_conversion!(corelib::layout::LayoutAlignment, LayoutAlignment);
|
declare_value_enum_conversion!(corelib::layout::LayoutAlignment, LayoutAlignment);
|
||||||
declare_value_enum_conversion!(corelib::items::ImageFit, ImageFit);
|
declare_value_enum_conversion!(corelib::items::ImageFit, ImageFit);
|
||||||
declare_value_enum_conversion!(corelib::input::KeyEventType, KeyEventType);
|
declare_value_enum_conversion!(corelib::input::KeyEventType, KeyEventType);
|
||||||
|
declare_value_enum_conversion!(corelib::items::EventResult, EventResult);
|
||||||
|
|
||||||
impl TryFrom<corelib::animations::Instant> for Value {
|
impl TryFrom<corelib::animations::Instant> for Value {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
|
@ -28,6 +28,7 @@ W := Window {
|
||||||
}
|
}
|
||||||
debug(event.text);
|
debug(event.text);
|
||||||
t.text += event.text;
|
t.text += event.text;
|
||||||
|
accept
|
||||||
}
|
}
|
||||||
Rectangle { color: yellow; }
|
Rectangle { color: yellow; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,8 @@ TestCase := Rectangle {
|
||||||
FocusScope {
|
FocusScope {
|
||||||
width: 75%;
|
width: 75%;
|
||||||
key-pressed(event) => {
|
key-pressed(event) => {
|
||||||
recieved += event.text
|
recieved += event.text;
|
||||||
|
accept
|
||||||
}
|
}
|
||||||
|
|
||||||
input2 := TextInput {
|
input2 := TextInput {
|
||||||
|
@ -81,26 +82,4 @@ assert_eq(instance.get_input2_text(), "Hello");
|
||||||
assert_eq(instance.get_input1_text(), "");
|
assert_eq(instance.get_input1_text(), "");
|
||||||
assert_eq(instance.get_recieved(), "ß");
|
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");
|
|
||||||
```
|
|
||||||
*/
|
*/
|
||||||
|
|
96
tests/cases/focus/event_propagation_2.60
Normal file
96
tests/cases/focus/event_propagation_2.60
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/* 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 := Window {
|
||||||
|
width: 100phx;
|
||||||
|
height: 100phx;
|
||||||
|
FocusScope {
|
||||||
|
key-pressed(event) => {
|
||||||
|
r1 += event.text;
|
||||||
|
return event.text == "a" ? accept : reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
key-pressed(event) => {
|
||||||
|
r2 += event.text;
|
||||||
|
return event.text == "b" ? accept : reject;
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
FocusScope {
|
||||||
|
key-pressed(event) => {
|
||||||
|
r3 += event.text;
|
||||||
|
return event.text == "c" ? accept : reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toggle) : FocusScope {
|
||||||
|
key-pressed(event) => {
|
||||||
|
r4 += event.text;
|
||||||
|
return event.text == "d" ? accept : reject;
|
||||||
|
}
|
||||||
|
FocusScope {
|
||||||
|
key-pressed(event) => {
|
||||||
|
r5 += event.text;
|
||||||
|
return event.text == "e" ? accept : reject;
|
||||||
|
}
|
||||||
|
TouchArea {
|
||||||
|
clicked => {
|
||||||
|
parent.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property<bool> toggle: true;
|
||||||
|
property<string> r1;
|
||||||
|
property<string> r2;
|
||||||
|
property<string> r3;
|
||||||
|
property<string> r4;
|
||||||
|
property<string> r5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
```rust
|
||||||
|
let instance = TestCase::new();
|
||||||
|
sixtyfps::testing::send_mouse_click(&instance, 50., 50.);
|
||||||
|
sixtyfps::testing::send_keyboard_string_sequence(&instance, "__abcdefghij__");
|
||||||
|
assert_eq!(instance.get_r1(), "__afghij__");
|
||||||
|
assert_eq!(instance.get_r2(), "__abfghij__");
|
||||||
|
assert_eq!(instance.get_r3(), "__abcfghij__");
|
||||||
|
assert_eq!(instance.get_r4(), "__abcdfghij__");
|
||||||
|
assert_eq!(instance.get_r5(), "__abcdefghij__");
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto handle = TestCase::create();
|
||||||
|
const TestCase &instance = *handle;
|
||||||
|
sixtyfps::testing::send_mouse_click(&handle, 50., 50.);
|
||||||
|
sixtyfps::testing::send_keyboard_string_sequence(&instance, "__abcdefghij__");
|
||||||
|
assert_eq(instance.get_r1(), "__afghij__");
|
||||||
|
assert_eq(instance.get_r2(), "__abfghij__");
|
||||||
|
assert_eq(instance.get_r3(), "__abcfghij__");
|
||||||
|
assert_eq(instance.get_r4(), "__abcdfghij__");
|
||||||
|
assert_eq(instance.get_r5(), "__abcdefghij__");
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
var instance = new sixtyfps.TestCase();
|
||||||
|
instance.send_mouse_click(50., 50.);
|
||||||
|
instance.send_keyboard_string_sequence("__abcdefghij__");
|
||||||
|
assert.equal(instance.r1, "__afghij__");
|
||||||
|
assert.equal(instance.r2, "__abfghij__");
|
||||||
|
assert.equal(instance.r3, "__abcfghij__");
|
||||||
|
assert.equal(instance.r4, "__abcdfghij__");
|
||||||
|
assert.equal(instance.r5, "__abcdefghij__");
|
||||||
|
|
||||||
|
```
|
||||||
|
*/
|
Loading…
Add table
Add a link
Reference in a new issue