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:
Simon Hausmann 2021-01-21 15:37:17 +01:00
parent db740831ee
commit 9ca87ab312
10 changed files with 223 additions and 463 deletions

View file

@ -198,9 +198,9 @@ pub mod re_exports {
PathArcTo, PathData, PathElement, PathEvent, PathLineTo, Point, Rect, Size, PathArcTo, PathData, PathElement, PathEvent, PathLineTo, Point, Rect, Size,
}; };
pub use sixtyfps_corelib::input::{ pub use sixtyfps_corelib::input::{
FocusEvent, InputEventResult, KeyCode, KeyEvent, KeyEventResult, KeyboardModifiers, FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyboardModifiers, MouseEvent,
MouseEvent, ALT_MODIFIER, CONTROL_MODIFIER, COPY_PASTE_MODIFIER, LOGO_MODIFIER, ALT_MODIFIER, CONTROL_MODIFIER, COPY_PASTE_MODIFIER, LOGO_MODIFIER, NO_MODIFIER,
NO_MODIFIER, SHIFT_MODIFIER, SHIFT_MODIFIER,
}; };
pub use sixtyfps_corelib::item_tree::{ pub use sixtyfps_corelib::item_tree::{
item_offset, visit_item_tree, ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable, item_offset, visit_item_tree, ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable,
@ -322,22 +322,6 @@ pub mod testing {
KEYBOARD_MODIFIERS.with(|x| x.set(modifiers)) 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. /// Simulate entering a sequence of ascii characters key by key.
pub fn send_keyboard_string_sequence< pub fn send_keyboard_string_sequence<
X: vtable::HasStaticVTable<sixtyfps_corelib::component::ComponentVTable> + HasWindow, X: vtable::HasStaticVTable<sixtyfps_corelib::component::ComponentVTable> + HasWindow,

View file

@ -11,15 +11,13 @@ LICENSE END */
*/ */
#![warn(missing_docs)] #![warn(missing_docs)]
use crate::component::ComponentRc;
use crate::graphics::Point; use crate::graphics::Point;
use crate::item_tree::ItemVisitorResult; use crate::item_tree::ItemVisitorResult;
use crate::items::{ItemRc, ItemRef, ItemWeak}; use crate::items::{ItemRc, ItemRef, ItemWeak};
use crate::Property; use crate::Property;
use crate::{component::ComponentRc, SharedString};
use const_field_offset::FieldOffsets; use const_field_offset::FieldOffsets;
use euclid::default::Vector2D; use euclid::default::Vector2D;
use sixtyfps_corelib_macros::*;
use std::convert::TryFrom;
use std::pin::Pin; use std::pin::Pin;
use std::rc::Rc; 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 /// InternalKeyCode is used to certain keys to unicode characters, since our
/// key mappings, different keys may produce different key codes. /// public key event only exposes a string. This enum captures this mapping.
/// Key codes are typically produced when pressing or releasing a key. #[derive(Debug, PartialEq, Clone)]
#[repr(C)] pub enum InternalKeyCode {
#[derive(Debug, Clone, Copy, PartialEq, MappedKeyCode)] /// Code corresponding to the left cursor key - encoded as 0xE ASCII (shift out)
#[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,
Left, Left,
Up, /// Code corresponding to the right cursor key -- encoded as 0xF ASCII (shift in)
Right, 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, 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, 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 { const LEFT_CODE: char = '\u{000E}'; // shift out
type Error = (); 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> { impl InternalKeyCode {
Ok(match value { /// Encodes the internal key code as string
'a' => Self::A, pub fn encode_to_string(&self) -> SharedString {
'b' => Self::B, match self {
'c' => Self::C, InternalKeyCode::Left => LEFT_CODE,
'd' => Self::D, InternalKeyCode::Right => RIGHT_CODE,
'e' => Self::E, InternalKeyCode::Home => HOME_CODE,
'f' => Self::F, InternalKeyCode::End => END_CODE,
'g' => Self::G, InternalKeyCode::Back => BACK_CODE,
'h' => Self::H, InternalKeyCode::Delete => DELETE_CODE,
'i' => Self::I, InternalKeyCode::Return => RETURN_CODE,
'j' => Self::J, }
'k' => Self::K, .to_string()
'l' => Self::L, .into()
'm' => Self::M, }
'n' => Self::N, /// Tries to see if the provided string corresponds to a single special
'o' => Self::O, /// encoded key.
'p' => Self::P, pub fn try_decode_from_string(str: &SharedString) -> Option<Self> {
'q' => Self::Q, let mut chars = str.chars();
'r' => Self::R, let ch = chars.next();
's' => Self::S, if ch.is_some() && chars.next().is_none() {
't' => Self::T, Some(match ch.unwrap() {
'u' => Self::U, LEFT_CODE => Self::Left,
'v' => Self::V, RIGHT_CODE => Self::Right,
'w' => Self::W, HOME_CODE => Self::Home,
'x' => Self::X, END_CODE => Self::End,
'y' => Self::Y, BACK_CODE => Self::Back,
'z' => Self::Z, DELETE_CODE => Self::Delete,
'1' => Self::Key1, RETURN_CODE => Self::Return,
'2' => Self::Key2, _ => return None,
'3' => Self::Key3, })
'4' => Self::Key4, } else {
'5' => Self::Key5, None
'6' => Self::Key6, }
'7' => Self::Key7,
'8' => Self::Key8,
'9' => Self::Key9,
_ => return Err(()),
})
} }
} }
@ -408,47 +254,18 @@ impl core::ops::BitOrAssign<KeyboardModifier> for KeyboardModifiers {
pub enum KeyEvent { pub enum KeyEvent {
/// A key on a keyboard was pressed. /// A key on a keyboard was pressed.
KeyPressed { KeyPressed {
/// The key code of the pressed key. /// The unicode representation of the key pressed.
code: KeyCode, string: SharedString,
/// The keyboard modifiers active at the time of the key press event. /// The keyboard modifiers active at the time of the key press event.
modifiers: KeyboardModifiers, modifiers: KeyboardModifiers,
}, },
/// A key on a keyboard was released. /// A key on a keyboard was released.
KeyReleased { KeyReleased {
/// The key code of the released key. /// The unicode representation of the key released.
code: KeyCode, string: SharedString,
/// The keyboard modifiers active at the time of the key release event. /// The keyboard modifiers active at the time of the key release event.
modifiers: KeyboardModifiers, 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. /// Represents how an item's key_event handler dealt with a key event.

View file

@ -426,15 +426,11 @@ impl Item for FocusScope {
fn key_event(self: Pin<&Self>, event: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult { fn key_event(self: Pin<&Self>, event: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
match event { match event {
KeyEvent::KeyPressed { .. } => {} KeyEvent::KeyPressed { string, .. } => {
KeyEvent::KeyReleased { .. } => {} Self::FIELD_OFFSETS.key_pressed.apply_pin(self).emit(&(string.clone(),));
KeyEvent::CharacterInput { unicode_scalar, .. } => { }
if let Some(char) = std::char::from_u32(*unicode_scalar) { KeyEvent::KeyReleased { string, .. } => {
let key = SharedString::from(char.to_string().as_str()); Self::FIELD_OFFSETS.key_released.apply_pin(self).emit(&(string.clone(),));
// 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,));
}
} }
}; };
KeyEventResult::EventAccepted KeyEventResult::EventAccepted

View file

@ -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 super::{Item, ItemConsts, ItemRc, VoidArg};
use crate::graphics::{Color, Point, Rect, Size}; use crate::graphics::{Color, Point, Rect, Size};
use crate::input::InternalKeyCode;
use crate::input::{ use crate::input::{
FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyboardModifiers, MouseEvent, FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyboardModifiers, MouseEvent,
MouseEventType, MouseEventType,
@ -269,18 +270,53 @@ impl Item for TextInput {
} }
match event { 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(); self.delete_selection();
let mut text: String = self.text().into(); let mut text: String = self.text().into();
// FIXME: respect grapheme boundaries // FIXME: respect grapheme boundaries
let insert_pos = self.cursor_position() as usize; let insert_pos = self.cursor_position() as usize;
let ch = char::try_from(*unicode_scalar).unwrap().to_string(); text.insert_str(insert_pos, &string);
text.insert_str(insert_pos, &ch);
self.as_ref().text.set(text.into()); 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().cursor_position.set(new_cursor_pos);
self.as_ref().anchor_position.set(new_cursor_pos); self.as_ref().anchor_position.set(new_cursor_pos);
@ -292,68 +328,6 @@ impl Item for TextInput {
KeyEventResult::EventAccepted 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, _ => KeyEventResult::EventIgnored,
} }
} }
@ -390,6 +364,20 @@ enum TextCursorDirection {
EndOfLine, 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 { enum AnchorMode {
KeepAnchor, KeepAnchor,
MoveAnchor, MoveAnchor,

View file

@ -10,8 +10,9 @@ LICENSE END */
//! Functions usefull for testing //! Functions usefull for testing
#![warn(missing_docs)] #![warn(missing_docs)]
use crate::input::{MouseEvent, MouseEventType}; use crate::input::{KeyEvent, KeyboardModifiers, MouseEvent, MouseEventType, SHIFT_MODIFIER};
use crate::window::ComponentWindow; use crate::window::ComponentWindow;
use crate::SharedString;
/// SixtyFPS animations do not use real time, but use a mocked time. /// SixtyFPS animations do not use real time, but use a mocked time.
/// Normally, the event loop update the time of the animation using /// 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. /// 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: crate::input::KeyboardModifiers, modifiers: KeyboardModifiers,
window: &ComponentWindow, 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() { for ch in sequence.chars() {
let mut modifiers = modifiers; let mut modifiers = modifiers;
let maybe_key_code = if ch.is_ascii_uppercase() { if ch.is_ascii_uppercase() {
modifiers |= crate::input::SHIFT_MODIFIER; modifiers |= SHIFT_MODIFIER;
ch.to_ascii_lowercase().try_into()
} else {
ch.try_into()
} }
.ok(); let string: SharedString = ch.to_string().into();
key_down(&maybe_key_code); window.process_key_input(&KeyEvent::KeyPressed { string: string.clone(), modifiers });
window.process_key_input(&KeyEvent::KeyReleased { string, modifiers });
window.process_key_input(&crate::input::KeyEvent::CharacterInput {
unicode_scalar: ch.into(),
modifiers,
});
key_up(&maybe_key_code);
} }
} }

View file

@ -175,30 +175,3 @@ fn callback_arg(ty: &syn::Type) -> Option<&syn::Type> {
} }
None 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()
}

View file

@ -16,10 +16,9 @@ LICENSE END */
use sixtyfps_corelib as corelib; use sixtyfps_corelib as corelib;
use corelib::graphics::Point; use corelib::graphics::Point;
use corelib::input::{KeyEvent, MouseEventType}; use corelib::input::{InternalKeyCode, KeyEvent, MouseEventType};
use corelib::window::*; use corelib::window::*;
use std::cell::RefCell; use std::cell::RefCell;
use std::convert::TryInto;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -292,19 +291,57 @@ pub fn run() {
if let Some(Some(window)) = if let Some(Some(window)) =
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade()) windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
{ {
if let Some(ref key_event) = if let Some(key_code) =
(input, window.current_keyboard_modifiers()).try_into().ok() 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 window
.self_weak .self_weak
.get() .get()
.unwrap() .unwrap()
.upgrade() .upgrade()
.unwrap() .unwrap()
.process_key_input(key_event); .process_key_input(&event);
// FIXME: remove this, it should be based on actual changes rather than this // FIXME: remove this, it should be based on actual changes rather than this
window.request_redraw(); window.request_redraw();
} };
} }
}); });
} }
@ -321,8 +358,8 @@ pub fn run() {
let modifiers = window.current_keyboard_modifiers(); let modifiers = window.current_keyboard_modifiers();
if !modifiers.control() && !modifiers.alt() && !modifiers.logo() { if !modifiers.control() && !modifiers.alt() && !modifiers.logo() {
let key_event = KeyEvent::CharacterInput { let key_event = KeyEvent::KeyReleased {
unicode_scalar: ch.into(), string: ch.to_string().into(),
modifiers, modifiers,
}; };
window window

View file

@ -12,7 +12,7 @@ use cpp::*;
use items::{ImageFit, TextHorizontalAlignment, TextVerticalAlignment}; use items::{ImageFit, TextHorizontalAlignment, TextVerticalAlignment};
use sixtyfps_corelib::component::ComponentRc; use sixtyfps_corelib::component::ComponentRc;
use sixtyfps_corelib::graphics::{FontRequest, Point, RenderingCache}; 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::item_rendering::{CachedRenderingData, ItemRenderer};
use sixtyfps_corelib::items::{self, ItemRef}; use sixtyfps_corelib::items::{self, ItemRef};
use sixtyfps_corelib::properties::PropertyTracker; use sixtyfps_corelib::properties::PropertyTracker;
@ -21,7 +21,6 @@ use sixtyfps_corelib::window::PlatformWindow;
use sixtyfps_corelib::{PathData, Property, Resource}; use sixtyfps_corelib::{PathData, Property, Resource};
use std::cell::RefCell; use std::cell::RefCell;
use std::convert::TryFrom;
use std::pin::Pin; use std::pin::Pin;
use std::ptr::NonNull; use std::ptr::NonNull;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
@ -661,35 +660,26 @@ impl QtWindow {
if modif & key_generated::Qt_KeyboardModifier_MetaModifier != 0 { if modif & key_generated::Qt_KeyboardModifier_MetaModifier != 0 {
modifiers |= sixtyfps_corelib::input::LOGO_MODIFIER 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 {
let event = if released { key_generated::Qt_Key_Key_Left => Some(InternalKeyCode::Left),
KeyEvent::KeyReleased { code, modifiers } key_generated::Qt_Key_Key_Right => Some(InternalKeyCode::Right),
} else { key_generated::Qt_Key_Key_Backspace => Some(InternalKeyCode::Back),
KeyEvent::KeyPressed { code, modifiers } key_generated::Qt_Key_Key_Delete => Some(InternalKeyCode::Delete),
}; key_generated::Qt_Key_Key_End => Some(InternalKeyCode::End),
self.self_weak.get().unwrap().upgrade().unwrap().process_key_input(&event); key_generated::Qt_Key_Key_Home => Some(InternalKeyCode::Home),
} key_generated::Qt_Key_Key_Return => Some(InternalKeyCode::Return),
if released && !text.is_empty() { _ => None,
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);
}
} }
.map_or_else(|| text.into(), |code| code.encode_to_string());
let event = if released {
KeyEvent::KeyReleased { string, modifiers }
} else {
KeyEvent::KeyPressed { string, modifiers }
};
self.self_weak.get().unwrap().upgrade().unwrap().process_key_input(&event);
timer_event(); timer_event();
} }

View file

@ -19,6 +19,14 @@ TestCase := TextInput {
/* /*
```rust ```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(); let instance = TestCase::new();
sixtyfps::testing::send_mouse_click(&instance, 50., 50.); sixtyfps::testing::send_mouse_click(&instance, 50., 50.);
assert!(instance.get_input_focused()); assert!(instance.get_input_focused());
@ -28,39 +36,39 @@ assert_eq!(instance.get_test_text(), "Test");
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::SHIFT_MODIFIER.into()); 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()); sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::NO_MODIFIER.into());
assert!(instance.get_has_selection()); 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()); 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"); assert_eq!(instance.get_test_text(), "Te");
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_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Right]); sixtyfps::testing::send_keyboard_string_sequence(&instance, &RIGHT_CODE.to_string());
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_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Right]); sixtyfps::testing::send_keyboard_string_sequence(&instance, &RIGHT_CODE.to_string());
assert_eq!(instance.get_test_cursor_pos(), 2); assert_eq!(instance.get_test_cursor_pos(), 2);
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_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Left]); sixtyfps::testing::send_keyboard_string_sequence(&instance, &LEFT_CODE.to_string());
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_key_clicks(&instance, &[sixtyfps::re_exports::KeyCode::Left]); sixtyfps::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);
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::SHIFT_MODIFIER.into()); 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()); sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::NO_MODIFIER.into());
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);
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!(!instance.get_has_selection());
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::SHIFT_MODIFIER.into()); 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()); sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::NO_MODIFIER.into());
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);

View file

@ -19,6 +19,11 @@ TestCase := TextInput {
/* /*
```rust ```rust
// from input.rs
const LEFT_CODE: char = '\u{000E}'; // shift out
const BACK_CODE: char = '\u{0007}'; // backspace \b
let instance = TestCase::new(); let instance = TestCase::new();
sixtyfps::testing::send_mouse_click(&instance, 50., 50.); sixtyfps::testing::send_mouse_click(&instance, 50., 50.);
assert!(instance.get_input_focused()); assert!(instance.get_input_focused());
@ -28,11 +33,11 @@ assert_eq!(instance.get_test_text(), "😍");
assert!(!instance.get_has_selection()); assert!(!instance.get_has_selection());
sixtyfps::testing::set_current_keyboard_modifiers(&instance, sixtyfps::re_exports::SHIFT_MODIFIER.into()); 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!(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(), 4); 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!(!instance.get_has_selection());
assert_eq!(instance.get_test_text(), ""); assert_eq!(instance.get_test_text(), "");