mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
Simplify KeyEvent
Fold CharacterInput into KeyPressed/KeyReleased and store the "key" as a string. Also, instead of exposing the KeyCode we're encoding special characters into the string.
This commit is contained in:
parent
db740831ee
commit
9ca87ab312
10 changed files with 223 additions and 463 deletions
|
@ -198,9 +198,9 @@ pub mod re_exports {
|
|||
PathArcTo, PathData, PathElement, PathEvent, PathLineTo, Point, Rect, Size,
|
||||
};
|
||||
pub use sixtyfps_corelib::input::{
|
||||
FocusEvent, InputEventResult, KeyCode, KeyEvent, KeyEventResult, KeyboardModifiers,
|
||||
MouseEvent, ALT_MODIFIER, CONTROL_MODIFIER, COPY_PASTE_MODIFIER, LOGO_MODIFIER,
|
||||
NO_MODIFIER, SHIFT_MODIFIER,
|
||||
FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyboardModifiers, MouseEvent,
|
||||
ALT_MODIFIER, CONTROL_MODIFIER, COPY_PASTE_MODIFIER, LOGO_MODIFIER, NO_MODIFIER,
|
||||
SHIFT_MODIFIER,
|
||||
};
|
||||
pub use sixtyfps_corelib::item_tree::{
|
||||
item_offset, visit_item_tree, ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable,
|
||||
|
@ -322,22 +322,6 @@ pub mod testing {
|
|||
KEYBOARD_MODIFIERS.with(|x| x.set(modifiers))
|
||||
}
|
||||
|
||||
/// Simulate a series of key press and release event
|
||||
pub fn send_key_clicks<
|
||||
X: vtable::HasStaticVTable<sixtyfps_corelib::component::ComponentVTable> + HasWindow,
|
||||
Component: Into<vtable::VRc<sixtyfps_corelib::component::ComponentVTable, X>> + Clone,
|
||||
>(
|
||||
component: &Component,
|
||||
key_codes: &[crate::re_exports::KeyCode],
|
||||
) {
|
||||
let component = component.clone().into();
|
||||
sixtyfps_corelib::tests::sixtyfps_send_key_clicks(
|
||||
&crate::re_exports::Slice::from_slice(key_codes),
|
||||
KEYBOARD_MODIFIERS.with(|x| x.get()),
|
||||
component.component_window(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Simulate entering a sequence of ascii characters key by key.
|
||||
pub fn send_keyboard_string_sequence<
|
||||
X: vtable::HasStaticVTable<sixtyfps_corelib::component::ComponentVTable> + HasWindow,
|
||||
|
|
|
@ -11,15 +11,13 @@ LICENSE END */
|
|||
*/
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use crate::component::ComponentRc;
|
||||
use crate::graphics::Point;
|
||||
use crate::item_tree::ItemVisitorResult;
|
||||
use crate::items::{ItemRc, ItemRef, ItemWeak};
|
||||
use crate::Property;
|
||||
use crate::{component::ComponentRc, SharedString};
|
||||
use const_field_offset::FieldOffsets;
|
||||
use euclid::default::Vector2D;
|
||||
use sixtyfps_corelib_macros::*;
|
||||
use std::convert::TryFrom;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -81,220 +79,68 @@ impl Default for InputEventResult {
|
|||
}
|
||||
}
|
||||
|
||||
/// A key code is a symbolic name for a key on a keyboard. Depending on the
|
||||
/// key mappings, different keys may produce different key codes.
|
||||
/// Key codes are typically produced when pressing or releasing a key.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, MappedKeyCode)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum KeyCode {
|
||||
Key1,
|
||||
Key2,
|
||||
Key3,
|
||||
Key4,
|
||||
Key5,
|
||||
Key6,
|
||||
Key7,
|
||||
Key8,
|
||||
Key9,
|
||||
Key0,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
Escape,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
Snapshot,
|
||||
Scroll,
|
||||
Pause,
|
||||
Insert,
|
||||
Home,
|
||||
Delete,
|
||||
End,
|
||||
PageDown,
|
||||
PageUp,
|
||||
/// InternalKeyCode is used to certain keys to unicode characters, since our
|
||||
/// public key event only exposes a string. This enum captures this mapping.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum InternalKeyCode {
|
||||
/// Code corresponding to the left cursor key - encoded as 0xE ASCII (shift out)
|
||||
Left,
|
||||
Up,
|
||||
/// Code corresponding to the right cursor key -- encoded as 0xF ASCII (shift in)
|
||||
Right,
|
||||
Down,
|
||||
/// Code corresponding to the home key -- encoded as 0x2 ASCII (start of text)
|
||||
Home,
|
||||
/// Code corresponding to the end key -- encoded as 0x3 ASCII (end of text)
|
||||
End,
|
||||
/// Code corresponding to the backspace key -- encoded as 0x7 ASCII (backspace)
|
||||
Back,
|
||||
/// Code corresponding to the delete key -- encoded as 0x7F ASCII (delete)
|
||||
Delete,
|
||||
/// Code corresponding to the return key -- encoded as 0xA ASCII (newline)
|
||||
Return,
|
||||
Space,
|
||||
Compose,
|
||||
Caret,
|
||||
Numlock,
|
||||
Numpad0,
|
||||
Numpad1,
|
||||
Numpad2,
|
||||
Numpad3,
|
||||
Numpad4,
|
||||
Numpad5,
|
||||
Numpad6,
|
||||
Numpad7,
|
||||
Numpad8,
|
||||
Numpad9,
|
||||
AbntC1,
|
||||
AbntC2,
|
||||
NumpadAdd,
|
||||
Apostrophe,
|
||||
Apps,
|
||||
Asterisk,
|
||||
At,
|
||||
Ax,
|
||||
Backslash,
|
||||
Calculator,
|
||||
Capital,
|
||||
Colon,
|
||||
Comma,
|
||||
Convert,
|
||||
NumpadDecimal,
|
||||
NumpadDivide,
|
||||
Equals,
|
||||
Grave,
|
||||
Kana,
|
||||
Kanji,
|
||||
LAlt,
|
||||
LBracket,
|
||||
LControl,
|
||||
LShift,
|
||||
LWin,
|
||||
Mail,
|
||||
MediaSelect,
|
||||
MediaStop,
|
||||
Minus,
|
||||
NumpadMultiply,
|
||||
Mute,
|
||||
MyComputer,
|
||||
NavigateForward,
|
||||
NavigateBackward,
|
||||
NextTrack,
|
||||
NoConvert,
|
||||
NumpadComma,
|
||||
NumpadEnter,
|
||||
NumpadEquals,
|
||||
OEM102,
|
||||
Period,
|
||||
PlayPause,
|
||||
Plus,
|
||||
Power,
|
||||
PrevTrack,
|
||||
RAlt,
|
||||
RBracket,
|
||||
RControl,
|
||||
RShift,
|
||||
RWin,
|
||||
Semicolon,
|
||||
Slash,
|
||||
Sleep,
|
||||
Stop,
|
||||
NumpadSubtract,
|
||||
Sysrq,
|
||||
Tab,
|
||||
Underline,
|
||||
Unlabeled,
|
||||
VolumeDown,
|
||||
VolumeUp,
|
||||
Wake,
|
||||
WebBack,
|
||||
WebFavorites,
|
||||
WebForward,
|
||||
WebHome,
|
||||
WebRefresh,
|
||||
WebSearch,
|
||||
WebStop,
|
||||
Yen,
|
||||
Copy,
|
||||
Paste,
|
||||
Cut,
|
||||
}
|
||||
|
||||
impl TryFrom<char> for KeyCode {
|
||||
type Error = ();
|
||||
const LEFT_CODE: char = '\u{000E}'; // shift out
|
||||
const RIGHT_CODE: char = '\u{000F}'; // shift in
|
||||
const HOME_CODE: char = '\u{0002}'; // start of text
|
||||
const END_CODE: char = '\u{0003}'; // end of text
|
||||
const BACK_CODE: char = '\u{0007}'; // backspace \b
|
||||
const DELETE_CODE: char = '\u{007F}'; // cancel
|
||||
const RETURN_CODE: char = '\u{000A}'; // \n
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
'a' => Self::A,
|
||||
'b' => Self::B,
|
||||
'c' => Self::C,
|
||||
'd' => Self::D,
|
||||
'e' => Self::E,
|
||||
'f' => Self::F,
|
||||
'g' => Self::G,
|
||||
'h' => Self::H,
|
||||
'i' => Self::I,
|
||||
'j' => Self::J,
|
||||
'k' => Self::K,
|
||||
'l' => Self::L,
|
||||
'm' => Self::M,
|
||||
'n' => Self::N,
|
||||
'o' => Self::O,
|
||||
'p' => Self::P,
|
||||
'q' => Self::Q,
|
||||
'r' => Self::R,
|
||||
's' => Self::S,
|
||||
't' => Self::T,
|
||||
'u' => Self::U,
|
||||
'v' => Self::V,
|
||||
'w' => Self::W,
|
||||
'x' => Self::X,
|
||||
'y' => Self::Y,
|
||||
'z' => Self::Z,
|
||||
'1' => Self::Key1,
|
||||
'2' => Self::Key2,
|
||||
'3' => Self::Key3,
|
||||
'4' => Self::Key4,
|
||||
'5' => Self::Key5,
|
||||
'6' => Self::Key6,
|
||||
'7' => Self::Key7,
|
||||
'8' => Self::Key8,
|
||||
'9' => Self::Key9,
|
||||
_ => return Err(()),
|
||||
impl InternalKeyCode {
|
||||
/// Encodes the internal key code as string
|
||||
pub fn encode_to_string(&self) -> SharedString {
|
||||
match self {
|
||||
InternalKeyCode::Left => LEFT_CODE,
|
||||
InternalKeyCode::Right => RIGHT_CODE,
|
||||
InternalKeyCode::Home => HOME_CODE,
|
||||
InternalKeyCode::End => END_CODE,
|
||||
InternalKeyCode::Back => BACK_CODE,
|
||||
InternalKeyCode::Delete => DELETE_CODE,
|
||||
InternalKeyCode::Return => RETURN_CODE,
|
||||
}
|
||||
.to_string()
|
||||
.into()
|
||||
}
|
||||
/// Tries to see if the provided string corresponds to a single special
|
||||
/// encoded key.
|
||||
pub fn try_decode_from_string(str: &SharedString) -> Option<Self> {
|
||||
let mut chars = str.chars();
|
||||
let ch = chars.next();
|
||||
if ch.is_some() && chars.next().is_none() {
|
||||
Some(match ch.unwrap() {
|
||||
LEFT_CODE => Self::Left,
|
||||
RIGHT_CODE => Self::Right,
|
||||
HOME_CODE => Self::Home,
|
||||
END_CODE => Self::End,
|
||||
BACK_CODE => Self::Back,
|
||||
DELETE_CODE => Self::Delete,
|
||||
RETURN_CODE => Self::Return,
|
||||
_ => return None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -408,47 +254,18 @@ impl core::ops::BitOrAssign<KeyboardModifier> for KeyboardModifiers {
|
|||
pub enum KeyEvent {
|
||||
/// A key on a keyboard was pressed.
|
||||
KeyPressed {
|
||||
/// The key code of the pressed key.
|
||||
code: KeyCode,
|
||||
/// The unicode representation of the key pressed.
|
||||
string: SharedString,
|
||||
/// The keyboard modifiers active at the time of the key press event.
|
||||
modifiers: KeyboardModifiers,
|
||||
},
|
||||
/// A key on a keyboard was released.
|
||||
KeyReleased {
|
||||
/// The key code of the released key.
|
||||
code: KeyCode,
|
||||
/// The unicode representation of the key released.
|
||||
string: SharedString,
|
||||
/// The keyboard modifiers active at the time of the key release event.
|
||||
modifiers: KeyboardModifiers,
|
||||
},
|
||||
/// A key on a keyboard was released that results in
|
||||
/// a character that's suitable for text input.
|
||||
CharacterInput {
|
||||
/// The u32 is a unicode scalar value that is safe to convert to char.
|
||||
unicode_scalar: u32,
|
||||
/// The keyboard modifiers active at the time of the char input event.
|
||||
modifiers: KeyboardModifiers,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<(&winit::event::KeyboardInput, KeyboardModifiers)> for KeyEvent {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(
|
||||
input: (&winit::event::KeyboardInput, KeyboardModifiers),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let key_code = match input.0.virtual_keycode {
|
||||
Some(code) => code.into(),
|
||||
None => return Err(()),
|
||||
};
|
||||
Ok(match input.0.state {
|
||||
winit::event::ElementState::Pressed => {
|
||||
KeyEvent::KeyPressed { code: key_code, modifiers: input.1 }
|
||||
}
|
||||
winit::event::ElementState::Released => {
|
||||
KeyEvent::KeyReleased { code: key_code, modifiers: input.1 }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents how an item's key_event handler dealt with a key event.
|
||||
|
|
|
@ -426,15 +426,11 @@ impl Item for FocusScope {
|
|||
|
||||
fn key_event(self: Pin<&Self>, event: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
||||
match event {
|
||||
KeyEvent::KeyPressed { .. } => {}
|
||||
KeyEvent::KeyReleased { .. } => {}
|
||||
KeyEvent::CharacterInput { unicode_scalar, .. } => {
|
||||
if let Some(char) = std::char::from_u32(*unicode_scalar) {
|
||||
let key = SharedString::from(char.to_string().as_str());
|
||||
// FIXME: handle pressed and release in their event
|
||||
Self::FIELD_OFFSETS.key_pressed.apply_pin(self).emit(&(key.clone(),));
|
||||
Self::FIELD_OFFSETS.key_released.apply_pin(self).emit(&(key,));
|
||||
KeyEvent::KeyPressed { string, .. } => {
|
||||
Self::FIELD_OFFSETS.key_pressed.apply_pin(self).emit(&(string.clone(),));
|
||||
}
|
||||
KeyEvent::KeyReleased { string, .. } => {
|
||||
Self::FIELD_OFFSETS.key_released.apply_pin(self).emit(&(string.clone(),));
|
||||
}
|
||||
};
|
||||
KeyEventResult::EventAccepted
|
||||
|
|
|
@ -22,6 +22,7 @@ When adding an item or a property, it needs to be kept in sync with different pl
|
|||
|
||||
use super::{Item, ItemConsts, ItemRc, VoidArg};
|
||||
use crate::graphics::{Color, Point, Rect, Size};
|
||||
use crate::input::InternalKeyCode;
|
||||
use crate::input::{
|
||||
FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyboardModifiers, MouseEvent,
|
||||
MouseEventType,
|
||||
|
@ -269,18 +270,53 @@ impl Item for TextInput {
|
|||
}
|
||||
|
||||
match event {
|
||||
KeyEvent::CharacterInput { unicode_scalar, .. } => {
|
||||
KeyEvent::KeyPressed { string, modifiers } => {
|
||||
if let Some(keycode) = InternalKeyCode::try_decode_from_string(string) {
|
||||
if let Ok(text_cursor_movement) = TextCursorDirection::try_from(keycode.clone())
|
||||
{
|
||||
TextInput::move_cursor(
|
||||
self,
|
||||
text_cursor_movement,
|
||||
(*modifiers).into(),
|
||||
window,
|
||||
);
|
||||
return KeyEventResult::EventAccepted;
|
||||
} else if keycode == InternalKeyCode::Back {
|
||||
TextInput::delete_previous(self, window);
|
||||
return KeyEventResult::EventAccepted;
|
||||
} else if keycode == InternalKeyCode::Delete {
|
||||
TextInput::delete_char(self, window);
|
||||
return KeyEventResult::EventAccepted;
|
||||
} else if keycode == InternalKeyCode::Return {
|
||||
Self::FIELD_OFFSETS.accepted.apply_pin(self).emit(&());
|
||||
return KeyEventResult::EventAccepted;
|
||||
}
|
||||
}
|
||||
KeyEventResult::EventIgnored
|
||||
}
|
||||
KeyEvent::KeyReleased { string, modifiers }
|
||||
// Only insert/interpreter non-control character strings
|
||||
if !string.is_empty() && string.as_str().chars().all(|ch| !ch.is_control()) =>
|
||||
{
|
||||
if modifiers.test_exclusive(crate::input::COPY_PASTE_MODIFIER) {
|
||||
if string == "c" {
|
||||
self.copy();
|
||||
return KeyEventResult::EventAccepted;
|
||||
} else if string == "v" {
|
||||
self.paste();
|
||||
return KeyEventResult::EventAccepted;
|
||||
}
|
||||
}
|
||||
self.delete_selection();
|
||||
|
||||
let mut text: String = self.text().into();
|
||||
|
||||
// FIXME: respect grapheme boundaries
|
||||
let insert_pos = self.cursor_position() as usize;
|
||||
let ch = char::try_from(*unicode_scalar).unwrap().to_string();
|
||||
text.insert_str(insert_pos, &ch);
|
||||
text.insert_str(insert_pos, &string);
|
||||
|
||||
self.as_ref().text.set(text.into());
|
||||
let new_cursor_pos = (insert_pos + ch.len()) as i32;
|
||||
let new_cursor_pos = (insert_pos + string.len()) as i32;
|
||||
self.as_ref().cursor_position.set(new_cursor_pos);
|
||||
self.as_ref().anchor_position.set(new_cursor_pos);
|
||||
|
||||
|
@ -292,68 +328,6 @@ impl Item for TextInput {
|
|||
|
||||
KeyEventResult::EventAccepted
|
||||
}
|
||||
KeyEvent::KeyPressed { code, modifiers } if *code == crate::input::KeyCode::Right => {
|
||||
TextInput::move_cursor(
|
||||
self,
|
||||
TextCursorDirection::Forward,
|
||||
(*modifiers).into(),
|
||||
window,
|
||||
);
|
||||
KeyEventResult::EventAccepted
|
||||
}
|
||||
KeyEvent::KeyPressed { code, modifiers } if *code == crate::input::KeyCode::Left => {
|
||||
TextInput::move_cursor(
|
||||
self,
|
||||
TextCursorDirection::Backward,
|
||||
(*modifiers).into(),
|
||||
window,
|
||||
);
|
||||
KeyEventResult::EventAccepted
|
||||
}
|
||||
KeyEvent::KeyPressed { code, modifiers } if *code == crate::input::KeyCode::Home => {
|
||||
TextInput::move_cursor(
|
||||
self,
|
||||
TextCursorDirection::StartOfLine,
|
||||
(*modifiers).into(),
|
||||
window,
|
||||
);
|
||||
KeyEventResult::EventAccepted
|
||||
}
|
||||
KeyEvent::KeyPressed { code, modifiers } if *code == crate::input::KeyCode::End => {
|
||||
TextInput::move_cursor(
|
||||
self,
|
||||
TextCursorDirection::EndOfLine,
|
||||
(*modifiers).into(),
|
||||
window,
|
||||
);
|
||||
KeyEventResult::EventAccepted
|
||||
}
|
||||
KeyEvent::KeyPressed { code, .. } if *code == crate::input::KeyCode::Back => {
|
||||
TextInput::delete_previous(self, window);
|
||||
KeyEventResult::EventAccepted
|
||||
}
|
||||
KeyEvent::KeyPressed { code, .. } if *code == crate::input::KeyCode::Delete => {
|
||||
TextInput::delete_char(self, window);
|
||||
KeyEventResult::EventAccepted
|
||||
}
|
||||
KeyEvent::KeyPressed { code, .. } if *code == crate::input::KeyCode::Return => {
|
||||
Self::FIELD_OFFSETS.accepted.apply_pin(self).emit(&());
|
||||
KeyEventResult::EventAccepted
|
||||
}
|
||||
KeyEvent::KeyReleased { code, modifiers }
|
||||
if modifiers.test_exclusive(crate::input::COPY_PASTE_MODIFIER)
|
||||
&& *code == crate::input::KeyCode::C =>
|
||||
{
|
||||
self.copy();
|
||||
KeyEventResult::EventAccepted
|
||||
}
|
||||
KeyEvent::KeyReleased { code, modifiers }
|
||||
if modifiers.test_exclusive(crate::input::COPY_PASTE_MODIFIER)
|
||||
&& *code == crate::input::KeyCode::V =>
|
||||
{
|
||||
self.paste();
|
||||
KeyEventResult::EventAccepted
|
||||
}
|
||||
_ => KeyEventResult::EventIgnored,
|
||||
}
|
||||
}
|
||||
|
@ -390,6 +364,20 @@ enum TextCursorDirection {
|
|||
EndOfLine,
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<InternalKeyCode> for TextCursorDirection {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: InternalKeyCode) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
InternalKeyCode::Left => Self::Backward,
|
||||
InternalKeyCode::Right => Self::Forward,
|
||||
InternalKeyCode::Home => Self::StartOfLine,
|
||||
InternalKeyCode::End => Self::EndOfLine,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum AnchorMode {
|
||||
KeepAnchor,
|
||||
MoveAnchor,
|
||||
|
|
|
@ -10,8 +10,9 @@ LICENSE END */
|
|||
//! Functions usefull for testing
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use crate::input::{MouseEvent, MouseEventType};
|
||||
use crate::input::{KeyEvent, KeyboardModifiers, MouseEvent, MouseEventType, SHIFT_MODIFIER};
|
||||
use crate::window::ComponentWindow;
|
||||
use crate::SharedString;
|
||||
|
||||
/// SixtyFPS animations do not use real time, but use a mocked time.
|
||||
/// Normally, the event loop update the time of the animation using
|
||||
|
@ -60,60 +61,21 @@ pub extern "C" fn sixtyfps_send_mouse_click(
|
|||
);
|
||||
}
|
||||
|
||||
/// Simulate a key down event.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sixtyfps_send_key_clicks(
|
||||
key_codes: &crate::slice::Slice<crate::input::KeyCode>,
|
||||
modifiers: crate::input::KeyboardModifiers,
|
||||
window: &ComponentWindow,
|
||||
) {
|
||||
for key_code in key_codes.iter() {
|
||||
window
|
||||
.process_key_input(&crate::input::KeyEvent::KeyPressed { code: *key_code, modifiers });
|
||||
window
|
||||
.process_key_input(&crate::input::KeyEvent::KeyReleased { code: *key_code, modifiers });
|
||||
}
|
||||
}
|
||||
|
||||
/// Simulate a character input event.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn send_keyboard_string_sequence(
|
||||
sequence: &crate::SharedString,
|
||||
modifiers: crate::input::KeyboardModifiers,
|
||||
modifiers: KeyboardModifiers,
|
||||
window: &ComponentWindow,
|
||||
) {
|
||||
use std::convert::TryInto;
|
||||
|
||||
let key_down = |maybe_code: &Option<crate::input::KeyCode>| {
|
||||
maybe_code.clone().map(|code| {
|
||||
window.process_key_input(&crate::input::KeyEvent::KeyPressed { code: code, modifiers });
|
||||
});
|
||||
};
|
||||
|
||||
let key_up = |maybe_code: &Option<crate::input::KeyCode>| {
|
||||
maybe_code.clone().map(|code| {
|
||||
window
|
||||
.process_key_input(&crate::input::KeyEvent::KeyReleased { code: code, modifiers });
|
||||
});
|
||||
};
|
||||
|
||||
for ch in sequence.chars() {
|
||||
let mut modifiers = modifiers;
|
||||
let maybe_key_code = if ch.is_ascii_uppercase() {
|
||||
modifiers |= crate::input::SHIFT_MODIFIER;
|
||||
ch.to_ascii_lowercase().try_into()
|
||||
} else {
|
||||
ch.try_into()
|
||||
if ch.is_ascii_uppercase() {
|
||||
modifiers |= SHIFT_MODIFIER;
|
||||
}
|
||||
.ok();
|
||||
let string: SharedString = ch.to_string().into();
|
||||
|
||||
key_down(&maybe_key_code);
|
||||
|
||||
window.process_key_input(&crate::input::KeyEvent::CharacterInput {
|
||||
unicode_scalar: ch.into(),
|
||||
modifiers,
|
||||
});
|
||||
|
||||
key_up(&maybe_key_code);
|
||||
window.process_key_input(&KeyEvent::KeyPressed { string: string.clone(), modifiers });
|
||||
window.process_key_input(&KeyEvent::KeyReleased { string, modifiers });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,30 +175,3 @@ fn callback_arg(ty: &syn::Type) -> Option<&syn::Type> {
|
|||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[proc_macro_derive(MappedKeyCode)]
|
||||
pub fn keycode_mapping(input: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||
|
||||
let variants = match &input.data {
|
||||
syn::Data::Enum(syn::DataEnum { variants, .. }) => variants,
|
||||
_ => {
|
||||
return syn::Error::new(input.ident.span(), "Only `enum` types are supported")
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
.iter()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
quote!(
|
||||
impl From<winit::event::VirtualKeyCode> for KeyCode {
|
||||
fn from(code: winit::event::VirtualKeyCode) -> Self {
|
||||
match code {
|
||||
#(winit::event::VirtualKeyCode::#variants => Self::#variants),*
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -16,10 +16,9 @@ LICENSE END */
|
|||
use sixtyfps_corelib as corelib;
|
||||
|
||||
use corelib::graphics::Point;
|
||||
use corelib::input::{KeyEvent, MouseEventType};
|
||||
use corelib::input::{InternalKeyCode, KeyEvent, MouseEventType};
|
||||
use corelib::window::*;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryInto;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
@ -292,19 +291,57 @@ pub fn run() {
|
|||
if let Some(Some(window)) =
|
||||
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
||||
{
|
||||
if let Some(ref key_event) =
|
||||
(input, window.current_keyboard_modifiers()).try_into().ok()
|
||||
if let Some(key_code) =
|
||||
input.virtual_keycode.and_then(|virtual_keycode| {
|
||||
match virtual_keycode {
|
||||
winit::event::VirtualKeyCode::Left => {
|
||||
Some(InternalKeyCode::Left)
|
||||
}
|
||||
winit::event::VirtualKeyCode::Right => {
|
||||
Some(InternalKeyCode::Right)
|
||||
}
|
||||
winit::event::VirtualKeyCode::Home => {
|
||||
Some(InternalKeyCode::Home)
|
||||
}
|
||||
winit::event::VirtualKeyCode::End => {
|
||||
Some(InternalKeyCode::End)
|
||||
}
|
||||
winit::event::VirtualKeyCode::Back => {
|
||||
Some(InternalKeyCode::Back)
|
||||
}
|
||||
winit::event::VirtualKeyCode::Delete => {
|
||||
Some(InternalKeyCode::Delete)
|
||||
}
|
||||
winit::event::VirtualKeyCode::Return => {
|
||||
Some(InternalKeyCode::Return)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
{
|
||||
let string = key_code.encode_to_string();
|
||||
let event = match input.state {
|
||||
glutin::event::ElementState::Pressed => KeyEvent::KeyPressed {
|
||||
string,
|
||||
modifiers: window.current_keyboard_modifiers(),
|
||||
},
|
||||
glutin::event::ElementState::Released => {
|
||||
KeyEvent::KeyReleased {
|
||||
string,
|
||||
modifiers: window.current_keyboard_modifiers(),
|
||||
}
|
||||
}
|
||||
};
|
||||
window
|
||||
.self_weak
|
||||
.get()
|
||||
.unwrap()
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.process_key_input(key_event);
|
||||
.process_key_input(&event);
|
||||
// FIXME: remove this, it should be based on actual changes rather than this
|
||||
window.request_redraw();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -321,8 +358,8 @@ pub fn run() {
|
|||
let modifiers = window.current_keyboard_modifiers();
|
||||
|
||||
if !modifiers.control() && !modifiers.alt() && !modifiers.logo() {
|
||||
let key_event = KeyEvent::CharacterInput {
|
||||
unicode_scalar: ch.into(),
|
||||
let key_event = KeyEvent::KeyReleased {
|
||||
string: ch.to_string().into(),
|
||||
modifiers,
|
||||
};
|
||||
window
|
||||
|
|
|
@ -12,7 +12,7 @@ use cpp::*;
|
|||
use items::{ImageFit, TextHorizontalAlignment, TextVerticalAlignment};
|
||||
use sixtyfps_corelib::component::ComponentRc;
|
||||
use sixtyfps_corelib::graphics::{FontRequest, Point, RenderingCache};
|
||||
use sixtyfps_corelib::input::{KeyCode, KeyEvent, MouseEventType};
|
||||
use sixtyfps_corelib::input::{InternalKeyCode, KeyEvent, MouseEventType};
|
||||
use sixtyfps_corelib::item_rendering::{CachedRenderingData, ItemRenderer};
|
||||
use sixtyfps_corelib::items::{self, ItemRef};
|
||||
use sixtyfps_corelib::properties::PropertyTracker;
|
||||
|
@ -21,7 +21,6 @@ use sixtyfps_corelib::window::PlatformWindow;
|
|||
use sixtyfps_corelib::{PathData, Property, Resource};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryFrom;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
@ -661,35 +660,26 @@ impl QtWindow {
|
|||
if modif & key_generated::Qt_KeyboardModifier_MetaModifier != 0 {
|
||||
modifiers |= sixtyfps_corelib::input::LOGO_MODIFIER
|
||||
}
|
||||
let code = match key as key_generated::Qt_Key {
|
||||
key_generated::Qt_Key_Key_Left => Some(KeyCode::Left),
|
||||
key_generated::Qt_Key_Key_Right => Some(KeyCode::Right),
|
||||
key_generated::Qt_Key_Key_Up => Some(KeyCode::Up),
|
||||
key_generated::Qt_Key_Key_Down => Some(KeyCode::Down),
|
||||
key_generated::Qt_Key_Key_Insert => Some(KeyCode::Insert),
|
||||
key_generated::Qt_Key_Key_Backspace => Some(KeyCode::Back),
|
||||
key_generated::Qt_Key_Key_Delete => Some(KeyCode::Delete),
|
||||
key_generated::Qt_Key_Key_End => Some(KeyCode::End),
|
||||
key_generated::Qt_Key_Key_Home => Some(KeyCode::Home),
|
||||
key_generated::Qt_Key_Key_Return => Some(KeyCode::Return),
|
||||
key_generated::Qt_Key_Key_Enter => Some(KeyCode::NumpadEnter),
|
||||
_ => text.chars().next().and_then(|x| KeyCode::try_from(x).ok()),
|
||||
};
|
||||
|
||||
if let Some(code) = code {
|
||||
let string = match key as key_generated::Qt_Key {
|
||||
key_generated::Qt_Key_Key_Left => Some(InternalKeyCode::Left),
|
||||
key_generated::Qt_Key_Key_Right => Some(InternalKeyCode::Right),
|
||||
key_generated::Qt_Key_Key_Backspace => Some(InternalKeyCode::Back),
|
||||
key_generated::Qt_Key_Key_Delete => Some(InternalKeyCode::Delete),
|
||||
key_generated::Qt_Key_Key_End => Some(InternalKeyCode::End),
|
||||
key_generated::Qt_Key_Key_Home => Some(InternalKeyCode::Home),
|
||||
key_generated::Qt_Key_Key_Return => Some(InternalKeyCode::Return),
|
||||
_ => None,
|
||||
}
|
||||
.map_or_else(|| text.into(), |code| code.encode_to_string());
|
||||
|
||||
let event = if released {
|
||||
KeyEvent::KeyReleased { code, modifiers }
|
||||
KeyEvent::KeyReleased { string, modifiers }
|
||||
} else {
|
||||
KeyEvent::KeyPressed { code, modifiers }
|
||||
KeyEvent::KeyPressed { string, modifiers }
|
||||
};
|
||||
self.self_weak.get().unwrap().upgrade().unwrap().process_key_input(&event);
|
||||
}
|
||||
if released && !text.is_empty() {
|
||||
for x in text.chars() {
|
||||
let event = KeyEvent::CharacterInput { unicode_scalar: x as _, modifiers };
|
||||
self.self_weak.get().unwrap().upgrade().unwrap().process_key_input(&event);
|
||||
}
|
||||
}
|
||||
|
||||
timer_event();
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,14 @@ TestCase := TextInput {
|
|||
|
||||
/*
|
||||
```rust
|
||||
|
||||
// from input.rs
|
||||
const LEFT_CODE: char = '\u{000E}'; // shift out
|
||||
const RIGHT_CODE: char = '\u{000F}'; // shift in
|
||||
const HOME_CODE: char = '\u{0002}'; // start of text
|
||||
const END_CODE: char = '\u{0003}'; // end of text
|
||||
const BACK_CODE: char = '\u{0007}'; // backspace \b
|
||||
|
||||
let instance = TestCase::new();
|
||||
sixtyfps::testing::send_mouse_click(&instance, 50., 50.);
|
||||
assert!(instance.get_input_focused());
|
||||
|
@ -28,39 +36,39 @@ assert_eq!(instance.get_test_text(), "Test");
|
|||
assert!(!instance.get_has_selection());
|
||||
|
||||
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::SHIFT_MODIFIER.into());
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Left]);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
|
||||
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::NO_MODIFIER.into());
|
||||
assert!(instance.get_has_selection());
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Back]);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
|
||||
assert!(!instance.get_has_selection());
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Back]);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
|
||||
|
||||
assert_eq!(instance.get_test_text(), "Te");
|
||||
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Right]);
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Right]);
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Right]);
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Right]);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &RIGHT_CODE.to_string());
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &RIGHT_CODE.to_string());
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &RIGHT_CODE.to_string());
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &RIGHT_CODE.to_string());
|
||||
assert_eq!(instance.get_test_cursor_pos(), 2);
|
||||
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Left]);
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Left]);
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Left]);
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Left]);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
|
||||
assert_eq!(instance.get_test_cursor_pos(), 0);
|
||||
|
||||
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::SHIFT_MODIFIER.into());
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::End]);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &END_CODE.to_string());
|
||||
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::NO_MODIFIER.into());
|
||||
assert!(instance.get_has_selection());
|
||||
assert_eq!(instance.get_test_cursor_pos(), 2);
|
||||
assert_eq!(instance.get_test_anchor_pos(), 0);
|
||||
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Left]);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
|
||||
assert!(!instance.get_has_selection());
|
||||
|
||||
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::SHIFT_MODIFIER.into());
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Home]);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &HOME_CODE.to_string());
|
||||
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::NO_MODIFIER.into());
|
||||
assert!(instance.get_has_selection());
|
||||
assert_eq!(instance.get_test_cursor_pos(), 0);
|
||||
|
|
|
@ -19,6 +19,11 @@ TestCase := TextInput {
|
|||
|
||||
/*
|
||||
```rust
|
||||
|
||||
// from input.rs
|
||||
const LEFT_CODE: char = '\u{000E}'; // shift out
|
||||
const BACK_CODE: char = '\u{0007}'; // backspace \b
|
||||
|
||||
let instance = TestCase::new();
|
||||
sixtyfps::testing::send_mouse_click(&instance, 50., 50.);
|
||||
assert!(instance.get_input_focused());
|
||||
|
@ -28,11 +33,11 @@ assert_eq!(instance.get_test_text(), "😍");
|
|||
assert!(!instance.get_has_selection());
|
||||
|
||||
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::SHIFT_MODIFIER.into());
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Left]);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
|
||||
assert!(instance.get_has_selection());
|
||||
assert_eq!(instance.get_test_cursor_pos(), 0);
|
||||
assert_eq!(instance.get_test_anchor_pos(), 4);
|
||||
sixtyfps::testing::send_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Back]);
|
||||
sixtyfps::testing::send_keyboard_string_sequence(&instance, &BACK_CODE.to_string());
|
||||
assert!(!instance.get_has_selection());
|
||||
|
||||
assert_eq!(instance.get_test_text(), "");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue