diff --git a/api/sixtyfps-cpp/include/sixtyfps.h b/api/sixtyfps-cpp/include/sixtyfps.h index c40605a08..71b4c4cf7 100644 --- a/api/sixtyfps-cpp/include/sixtyfps.h +++ b/api/sixtyfps-cpp/include/sixtyfps.h @@ -37,6 +37,7 @@ extern const cbindgen_private::ItemVTable RectangleVTable; extern const cbindgen_private::ItemVTable BorderRectangleVTable; extern const cbindgen_private::ItemVTable TextVTable; extern const cbindgen_private::ItemVTable TouchAreaVTable; +extern const cbindgen_private::ItemVTable FocusScopeVTable; extern const cbindgen_private::ItemVTable ImageVTable; extern const cbindgen_private::ItemVTable ClippedImageVTable; extern const cbindgen_private::ItemVTable PathVTable; @@ -139,6 +140,7 @@ using cbindgen_private::BorderRectangle; using cbindgen_private::Clip; using cbindgen_private::ClippedImage; using cbindgen_private::Flickable; +using cbindgen_private::FocusScope; using cbindgen_private::Image; using cbindgen_private::Path; using cbindgen_private::Rectangle; diff --git a/sixtyfps_compiler/builtins.60 b/sixtyfps_compiler/builtins.60 index 483019e2e..960e4d1ad 100644 --- a/sixtyfps_compiler/builtins.60 +++ b/sixtyfps_compiler/builtins.60 @@ -84,6 +84,17 @@ export TouchArea := _ { //-default_size_binding:expands_to_parent_geometry } +export FocusScope := _ { + property x; + property y; + property width; + property height; + property has_focus; + callback key_pressed(string); + callback key_released(string); + //-default_size_binding:expands_to_parent_geometry +} + export Flickable := _ { property x; property y; diff --git a/sixtyfps_runtime/corelib/items.rs b/sixtyfps_runtime/corelib/items.rs index 7c60299d6..939f56f55 100644 --- a/sixtyfps_runtime/corelib/items.rs +++ b/sixtyfps_runtime/corelib/items.rs @@ -372,6 +372,97 @@ ItemVTable_static! { pub static TouchAreaVTable for TouchArea } +/// A runtime item that exposes key +#[repr(C)] +#[derive(FieldOffsets, Default, SixtyFPSElement)] +#[pin] +pub struct FocusScope { + pub x: Property, + pub y: Property, + pub width: Property, + pub height: Property, + pub has_focus: Property, + pub key_pressed: Callback<(SharedString,)>, + pub key_released: Callback<(SharedString,)>, + /// FIXME: remove this + pub cached_rendering_data: CachedRenderingData, +} + +impl Item for FocusScope { + fn init(self: Pin<&Self>, _window: &ComponentWindow) {} + + fn geometry(self: Pin<&Self>) -> Rect { + euclid::rect(self.x(), self.y(), self.width(), self.height()) + } + + fn layouting_info(self: Pin<&Self>, _window: &ComponentWindow) -> LayoutInfo { + LayoutInfo::default() + } + + fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size { + Default::default() + } + + fn input_event( + self: Pin<&Self>, + event: MouseEvent, + window: &ComponentWindow, + self_rc: &ItemRc, + ) -> InputEventResult { + /*if !self.enabled() { + return InputEventResult::EventIgnored; + }*/ + if matches!(event.what, MouseEventType::MousePressed) { + if !self.has_focus() { + window.set_focus_item(self_rc); + } + } + InputEventResult::EventAccepted + } + + 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,)); + } + } + }; + KeyEventResult::EventAccepted + } + + fn focus_event(self: Pin<&Self>, event: &FocusEvent, _window: &ComponentWindow) { + match event { + FocusEvent::FocusIn | FocusEvent::WindowReceivedFocus => { + self.has_focus.set(true); + } + FocusEvent::FocusOut | FocusEvent::WindowLostFocus => { + self.has_focus.set(false); + } + } + } + + fn render(self: Pin<&Self>, _pos: Point, _backend: &mut ItemRendererRef) {} +} + +impl ItemConsts for FocusScope { + const cached_rendering_data_offset: const_field_offset::FieldOffset< + FocusScope, + CachedRenderingData, + > = FocusScope::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection(); +} + +ItemVTable_static! { + /// The VTable for `FocusScope` + #[no_mangle] + pub static FocusScopeVTable for FocusScope +} + #[repr(C)] #[derive(FieldOffsets, Default, SixtyFPSElement)] #[pin] diff --git a/sixtyfps_runtime/corelib/rtti.rs b/sixtyfps_runtime/corelib/rtti.rs index 203ab73c7..9efe30b29 100644 --- a/sixtyfps_runtime/corelib/rtti.rs +++ b/sixtyfps_runtime/corelib/rtti.rs @@ -19,7 +19,7 @@ use core::pin::Pin; macro_rules! declare_ValueType { ($($ty:ty,)*) => { - pub trait ValueType: 'static + Default $(+ TryInto<$ty> + TryFrom<$ty>)* {} + pub trait ValueType: 'static + Default + Clone $(+ TryInto<$ty> + TryFrom<$ty>)* {} }; } declare_ValueType![ @@ -225,10 +225,6 @@ pub trait CallbackInfo { item: Pin<&Item>, handler: Box Value>, ) -> Result<(), ()>; - - /// The offset of the Callback<> in the item. - /// The use of this is unsafe - fn offset(&self) -> usize; } impl CallbackInfo @@ -249,9 +245,32 @@ impl CallbackInfo }); Ok(()) } +} - fn offset(&self) -> usize { - todo!() +impl CallbackInfo + for FieldOffset> +where + Value: TryInto, + T: TryInto, +{ + fn emit(&self, item: Pin<&Item>, args: &[Value]) -> Result { + let value = args.first().ok_or(())?; + let value = value.clone().try_into().map_err(|_| ())?; + self.apply_pin(item).emit(&(value,)); + Ok(Value::default()) + } + + fn set_handler( + &self, + item: Pin<&Item>, + handler: Box Value>, + ) -> Result<(), ()> { + self.apply_pin(item).set_handler(move |(val,)| { + let val: Value = val.clone().try_into().ok().unwrap(); + handler(&[val]); + Value::default(); + }); + Ok(()) } } diff --git a/sixtyfps_runtime/interpreter/dynamic_component.rs b/sixtyfps_runtime/interpreter/dynamic_component.rs index 06ca053de..5465e748a 100644 --- a/sixtyfps_runtime/interpreter/dynamic_component.rs +++ b/sixtyfps_runtime/interpreter/dynamic_component.rs @@ -478,6 +478,7 @@ fn generate_component<'id>( rtti_for::(), rtti_for::(), rtti_for::(), + rtti_for::(), rtti_for::(), rtti_for_flickable(), rtti_for::(), diff --git a/sixtyfps_runtime/interpreter/eval.rs b/sixtyfps_runtime/interpreter/eval.rs index da1cd041c..2c5484b12 100644 --- a/sixtyfps_runtime/interpreter/eval.rs +++ b/sixtyfps_runtime/interpreter/eval.rs @@ -71,7 +71,6 @@ impl> ErasedPropertyIn pub trait ErasedCallbackInfo { fn emit(&self, item: Pin, args: &[Value]) -> Value; fn set_handler(&self, item: Pin, handler: Box Value>); - fn offset(&self) -> usize; } impl> ErasedCallbackInfo @@ -84,10 +83,6 @@ impl> ErasedCallbackIn fn set_handler(&self, item: Pin, handler: Box Value>) { (*self).set_handler(ItemRef::downcast_pin(item).unwrap(), handler).unwrap() } - - fn offset(&self) -> usize { - (*self).offset() - } } /// A Pointer to a model diff --git a/tests/cases/examples/key_press.60 b/tests/cases/examples/key_press.60 new file mode 100644 index 000000000..9325550cb --- /dev/null +++ b/tests/cases/examples/key_press.60 @@ -0,0 +1,27 @@ +/* LICENSE BEGIN + This file is part of the SixtyFPS Project -- https://sixtyfps.io + Copyright (c) 2020 Olivier Goffart + Copyright (c) 2020 Simon Hausmann + + SPDX-License-Identifier: GPL-3.0-only + This file is also available under commercial licensing terms. + Please contact info@sixtyfps.io for more information. +LICENSE END */ + +W := Window { + VerticalLayout { + Rectangle { color: field.has_focus ? blue: red; } + field := FocusScope { + vertical_stretch: 1; + key-pressed(key) => { + debug(key); + t.text += key; + } + Rectangle { color: yellow; } + } + t:= Text { + text: "> "; + } + } +} + diff --git a/tests/driver_lib/cbindgen.rs b/tests/driver_lib/cbindgen.rs index a0adc48a6..f5a6eacb5 100644 --- a/tests/driver_lib/cbindgen.rs +++ b/tests/driver_lib/cbindgen.rs @@ -47,6 +47,7 @@ fn gen_corelib(include_dir: &Path) -> anyhow::Result<()> { "Image", "ClippedImage", "TouchArea", + "FocusScope", "Flickable", "Text", "Path",