Mouse grab in rust

This commit is contained in:
Olivier Goffart 2020-08-07 12:54:58 +02:00
parent 5aa7ee86fe
commit 0a56912d0f
9 changed files with 263 additions and 139 deletions

View file

@ -73,6 +73,7 @@ pub(crate) mod repeater;
pub mod re_exports {
pub use crate::repeater::*;
pub use const_field_offset::{self, FieldOffsets};
pub use once_cell::sync::Lazy;
pub use once_cell::unsync::OnceCell;
pub use pin_weak::rc::*;
pub use sixtyfps_corelib::abi::datastructures::*;
@ -81,9 +82,12 @@ pub mod re_exports {
pub use sixtyfps_corelib::graphics::{
PathArcTo, PathData, PathElement, PathEvent, PathLineTo, Point, Rect, Size,
};
pub use sixtyfps_corelib::input::{InputEventResult, MouseEvent};
pub use sixtyfps_corelib::input::{
process_ungrabbed_mouse_event, InputEventResult, MouseEvent,
};
pub use sixtyfps_corelib::item_tree::{
visit_item_tree, ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable, VisitChildrenResult,
item_offset, visit_item_tree, ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable,
VisitChildrenResult,
};
pub use sixtyfps_corelib::items::*;
pub use sixtyfps_corelib::layout::LayoutInfo;

View file

@ -53,6 +53,15 @@ where
sixtyfps_corelib::item_tree::VisitChildrenResult::CONTINUE
}
/// Forward an input event to a particular item
pub fn input_event(
&self,
idx: usize,
event: sixtyfps_corelib::input::MouseEvent,
) -> sixtyfps_corelib::input::InputEventResult {
self.components.borrow()[idx].as_ref().input_event(event)
}
/// Return the amount of item currently in the component
pub fn len(&self) -> usize {
self.components.borrow().len()

View file

@ -146,6 +146,8 @@ MainWindow := Rectangle {
clicked => {
if (root.active_page == 0) {
root.active_page = idx + 1;
} else {
root.active_page = 0;
}
}
}

View file

@ -62,7 +62,7 @@ pub struct FieldOffset<T, U, PinFlag = NotPinnedFlag>(
/// of.apply(foo)
/// }
/// ```
PhantomData<(PhantomContra<T>, *const U, PinFlag)>,
PhantomData<(PhantomContra<T>, U, PinFlag)>,
);
/// Type that can be used in the `Flag` parameter of `FieldOffset` to specify that

View file

@ -460,6 +460,7 @@ impl<Base, T: ?Sized + VTableMeta, Flag> VOffset<Base, T, Flag> {
}
}
#[inline]
pub fn new<X: HasStaticVTable<T>>(o: FieldOffset<Base, X, Flag>) -> Self {
Self { vtable: X::static_vtable(), offset: o.get_byte_offset(), phantom: PhantomData }
}
@ -467,12 +468,14 @@ impl<Base, T: ?Sized + VTableMeta, Flag> VOffset<Base, T, Flag> {
/// Create a new VOffset from raw data
///
/// Safety: there must be a field that matches the vtable at offset T in base
#[inline]
pub unsafe fn from_raw(vtable: &'static T::VTable, offset: usize) -> Self {
Self { vtable, offset, phantom: PhantomData }
}
}
impl<Base, T: ?Sized + VTableMeta> VOffset<Base, T, PinnedFlag> {
#[inline]
pub fn apply_pin<'a>(self, x: Pin<&'a Base>) -> Pin<VRef<'a, T>> {
let ptr = x.get_ref() as *const Base as *mut u8;
unsafe {

View file

@ -162,6 +162,7 @@ fn generate_component(
let mut repeated_element_components = Vec::new();
let mut repeated_dynmodel_names = Vec::new();
let mut repeated_visit_branch = Vec::new();
let mut repeated_input_branch = Vec::new();
let mut init = Vec::new();
super::build_array_helper(component, |item_rc, children_index, is_flickable_rect| {
let item = item_rc.borrow();
@ -249,6 +250,10 @@ fn generate_component(
repeated_dynmodel_names.push(model_name);
}
repeated_input_branch.push(quote!(
#repeater_index => self.#repeater_id.input_event(rep_index, event),
));
item_tree_array.push(quote!(
sixtyfps::re_exports::ItemTreeNode::DynamicTree {
index: #repeater_index,
@ -408,6 +413,8 @@ fn generate_component(
// Trick so we can use `#()` as a `if let Some` in `quote!`
let parent_component_type = parent_component_type.iter().collect::<Vec<_>>();
let item_tree_array_len = item_tree_array.len();
if diag.has_error() {
return None;
}
@ -427,6 +434,7 @@ fn generate_component(
#(#repeated_dynmodel_names : sixtyfps::re_exports::PropertyListenerScope,)*
self_weak: sixtyfps::re_exports::OnceCell<sixtyfps::re_exports::PinWeak<#component_id>>,
#(parent : sixtyfps::re_exports::PinWeak<#parent_component_type>,)*
mouse_grabber: ::core::cell::Cell<sixtyfps::re_exports::VisitChildrenResult>
}
impl sixtyfps::re_exports::Component for #component_id {
@ -434,8 +442,7 @@ fn generate_component(
-> sixtyfps::re_exports::VisitChildrenResult
{
use sixtyfps::re_exports::*;
let tree = &[#(#item_tree_array),*];
return sixtyfps::re_exports::visit_item_tree(self, VRef::new_pin(self), tree, index, visitor, visit_dynamic);
return sixtyfps::re_exports::visit_item_tree(self, VRef::new_pin(self), Self::item_tree(), index, visitor, visit_dynamic);
#[allow(unused)]
fn visit_dynamic(self_pinned: ::core::pin::Pin<&#component_id>, visitor: ItemVisitorRefMut, dyn_index: usize) -> VisitChildrenResult {
match dyn_index {
@ -445,8 +452,35 @@ fn generate_component(
}
}
fn input_event(self: ::core::pin::Pin<&Self>, _ : sixtyfps::re_exports::MouseEvent) -> sixtyfps::re_exports::InputEventResult {
todo!()
fn input_event(self: ::core::pin::Pin<&Self>, mouse_event : sixtyfps::re_exports::MouseEvent) -> sixtyfps::re_exports::InputEventResult {
use sixtyfps::re_exports::*;
let mouse_grabber = self.mouse_grabber.get();
#[allow(unused)]
let (status, new_grab) = if let Some((item_index, rep_index)) = mouse_grabber.aborted_indexes() {
let tree = Self::item_tree();
let offset = item_offset(self, tree, item_index);
let mut event = mouse_event.clone();
event.pos -= offset.to_vector();
let res = match tree[item_index] {
ItemTreeNode::Item { item, .. } => {
item.apply_pin(self).as_ref().input_event(event)
}
ItemTreeNode::DynamicTree { index } => {
match index {
#(#repeated_input_branch)*
_ => panic!("invalid index {}", index),
}
}
};
match res {
InputEventResult::GrabMouse => (res, mouse_grabber),
_ => (res, VisitChildrenResult::CONTINUE),
}
} else {
process_ungrabbed_mouse_event(VRef::new_pin(self), mouse_event)
};
self.mouse_grabber.set(new_grab);
status
}
#layouts
@ -467,6 +501,7 @@ fn generate_component(
#(#repeated_dynmodel_names : ::core::default::Default::default(),)*
self_weak : ::core::default::Default::default(),
#(parent : parent as sixtyfps::re_exports::PinWeak::<#parent_component_type>,)*
mouse_grabber: ::core::cell::Cell::new(sixtyfps::re_exports::VisitChildrenResult::CONTINUE),
};
let self_pinned = std::rc::Rc::pin(self_);
self_pinned.self_weak.set(PinWeak::downgrade(self_pinned.clone())).map_err(|_|())
@ -475,6 +510,14 @@ fn generate_component(
self_pinned
}
#(#property_and_signal_accessors)*
fn item_tree() -> &'static [sixtyfps::re_exports::ItemTreeNode<Self>] {
use sixtyfps::re_exports::*;
// FIXME: ideally this should be a const
static ITEM_TREE : Lazy<[sixtyfps::re_exports::ItemTreeNode<#component_id>; #item_tree_array_len]> =
Lazy::new(|| [#(#item_tree_array),*]);
&*ITEM_TREE
}
}
#(#extra_components)*
@ -1236,27 +1279,3 @@ fn compile_path(path: &Path, component: &Rc<Component>) -> TokenStream {
}
}
}
/*
quote! {
fn process_input_event(self: ::core::pin::Pin<&Self>, mouse_event) {
if self.grab == -1 {
sixtyfps::re_exports::process_ungrabbed_input_event(mouse_event)
} else {
let inx =self.grab & 0xffff;
match child_array[inx] {
DynamicItem(repeater_offset) => {
let repeater_index = self.grab >> 16;
match repeater_offset => {
#(repeater_id => {
self.#repeater_name.component[repeater_index].proccess_input_event()
} )*
}
}
}
}
}
}
*/

View file

@ -4,7 +4,7 @@ TODO: Keyboard events
*/
use crate::graphics::Point;
use crate::item_tree::ItemVisitorResult;
use crate::item_tree::{ItemVisitorResult, VisitChildrenResult};
use crate::ComponentRefPin;
use euclid::default::Vector2D;
@ -36,7 +36,7 @@ pub struct MouseEvent {
/// to notify the run-time about how the event was handled and
/// what the next steps are.
#[repr(C)]
#[derive(Debug)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum InputEventResult {
/// The event was accepted. This may result in additional events, for example
/// accepting a mouse move will result in a MouseExit event later.
@ -60,7 +60,7 @@ pub enum InputEventResult {
pub fn process_ungrabbed_mouse_event(
component: ComponentRefPin,
event: MouseEvent,
) -> (InputEventResult, isize) {
) -> (InputEventResult, VisitChildrenResult) {
let offset = Vector2D::new(0., 0.);
let mut result = InputEventResult::EventIgnored;
@ -90,8 +90,39 @@ pub fn process_ungrabbed_mouse_event(
},
offset,
);
(result, item_index)
(
result,
if result == InputEventResult::GrabMouse {
item_index
} else {
VisitChildrenResult::CONTINUE
},
)
}
/*
/// The event must be in the component coordinate
/// Returns the new grabber.
pub fn process_grabbed_mouse_event(
component: ComponentRefPin,
item: core::pin::Pin<ItemRef>,
offset: Point,
event: MouseEvent,
old_grab: VisitChildrenResult,
) -> (InputEventResult, VisitChildrenResult) {
let mut event2 = event.clone();
event2.pos -= offset.to_vector();
let res = item.as_ref().input_event(event2);
match res {
InputEventResult::EventIgnored => {
// We need then to forward to another event
process_ungrabbed_mouse_event(component, event)
}
InputEventResult::GrabMouse => (res, old_grab),
InputEventResult::EventAccepted => (res, VisitChildrenResult::CONTINUE),
}
}*/
pub(crate) mod ffi {
use super::*;
@ -101,7 +132,18 @@ pub(crate) mod ffi {
pub extern "C" fn sixtyfps_process_ungrabbed_mouse_event(
component: ComponentRefPin,
event: MouseEvent,
) -> (InputEventResult, isize) {
) -> (InputEventResult, crate::item_tree::VisitChildrenResult) {
process_ungrabbed_mouse_event(component, event)
}
/*
#[no_mangle]
pub extern "C" fn sixtyfps_process_grabbed_mouse_event(
component: ComponentRefPin,
item: core::pin::Pin<ItemRef>,
offset: Point,
event: MouseEvent,
old_grab: VisitChildrenResult,
) -> (InputEventResult, crate::item_tree::VisitChildrenResult) {
process_grabbed_mouse_event(component, item, offset, event, old_grab)
}*/
}

View file

@ -35,6 +35,13 @@ impl VisitChildrenResult {
None
}
}
pub fn aborted_indexes(&self) -> Option<(usize, usize)> {
if self.0 != -1 {
Some(((self.0 & 0xffff_ffff) as usize, (self.0 >> 32) as usize))
} else {
None
}
}
}
/// The item tree is an array of ItemTreeNode representing a static tree of items
@ -70,14 +77,12 @@ pub struct ItemVisitorVTable {
/// as the parent's component.
/// `index` is to be used again in the visit_item_children function of the Component (the one passed as parameter)
/// and `item` is a reference to the item itself
///
/// returns true to continue, or false to abort the visit
visit_item: fn(
VRefMut<ItemVisitorVTable>,
component: Pin<VRef<ComponentVTable>>,
index: isize,
item: Pin<VRef<ItemVTable>>,
) -> bool,
) -> VisitChildrenResult,
/// Destructor
drop: fn(VRefMut<ItemVisitorVTable>),
}
@ -85,16 +90,129 @@ pub struct ItemVisitorVTable {
/// Type alias to `vtable::VRefMut<ItemVisitorVTable>`
pub type ItemVisitorRefMut<'a> = vtable::VRefMut<'a, ItemVisitorVTable>;
impl<T: FnMut(crate::ComponentRefPin, isize, Pin<ItemRef>) -> bool> ItemVisitor for T {
impl<T: FnMut(crate::ComponentRefPin, isize, Pin<ItemRef>) -> VisitChildrenResult> ItemVisitor
for T
{
fn visit_item(
&mut self,
component: crate::ComponentRefPin,
index: isize,
item: Pin<ItemRef>,
) -> bool {
) -> VisitChildrenResult {
self(component, index, item)
}
}
pub enum ItemVisitorResult<State> {
Continue(State),
Abort,
}
/// Visit each items recursively
///
/// The state parametter returned by the visitor is passed to each children.
///
/// Returns the index of the item that cancelled, or -1 if nobody cancelled
pub fn visit_items<State>(
component: ComponentRefPin,
mut visitor: impl FnMut(ComponentRefPin, Pin<ItemRef>, &State) -> ItemVisitorResult<State>,
state: State,
) -> VisitChildrenResult {
visit_internal(component, &mut visitor, -1, &state)
}
fn visit_internal<State>(
component: ComponentRefPin,
visitor: &mut impl FnMut(ComponentRefPin, Pin<ItemRef>, &State) -> ItemVisitorResult<State>,
index: isize,
state: &State,
) -> VisitChildrenResult {
let mut actual_visitor = |component: ComponentRefPin,
index: isize,
item: Pin<ItemRef>|
-> VisitChildrenResult {
match visitor(component, item, state) {
ItemVisitorResult::Continue(state) => visit_internal(component, visitor, index, &state),
ItemVisitorResult::Abort => VisitChildrenResult::abort(index as usize, 0),
}
};
vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
component.as_ref().visit_children_item(index, actual_visitor)
}
/// Visit the children within an array of ItemTreeNode
///
/// The dynamic visitor is called for the dynamic nodes, its signature is
/// `fn(base: &Base, visitor: vtable::VRefMut<ItemVisitorVTable>, dyn_index: usize)`
///
/// FIXME: the design of this use lots of indirection and stack frame in recursive functions
/// Need to check if the compiler is able to optimize away some of it.
/// Possibly we should generate code that directly call the visitor instead
pub fn visit_item_tree<Base>(
base: Pin<&Base>,
component: ComponentRefPin,
item_tree: &[ItemTreeNode<Base>],
index: isize,
mut visitor: vtable::VRefMut<ItemVisitorVTable>,
visit_dynamic: impl Fn(Pin<&Base>, vtable::VRefMut<ItemVisitorVTable>, usize) -> VisitChildrenResult,
) -> VisitChildrenResult {
let mut visit_at_index = |idx: usize| -> VisitChildrenResult {
match &item_tree[idx] {
ItemTreeNode::Item { item, .. } => {
visitor.visit_item(component, idx as isize, item.apply_pin(base))
}
ItemTreeNode::DynamicTree { index } => {
if let Some(sub_idx) =
visit_dynamic(base, visitor.borrow_mut(), *index).aborted_index()
{
VisitChildrenResult::abort(idx, sub_idx)
} else {
VisitChildrenResult::CONTINUE
}
}
}
};
if index == -1 {
visit_at_index(0)
} else {
match &item_tree[index as usize] {
ItemTreeNode::Item { children_index, chilren_count, .. } => {
for c in *children_index..(*children_index + *chilren_count) {
let maybe_abort_index = visit_at_index(c as usize);
if maybe_abort_index.has_aborted() {
return maybe_abort_index;
}
}
}
ItemTreeNode::DynamicTree { .. } => panic!("should not be called with dynamic items"),
};
VisitChildrenResult::CONTINUE
}
}
/// Attempt to find out the x, y position of the parent in the component's coordinate
pub fn item_offset<Base>(
base: Pin<&Base>,
item_tree: &[ItemTreeNode<Base>],
index: usize,
) -> crate::graphics::Point {
let index = index as u32;
// FIXME: This algorithm is shit
let parent = item_tree.iter().find_map(|n| match n {
ItemTreeNode::Item { item, chilren_count, children_index } => {
if *children_index > index && *children_index + *chilren_count < index {
Some(item)
} else {
None
}
}
ItemTreeNode::DynamicTree { .. } => None,
});
if let Some(parent) = parent {
parent.apply_pin(base).as_ref().geometry().origin
} else {
crate::graphics::Point::default()
}
}
pub(crate) mod ffi {
#![allow(unsafe_code)]
@ -126,101 +244,20 @@ pub(crate) mod ffi {
|a, b, c| visit_dynamic(a.get_ref(), b, c),
)
}
}
pub enum ItemVisitorResult<State> {
Continue(State),
Abort,
}
/// Visit each items recursively
/// Expose `crate::item_tree::item_offset` to C++
///
/// The state parametter returned by the visitor is passed to each children.
///
/// Returns the index of the item that cancelled, or -1 if nobody cancelled
pub fn visit_items<State>(
component: ComponentRefPin,
mut visitor: impl FnMut(ComponentRefPin, Pin<ItemRef>, &State) -> ItemVisitorResult<State>,
state: State,
) -> isize {
visit_internal(component, &mut visitor, -1, &state)
}
fn visit_internal<State>(
component: ComponentRefPin,
visitor: &mut impl FnMut(ComponentRefPin, Pin<ItemRef>, &State) -> ItemVisitorResult<State>,
index: isize,
state: &State,
) -> isize {
let mut result = -1;
let mut actual_visitor =
|component: ComponentRefPin, index: isize, item: Pin<ItemRef>| -> bool {
match visitor(component, item, state) {
ItemVisitorResult::Continue(state) => {
result = visit_internal(component, visitor, index, &state);
result == -1
}
ItemVisitorResult::Abort => {
result = index;
false
}
}
};
vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
component.as_ref().visit_children_item(index, actual_visitor);
result
}
/// Visit the children within an array of ItemTreeNode
///
/// The dynamic visitor is called for the dynamic nodes, its signature is
/// `fn(base: &Base, visitor: vtable::VRefMut<ItemVisitorVTable>, dyn_index: usize)`
///
/// FIXME: the design of this use lots of indirection and stack frame in recursive functions
/// Need to check if the compiler is able to optimize away some of it.
/// Possibly we should generate code that directly call the visitor instead
pub fn visit_item_tree<Base>(
base: Pin<&Base>,
component: ComponentRefPin,
item_tree: &[ItemTreeNode<Base>],
index: isize,
mut visitor: vtable::VRefMut<ItemVisitorVTable>,
visit_dynamic: impl Fn(Pin<&Base>, vtable::VRefMut<ItemVisitorVTable>, usize) -> VisitChildrenResult,
) -> VisitChildrenResult {
let mut visit_at_index = |idx: usize| -> VisitChildrenResult {
match &item_tree[idx] {
ItemTreeNode::Item { item, .. } => {
if visitor.visit_item(component, idx as isize, item.apply_pin(base)) {
VisitChildrenResult::CONTINUE
} else {
VisitChildrenResult::abort(idx, 0)
}
}
ItemTreeNode::DynamicTree { index } => {
if let Some(sub_idx) =
visit_dynamic(base, visitor.borrow_mut(), *index).aborted_index()
{
VisitChildrenResult::abort(idx, sub_idx)
} else {
VisitChildrenResult::CONTINUE
}
}
}
};
if index == -1 {
visit_at_index(0)
} else {
match &item_tree[index as usize] {
ItemTreeNode::Item { children_index, chilren_count, .. } => {
for c in *children_index..(*children_index + *chilren_count) {
let maybe_abort_index = visit_at_index(c as usize);
if maybe_abort_index.has_aborted() {
return maybe_abort_index;
}
}
}
ItemTreeNode::DynamicTree { .. } => panic!("should not be called with dynamic items"),
};
VisitChildrenResult::CONTINUE
/// Safety: Assume a correct implementation of the item_tree array
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_item_offset(
component: Pin<VRef<ComponentVTable>>,
item_tree: Slice<ItemTreeNode<u8>>,
index: usize,
) -> crate::graphics::Point {
crate::item_tree::item_offset(
Pin::new_unchecked(&*(component.as_ptr() as *const u8)),
item_tree.as_slice(),
index,
)
}
}

View file

@ -371,14 +371,22 @@ impl Item for TouchArea {
Self::FIELD_OFFSETS.pressed.apply_pin(self).set(match event.what {
MouseEventType::MousePressed => true,
MouseEventType::MouseExit | MouseEventType::MouseReleased => false,
MouseEventType::MouseMoved => return InputEventResult::EventAccepted,
MouseEventType::MouseMoved => {
return if Self::FIELD_OFFSETS.pressed.apply_pin(self).get() {
InputEventResult::GrabMouse
} else {
InputEventResult::EventIgnored
}
}
});
if matches!(event.what, MouseEventType::MouseReleased) {
Self::FIELD_OFFSETS.clicked.apply_pin(self).emit(())
}
Self::FIELD_OFFSETS.clicked.apply_pin(self).emit(());
InputEventResult::EventAccepted
} else {
InputEventResult::GrabMouse
}
}
}
impl ItemConsts for TouchArea {
const cached_rendering_data_offset: const_field_offset::FieldOffset<