Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ All notable changes to this project are documented in this file.
- Added two-way bindings to model row data. (#2013)
- `@markdown()`: Fixed interpolation in link URLs and colors
- Added `accessible-orientation` and `accessible-live-region` properties
- `FocusScope`: Added `consume-focus-click` property to let the pointer press that transfers the
focus propagate to elements behind the `FocusScope` when set to false. (#12044)
- Added `DragArea` and `DropArea` elements for drag and drop support within a window.
- Added `data-transfer` type
- Deprecated calling `init()` explicitly (#11696)
Expand Down
7 changes: 7 additions & 0 deletions internal/compiler/builtins.slint
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,13 @@ export component FocusScope {
///
/// This property has no effect if the `enabled` property is set to false.
in property <bool> focus-on-tab-navigation: true;
/// When false, the pointer press that gives the `FocusScope` the focus via `focus-on-click` is not
/// consumed: after the focus is transferred, the press continues to be processed by elements behind
/// the `FocusScope`. Note that an enclosing `FocusScope` with default settings may then take the
/// focus instead.
///
/// This property has no effect if `enabled` or `focus-on-click` is set to false.
in property <bool> consume-focus-click: true;
//! ## Functions
//!
//! ### focus()
Expand Down
7 changes: 6 additions & 1 deletion internal/core/items/input_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ pub struct FocusScope {
pub has_focus: Property<bool>,
pub focus_on_click: Property<bool>,
pub focus_on_tab_navigation: Property<bool>,
pub consume_focus_click: Property<bool>,
pub key_pressed: Callback<KeyEventArg, EventResult>,
pub key_released: Callback<KeyEventArg, EventResult>,
pub capture_key_pressed: Callback<KeyEventArg, EventResult>,
Expand Down Expand Up @@ -547,7 +548,11 @@ impl Item for FocusScope {
true,
FocusReason::PointerClick,
);
InputEventResult::EventAccepted
if self.consume_focus_click() {
InputEventResult::EventAccepted
} else {
InputEventResult::EventIgnored
}
} else {
InputEventResult::EventIgnored
}
Expand Down
139 changes: 139 additions & 0 deletions tests/cases/focus/consume_focus_click.slint
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// 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 Window {
width: 400phx;
height: 200phx;

// Left half: default behavior — the press that delivers focus is consumed
// by the FocusScope and never reaches the TouchArea behind it.
Rectangle {
x: 0;
y: 0;
width: 200phx;
height: 200phx;
ta1 := TouchArea {
pointer-event(event) => {
if event.kind == PointerEventKind.down {
presses1 += 1;
}
}
clicked => { clicks1 += 1; }
}
fs1 := FocusScope { }
}

// Right half: with consume-focus-click false, the same press both
// transfers the focus and propagates to the TouchArea.
Rectangle {
x: 200phx;
y: 0;
width: 200phx;
height: 200phx;
ta2 := TouchArea {
pointer-event(event) => {
if event.kind == PointerEventKind.down {
presses2 += 1;
}
}
clicked => { clicks2 += 1; }
}
fs2 := FocusScope {
consume-focus-click: false;
}
}

out property <bool> fs1-has-focus: fs1.has-focus;
out property <bool> fs2-has-focus: fs2.has-focus;
in-out property <int> presses1;
in-out property <int> clicks1;
in-out property <int> presses2;
in-out property <int> clicks2;
}

/*
```rust
let instance = TestCase::new().unwrap();

// Default: the first click focuses fs1 but is consumed, so ta1 sees nothing.
slint_testing::send_mouse_click(&instance, 100., 100.);
assert!(instance.get_fs1_has_focus());
assert_eq!(instance.get_presses1(), 0);
assert_eq!(instance.get_clicks1(), 0);

// Once fs1 has focus, clicks reach the TouchArea.
slint_testing::send_mouse_click(&instance, 100., 100.);
assert!(instance.get_fs1_has_focus());
assert_eq!(instance.get_presses1(), 1);
assert_eq!(instance.get_clicks1(), 1);

// consume-focus-click: false — the first click focuses fs2 AND reaches ta2.
slint_testing::send_mouse_click(&instance, 300., 100.);
assert!(instance.get_fs2_has_focus());
assert!(!instance.get_fs1_has_focus());
assert_eq!(instance.get_presses2(), 1);
assert_eq!(instance.get_clicks2(), 1);

// Subsequent clicks behave the same.
slint_testing::send_mouse_click(&instance, 300., 100.);
assert_eq!(instance.get_presses2(), 2);
assert_eq!(instance.get_clicks2(), 2);
```

```cpp
auto handle = TestCase::create();
const TestCase &instance = *handle;

// Default: the first click focuses fs1 but is consumed, so ta1 sees nothing.
slint_testing::send_mouse_click(&instance, 100., 100.);
assert(instance.get_fs1_has_focus());
assert_eq(instance.get_presses1(), 0);
assert_eq(instance.get_clicks1(), 0);

// Once fs1 has focus, clicks reach the TouchArea.
slint_testing::send_mouse_click(&instance, 100., 100.);
assert(instance.get_fs1_has_focus());
assert_eq(instance.get_presses1(), 1);
assert_eq(instance.get_clicks1(), 1);

// consume-focus-click: false — the first click focuses fs2 AND reaches ta2.
slint_testing::send_mouse_click(&instance, 300., 100.);
assert(instance.get_fs2_has_focus());
assert(!instance.get_fs1_has_focus());
assert_eq(instance.get_presses2(), 1);
assert_eq(instance.get_clicks2(), 1);

// Subsequent clicks behave the same.
slint_testing::send_mouse_click(&instance, 300., 100.);
assert_eq(instance.get_presses2(), 2);
assert_eq(instance.get_clicks2(), 2);
```

```js
let instance = new slint.TestCase({});

// Default: the first click focuses fs1 but is consumed, so ta1 sees nothing.
slintlib.private_api.send_mouse_click(instance, 100., 100.);
assert(instance.fs1_has_focus);
assert.equal(instance.presses1, 0);
assert.equal(instance.clicks1, 0);

// Once fs1 has focus, clicks reach the TouchArea.
slintlib.private_api.send_mouse_click(instance, 100., 100.);
assert(instance.fs1_has_focus);
assert.equal(instance.presses1, 1);
assert.equal(instance.clicks1, 1);

// consume-focus-click: false — the first click focuses fs2 AND reaches ta2.
slintlib.private_api.send_mouse_click(instance, 300., 100.);
assert(instance.fs2_has_focus);
assert(!instance.fs1_has_focus);
assert.equal(instance.presses2, 1);
assert.equal(instance.clicks2, 1);

// Subsequent clicks behave the same.
slintlib.private_api.send_mouse_click(instance, 300., 100.);
assert.equal(instance.presses2, 2);
assert.equal(instance.clicks2, 2);
```
*/
Loading