Add support for dispatching key events through the public platform API

This change adds `KeyPress` and `KeyRelease` variants to the
`WindowEvent` enum, along with the new `slint::Key` enum, that allows
encoding keys.
This commit is contained in:
Florian Blasius 2022-10-19 14:53:38 +02:00 committed by Simon Hausmann
parent e3838543fe
commit 61c39b5fa1
36 changed files with 681 additions and 315 deletions

View file

@ -10,12 +10,16 @@ All notable changes to this project are documented in this file.
- `Window`'s `default-font-size` property is now always set to a non-zero value, provided by - `Window`'s `default-font-size` property is now always set to a non-zero value, provided by
either the style or the backend. either the style or the backend.
- In the interpreter, calling `set_property` or `get_property` on properties of the base no longer works. - In the interpreter, calling `set_property` or `get_property` on properties of the base no longer works.
- Renamed the `Keys` namespace for use in `key-pressed`/`key-released` callbacks to `Key`. The
old name continues to work.
### Added ### Added
- Added `material` style with `material-light` and `fluent-dark` as explicit styles - Added `material` style with `material-light` and `fluent-dark` as explicit styles
- Added `Window::is_visible` - Added `Window::is_visible`
- Added `From<char>` for `SharedString` in Rust. - Added `From<char>` for `SharedString` in Rust.
- Added `KeyPressed` and `KeyReleased` variants to `slint::WindowEvent` in Rust, along
with `slint::Key`, for use by custom platform backends.
### Fixed ### Fixed

View file

@ -1005,15 +1005,25 @@ namespace slint::testing {
using cbindgen_private::KeyboardModifiers; using cbindgen_private::KeyboardModifiers;
/// Send a key events to the given component instance
inline void send_keyboard_char(const slint::interpreter::ComponentInstance *component,
const slint::SharedString &str, bool pressed)
{
const cbindgen_private::WindowAdapterRcOpaque *win_ptr = nullptr;
cbindgen_private::slint_interpreter_component_instance_window(
reinterpret_cast<const cbindgen_private::ErasedComponentBox *>(component), &win_ptr);
cbindgen_private::slint_send_keyboard_char(
&str, pressed, reinterpret_cast<const cbindgen_private::WindowAdapterRc *>(win_ptr));
}
/// Send a key events to the given component instance /// Send a key events to the given component instance
inline void send_keyboard_string_sequence(const slint::interpreter::ComponentInstance *component, inline void send_keyboard_string_sequence(const slint::interpreter::ComponentInstance *component,
const slint::SharedString &str, const slint::SharedString &str)
KeyboardModifiers modifiers = {})
{ {
const cbindgen_private::WindowAdapterRcOpaque *win_ptr = nullptr; const cbindgen_private::WindowAdapterRcOpaque *win_ptr = nullptr;
cbindgen_private::slint_interpreter_component_instance_window( cbindgen_private::slint_interpreter_component_instance_window(
reinterpret_cast<const cbindgen_private::ErasedComponentBox *>(component), &win_ptr); reinterpret_cast<const cbindgen_private::ErasedComponentBox *>(component), &win_ptr);
cbindgen_private::send_keyboard_string_sequence( cbindgen_private::send_keyboard_string_sequence(
&str, modifiers, reinterpret_cast<const cbindgen_private::WindowAdapterRc *>(win_ptr)); &str, reinterpret_cast<const cbindgen_private::WindowAdapterRc *>(win_ptr));
} }
} }

View file

@ -24,12 +24,17 @@ inline void send_mouse_click(const Component *component, float x, float y)
} }
template<typename Component> template<typename Component>
inline void send_keyboard_string_sequence(const Component *component, inline void send_keyboard_char(const Component *component, const slint::SharedString &str,
const slint::SharedString &str, bool pressed)
cbindgen_private::KeyboardModifiers modifiers = {})
{ {
cbindgen_private::send_keyboard_string_sequence(&str, modifiers, cbindgen_private::slint_send_keyboard_char(&str, pressed, &component->m_window.window_handle());
&component->m_window.window_handle()); }
template<typename Component>
inline void send_keyboard_string_sequence(const Component *component,
const slint::SharedString &str)
{
cbindgen_private::send_keyboard_string_sequence(&str, &component->m_window.window_handle());
} }
#define assert_eq(A, B) \ #define assert_eq(A, B) \

View file

@ -478,7 +478,9 @@ SCENARIO("Send key events")
property <string> result; property <string> result;
scope := FocusScope { scope := FocusScope {
key-pressed(event) => { key-pressed(event) => {
if (event.text != Key.Shift && event.text != Key.Control) {
result += event.text; result += event.text;
}
return accept; return accept;
} }
} }
@ -487,7 +489,7 @@ SCENARIO("Send key events")
""); "");
REQUIRE(comp_def.has_value()); REQUIRE(comp_def.has_value());
auto instance = comp_def->create(); auto instance = comp_def->create();
slint::testing::send_keyboard_string_sequence(&*instance, "Hello keys!", {}); slint::testing::send_keyboard_string_sequence(&*instance, "Hello keys!");
REQUIRE(*instance->get_property("result")->to_string() == "Hello keys!"); REQUIRE(*instance->get_property("result")->to_string() == "Hello keys!");
} }

View file

@ -162,7 +162,8 @@ pub mod re_exports {
}; };
pub use i_slint_core::graphics::*; pub use i_slint_core::graphics::*;
pub use i_slint_core::input::{ pub use i_slint_core::input::{
FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyboardModifiers, MouseEvent, key_codes::Key, FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyboardModifiers,
MouseEvent,
}; };
pub use i_slint_core::item_tree::{ pub use i_slint_core::item_tree::{
visit_item_tree, ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable, ItemWeak, visit_item_tree, ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable, ItemWeak,

View file

@ -494,7 +494,7 @@ The FocusScope exposes callback to intercept the pressed key when it has focus.
The KeyEvent has a text property which is a character of the key entered. The KeyEvent has a text property which is a character of the key entered.
When a non-printable key is pressed, the character will be either a control character, When a non-printable key is pressed, the character will be either a control character,
or it will be mapped to a private unicode character. The mapping of these non-printable, special characters is available in the [`Keys`](#keys) namespace or it will be mapped to a private unicode character. The mapping of these non-printable, special characters is available in the [`Key`](#key) namespace
### Properties ### Properties
@ -522,7 +522,7 @@ Example := Window {
if (event.modifiers.control) { if (event.modifiers.control) {
debug("control was pressed during this event"); debug("control was pressed during this event");
} }
if (event.text == Keys.Escape) { if (event.text == Key.Escape) {
debug("Esc key was pressed") debug("Esc key was pressed")
} }
accept accept
@ -820,9 +820,9 @@ This structure is generated and passed to the `pointer-event` callback of the `T
The following namespaces provide access to common constants such as special keys or named colors. The following namespaces provide access to common constants such as special keys or named colors.
## `Keys` ## `Key`
Use the constants in the `Keys` namespace to handle pressing of keys that don't have a printable character. Check the value of [`KeyEvent`](#keyevent)'s `text` property Use the constants in the `Key` namespace to handle pressing of keys that don't have a printable character. Check the value of [`KeyEvent`](#keyevent)'s `text` property
against the constants below. against the constants below.
* **`Backspace`** * **`Backspace`**
@ -831,6 +831,15 @@ against the constants below.
* **`Escape`** * **`Escape`**
* **`Backtab`** * **`Backtab`**
* **`Delete`** * **`Delete`**
* **`Shift`**
* **`Control`**
* **`Alt`**
* **`AltGr`**
* **`CapsLock`**
* **`ShiftR`**
* **`ControlR`**
* **`Meta`**
* **`MetaR`**
* **`UpArrow`** * **`UpArrow`**
* **`DownArrow`** * **`DownArrow`**
* **`LeftArrow`** * **`LeftArrow`**

View file

@ -28,17 +28,17 @@ export Carousel := FocusScope {
focus-scope:= FocusScope { focus-scope:= FocusScope {
key-pressed(event) => { key-pressed(event) => {
if(event.text == Keys.UpArrow) { if(event.text == Key.UpArrow) {
root.move-focus-up(); root.move-focus-up();
return accept; return accept;
} }
if(event.text == Keys.RightArrow) { if(event.text == Key.RightArrow) {
root.move-right(); root.move-right();
return accept; return accept;
} }
if(event.text == Keys.LeftArrow) { if(event.text == Key.LeftArrow) {
root.move-left(); root.move-left();
return accept; return accept;
} }

View file

@ -74,11 +74,11 @@ export SideBar := Rectangle {
current-item = current-focused; current-item = current-focused;
return accept; return accept;
} }
if (event.text == Keys.UpArrow) { if (event.text == Key.UpArrow) {
focused-tab = Math.max(focused-tab - 1, 0); focused-tab = Math.max(focused-tab - 1, 0);
return accept; return accept;
} }
if (event.text == Keys.DownArrow) { if (event.text == Key.DownArrow) {
focused-tab = Math.min(focused-tab + 1, model.length - 1); focused-tab = Math.min(focused-tab + 1, model.length - 1);
return accept; return accept;
} }

View file

@ -11,7 +11,7 @@ use i_slint_core::graphics::rendering_metrics_collector::{
RenderingMetrics, RenderingMetricsCollector, RenderingMetrics, RenderingMetricsCollector,
}; };
use i_slint_core::graphics::{euclid, Brush, Color, FontRequest, Image, Point, SharedImageBuffer}; use i_slint_core::graphics::{euclid, Brush, Color, FontRequest, Image, Point, SharedImageBuffer};
use i_slint_core::input::{KeyEvent, KeyEventType, MouseEvent}; use i_slint_core::input::{KeyEventType, KeyInputEvent, MouseEvent};
use i_slint_core::item_rendering::{ItemCache, ItemRenderer}; use i_slint_core::item_rendering::{ItemCache, ItemRenderer};
use i_slint_core::items::{ use i_slint_core::items::{
self, FillRule, ImageRendering, InputType, ItemRc, ItemRef, Layer, MouseCursor, Opacity, self, FillRule, ImageRendering, InputType, ItemRc, ItemRef, Layer, MouseCursor, Opacity,
@ -172,19 +172,17 @@ cpp! {{
} }
void keyPressEvent(QKeyEvent *event) override { void keyPressEvent(QKeyEvent *event) override {
uint modifiers = uint(event->modifiers());
QString text = event->text(); QString text = event->text();
int key = event->key(); int key = event->key();
rust!(Slint_keyPress [rust_window: &QtWindow as "void*", key: i32 as "int", text: qttypes::QString as "QString", modifiers: u32 as "uint"] { rust!(Slint_keyPress [rust_window: &QtWindow as "void*", key: i32 as "int", text: qttypes::QString as "QString"] {
rust_window.key_event(key, text.clone(), modifiers, false); rust_window.key_event(key, text.clone(), false);
}); });
} }
void keyReleaseEvent(QKeyEvent *event) override { void keyReleaseEvent(QKeyEvent *event) override {
uint modifiers = uint(event->modifiers());
QString text = event->text(); QString text = event->text();
int key = event->key(); int key = event->key();
rust!(Slint_keyRelease [rust_window: &QtWindow as "void*", key: i32 as "int", text: qttypes::QString as "QString", modifiers: u32 as "uint"] { rust!(Slint_keyRelease [rust_window: &QtWindow as "void*", key: i32 as "int", text: qttypes::QString as "QString"] {
rust_window.key_event(key, text.clone(), modifiers, true); rust_window.key_event(key, text.clone(), true);
}); });
} }
@ -280,23 +278,23 @@ cpp! {{
let runtime_window = WindowInner::from_pub(&rust_window.window); let runtime_window = WindowInner::from_pub(&rust_window.window);
if !preedit_string.is_empty() { if !preedit_string.is_empty() {
let event = KeyEvent { let event = KeyInputEvent {
event_type: KeyEventType::UpdateComposition, event_type: KeyEventType::UpdateComposition,
text: preedit_string.to_string().into(), text: preedit_string.to_string().into(),
preedit_selection_start: replacement_start as usize, preedit_selection_start: replacement_start as usize,
preedit_selection_end: replacement_start as usize + replacement_length as usize, preedit_selection_end: replacement_start as usize + replacement_length as usize,
..Default::default() ..Default::default()
}; };
runtime_window.process_key_input(&event); runtime_window.process_key_input(event);
} }
if !commit_string.is_empty() { if !commit_string.is_empty() {
let event = KeyEvent { let event = KeyInputEvent {
event_type: KeyEventType::CommitComposition, event_type: KeyEventType::CommitComposition,
text: commit_string.to_string().into(), text: commit_string.to_string().into(),
..Default::default() ..Default::default()
}; };
runtime_window.process_key_input(&event); runtime_window.process_key_input(event);
} }
}); });
} }
@ -1419,25 +1417,18 @@ impl QtWindow {
timer_event(); timer_event();
} }
fn key_event(&self, key: i32, text: qttypes::QString, qt_modifiers: u32, released: bool) { fn key_event(&self, key: i32, text: qttypes::QString, released: bool) {
i_slint_core::animations::update_animations(); i_slint_core::animations::update_animations();
let text: String = text.into(); let text: String = text.into();
let modifiers = i_slint_core::input::KeyboardModifiers {
control: (qt_modifiers & key_generated::Qt_KeyboardModifier_ControlModifier) != 0,
alt: (qt_modifiers & key_generated::Qt_KeyboardModifier_AltModifier) != 0,
shift: (qt_modifiers & key_generated::Qt_KeyboardModifier_ShiftModifier) != 0,
meta: (qt_modifiers & key_generated::Qt_KeyboardModifier_MetaModifier) != 0,
};
let text = qt_key_to_string(key as key_generated::Qt_Key, text); let text = qt_key_to_string(key as key_generated::Qt_Key, text);
let event = KeyEvent { let event = KeyInputEvent {
event_type: if released { KeyEventType::KeyReleased } else { KeyEventType::KeyPressed }, event_type: if released { KeyEventType::KeyReleased } else { KeyEventType::KeyPressed },
text, text,
modifiers,
..Default::default() ..Default::default()
}; };
WindowInner::from_pub(&self.window).process_key_input(&event); WindowInner::from_pub(&self.window).process_key_input(event);
timer_event(); timer_event();
} }
@ -2024,8 +2015,7 @@ mod key_codes {
$($(key_generated::$qt => $char,)*)* $($(key_generated::$qt => $char,)*)*
_ => return None, _ => return None,
}; };
let mut buffer = [0; 6]; Some(char.into())
Some(i_slint_core::SharedString::from(char.encode_utf8(&mut buffer) as &str))
} }
}; };
} }

View file

@ -132,14 +132,11 @@ pub fn init() {
/// This module contains functions useful for unit tests /// This module contains functions useful for unit tests
mod for_unit_test { mod for_unit_test {
use core::cell::Cell;
use i_slint_core::api::ComponentHandle; use i_slint_core::api::ComponentHandle;
pub use i_slint_core::tests::slint_mock_elapsed_time as mock_elapsed_time; pub use i_slint_core::tests::slint_mock_elapsed_time as mock_elapsed_time;
use i_slint_core::window::WindowInner; use i_slint_core::window::WindowInner;
use i_slint_core::SharedString; use i_slint_core::SharedString;
thread_local!(static KEYBOARD_MODIFIERS : Cell<i_slint_core::input::KeyboardModifiers> = Default::default());
/// Simulate a mouse click /// Simulate a mouse click
pub fn send_mouse_click< pub fn send_mouse_click<
X: vtable::HasStaticVTable<i_slint_core::component::ComponentVTable> + 'static, X: vtable::HasStaticVTable<i_slint_core::component::ComponentVTable> + 'static,
@ -159,15 +156,20 @@ mod for_unit_test {
); );
} }
/// Simulate a change in keyboard modifiers being pressed /// Simulate entering a sequence of ascii characters key by (pressed or released).
pub fn set_current_keyboard_modifiers< pub fn send_keyboard_char<
X: vtable::HasStaticVTable<i_slint_core::component::ComponentVTable>, X: vtable::HasStaticVTable<i_slint_core::component::ComponentVTable>,
Component: Into<vtable::VRc<i_slint_core::component::ComponentVTable, X>> + ComponentHandle, Component: Into<vtable::VRc<i_slint_core::component::ComponentVTable, X>> + ComponentHandle,
>( >(
_component: &Component, component: &Component,
modifiers: i_slint_core::input::KeyboardModifiers, string: char,
pressed: bool,
) { ) {
KEYBOARD_MODIFIERS.with(|x| x.set(modifiers)) i_slint_core::tests::slint_send_keyboard_char(
&SharedString::from(string),
pressed,
&WindowInner::from_pub(component.window()).window_adapter(),
)
} }
/// Simulate entering a sequence of ascii characters key by key. /// Simulate entering a sequence of ascii characters key by key.
@ -180,7 +182,6 @@ mod for_unit_test {
) { ) {
i_slint_core::tests::send_keyboard_string_sequence( i_slint_core::tests::send_keyboard_string_sequence(
&SharedString::from(sequence), &SharedString::from(sequence),
KEYBOARD_MODIFIERS.with(|x| x.get()),
&WindowInner::from_pub(component.window()).window_adapter(), &WindowInner::from_pub(component.window()).window_adapter(),
) )
} }

View file

@ -15,7 +15,7 @@ use i_slint_core as corelib;
use corelib::api::EventLoopError; use corelib::api::EventLoopError;
use corelib::graphics::euclid; use corelib::graphics::euclid;
use corelib::input::{KeyEvent, KeyEventType, KeyboardModifiers, MouseEvent}; use corelib::input::{KeyEventType, KeyInputEvent, MouseEvent};
use corelib::window::*; use corelib::window::*;
use corelib::{Coord, SharedString}; use corelib::{Coord, SharedString};
use std::cell::{Cell, RefCell, RefMut}; use std::cell::{Cell, RefCell, RefMut};
@ -30,7 +30,6 @@ pub(crate) static QUIT_ON_LAST_WINDOW_CLOSED: std::sync::atomic::AtomicBool =
pub trait WinitWindow: WindowAdapter { pub trait WinitWindow: WindowAdapter {
fn currently_pressed_key_code(&self) -> &Cell<Option<winit::event::VirtualKeyCode>>; fn currently_pressed_key_code(&self) -> &Cell<Option<winit::event::VirtualKeyCode>>;
fn current_keyboard_modifiers(&self) -> &Cell<KeyboardModifiers>;
/// Returns true if during the drawing request_redraw() was called. /// Returns true if during the drawing request_redraw() was called.
fn draw(&self) -> bool; fn draw(&self) -> bool;
fn with_window_handle(&self, callback: &mut dyn FnMut(&winit::window::Window)); fn with_window_handle(&self, callback: &mut dyn FnMut(&winit::window::Window));
@ -225,8 +224,7 @@ mod key_codes {
$($(winit::event::VirtualKeyCode::$winit => $char,)*)* $($(winit::event::VirtualKeyCode::$winit => $char,)*)*
_ => return None, _ => return None,
}; };
let mut buffer = [0; 6]; Some(char.into())
Some(i_slint_core::SharedString::from(char.encode_utf8(&mut buffer) as &str))
} }
}; };
} }
@ -239,23 +237,6 @@ fn process_window_event(
cursor_pos: &mut LogicalPoint, cursor_pos: &mut LogicalPoint,
pressed: &mut bool, pressed: &mut bool,
) { ) {
fn key_event(
event_type: KeyEventType,
text: SharedString,
modifiers: KeyboardModifiers,
) -> KeyEvent {
let mut event = KeyEvent { event_type, text, modifiers, ..Default::default() };
let tab = String::from(corelib::input::key_codes::Tab);
// map Shift-Tab into (Shift) Backtab to have a similar behavior as Qt backend
if event.text == tab && modifiers.shift {
event.text = SharedString::from(String::from(corelib::input::key_codes::Backtab));
}
event
}
let runtime_window = WindowInner::from_pub(window.window()); let runtime_window = WindowInner::from_pub(window.window());
match event { match event {
WindowEvent::Resized(size) => { WindowEvent::Resized(size) => {
@ -279,7 +260,7 @@ fn process_window_event(
.and_then(winit_key_code_to_string) .and_then(winit_key_code_to_string)
.filter(|key_text| !key_text.starts_with(char::is_control)) .filter(|key_text| !key_text.starts_with(char::is_control))
} else { } else {
Some(ch.to_string().into()) Some(SharedString::from(ch))
}; };
let text = match text { let text = match text {
@ -287,13 +268,16 @@ fn process_window_event(
None => return, None => return,
}; };
let modifiers = window.current_keyboard_modifiers().get(); runtime_window.process_key_input(KeyInputEvent {
event_type: KeyEventType::KeyPressed,
let mut event = key_event(KeyEventType::KeyPressed, text, modifiers); text: text.clone(),
..Default::default()
runtime_window.process_key_input(&event); });
event.event_type = KeyEventType::KeyReleased; runtime_window.process_key_input(KeyInputEvent {
runtime_window.process_key_input(&event); event_type: KeyEventType::KeyReleased,
text: text.clone(),
..Default::default()
});
} }
WindowEvent::Focused(have_focus) => { WindowEvent::Focused(have_focus) => {
let have_focus = have_focus || window.input_method_focused(); let have_focus = have_focus || window.input_method_focused();
@ -305,63 +289,51 @@ fn process_window_event(
} }
} }
WindowEvent::KeyboardInput { ref input, .. } => { WindowEvent::KeyboardInput { ref input, .. } => {
// For now: Match Qt's behavior of mapping command to control and control to meta (LWin/RWin).
let key_code = input.virtual_keycode.map(|key_code| match key_code {
#[cfg(target_os = "macos")]
winit::event::VirtualKeyCode::LControl => winit::event::VirtualKeyCode::LWin,
#[cfg(target_os = "macos")]
winit::event::VirtualKeyCode::RControl => winit::event::VirtualKeyCode::RWin,
#[cfg(target_os = "macos")]
winit::event::VirtualKeyCode::LWin => winit::event::VirtualKeyCode::LControl,
#[cfg(target_os = "macos")]
winit::event::VirtualKeyCode::RWin => winit::event::VirtualKeyCode::RControl,
code @ _ => code,
});
window.currently_pressed_key_code().set(match input.state { window.currently_pressed_key_code().set(match input.state {
winit::event::ElementState::Pressed => input.virtual_keycode, winit::event::ElementState::Pressed => key_code,
_ => None, _ => None,
}); });
if let Some(text) = input.virtual_keycode.and_then(key_codes::winit_key_to_string) { if let Some(text) = key_code.and_then(key_codes::winit_key_to_string) {
#[allow(unused_mut)] runtime_window.process_key_input(KeyInputEvent {
let mut modifiers = window.current_keyboard_modifiers().get(); event_type: match input.state {
// On wasm, the WindowEvent::ModifiersChanged event is not received
#[cfg(target_arch = "wasm32")]
#[allow(deprecated)]
{
modifiers.shift |= input.modifiers.shift();
modifiers.control |= input.modifiers.ctrl();
modifiers.meta |= input.modifiers.logo();
modifiers.alt |= input.modifiers.alt();
}
let event = key_event(
match input.state {
winit::event::ElementState::Pressed => KeyEventType::KeyPressed, winit::event::ElementState::Pressed => KeyEventType::KeyPressed,
winit::event::ElementState::Released => KeyEventType::KeyReleased, winit::event::ElementState::Released => KeyEventType::KeyReleased,
}, },
text, text,
modifiers, ..Default::default()
); });
runtime_window.process_key_input(&event);
}; };
} }
WindowEvent::Ime(winit::event::Ime::Preedit(string, preedit_selection)) => { WindowEvent::Ime(winit::event::Ime::Preedit(string, preedit_selection)) => {
let preedit_selection = preedit_selection.unwrap_or((0, 0)); let preedit_selection = preedit_selection.unwrap_or((0, 0));
let event = KeyEvent { let event = KeyInputEvent {
event_type: KeyEventType::UpdateComposition, event_type: KeyEventType::UpdateComposition,
text: string.into(), text: string.into(),
preedit_selection_start: preedit_selection.0, preedit_selection_start: preedit_selection.0,
preedit_selection_end: preedit_selection.1, preedit_selection_end: preedit_selection.1,
..Default::default() ..Default::default()
}; };
runtime_window.process_key_input(&event); runtime_window.process_key_input(event);
} }
WindowEvent::Ime(winit::event::Ime::Commit(string)) => { WindowEvent::Ime(winit::event::Ime::Commit(string)) => {
let event = KeyEvent { let event = KeyInputEvent {
event_type: KeyEventType::CommitComposition, event_type: KeyEventType::CommitComposition,
text: string.into(), text: string.into(),
..Default::default() ..Default::default()
}; };
runtime_window.process_key_input(&event); runtime_window.process_key_input(event);
}
WindowEvent::ModifiersChanged(state) => {
// To provide an easier cross-platform behavior, we map the command key to control
// on macOS, and control to meta.
#[cfg(target_os = "macos")]
let (control, meta) = (state.logo(), state.ctrl());
#[cfg(not(target_os = "macos"))]
let (control, meta) = (state.ctrl(), state.logo());
let modifiers =
KeyboardModifiers { shift: state.shift(), alt: state.alt(), control, meta };
window.current_keyboard_modifiers().set(modifiers);
} }
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
let position = position.to_logical(runtime_window.scale_factor() as f64); let position = position.to_logical(runtime_window.scale_factor() as f64);

View file

@ -14,7 +14,6 @@ use crate::renderer::{WinitCompatibleCanvas, WinitCompatibleRenderer};
use const_field_offset::FieldOffsets; use const_field_offset::FieldOffsets;
use corelib::component::ComponentRc; use corelib::component::ComponentRc;
use corelib::graphics::euclid::num::Zero; use corelib::graphics::euclid::num::Zero;
use corelib::input::KeyboardModifiers;
use corelib::items::{ItemRef, MouseCursor}; use corelib::items::{ItemRef, MouseCursor};
use corelib::layout::Orientation; use corelib::layout::Orientation;
use corelib::lengths::{LogicalLength, LogicalPoint, LogicalSize}; use corelib::lengths::{LogicalLength, LogicalPoint, LogicalSize};
@ -83,7 +82,6 @@ pub(crate) struct GLWindow<Renderer: WinitCompatibleRenderer + 'static> {
window: corelib::api::Window, window: corelib::api::Window,
self_weak: Weak<Self>, self_weak: Weak<Self>,
map_state: RefCell<GraphicsWindowBackendState<Renderer>>, map_state: RefCell<GraphicsWindowBackendState<Renderer>>,
keyboard_modifiers: std::cell::Cell<KeyboardModifiers>,
currently_pressed_key_code: std::cell::Cell<Option<winit::event::VirtualKeyCode>>, currently_pressed_key_code: std::cell::Cell<Option<winit::event::VirtualKeyCode>>,
pending_redraw: Cell<bool>, pending_redraw: Cell<bool>,
@ -108,7 +106,6 @@ impl<Renderer: WinitCompatibleRenderer + 'static> GLWindow<Renderer> {
requested_position: None, requested_position: None,
requested_size: None, requested_size: None,
}), }),
keyboard_modifiers: Default::default(),
currently_pressed_key_code: Default::default(), currently_pressed_key_code: Default::default(),
pending_redraw: Cell::new(false), pending_redraw: Cell::new(false),
renderer: Renderer::new( renderer: Renderer::new(
@ -184,10 +181,6 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WinitWindow for GLWindow<Rende
&self.currently_pressed_key_code &self.currently_pressed_key_code
} }
fn current_keyboard_modifiers(&self) -> &Cell<KeyboardModifiers> {
&self.keyboard_modifiers
}
/// Draw the items of the specified `component` in the given window. /// Draw the items of the specified `component` in the given window.
fn draw(&self) -> bool { fn draw(&self) -> bool {
let window = match self.borrow_mapped_window() { let window = match self.borrow_mapped_window() {

View file

@ -20,7 +20,7 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use i_slint_core::input::{KeyEvent, KeyEventType, KeyboardModifiers}; use i_slint_core::input::{KeyEventType, KeyInputEvent};
use i_slint_core::window::{WindowAdapter, WindowInner}; use i_slint_core::window::{WindowAdapter, WindowInner};
use i_slint_core::SharedString; use i_slint_core::SharedString;
use wasm_bindgen::closure::Closure; use wasm_bindgen::closure::Closure;
@ -83,8 +83,7 @@ impl WasmInputHelper {
if let (Some(window_adapter), Some(text)) = (win.upgrade(), event_text(&e)) { if let (Some(window_adapter), Some(text)) = (win.upgrade(), event_text(&e)) {
e.prevent_default(); e.prevent_default();
shared_state2.borrow_mut().has_key_down = true; shared_state2.borrow_mut().has_key_down = true;
WindowInner::from_pub(window_adapter.window()).process_key_input(&KeyEvent { WindowInner::from_pub(window_adapter.window()).process_key_input(KeyInputEvent {
modifiers: modifiers(&e),
text, text,
event_type: KeyEventType::KeyPressed, event_type: KeyEventType::KeyPressed,
..Default::default() ..Default::default()
@ -98,8 +97,7 @@ impl WasmInputHelper {
if let (Some(window_adapter), Some(text)) = (win.upgrade(), event_text(&e)) { if let (Some(window_adapter), Some(text)) = (win.upgrade(), event_text(&e)) {
e.prevent_default(); e.prevent_default();
shared_state2.borrow_mut().has_key_down = false; shared_state2.borrow_mut().has_key_down = false;
WindowInner::from_pub(window_adapter.window()).process_key_input(&KeyEvent { WindowInner::from_pub(window_adapter.window()).process_key_input(KeyInputEvent {
modifiers: modifiers(&e),
text, text,
event_type: KeyEventType::KeyReleased, event_type: KeyEventType::KeyReleased,
..Default::default() ..Default::default()
@ -116,12 +114,12 @@ impl WasmInputHelper {
if !shared_state2.borrow_mut().has_key_down { if !shared_state2.borrow_mut().has_key_down {
let window_inner = WindowInner::from_pub(window_adapter.window()); let window_inner = WindowInner::from_pub(window_adapter.window());
let text = SharedString::from(data.as_str()); let text = SharedString::from(data.as_str());
window_inner.process_key_input(&KeyEvent { window_inner.process_key_input(KeyInputEvent {
text: text.clone(), text: text.clone(),
event_type: KeyEventType::KeyPressed, event_type: KeyEventType::KeyPressed,
..Default::default() ..Default::default()
}); });
window_inner.process_key_input(&KeyEvent { window_inner.process_key_input(KeyInputEvent {
text, text,
event_type: KeyEventType::KeyReleased, event_type: KeyEventType::KeyReleased,
..Default::default() ..Default::default()
@ -138,7 +136,7 @@ impl WasmInputHelper {
h.add_event_listener("compositionend", move |e: web_sys::CompositionEvent| { h.add_event_listener("compositionend", move |e: web_sys::CompositionEvent| {
if let (Some(window_adapter), Some(data)) = (win.upgrade(), e.data()) { if let (Some(window_adapter), Some(data)) = (win.upgrade(), e.data()) {
let window_inner = WindowInner::from_pub(window_adapter.window()); let window_inner = WindowInner::from_pub(window_adapter.window());
window_inner.process_key_input(&KeyEvent { window_inner.process_key_input(KeyInputEvent {
text: data.into(), text: data.into(),
event_type: KeyEventType::CommitComposition, event_type: KeyEventType::CommitComposition,
..Default::default() ..Default::default()
@ -153,7 +151,7 @@ impl WasmInputHelper {
let window_inner = WindowInner::from_pub(window_adapter.window()); let window_inner = WindowInner::from_pub(window_adapter.window());
let text: SharedString = data.into(); let text: SharedString = data.into();
let preedit_cursor_pos = text.len(); let preedit_cursor_pos = text.len();
window_inner.process_key_input(&KeyEvent { window_inner.process_key_input(KeyInputEvent {
text, text,
event_type: KeyEventType::UpdateComposition, event_type: KeyEventType::UpdateComposition,
preedit_selection_start: preedit_cursor_pos, preedit_selection_start: preedit_cursor_pos,
@ -214,10 +212,7 @@ fn event_text(e: &web_sys::KeyboardEvent) -> Option<SharedString> {
let key = e.key(); let key = e.key();
let convert = |char: char| { let convert = |char: char| Some(char.into());
let mut buffer = [0; 6];
Some(SharedString::from(char.encode_utf8(&mut buffer) as &str))
};
macro_rules! check_non_printable_code { macro_rules! check_non_printable_code {
($($char:literal # $name:ident # $($_qt:ident)|* # $($_winit:ident)|* ;)*) => { ($($char:literal # $name:ident # $($_qt:ident)|* # $($_winit:ident)|* ;)*) => {
@ -243,12 +238,3 @@ fn event_text(e: &web_sys::KeyboardEvent) -> Option<SharedString> {
None None
} }
} }
fn modifiers(e: &web_sys::KeyboardEvent) -> KeyboardModifiers {
KeyboardModifiers {
alt: e.alt_key(),
control: e.ctrl_key(),
meta: e.meta_key(),
shift: e.shift_key(),
}
}

View file

@ -18,6 +18,20 @@ macro_rules! for_each_special_keys {
'\u{0019}' # Backtab # Qt_Key_Key_Backtab # ; '\u{0019}' # Backtab # Qt_Key_Key_Backtab # ;
'\u{007f}' # Delete # Qt_Key_Key_Delete # Delete ; '\u{007f}' # Delete # Qt_Key_Key_Delete # Delete ;
// The modifier key codes comes from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode.
'\u{0010}' # Shift # Qt_Key_Key_Shift # LShift ;
'\u{0011}' # Control # Qt_Key_Key_Meta # LControl ;
'\u{0012}' # Alt # Qt_Key_Key_Alt # LAlt ;
'\u{0013}' # AltGr # Qt_Key_Key_AltGr # RAlt ;
'\u{0014}' # CapsLock # Qt_Key_Key_CapsLock # ;
'\u{0015}' # ShiftR # # RShift ;
'\u{0016}' # ControlR # # RControl ;
// meta defines the macos command key (check DOM_VK_META on https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode)
'\u{00E0}' # Meta # Qt_Key_Key_Control # LWin ;
'\u{00E1}' # MetaR # # RWin ;
'\u{F700}' # UpArrow # Qt_Key_Key_Up # Up ; '\u{F700}' # UpArrow # Qt_Key_Key_Up # Up ;
'\u{F701}' # DownArrow # Qt_Key_Key_Down # Down ; '\u{F701}' # DownArrow # Qt_Key_Key_Down # Down ;
'\u{F702}' # LeftArrow # Qt_Key_Key_Left # Left ; '\u{F702}' # LeftArrow # Qt_Key_Key_Left # Left ;

View file

@ -89,6 +89,7 @@ pub enum BuiltinNamespace {
Colors, Colors,
Math, Math,
Keys, Keys,
Key,
SlintInternal, SlintInternal,
} }
@ -102,6 +103,7 @@ impl LookupResult {
pub fn deprecated(&self) -> Option<&str> { pub fn deprecated(&self) -> Option<&str> {
match self { match self {
Self::Expression { deprecated: Some(x), .. } => Some(x.as_str()), Self::Expression { deprecated: Some(x), .. } => Some(x.as_str()),
Self::Namespace(BuiltinNamespace::Keys) => Some("Key"),
_ => None, _ => None,
} }
} }
@ -152,6 +154,7 @@ impl LookupObject for LookupResult {
} }
LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.for_each_entry(ctx, f), LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.for_each_entry(ctx, f),
LookupResult::Namespace(BuiltinNamespace::Keys) => KeysLookup.for_each_entry(ctx, f), LookupResult::Namespace(BuiltinNamespace::Keys) => KeysLookup.for_each_entry(ctx, f),
LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.for_each_entry(ctx, f),
LookupResult::Namespace(BuiltinNamespace::SlintInternal) => { LookupResult::Namespace(BuiltinNamespace::SlintInternal) => {
SlintInternal.for_each_entry(ctx, f) SlintInternal.for_each_entry(ctx, f)
} }
@ -167,6 +170,7 @@ impl LookupObject for LookupResult {
} }
LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.lookup(ctx, name), LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.lookup(ctx, name),
LookupResult::Namespace(BuiltinNamespace::Keys) => KeysLookup.lookup(ctx, name), LookupResult::Namespace(BuiltinNamespace::Keys) => KeysLookup.lookup(ctx, name),
LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.lookup(ctx, name),
LookupResult::Namespace(BuiltinNamespace::SlintInternal) => { LookupResult::Namespace(BuiltinNamespace::SlintInternal) => {
SlintInternal.lookup(ctx, name) SlintInternal.lookup(ctx, name)
} }
@ -701,6 +705,7 @@ impl LookupObject for BuiltinNamespaceLookup {
None.or_else(|| f("Colors", LookupResult::Namespace(BuiltinNamespace::Colors))) None.or_else(|| f("Colors", LookupResult::Namespace(BuiltinNamespace::Colors)))
.or_else(|| f("Math", LookupResult::Namespace(BuiltinNamespace::Math))) .or_else(|| f("Math", LookupResult::Namespace(BuiltinNamespace::Math)))
.or_else(|| f("Keys", LookupResult::Namespace(BuiltinNamespace::Keys))) .or_else(|| f("Keys", LookupResult::Namespace(BuiltinNamespace::Keys)))
.or_else(|| f("Key", LookupResult::Namespace(BuiltinNamespace::Key)))
.or_else(|| { .or_else(|| {
f("SlintInternal", LookupResult::Namespace(BuiltinNamespace::SlintInternal)) f("SlintInternal", LookupResult::Namespace(BuiltinNamespace::SlintInternal))
}) })

View file

@ -23,4 +23,15 @@ Xxx := Rectangle {
color = #000000; color = #000000;
// ^warning{The property 'color' has been deprecated. Please use 'background' instead} // ^warning{The property 'color' has been deprecated. Please use 'background' instead}
} }
Foo := FocusScope {
key-pressed(event) => {
if (event.text == Keys.Escape) {
// ^warning{The property 'Keys' has been deprecated. Please use 'Key' instead}
debug("Esc key was pressed")
}
accept
}
}
} }

View file

@ -202,10 +202,10 @@ export SpinBox := FocusScope {
} }
key-pressed(event) => { key-pressed(event) => {
if (enabled && event.text == Keys.UpArrow && value < maximum) { if (enabled && event.text == Key.UpArrow && value < maximum) {
value += 1; value += 1;
accept accept
} else if (enabled && event.text == Keys.DownArrow && value > minimum) { } else if (enabled && event.text == Key.DownArrow && value > minimum) {
value -= 1; value -= 1;
accept accept
} else { } else {
@ -291,10 +291,10 @@ export Slider := Rectangle {
width: 0px; width: 0px;
key-pressed(event) => { key-pressed(event) => {
if (enabled && event.text == Keys.RightArrow) { if (enabled && event.text == Key.RightArrow) {
value = Math.min(value + 1, maximum); value = Math.min(value + 1, maximum);
accept accept
} else if (enabled && event.text == Keys.LeftArrow) { } else if (enabled && event.text == Key.LeftArrow) {
value = Math.max(value - 1, minimum); value = Math.max(value - 1, minimum);
accept accept
} else { } else {
@ -431,11 +431,11 @@ export TabBarImpl := Rectangle {
current = current-focused; current = current-focused;
return accept; return accept;
} }
if (event.text == Keys.LeftArrow) { if (event.text == Key.LeftArrow) {
focused-tab = Math.max(focused-tab - 1, 0); focused-tab = Math.max(focused-tab - 1, 0);
return accept; return accept;
} }
if (event.text == Keys.RightArrow) { if (event.text == Key.RightArrow) {
focused-tab = Math.min(focused-tab + 1, num-tabs - 1); focused-tab = Math.min(focused-tab + 1, num-tabs - 1);
return accept; return accept;
} }
@ -516,10 +516,10 @@ export StandardListView := ListView {
} }
FocusScope { FocusScope {
key-pressed(event) => { key-pressed(event) => {
if (event.text == Keys.UpArrow && current-item > 0) { if (event.text == Key.UpArrow && current-item > 0) {
current-item -= 1; current-item -= 1;
return accept; return accept;
} else if (event.text == Keys.DownArrow && current-item + 1 < model.length) { } else if (event.text == Key.DownArrow && current-item + 1 < model.length) {
current-item += 1; current-item += 1;
return accept; return accept;
} }
@ -539,16 +539,16 @@ export ComboBox := FocusScope {
accessible-value <=> current-value; accessible-value <=> current-value;
key-pressed(event) => { key-pressed(event) => {
if (event.text == Keys.UpArrow) { if (event.text == Key.UpArrow) {
current-index = Math.max(current-index - 1, 0); current-index = Math.max(current-index - 1, 0);
current-value = model[current-index]; current-value = model[current-index];
return accept; return accept;
} else if (event.text == Keys.DownArrow) { } else if (event.text == Key.DownArrow) {
current-index = Math.min(current-index + 1, model.length - 1); current-index = Math.min(current-index + 1, model.length - 1);
current-value = model[current-index]; current-value = model[current-index];
return accept; return accept;
// PopupWindow can not get hidden again at this time, so do not allow to pop that up. // PopupWindow can not get hidden again at this time, so do not allow to pop that up.
// } else if (event.text == Keys.Return) { // } else if (event.text == Key.Return) {
// touch.clicked() // touch.clicked()
// return accept; // return accept;
} }

View file

@ -88,11 +88,11 @@ export ComboBox := FocusScope {
} }
key-pressed(event) => { key-pressed(event) => {
if (event.text == Keys.UpArrow) { if (event.text == Key.UpArrow) {
current-index = Math.max(current-index - 1, 0); current-index = Math.max(current-index - 1, 0);
current-value = model[current-index]; current-value = model[current-index];
return accept; return accept;
} else if (event.text == Keys.DownArrow) { } else if (event.text == Key.DownArrow) {
current-index = Math.min(current-index + 1, model.length - 1); current-index = Math.min(current-index + 1, model.length - 1);
current-value = model[current-index]; current-value = model[current-index];
return accept; return accept;

View file

@ -23,10 +23,10 @@ export StandardListView := ListView {
FocusScope { FocusScope {
key-pressed(event) => { key-pressed(event) => {
if (event.text == Keys.UpArrow && current-item > 0) { if (event.text == Key.UpArrow && current-item > 0) {
current-item -= 1; current-item -= 1;
return accept; return accept;
} else if (event.text == Keys.DownArrow && current-item + 1 < model.length) { } else if (event.text == Key.DownArrow && current-item + 1 < model.length) {
current-item += 1; current-item += 1;
return accept; return accept;
} }

View file

@ -86,10 +86,10 @@ export Slider := Rectangle {
width: 0px; width: 0px;
key-pressed(event) => { key-pressed(event) => {
if (enabled && event.text == Keys.RightArrow) { if (enabled && event.text == Key.RightArrow) {
value = Math.min(value + 1, maximum); value = Math.min(value + 1, maximum);
accept accept
} else if (enabled && event.text == Keys.LeftArrow) { } else if (enabled && event.text == Key.LeftArrow) {
value = Math.max(value - 1, minimum); value = Math.max(value - 1, minimum);
accept accept
} else { } else {

View file

@ -149,10 +149,10 @@ export SpinBox := FocusScope {
} }
key-pressed(event) => { key-pressed(event) => {
if (enabled && event.text == Keys.UpArrow && value < maximum) { if (enabled && event.text == Key.UpArrow && value < maximum) {
value += 1; value += 1;
accept accept
} else if (enabled && event.text == Keys.DownArrow && value > minimum) { } else if (enabled && event.text == Key.DownArrow && value > minimum) {
value -= 1; value -= 1;
accept accept
} else { } else {

View file

@ -112,11 +112,11 @@ export TabBarImpl := Rectangle {
current = current-focused; current = current-focused;
return accept; return accept;
} }
if (event.text == Keys.LeftArrow) { if (event.text == Key.LeftArrow) {
focused-tab = Math.max(focused-tab - 1, 0); focused-tab = Math.max(focused-tab - 1, 0);
return accept; return accept;
} }
if (event.text == Keys.RightArrow) { if (event.text == Key.RightArrow) {
focused-tab = Math.min(focused-tab + 1, num-tabs - 1); focused-tab = Math.min(focused-tab + 1, num-tabs - 1);
return accept; return accept;
} }

View file

@ -58,10 +58,10 @@ export Slider := NativeSlider {
width: 0px; width: 0px;
key-pressed(event) => { key-pressed(event) => {
if (root.enabled && event.text == Keys.RightArrow) { if (root.enabled && event.text == Key.RightArrow) {
root.value = Math.min(root.value + 1, root.maximum); root.value = Math.min(root.value + 1, root.maximum);
accept accept
} else if (root.enabled && event.text == Keys.LeftArrow) { } else if (root.enabled && event.text == Key.LeftArrow) {
root.value = Math.max(root.value - 1, root.minimum); root.value = Math.max(root.value - 1, root.minimum);
accept accept
} else { } else {
@ -127,10 +127,10 @@ export StandardListView := ListView {
} }
FocusScope { FocusScope {
key-pressed(event) => { key-pressed(event) => {
if (event.text == Keys.UpArrow && current-item > 0) { if (event.text == Key.UpArrow && current-item > 0) {
current-item -= 1; current-item -= 1;
accept accept
} else if (event.text == Keys.DownArrow && current-item + 1 < model.length) { } else if (event.text == Key.DownArrow && current-item + 1 < model.length) {
current-item += 1; current-item += 1;
accept accept
} else { } else {
@ -180,16 +180,16 @@ export ComboBox := NativeComboBox {
fs := FocusScope { fs := FocusScope {
key-pressed(event) => { key-pressed(event) => {
if (event.text == Keys.UpArrow) { if (event.text == Key.UpArrow) {
root.current-index = Math.max(root.current-index - 1, 0); root.current-index = Math.max(root.current-index - 1, 0);
root.current-value = model[root.current-index]; root.current-value = model[root.current-index];
return accept; return accept;
} else if (event.text == Keys.DownArrow) { } else if (event.text == Key.DownArrow) {
root.current-index = Math.min(root.current-index + 1, root.model.length - 1); root.current-index = Math.min(root.current-index + 1, root.model.length - 1);
root.current-value = model[root.current-index]; root.current-value = model[root.current-index];
return accept; return accept;
// PopupWindow can not get hidden again at this time, so do not allow to pop that up. // PopupWindow can not get hidden again at this time, so do not allow to pop that up.
// } else if (event.text == Keys.Return) { // } else if (event.text == Key.Return) {
// touch.clicked() // touch.clicked()
// return accept; // return accept;
} }
@ -223,11 +223,11 @@ export TabBarImpl := Rectangle {
fs := FocusScope { fs := FocusScope {
width: 0px; // Do not react on clicks width: 0px; // Do not react on clicks
key-pressed(event) => { key-pressed(event) => {
if (event.text == Keys.LeftArrow) { if (event.text == Key.LeftArrow) {
root.current = Math.max(root.current - 1, 0); root.current = Math.max(root.current - 1, 0);
return accept; return accept;
} }
if (event.text == Keys.RightArrow) { if (event.text == Key.RightArrow) {
root.current = Math.min(root.current + 1, num-tabs - 1); root.current = Math.min(root.current + 1, num-tabs - 1);
return accept; return accept;
} }

View file

@ -10,8 +10,12 @@ This module contains types that are public and re-exported in the slint-rs as we
use alloc::boxed::Box; use alloc::boxed::Box;
use crate::component::ComponentVTable; use crate::component::ComponentVTable;
use crate::input::{KeyEventType, KeyInputEvent, MouseEvent};
use crate::window::{WindowAdapter, WindowInner}; use crate::window::{WindowAdapter, WindowInner};
// reexport key enum to the public api
pub use crate::input::key_codes::Key;
/// A position represented in the coordinate space of logical pixels. That is the space before applying /// A position represented in the coordinate space of logical pixels. That is the space before applying
/// a display device specific scale factor. /// a display device specific scale factor.
#[derive(Debug, Default, Copy, Clone, PartialEq)] #[derive(Debug, Default, Copy, Clone, PartialEq)]
@ -421,7 +425,44 @@ impl Window {
/// Any position fields in the event must be in the logical pixel coordinate system relative to /// Any position fields in the event must be in the logical pixel coordinate system relative to
/// the top left corner of the window. /// the top left corner of the window.
pub fn dispatch_event(&self, event: WindowEvent) { pub fn dispatch_event(&self, event: WindowEvent) {
self.0.process_mouse_input(event.into()) match event {
WindowEvent::PointerPressed { position, button } => {
self.0.process_mouse_input(MouseEvent::Pressed {
position: position.to_euclid().cast(),
button,
});
}
WindowEvent::PointerReleased { position, button } => {
self.0.process_mouse_input(MouseEvent::Released {
position: position.to_euclid().cast(),
button,
});
}
WindowEvent::PointerMoved { position } => {
self.0.process_mouse_input(MouseEvent::Moved {
position: position.to_euclid().cast(),
});
}
WindowEvent::PointerScrolled { position, delta_x, delta_y } => {
self.0.process_mouse_input(MouseEvent::Wheel {
position: position.to_euclid().cast(),
delta_x,
delta_y,
});
}
WindowEvent::PointerExited => self.0.process_mouse_input(MouseEvent::Exit),
WindowEvent::KeyPressed { text } => self.0.process_key_input(KeyInputEvent {
text: SharedString::from(text),
event_type: KeyEventType::KeyPressed,
..Default::default()
}),
WindowEvent::KeyReleased { text } => self.0.process_key_input(KeyInputEvent {
text: SharedString::from(text),
event_type: KeyEventType::KeyReleased,
..Default::default()
}),
}
} }
/// Returns true if there is an animation currently active on any property in the Window; false otherwise. /// Returns true if there is an animation currently active on any property in the Window; false otherwise.
@ -438,6 +479,7 @@ impl Window {
} }
pub use crate::input::PointerEventButton; pub use crate::input::PointerEventButton;
pub use crate::SharedString;
/// A event that describes user input. /// A event that describes user input.
/// ///
@ -449,7 +491,7 @@ pub use crate::input::PointerEventButton;
/// ///
/// All position fields are in logical window coordinates. /// All position fields are in logical window coordinates.
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
#[non_exhaustive] #[non_exhaustive]
pub enum WindowEvent { pub enum WindowEvent {
/// A pointer was pressed. /// A pointer was pressed.
@ -476,6 +518,30 @@ pub enum WindowEvent {
}, },
/// The pointer exited the window. /// The pointer exited the window.
PointerExited, PointerExited,
/// A key was pressed.
KeyPressed {
// FIXME: use SharedString instead of char (breaking change)
/// The unicode representation of the key pressed.
///
/// # Example
/// A specific key can be mapped to a unicode by using the `Key` enum
/// ```rust
/// let _ = slint::WindowEvent::KeyPressed { text: slint::Key::Shift.into() };
/// ```
text: char,
},
/// A key was pressed.
KeyReleased {
// FIXME: use SharedString instead of char (breaking change)
/// The unicode representation of the key released.
/// ///
/// # Example
/// A specific key can be mapped to a unicode by using the `Key` enum
/// ```rust
/// let _ = slint::WindowEvent::KeyReleased { text: slint::Key::Shift.into() };
/// ```
text: char,
},
} }
impl WindowEvent { impl WindowEvent {
@ -486,7 +552,7 @@ impl WindowEvent {
WindowEvent::PointerReleased { position, .. } => Some(*position), WindowEvent::PointerReleased { position, .. } => Some(*position),
WindowEvent::PointerMoved { position } => Some(*position), WindowEvent::PointerMoved { position } => Some(*position),
WindowEvent::PointerScrolled { position, .. } => Some(*position), WindowEvent::PointerScrolled { position, .. } => Some(*position),
WindowEvent::PointerExited => None, _ => None,
} }
} }
} }

View file

@ -68,26 +68,6 @@ impl MouseEvent {
} }
} }
impl From<crate::api::WindowEvent> for MouseEvent {
fn from(event: crate::api::WindowEvent) -> Self {
match event {
crate::api::WindowEvent::PointerPressed { position, button } => {
MouseEvent::Pressed { position: position.to_euclid().cast(), button }
}
crate::api::WindowEvent::PointerReleased { position, button } => {
MouseEvent::Released { position: position.to_euclid().cast(), button }
}
crate::api::WindowEvent::PointerMoved { position } => {
MouseEvent::Moved { position: position.to_euclid().cast() }
}
crate::api::WindowEvent::PointerScrolled { position, delta_x, delta_y } => {
MouseEvent::Wheel { position: position.to_euclid().cast(), delta_x, delta_y }
}
crate::api::WindowEvent::PointerExited => MouseEvent::Exit,
}
}
}
/// This value is returned by the `input_event` function of an Item /// This value is returned by the `input_event` function of an Item
/// to notify the run-time about how the event was handled and /// to notify the run-time about how the event was handled and
/// what the next steps are. /// what the next steps are.
@ -145,18 +125,87 @@ impl Default for InputEventFilterResult {
} }
} }
/// This module contains the constant character code used to represent the keys /// This module contains the constant character code used to represent the keys.
#[allow(missing_docs, non_upper_case_globals)] #[allow(missing_docs, non_upper_case_globals)]
pub mod key_codes { pub mod key_codes {
macro_rules! declare_consts_for_special_keys { macro_rules! declare_consts_for_special_keys {
($($char:literal # $name:ident # $($_qt:ident)|* # $($_winit:ident)|* ;)*) => { ($($char:literal # $name:ident # $($_qt:ident)|* # $($_winit:ident)|* ;)*) => {
$(pub const $name : char = $char;)* $(pub const $name : char = $char;)*
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone, PartialEq)]
#[non_exhaustive]
#[repr(C)]
/// The `Key` enum is used to map a specific key by name e.g. `Key::Control` to an
/// internal used unicode representation. The enum is convertible to [`std::char`] and [`slint::SharedString`](`crate::SharedString`).
/// Use this with [`slint::WindowEvent`](`crate::api::WindowEvent`) to supply key events to Slint's platform abstraction.
///
/// ```
/// let slint_key_code: char = slint::Key::Tab.into();
/// assert_eq!(slint_key_code, '\t')
/// ```
pub enum Key {
$($name,)*
}
impl From<Key> for char {
fn from(k: Key) -> Self {
match k {
$(Key::$name => $name,)*
}
}
}
impl From<Key> for crate::SharedString {
fn from(k: Key) -> Self {
char::from(k).into()
}
}
}; };
} }
i_slint_common::for_each_special_keys!(declare_consts_for_special_keys); i_slint_common::for_each_special_keys!(declare_consts_for_special_keys);
} }
/// Internal struct to maintain the pressed/released state of the keys that
/// map to keyboard modifiers.
#[derive(Clone, Copy, Default, Debug)]
pub(crate) struct InternalKeyboardModifierState {
left_alt: bool,
right_alt: bool,
left_control: bool,
right_control: bool,
left_meta: bool,
right_meta: bool,
left_shift: bool,
right_shift: bool,
}
impl InternalKeyboardModifierState {
/// Updates a flag of the modifiers if the key of the given text is pressed.
/// Returns an updated modifier if detected; None otherwise;
pub(crate) fn state_update(mut self, pressed: bool, text: &SharedString) -> Option<Self> {
if let Some(key_code) = text.chars().next() {
match key_code {
key_codes::Alt => self.left_alt = pressed,
key_codes::Control => self.left_control = pressed,
key_codes::ControlR => self.right_control = pressed,
key_codes::Shift => self.left_shift = pressed,
key_codes::ShiftR => self.right_shift = pressed,
key_codes::Meta => self.left_meta = pressed,
key_codes::MetaR => self.right_meta = pressed,
_ => return None,
};
// Encoded keyboard modifiers must appear as individual key events. This could
// be relaxed by implementing a string split, but right now WindowEvent::KeyPressed
// holds only a single char.
debug_assert_eq!(key_code.len_utf8(), text.len());
}
Some(self)
}
}
/// KeyboardModifier provides booleans to indicate possible modifier keys /// KeyboardModifier provides booleans to indicate possible modifier keys
/// on a keyboard, such as Shift, Control, etc. /// on a keyboard, such as Shift, Control, etc.
/// ///
@ -170,12 +219,23 @@ pub struct KeyboardModifiers {
pub alt: bool, pub alt: bool,
/// Indicates the control key on a keyboard. /// Indicates the control key on a keyboard.
pub control: bool, pub control: bool,
/// Indicates the logo key on macOS and the windows key on Windows. /// Indicates the command key on macos.
pub meta: bool, pub meta: bool,
/// Indicates the shift key on a keyboard. /// Indicates the shift key on a keyboard.
pub shift: bool, pub shift: bool,
} }
impl From<InternalKeyboardModifierState> for KeyboardModifiers {
fn from(internal_state: InternalKeyboardModifierState) -> Self {
Self {
alt: internal_state.left_alt | internal_state.right_alt,
control: internal_state.left_control | internal_state.right_control,
meta: internal_state.left_meta | internal_state.right_meta,
shift: internal_state.left_shift | internal_state.right_shift,
}
}
}
/// This enum defines the different kinds of key events that can happen. /// This enum defines the different kinds of key events that can happen.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(C)] #[repr(C)]
@ -200,9 +260,29 @@ impl Default for KeyEventType {
/// Represents a key event sent by the windowing system. /// Represents a key event sent by the windowing system.
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
#[repr(C)] #[repr(C)]
pub struct KeyInputEvent {
/// The unicode representation of the key pressed.
pub text: SharedString,
// note: this field is not exported in the .slint in the KeyEvent builtin struct
/// Indicates whether the key was pressed or released
pub event_type: KeyEventType,
/// If the event type is KeyEventType::UpdateComposition, then this field specifies
/// the start of the selection as byte offsets within the preedit text.
pub preedit_selection_start: usize,
/// If the event type is KeyEventType::UpdateComposition, then this field specifies
/// the end of the selection as byte offsets within the preedit text.
pub preedit_selection_end: usize,
}
/// Represents a key event.
#[derive(Debug, Clone, PartialEq, Default)]
#[repr(C)]
pub struct KeyEvent { pub struct KeyEvent {
/// The keyboard modifiers active at the time of the key press event. /// The keyboard modifiers active at the time of the key press event.
pub modifiers: KeyboardModifiers, pub modifiers: KeyboardModifiers,
/// The unicode representation of the key pressed. /// The unicode representation of the key pressed.
pub text: SharedString, pub text: SharedString,

View file

@ -5,7 +5,7 @@
#![warn(missing_docs)] #![warn(missing_docs)]
#![allow(unsafe_code)] #![allow(unsafe_code)]
use crate::input::{KeyEvent, KeyEventType, KeyboardModifiers, MouseEvent}; use crate::input::{key_codes::Key, KeyEventType, KeyInputEvent, MouseEvent};
use crate::window::WindowInner; use crate::window::WindowInner;
use crate::Coord; use crate::Coord;
use crate::SharedString; use crate::SharedString;
@ -57,33 +57,54 @@ pub extern "C" fn slint_send_mouse_click(
); );
} }
/// Simulate a character input event (pressed or released).
#[no_mangle]
pub extern "C" fn slint_send_keyboard_char(
string: &crate::SharedString,
pressed: bool,
window_adapter: &crate::window::WindowAdapterRc,
) {
WindowInner::from_pub(window_adapter.window()).process_key_input(KeyInputEvent {
event_type: if pressed { KeyEventType::KeyPressed } else { KeyEventType::KeyReleased },
text: string.clone(),
..Default::default()
});
}
/// Simulate a character input event. /// Simulate a character input event.
#[no_mangle] #[no_mangle]
pub extern "C" fn send_keyboard_string_sequence( pub extern "C" fn send_keyboard_string_sequence(
sequence: &crate::SharedString, sequence: &crate::SharedString,
modifiers: KeyboardModifiers,
window_adapter: &crate::window::WindowAdapterRc, window_adapter: &crate::window::WindowAdapterRc,
) { ) {
for ch in sequence.chars() { for ch in sequence.chars() {
let mut modifiers = modifiers;
if ch.is_ascii_uppercase() { if ch.is_ascii_uppercase() {
modifiers.shift = true; WindowInner::from_pub(window_adapter.window()).process_key_input(KeyInputEvent {
event_type: KeyEventType::KeyPressed,
text: Key::Shift.into(),
..Default::default()
});
} }
let mut buffer = [0; 6]; let text = SharedString::from(ch);
let text = SharedString::from(ch.encode_utf8(&mut buffer) as &str);
WindowInner::from_pub(window_adapter.window()).process_key_input(&KeyEvent { WindowInner::from_pub(window_adapter.window()).process_key_input(KeyInputEvent {
event_type: KeyEventType::KeyPressed, event_type: KeyEventType::KeyPressed,
text: text.clone(), text: text.clone(),
modifiers,
..Default::default() ..Default::default()
}); });
WindowInner::from_pub(window_adapter.window()).process_key_input(&KeyEvent { WindowInner::from_pub(window_adapter.window()).process_key_input(KeyInputEvent {
event_type: KeyEventType::KeyReleased, event_type: KeyEventType::KeyReleased,
text, text,
modifiers,
..Default::default() ..Default::default()
}); });
if ch.is_ascii_uppercase() {
WindowInner::from_pub(window_adapter.window()).process_key_input(KeyInputEvent {
event_type: KeyEventType::KeyReleased,
text: Key::Shift.into(),
..Default::default()
});
}
} }
} }

View file

@ -12,7 +12,8 @@ use crate::api::{
use crate::component::{ComponentRc, ComponentRef, ComponentVTable, ComponentWeak}; use crate::component::{ComponentRc, ComponentRef, ComponentVTable, ComponentWeak};
use crate::graphics::Point; use crate::graphics::Point;
use crate::input::{ use crate::input::{
key_codes, KeyEvent, KeyEventType, MouseEvent, MouseInputState, TextCursorBlinker, key_codes, InternalKeyboardModifierState, KeyEvent, KeyEventType, KeyInputEvent,
KeyboardModifiers, MouseEvent, MouseInputState, TextCursorBlinker,
}; };
use crate::item_tree::ItemRc; use crate::item_tree::ItemRc;
use crate::items::{ItemRef, MouseCursor}; use crate::items::{ItemRef, MouseCursor};
@ -35,6 +36,17 @@ fn previous_focus_item(item: ItemRc) -> ItemRc {
item.previous_focus_item() item.previous_focus_item()
} }
/// Transforms a `KeyInputEvent` into an `KeyEvent` with the given `KeyboardModifiers`.
fn input_as_key_event(input: KeyInputEvent, modifiers: KeyboardModifiers) -> KeyEvent {
KeyEvent {
modifiers,
text: input.text,
event_type: input.event_type,
preedit_selection_start: input.preedit_selection_start,
preedit_selection_end: input.preedit_selection_end,
}
}
/// This trait represents the adaptation layer between the [`Window`] API, and the /// This trait represents the adaptation layer between the [`Window`] API, and the
/// internal type from the backend that provides functionality such as device-independent pixels, /// internal type from the backend that provides functionality such as device-independent pixels,
/// window resizing, and other typically windowing system related tasks. /// window resizing, and other typically windowing system related tasks.
@ -202,6 +214,7 @@ pub struct WindowInner {
window_adapter_weak: Weak<dyn WindowAdapter>, window_adapter_weak: Weak<dyn WindowAdapter>,
component: RefCell<ComponentWeak>, component: RefCell<ComponentWeak>,
mouse_input_state: Cell<MouseInputState>, mouse_input_state: Cell<MouseInputState>,
modifiers: Cell<InternalKeyboardModifierState>,
redraw_tracker: Pin<Box<PropertyTracker<WindowRedrawTracker>>>, redraw_tracker: Pin<Box<PropertyTracker<WindowRedrawTracker>>>,
/// Gets dirty when the layout restrictions, or some other property of the windows change /// Gets dirty when the layout restrictions, or some other property of the windows change
window_properties_tracker: Pin<Box<PropertyTracker<WindowPropertiesTracker>>>, window_properties_tracker: Pin<Box<PropertyTracker<WindowPropertiesTracker>>>,
@ -251,6 +264,7 @@ impl WindowInner {
window_adapter_weak, window_adapter_weak,
component: Default::default(), component: Default::default(),
mouse_input_state: Default::default(), mouse_input_state: Default::default(),
modifiers: Default::default(),
redraw_tracker: Box::pin(redraw_tracker), redraw_tracker: Box::pin(redraw_tracker),
window_properties_tracker: Box::pin(window_properties_tracker), window_properties_tracker: Box::pin(window_properties_tracker),
focus_item: Default::default(), focus_item: Default::default(),
@ -271,6 +285,7 @@ impl WindowInner {
self.close_popup(); self.close_popup();
self.focus_item.replace(Default::default()); self.focus_item.replace(Default::default());
self.mouse_input_state.replace(Default::default()); self.mouse_input_state.replace(Default::default());
self.modifiers.replace(Default::default());
self.component.replace(ComponentRc::downgrade(component)); self.component.replace(ComponentRc::downgrade(component));
self.window_properties_tracker.set_dirty(); // component changed, layout constraints for sure must be re-calculated self.window_properties_tracker.set_dirty(); // component changed, layout constraints for sure must be re-calculated
let window_adapter = self.window_adapter(); let window_adapter = self.window_adapter();
@ -370,7 +385,18 @@ impl WindowInner {
/// Arguments: /// Arguments:
/// * `event`: The key event received by the windowing system. /// * `event`: The key event received by the windowing system.
/// * `component`: The Slint compiled component that provides the tree of items. /// * `component`: The Slint compiled component that provides the tree of items.
pub fn process_key_input(&self, event: &KeyEvent) { pub fn process_key_input(&self, event: KeyInputEvent) {
if let Some(updated_modifier) = self
.modifiers
.get()
.state_update(event.event_type == KeyEventType::KeyPressed, &event.text)
{
// Updates the key modifiers depending on the key code and pressed state.
self.modifiers.set(updated_modifier);
}
let event = input_as_key_event(event, self.modifiers.get().into());
let mut item = self.focus_item.borrow().clone().upgrade(); let mut item = self.focus_item.borrow().clone().upgrade();
while let Some(focus_item) = item { while let Some(focus_item) = item {
if !focus_item.is_visible() { if !focus_item.is_visible() {
@ -378,7 +404,7 @@ impl WindowInner {
self.take_focus_item(); self.take_focus_item();
} else { } else {
if focus_item.borrow().as_ref().key_event( if focus_item.borrow().as_ref().key_event(
event, &event,
&self.window_adapter(), &self.window_adapter(),
&focus_item, &focus_item,
) == crate::input::KeyEventResult::EventAccepted ) == crate::input::KeyEventResult::EventAccepted
@ -390,9 +416,13 @@ impl WindowInner {
} }
// Make Tab/Backtab handle keyboard focus // Make Tab/Backtab handle keyboard focus
if event.text.starts_with(key_codes::Tab) && event.event_type == KeyEventType::KeyPressed { if event.text.starts_with(key_codes::Tab)
&& !event.modifiers.shift
&& event.event_type == KeyEventType::KeyPressed
{
self.focus_next_item(); self.focus_next_item();
} else if event.text.starts_with(key_codes::Backtab) } else if (event.text.starts_with(key_codes::Backtab)
|| (event.text.starts_with(key_codes::Tab) && event.modifiers.shift))
&& event.event_type == KeyEventType::KeyPressed && event.event_type == KeyEventType::KeyPressed
{ {
self.focus_previous_item(); self.focus_previous_item();

View file

@ -1108,6 +1108,18 @@ pub mod testing {
&WindowInner::from_pub(comp.window()).window_adapter(), &WindowInner::from_pub(comp.window()).window_adapter(),
); );
} }
/// Wrapper around [`i_slint_core::tests::slint_send_keyboard_char`]
pub fn send_keyboard_char(
comp: &super::ComponentInstance,
string: i_slint_core::SharedString,
pressed: bool,
) {
i_slint_core::tests::slint_send_keyboard_char(
&string,
pressed,
&WindowInner::from_pub(comp.window()).window_adapter(),
);
}
/// Wrapper around [`i_slint_core::tests::send_keyboard_string_sequence`] /// Wrapper around [`i_slint_core::tests::send_keyboard_string_sequence`]
pub fn send_keyboard_string_sequence( pub fn send_keyboard_string_sequence(
comp: &super::ComponentInstance, comp: &super::ComponentInstance,
@ -1115,7 +1127,6 @@ pub mod testing {
) { ) {
i_slint_core::tests::send_keyboard_string_sequence( i_slint_core::tests::send_keyboard_string_sequence(
&string, &string,
Default::default(),
&WindowInner::from_pub(comp.window()).window_adapter(), &WindowInner::from_pub(comp.window()).window_adapter(),
); );
} }

View file

@ -7,10 +7,10 @@ W := Window {
field := FocusScope { field := FocusScope {
vertical_stretch: 1; vertical_stretch: 1;
key-pressed(event) => { key-pressed(event) => {
if (event.text == Keys.F1) { if (event.text == Key.F1) {
debug("F1"); debug("F1");
} }
if (event.text == Keys.PageUp) { if (event.text == Key.PageUp) {
debug("PageUp"); debug("PageUp");
} }
if (event.modifiers.control) { if (event.modifiers.control) {

View file

@ -13,7 +13,9 @@ TestCase := Rectangle {
FocusScope { FocusScope {
width: 75%; width: 75%;
key-pressed(event) => { key-pressed(event) => {
if (event.text != Key.Shift && event.text != Key.Control) {
received += event.text; received += event.text;
}
accept accept
} }
@ -39,12 +41,6 @@ TestCase := Rectangle {
} }
/* /*
```rust
let ctrl_modifier = slint::private_unstable_api::re_exports::KeyboardModifiers {
control: true,
..Default::default()
};
let instance = TestCase::new(); let instance = TestCase::new();
assert!(!instance.get_input1_focused()); assert!(!instance.get_input1_focused());
@ -55,7 +51,7 @@ assert_eq!(instance.get_input2_text(), "Hello");
assert_eq!(instance.get_input1_text(), ""); assert_eq!(instance.get_input1_text(), "");
assert_eq!(instance.get_received(), ""); assert_eq!(instance.get_received(), "");
slint_testing::set_current_keyboard_modifiers(&instance, ctrl_modifier); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), true);
slint_testing::send_keyboard_string_sequence(&instance, "ß"); slint_testing::send_keyboard_string_sequence(&instance, "ß");
assert_eq!(instance.get_input2_text(), "Hello"); assert_eq!(instance.get_input2_text(), "Hello");
assert_eq!(instance.get_input1_text(), ""); assert_eq!(instance.get_input1_text(), "");
@ -63,9 +59,6 @@ assert_eq!(instance.get_received(), "ß");
``` ```
```cpp ```cpp
slint::cbindgen_private::KeyboardModifiers ctrl_modifier{};
ctrl_modifier.control = true;
auto handle = TestCase::create(); auto handle = TestCase::create();
const TestCase &instance = *handle; const TestCase &instance = *handle;
@ -77,7 +70,9 @@ assert_eq(instance.get_input2_text(), "Hello");
assert_eq(instance.get_input1_text(), ""); assert_eq(instance.get_input1_text(), "");
assert_eq(instance.get_received(), ""); assert_eq(instance.get_received(), "");
slint_testing::send_keyboard_string_sequence(&instance, "ß", ctrl_modifier); // Control key
slint_testing::send_keyboard_char(&instance, slint::SharedString(u8"\U00000011"), true);
slint_testing::send_keyboard_string_sequence(&instance, "ß");
assert_eq(instance.get_input2_text(), "Hello"); assert_eq(instance.get_input2_text(), "Hello");
assert_eq(instance.get_input1_text(), ""); assert_eq(instance.get_input1_text(), "");
assert_eq(instance.get_received(), "ß"); assert_eq(instance.get_received(), "ß");

View file

@ -21,23 +21,27 @@ const RIGHT_CODE: char = '\u{F703}';
const DEL_CODE: char = '\u{007f}'; const DEL_CODE: char = '\u{007f}';
const BACK_CODE: char = '\u{0008}'; // backspace \b const BACK_CODE: char = '\u{0008}'; // backspace \b
let shift_modifier = slint::private_unstable_api::re_exports::KeyboardModifiers { fn send_move_mod_modifier(instance: &TestCase, pressed: bool) {
shift: true, if cfg!(not(target_os = "macos")) {
..Default::default() slint_testing::send_keyboard_char(instance, slint::private_unstable_api::re_exports::Key::Control.into(), pressed);
}; }
let move_mod_shift_modifier = slint::private_unstable_api::re_exports::KeyboardModifiers { if cfg!(target_os = "macos") {
shift: true, slint_testing::send_keyboard_char(instance, slint::private_unstable_api::re_exports::Key::Alt.into(), pressed);
control: cfg!(not(target_os = "macos")), }
alt: cfg!(target_os = "macos"), }
..Default::default()
};
let move_mod_modifier = slint::private_unstable_api::re_exports::KeyboardModifiers { fn send_move_mod_shift_modifier(instance: &TestCase, pressed: bool) {
control: cfg!(not(target_os = "macos")), slint_testing::send_keyboard_char(instance, slint::private_unstable_api::re_exports::Key::Shift.into(), pressed);
alt: cfg!(target_os = "macos"),
..Default::default() if cfg!(not(target_os = "macos")) {
}; slint_testing::send_keyboard_char(instance, slint::private_unstable_api::re_exports::Key::Control.into(), pressed);
}
if cfg!(target_os = "macos") {
slint_testing::send_keyboard_char(instance, slint::private_unstable_api::re_exports::Key::Alt.into(), pressed);
}
}
let instance = TestCase::new(); let instance = TestCase::new();
slint_testing::send_mouse_click(&instance, 50., 50.); slint_testing::send_mouse_click(&instance, 50., 50.);
@ -47,9 +51,9 @@ slint_testing::send_keyboard_string_sequence(&instance, "Test");
assert_eq!(instance.get_test_text(), "Test"); assert_eq!(instance.get_test_text(), "Test");
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
slint_testing::set_current_keyboard_modifiers(&instance, shift_modifier); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), true);
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), false);
assert!(instance.get_has_selection()); assert!(instance.get_has_selection());
slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
@ -69,9 +73,9 @@ slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
assert_eq!(instance.get_test_cursor_pos(), 0); assert_eq!(instance.get_test_cursor_pos(), 0);
slint_testing::set_current_keyboard_modifiers(&instance, move_mod_shift_modifier); send_move_mod_shift_modifier(&instance, true);
slint_testing::send_keyboard_string_sequence(&instance, &DOWN_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &DOWN_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); send_move_mod_shift_modifier(&instance, false);
assert!(instance.get_has_selection()); assert!(instance.get_has_selection());
assert_eq!(instance.get_test_cursor_pos(), 2); assert_eq!(instance.get_test_cursor_pos(), 2);
assert_eq!(instance.get_test_anchor_pos(), 0); assert_eq!(instance.get_test_anchor_pos(), 0);
@ -79,20 +83,17 @@ assert_eq!(instance.get_test_anchor_pos(), 0);
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
slint_testing::set_current_keyboard_modifiers(&instance, move_mod_shift_modifier); send_move_mod_shift_modifier(&instance, true);
slint_testing::send_keyboard_string_sequence(&instance, &UP_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &UP_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); send_move_mod_shift_modifier(&instance, false);
assert!(instance.get_has_selection()); assert!(instance.get_has_selection());
assert_eq!(instance.get_test_cursor_pos(), 0); assert_eq!(instance.get_test_cursor_pos(), 0);
assert_eq!(instance.get_test_anchor_pos(), 1); assert_eq!(instance.get_test_anchor_pos(), 1);
// Select all and start over // Select all and start over
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers { slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), true);
control: true,
..Default::default()
});
slint_testing::send_keyboard_string_sequence(&instance, &"a"); slint_testing::send_keyboard_string_sequence(&instance, &"a");
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), false);
slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
assert_eq!(instance.get_test_text(), ""); assert_eq!(instance.get_test_text(), "");
@ -108,15 +109,15 @@ assert_eq!(instance.get_test_cursor_pos(), 22);
// Delete word backwards when the cursor is between the 'F' of Fifth and the leading space. // Delete word backwards when the cursor is between the 'F' of Fifth and the leading space.
// -> Delete "Word" // -> Delete "Word"
slint_testing::set_current_keyboard_modifiers(&instance, move_mod_modifier); send_move_mod_modifier(&instance, true);
slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); send_move_mod_modifier(&instance, false);
assert_eq!(instance.get_test_text(), "First Word Third Fifth"); assert_eq!(instance.get_test_text(), "First Word Third Fifth");
// Once more :-) // Once more :-)
slint_testing::set_current_keyboard_modifiers(&instance, move_mod_modifier); send_move_mod_modifier(&instance, true);
slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); send_move_mod_modifier(&instance, false);
assert_eq!(instance.get_test_text(), "First Word Fifth"); assert_eq!(instance.get_test_text(), "First Word Fifth");
// Move cursor between the "d" of "Word" and the trailing space // Move cursor between the "d" of "Word" and the trailing space
@ -128,18 +129,15 @@ slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
// Delete word forwards // Delete word forwards
slint_testing::set_current_keyboard_modifiers(&instance, move_mod_modifier); send_move_mod_modifier(&instance, true);
slint_testing::send_keyboard_string_sequence(&instance, &DEL_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &DEL_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); send_move_mod_modifier(&instance, false);
assert_eq!(instance.get_test_text(), "First Fifth"); assert_eq!(instance.get_test_text(), "First Fifth");
// Select all and start over // Select all and start over
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers { slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), true);
control: true,
..Default::default()
});
slint_testing::send_keyboard_string_sequence(&instance, &"a"); slint_testing::send_keyboard_string_sequence(&instance, &"a");
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), false);
slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
assert_eq!(instance.get_test_text(), ""); assert_eq!(instance.get_test_text(), "");
@ -150,16 +148,16 @@ assert_eq!(instance.get_test_text(), "First Second");
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, shift_modifier); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), true);
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), false);
assert!(instance.get_has_selection()); assert!(instance.get_has_selection());
// When there's an existing selection, always just delete that // When there's an existing selection, always just delete that
slint_testing::set_current_keyboard_modifiers(&instance, move_mod_modifier); send_move_mod_modifier(&instance, true);
slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); send_move_mod_modifier(&instance, false);
assert_eq!(instance.get_test_text(), "First Send"); assert_eq!(instance.get_test_text(), "First Send");
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
@ -168,15 +166,15 @@ slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, shift_modifier); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), true);
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), false);
assert!(instance.get_has_selection()); assert!(instance.get_has_selection());
// When there's an existing selection, always just delete that // When there's an existing selection, always just delete that
slint_testing::set_current_keyboard_modifiers(&instance, move_mod_modifier); send_move_mod_modifier(&instance, true);
slint_testing::send_keyboard_string_sequence(&instance, &DEL_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &DEL_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); send_move_mod_modifier(&instance, false);
assert_eq!(instance.get_test_text(), "Fist Send"); assert_eq!(instance.get_test_text(), "Fist Send");
``` ```
*/ */

View file

@ -17,11 +17,6 @@ TestCase := TextInput {
const LEFT_CODE: char = '\u{F702}'; const LEFT_CODE: char = '\u{F702}';
const BACK_CODE: char = '\u{0008}'; // backspace \b const BACK_CODE: char = '\u{0008}'; // backspace \b
let shift_modifier = slint::private_unstable_api::re_exports::KeyboardModifiers {
shift: true,
..Default::default()
};
let instance = TestCase::new(); let instance = TestCase::new();
slint_testing::send_mouse_click(&instance, 50., 50.); slint_testing::send_mouse_click(&instance, 50., 50.);
assert!(instance.get_input_focused()); assert!(instance.get_input_focused());
@ -31,9 +26,9 @@ assert_eq!(instance.get_test_text(), "e\u{0301}");
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
// Test that selecting the grapheme works // Test that selecting the grapheme works
slint_testing::set_current_keyboard_modifiers(&instance, shift_modifier); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), true);
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), false);
assert!(instance.get_has_selection()); assert!(instance.get_has_selection());
slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());

View file

@ -16,16 +16,6 @@ TestCase := TextInput {
const LEFT_CODE: char = '\u{F702}'; const LEFT_CODE: char = '\u{F702}';
let shift_modifier = slint::private_unstable_api::re_exports::KeyboardModifiers {
shift: true,
..Default::default()
};
let control_modifier = slint::private_unstable_api::re_exports::KeyboardModifiers {
control: true,
..Default::default()
};
let instance = TestCase::new(); let instance = TestCase::new();
slint_testing::send_mouse_click(&instance, 50., 50.); slint_testing::send_mouse_click(&instance, 50., 50.);
assert!(instance.get_input_focused()); assert!(instance.get_input_focused());
@ -34,14 +24,16 @@ slint_testing::send_keyboard_string_sequence(&instance, "Test");
assert_eq!(instance.get_test_text(), "Test"); assert_eq!(instance.get_test_text(), "Test");
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
slint_testing::set_current_keyboard_modifiers(&instance, control_modifier); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), true);
slint_testing::send_keyboard_string_sequence(&instance, "a"); slint_testing::send_keyboard_string_sequence(&instance, "a");
slint_testing::set_current_keyboard_modifiers(&instance, shift_modifier); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), false);
slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), true);
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
slint_testing::set_current_keyboard_modifiers(&instance, control_modifier); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), false);
slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), true);
slint_testing::send_keyboard_string_sequence(&instance, "x"); slint_testing::send_keyboard_string_sequence(&instance, "x");
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), false);
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
assert_eq!(instance.get_test_text(), "st"); assert_eq!(instance.get_test_text(), "st");
assert_eq!(instance.get_test_cursor_pos(), 0); assert_eq!(instance.get_test_cursor_pos(), 0);

View file

@ -0,0 +1,186 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
TestCase := Window {
width: 100phx;
height: 100phx;
ti := FocusScope {
key-pressed(event) => {
debug("pressy");
debug(event.modifiers.shift);
shift_modifier = event.modifiers.shift;
alt_modifier = event.modifiers.alt;
control_modifier = event.modifiers.control;
meta_modifier = event.modifiers.meta;
accept;
}
key-released(event) => {
shift_modifier = event.modifiers.shift;
alt_modifier = event.modifiers.alt;
control_modifier = event.modifiers.control;
meta_modifier = event.modifiers.meta;
accept;
}
}
property <bool> input_focused: ti.has_focus;
property <bool> shift_modifier;
property <bool> alt_modifier;
property <bool> control_modifier;
property <bool> meta_modifier;
}
/*
```rust
const SHIFT_CODE: char = '\u{0010}';
const SHIFTR_CODE: char = '\u{0015}';
const ALT_CODE: char = '\u{0012}';
const ALTGR_CODE: char = '\u{0013}';
const CONTROL_CODE: char = '\u{0011}';
const CONTROLR_CODE: char = '\u{0016}';
const META_CODE: char = '\u{00E0}';
const METAR_CODE: char = '\u{00E1}';
let instance = TestCase::new();
slint_testing::send_mouse_click(&instance, 5., 5.);
assert!(instance.get_input_focused());
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, SHIFT_CODE, true);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), true);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, SHIFTR_CODE, true);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), true);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, SHIFT_CODE, false);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), true);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, SHIFTR_CODE, false);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, ALT_CODE, true);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), true);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, ALTGR_CODE, true);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), true);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
// AltGr does not set the alt modifier
slint_testing::send_keyboard_char(&instance, ALT_CODE, false);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, ALTGR_CODE, false);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, CONTROL_CODE, true);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), true);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, CONTROLR_CODE, true);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), true);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, CONTROL_CODE, false);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), true);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, CONTROLR_CODE, false);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
slint_testing::send_keyboard_char(&instance, META_CODE, true);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), true);
slint_testing::send_keyboard_char(&instance, METAR_CODE, true);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), true);
slint_testing::send_keyboard_char(&instance, META_CODE, false);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), true);
slint_testing::send_keyboard_char(&instance, METAR_CODE, false);
slint_testing::send_keyboard_char(&instance, 'a', true);
slint_testing::send_keyboard_char(&instance, 'a', false);
assert_eq!(instance.get_shift_modifier(), false);
assert_eq!(instance.get_alt_modifier(), false);
assert_eq!(instance.get_control_modifier(), false);
assert_eq!(instance.get_meta_modifier(), false);
```
*/

View file

@ -14,12 +14,6 @@ TestCase := TextInput {
/* /*
```rust ```rust
let control_modifier = slint::private_unstable_api::re_exports::KeyboardModifiers {
control: true,
..Default::default()
};
let instance = TestCase::new(); let instance = TestCase::new();
slint_testing::send_mouse_click(&instance, 50., 50.); slint_testing::send_mouse_click(&instance, 50., 50.);
assert!(instance.get_input_focused()); assert!(instance.get_input_focused());
@ -28,9 +22,9 @@ slint_testing::send_keyboard_string_sequence(&instance, "Test");
assert_eq!(instance.get_test_text(), "Test"); assert_eq!(instance.get_test_text(), "Test");
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
slint_testing::set_current_keyboard_modifiers(&instance, control_modifier); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), true);
slint_testing::send_keyboard_string_sequence(&instance, "a"); slint_testing::send_keyboard_string_sequence(&instance, "a");
slint_testing::set_current_keyboard_modifiers(&instance, slint::private_unstable_api::re_exports::KeyboardModifiers::default()); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Control.into(), true);
assert!(instance.get_has_selection()); assert!(instance.get_has_selection());
assert_eq!(instance.get_test_cursor_pos(), 4); assert_eq!(instance.get_test_cursor_pos(), 4);
assert_eq!(instance.get_test_anchor_pos(), 0); assert_eq!(instance.get_test_anchor_pos(), 0);

View file

@ -17,11 +17,6 @@ TestCase := TextInput {
const LEFT_CODE: char = '\u{F702}'; const LEFT_CODE: char = '\u{F702}';
const BACK_CODE: char = '\u{0008}'; // backspace \b const BACK_CODE: char = '\u{0008}'; // backspace \b
let shift_modifier = slint::private_unstable_api::re_exports::KeyboardModifiers {
shift: true,
..Default::default()
};
let instance = TestCase::new(); let instance = TestCase::new();
slint_testing::send_mouse_click(&instance, 50., 50.); slint_testing::send_mouse_click(&instance, 50., 50.);
assert!(instance.get_input_focused()); assert!(instance.get_input_focused());
@ -30,7 +25,7 @@ slint_testing::send_keyboard_string_sequence(&instance, "😍");
assert_eq!(instance.get_test_text(), "😍"); assert_eq!(instance.get_test_text(), "😍");
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
slint_testing::set_current_keyboard_modifiers(&instance, shift_modifier); slint_testing::send_keyboard_char(&instance, slint::private_unstable_api::re_exports::Key::Shift.into(), true);
slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string()); slint_testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
assert!(instance.get_has_selection()); assert!(instance.get_has_selection());
assert_eq!(instance.get_test_cursor_pos(), 0); assert_eq!(instance.get_test_cursor_pos(), 0);