C++ two way bindings

This commit is contained in:
Olivier Goffart 2020-09-25 15:21:22 +02:00
parent cc3ac5fbed
commit abe24e2e9e
5 changed files with 149 additions and 46 deletions

View file

@ -201,9 +201,8 @@ inline KeyEventResult process_key_event(ComponentRef component, int64_t focus_it
return comp.vtable->key_event(comp, event, window); return comp.vtable->key_event(comp, event, window);
}; };
} }
} else {
return KeyEventResult::EventIgnored;
} }
return KeyEventResult::EventIgnored;
} }
template<typename GetDynamic> template<typename GetDynamic>
@ -247,6 +246,7 @@ inline FocusEventResult process_focus_event(ComponentRef component, int64_t &foc
return FocusEventResult::FocusItemNotFound; return FocusEventResult::FocusItemNotFound;
} }
} }
return FocusEventResult::FocusItemNotFound;
} }
} }

View file

@ -9,6 +9,7 @@
LICENSE END */ LICENSE END */
#pragma once #pragma once
#include <string_view> #include <string_view>
#include <memory>
namespace sixtyfps { namespace sixtyfps {
namespace cbindgen_private { namespace cbindgen_private {
@ -41,7 +42,7 @@ struct Property
void set(const T &value) const void set(const T &value) const
{ {
this->value = value; this->value = value;
cbindgen_private::sixtyfps_property_set_changed(&inner); cbindgen_private::sixtyfps_property_set_changed(&inner, &this->value);
} }
const T &get() const const T &get() const
@ -58,7 +59,8 @@ struct Property
[](void *user_data, void *value) { [](void *user_data, void *value) {
*reinterpret_cast<T *>(value) = (*reinterpret_cast<F *>(user_data))(); *reinterpret_cast<T *>(value) = (*reinterpret_cast<F *>(user_data))();
}, },
new F(binding), [](void *user_data) { delete reinterpret_cast<F *>(user_data); }); new F(binding), [](void *user_data) { delete reinterpret_cast<F *>(user_data); },
nullptr);
} }
inline void set_animated_value(const T &value, inline void set_animated_value(const T &value,
@ -69,6 +71,36 @@ struct Property
bool is_dirty() const { return cbindgen_private::sixtyfps_property_is_dirty(&inner); } bool is_dirty() const { return cbindgen_private::sixtyfps_property_is_dirty(&inner); }
static void link_two_way(Property<T> *p1, Property<T> *p2) {
auto value = p2->get();
cbindgen_private::PropertyHandleOpaque handle{};
if ((p2->inner._0 & 0b10) == 0b10) {
std::swap(handle,p2->inner);
}
auto common_property = std::make_shared<Property<T>>(handle, std::move(value));
struct TwoWayBinding {
std::shared_ptr<Property<T>> common_property;
};
auto del_fn = [](void *user_data) { delete reinterpret_cast<TwoWayBinding *>(user_data); };
auto call_fn = [](void *user_data, void *value) {
*reinterpret_cast<T *>(value) =
reinterpret_cast<TwoWayBinding *>(user_data)->common_property->get();
};
auto intercept_fn = [] (void *user_data, const void *value) {
reinterpret_cast<TwoWayBinding *>(user_data)->common_property->set(
*reinterpret_cast<const T *>(value));
return true;
};
cbindgen_private::sixtyfps_property_set_binding(&p1->inner, call_fn,
new TwoWayBinding{common_property}, del_fn, intercept_fn);
cbindgen_private::sixtyfps_property_set_binding(&p2->inner, call_fn,
new TwoWayBinding{common_property}, del_fn, intercept_fn);
}
/// Internal (private) constructor used by link_two_way
explicit Property(cbindgen_private::PropertyHandleOpaque inner, T value)
: inner(inner), value(std::move(value)) {}
private: private:
cbindgen_private::PropertyHandleOpaque inner; cbindgen_private::PropertyHandleOpaque inner;
mutable T value {}; mutable T value {};

View file

@ -306,7 +306,12 @@ fn property_set_binding_code(
} }
} }
fn handle_item(elem: &ElementRc, main_struct: &mut Struct, init: &mut Vec<String>) { fn handle_item(
elem: &ElementRc,
main_struct: &mut Struct,
init_begin: &mut Vec<String>,
init_end: &mut Vec<String>,
) {
let item = elem.borrow(); let item = elem.borrow();
main_struct.members.push(( main_struct.members.push((
Access::Private, Access::Private,
@ -317,10 +322,13 @@ fn handle_item(elem: &ElementRc, main_struct: &mut Struct, init: &mut Vec<String
}), }),
)); ));
let component = item.enclosing_component.upgrade().unwrap();
let id = &item.id; let id = &item.id;
init.extend(item.bindings.iter().map(|(s, i)| { for (prop_name, binding_expression) in &item.bindings {
if let Type::Signal { args } = item.lookup_property(s.as_str()) { let prop_ty = item.lookup_property(prop_name.as_str());
let signal_accessor_prefix = if item.property_declarations.contains_key(s) { if let Type::Signal { args } = &prop_ty {
let signal_accessor_prefix = if item.property_declarations.contains_key(prop_name) {
String::new() String::new()
} else { } else {
format!("{id}.", id = id.clone()) format!("{id}.", id = id.clone())
@ -329,21 +337,26 @@ fn handle_item(elem: &ElementRc, main_struct: &mut Struct, init: &mut Vec<String
format!("[[maybe_unused]] {} arg_{}", ty.cpp_type().unwrap_or_default(), i) format!("[[maybe_unused]] {} arg_{}", ty.cpp_type().unwrap_or_default(), i)
}); });
format!( init_begin.push(format!(
"{signal_accessor_prefix}{prop}.set_handler( "{signal_accessor_prefix}{prop}.set_handler(
[this]({params}) {{ [this]({params}) {{
[[maybe_unused]] auto self = this; [[maybe_unused]] auto self = this;
{code}; {code};
}});", }});",
signal_accessor_prefix = signal_accessor_prefix, signal_accessor_prefix = signal_accessor_prefix,
prop = s, prop = prop_name,
params = params.join(", "), params = params.join(", "),
code = compile_expression(i, &item.enclosing_component.upgrade().unwrap()) code = compile_expression(binding_expression, &component)
) ));
} else if let Expression::TwoWayBinding(_nr) = &i.expression { } else if let Expression::TwoWayBinding(nr) = &binding_expression.expression {
"std::printf(\"Two way binding not implemented in C++\");".into() init_end.push(format!(
"sixtyfps::Property<{ty}>::link_two_way(&{p1}, &{p2});",
ty = prop_ty.cpp_type().unwrap_or_default(),
p1 = access_member(elem, prop_name, &component, "this"),
p2 = access_member(&nr.element.upgrade().unwrap(), &nr.name, &component, "this")
));
} else { } else {
let accessor_prefix = if item.property_declarations.contains_key(s) { let accessor_prefix = if item.property_declarations.contains_key(prop_name) {
String::new() String::new()
} else { } else {
format!("{id}.", id = id.clone()) format!("{id}.", id = id.clone())
@ -351,8 +364,8 @@ fn handle_item(elem: &ElementRc, main_struct: &mut Struct, init: &mut Vec<String
let component = &item.enclosing_component.upgrade().unwrap(); let component = &item.enclosing_component.upgrade().unwrap();
let init = compile_expression(i, component); let init = compile_expression(binding_expression, component);
let setter = if i.is_constant() { let setter = if binding_expression.is_constant() {
format!("set({});", init) format!("set({});", init)
} else { } else {
let binding_code = format!( let binding_code = format!(
@ -362,10 +375,10 @@ fn handle_item(elem: &ElementRc, main_struct: &mut Struct, init: &mut Vec<String
}}", }}",
init = init init = init
); );
property_set_binding_code(component, &item, s, binding_code) property_set_binding_code(component, &item, prop_name, binding_code)
}; };
init_begin.push(
if let Some(vp) = super::as_flickable_viewport_property(elem, s) { if let Some(vp) = super::as_flickable_viewport_property(elem, prop_name) {
format!( format!(
"{accessor_prefix}viewport.{cpp_prop}.{setter};", "{accessor_prefix}viewport.{cpp_prop}.{setter};",
accessor_prefix = accessor_prefix, accessor_prefix = accessor_prefix,
@ -376,12 +389,13 @@ fn handle_item(elem: &ElementRc, main_struct: &mut Struct, init: &mut Vec<String
format!( format!(
"{accessor_prefix}{cpp_prop}.{setter};", "{accessor_prefix}{cpp_prop}.{setter};",
accessor_prefix = accessor_prefix, accessor_prefix = accessor_prefix,
cpp_prop = s, cpp_prop = prop_name,
setter = setter, setter = setter,
) )
},
);
} }
} }
}));
} }
fn handle_repeater( fn handle_repeater(
@ -523,6 +537,7 @@ fn generate_component(
let is_root = component.parent_element.upgrade().is_none(); let is_root = component.parent_element.upgrade().is_none();
let mut init = vec!["[[maybe_unused]] auto self = this;".into()]; let mut init = vec!["[[maybe_unused]] auto self = this;".into()];
let mut init_last = vec![];
for (cpp_name, property_decl) in component.root_element.borrow().property_declarations.iter() { for (cpp_name, property_decl) in component.root_element.borrow().property_declarations.iter() {
let ty = if let Type::Signal { args } = &property_decl.property_type { let ty = if let Type::Signal { args } = &property_decl.property_type {
@ -762,10 +777,12 @@ fn generate_component(
if super::is_flickable(item_rc) { 1 } else { item.children.len() }, if super::is_flickable(item_rc) { 1 } else { item.children.len() },
children_offset, children_offset,
)); ));
handle_item(item_rc, &mut component_struct, &mut init); handle_item(item_rc, &mut component_struct, &mut init, &mut init_last);
} }
}); });
init.append(&mut init_last);
component_struct.members.push(( component_struct.members.push((
Access::Public, Access::Public,
Declaration::Function(Function { Declaration::Function(Function {

View file

@ -1172,6 +1172,7 @@ fn test_property_listener_scope() {
pub(crate) mod ffi { pub(crate) mod ffi {
use super::*; use super::*;
use core::pin::Pin;
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
type c_void = (); type c_void = ();
@ -1197,10 +1198,18 @@ pub(crate) mod ffi {
} }
/// Mark the fact that the property was changed and that its binding need to be removed, and /// Mark the fact that the property was changed and that its binding need to be removed, and
/// The dependencies marked dirty /// the dependencies marked dirty.
/// To be called after the `value` has been changed
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sixtyfps_property_set_changed(handle: &PropertyHandleOpaque) { pub unsafe extern "C" fn sixtyfps_property_set_changed(
handle: &PropertyHandleOpaque,
value: *const c_void,
) {
if !handle.0.access(|b| {
b.map_or(false, |b| (b.vtable.intercept_set)(&*b as *const BindingHolder, value))
}) {
handle.0.remove_binding(); handle.0.remove_binding();
}
handle.0.mark_dirty(); handle.0.mark_dirty();
} }
@ -1208,11 +1217,17 @@ pub(crate) mod ffi {
binding: extern "C" fn(*mut c_void, *mut c_void), binding: extern "C" fn(*mut c_void, *mut c_void),
user_data: *mut c_void, user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>, drop_user_data: Option<extern "C" fn(*mut c_void)>,
) -> impl Fn(*mut ()) -> BindingResult { intercept_set: Option<
extern "C" fn(user_data: *mut c_void, pointer_to_value: *const c_void) -> bool,
>,
) -> impl BindingCallable {
struct CFunctionBinding<T> { struct CFunctionBinding<T> {
binding_function: extern "C" fn(*mut c_void, *mut T), binding_function: extern "C" fn(*mut c_void, *mut T),
user_data: *mut c_void, user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>, drop_user_data: Option<extern "C" fn(*mut c_void)>,
intercept_set: Option<
extern "C" fn(user_data: *mut c_void, pointer_to_value: *const c_void) -> bool,
>,
} }
impl<T> Drop for CFunctionBinding<T> { impl<T> Drop for CFunctionBinding<T> {
@ -1223,12 +1238,20 @@ pub(crate) mod ffi {
} }
} }
let b = CFunctionBinding { binding_function: binding, user_data, drop_user_data }; impl<T> BindingCallable for CFunctionBinding<T> {
unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult {
move |value_ptr| { (self.binding_function)(self.user_data, value as *mut T);
(b.binding_function)(b.user_data, value_ptr);
BindingResult::KeepBinding BindingResult::KeepBinding
} }
unsafe fn intercept_set(self: Pin<&Self>, value: *const ()) -> bool {
match self.intercept_set {
None => false,
Some(intercept_set) => intercept_set(self.user_data, value),
}
}
}
CFunctionBinding { binding_function: binding, user_data, drop_user_data, intercept_set }
} }
/// Set a binding /// Set a binding
@ -1244,8 +1267,11 @@ pub(crate) mod ffi {
binding: extern "C" fn(user_data: *mut c_void, pointer_to_value: *mut c_void), binding: extern "C" fn(user_data: *mut c_void, pointer_to_value: *mut c_void),
user_data: *mut c_void, user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>, drop_user_data: Option<extern "C" fn(*mut c_void)>,
intercept_set: Option<
extern "C" fn(user_data: *mut c_void, pointer_to_value: *const c_void) -> bool,
>,
) { ) {
let binding = make_c_function_binding(binding, user_data, drop_user_data); let binding = make_c_function_binding(binding, user_data, drop_user_data, intercept_set);
handle.0.set_binding(binding); handle.0.set_binding(binding);
} }
@ -1334,6 +1360,7 @@ pub(crate) mod ffi {
binding, binding,
user_data, user_data,
drop_user_data, drop_user_data,
None,
)) as usize) )) as usize)
| 0b10, | 0b10,
), ),

View file

@ -63,4 +63,31 @@ assert_eq!(instance.get_ti2_text(), sixtyfps::SharedString::from("Bonjour"));
assert_eq!(instance.get_text_item_text(), sixtyfps::SharedString::from("Bonjour")); assert_eq!(instance.get_text_item_text(), sixtyfps::SharedString::from("Bonjour"));
``` ```
```cpp
TestCase instance;
assert_eq(instance.get_text1(), sixtyfps::SharedString("Hello"));
assert_eq(instance.get_text2(), sixtyfps::SharedString("Blah"));
assert_eq(instance.get_ti1_text(), sixtyfps::SharedString("Hello"));
assert_eq(instance.get_ti2_text(), sixtyfps::SharedString("Blah"));
assert_eq(instance.get_text_item_text(), sixtyfps::SharedString("Blah"));
instance.set_text1(sixtyfps::SharedString("Text1New"));
instance.set_text2(sixtyfps::SharedString("Text2New"));
assert_eq(instance.get_text1(), sixtyfps::SharedString("Text1New"));
assert_eq(instance.get_text2(), sixtyfps::SharedString("Text2New"));
assert_eq(instance.get_ti1_text(), sixtyfps::SharedString("Text1New"));
assert_eq(instance.get_ti2_text(), sixtyfps::SharedString("Text2New"));
assert_eq(instance.get_text_item_text(), sixtyfps::SharedString("Text2New"));
instance.emit_set_ti1(sixtyfps::SharedString("Hallo"));
instance.emit_set_ti2(sixtyfps::SharedString("Bonjour"));
assert_eq(instance.get_text1(), sixtyfps::SharedString("Hallo"));
assert_eq(instance.get_text2(), sixtyfps::SharedString("Text2New"));
assert_eq(instance.get_ti1_text(), sixtyfps::SharedString("Hallo"));
assert_eq(instance.get_ti2_text(), sixtyfps::SharedString("Bonjour"));
assert_eq(instance.get_text_item_text(), sixtyfps::SharedString("Bonjour"));
```
*/ */