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);
};
}
} else {
return KeyEventResult::EventIgnored;
}
return KeyEventResult::EventIgnored;
}
template<typename GetDynamic>
@ -247,6 +246,7 @@ inline FocusEventResult process_focus_event(ComponentRef component, int64_t &foc
return FocusEventResult::FocusItemNotFound;
}
}
return FocusEventResult::FocusItemNotFound;
}
}

View file

@ -9,6 +9,7 @@
LICENSE END */
#pragma once
#include <string_view>
#include <memory>
namespace sixtyfps {
namespace cbindgen_private {
@ -41,7 +42,7 @@ struct Property
void set(const T &value) const
{
this->value = value;
cbindgen_private::sixtyfps_property_set_changed(&inner);
cbindgen_private::sixtyfps_property_set_changed(&inner, &this->value);
}
const T &get() const
@ -58,7 +59,8 @@ struct Property
[](void *user_data, void *value) {
*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,
@ -69,6 +71,36 @@ struct Property
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:
cbindgen_private::PropertyHandleOpaque inner;
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();
main_struct.members.push((
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;
init.extend(item.bindings.iter().map(|(s, i)| {
if let Type::Signal { args } = item.lookup_property(s.as_str()) {
let signal_accessor_prefix = if item.property_declarations.contains_key(s) {
for (prop_name, binding_expression) in &item.bindings {
let prop_ty = item.lookup_property(prop_name.as_str());
if let Type::Signal { args } = &prop_ty {
let signal_accessor_prefix = if item.property_declarations.contains_key(prop_name) {
String::new()
} else {
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!(
init_begin.push(format!(
"{signal_accessor_prefix}{prop}.set_handler(
[this]({params}) {{
[[maybe_unused]] auto self = this;
{code};
}});",
signal_accessor_prefix = signal_accessor_prefix,
prop = s,
prop = prop_name,
params = params.join(", "),
code = compile_expression(i, &item.enclosing_component.upgrade().unwrap())
)
} else if let Expression::TwoWayBinding(_nr) = &i.expression {
"std::printf(\"Two way binding not implemented in C++\");".into()
code = compile_expression(binding_expression, &component)
));
} else if let Expression::TwoWayBinding(nr) = &binding_expression.expression {
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 {
let accessor_prefix = if item.property_declarations.contains_key(s) {
let accessor_prefix = if item.property_declarations.contains_key(prop_name) {
String::new()
} else {
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 init = compile_expression(i, component);
let setter = if i.is_constant() {
let init = compile_expression(binding_expression, component);
let setter = if binding_expression.is_constant() {
format!("set({});", init)
} else {
let binding_code = format!(
@ -362,10 +375,10 @@ fn handle_item(elem: &ElementRc, main_struct: &mut Struct, init: &mut Vec<String
}}",
init = init
);
property_set_binding_code(component, &item, s, binding_code)
property_set_binding_code(component, &item, prop_name, binding_code)
};
if let Some(vp) = super::as_flickable_viewport_property(elem, s) {
init_begin.push(
if let Some(vp) = super::as_flickable_viewport_property(elem, prop_name) {
format!(
"{accessor_prefix}viewport.{cpp_prop}.{setter};",
accessor_prefix = accessor_prefix,
@ -376,12 +389,13 @@ fn handle_item(elem: &ElementRc, main_struct: &mut Struct, init: &mut Vec<String
format!(
"{accessor_prefix}{cpp_prop}.{setter};",
accessor_prefix = accessor_prefix,
cpp_prop = s,
cpp_prop = prop_name,
setter = setter,
)
},
);
}
}
}));
}
fn handle_repeater(
@ -523,6 +537,7 @@ fn generate_component(
let is_root = component.parent_element.upgrade().is_none();
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() {
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() },
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((
Access::Public,
Declaration::Function(Function {

View file

@ -1172,6 +1172,7 @@ fn test_property_listener_scope() {
pub(crate) mod ffi {
use super::*;
use core::pin::Pin;
#[allow(non_camel_case_types)]
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
/// The dependencies marked dirty
/// the dependencies marked dirty.
/// To be called after the `value` has been changed
#[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.mark_dirty();
}
@ -1208,11 +1217,17 @@ pub(crate) mod ffi {
binding: extern "C" fn(*mut c_void, *mut c_void),
user_data: *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> {
binding_function: extern "C" fn(*mut c_void, *mut T),
user_data: *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> {
@ -1223,12 +1238,20 @@ pub(crate) mod ffi {
}
}
let b = CFunctionBinding { binding_function: binding, user_data, drop_user_data };
move |value_ptr| {
(b.binding_function)(b.user_data, value_ptr);
impl<T> BindingCallable for CFunctionBinding<T> {
unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult {
(self.binding_function)(self.user_data, value as *mut T);
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
@ -1244,8 +1267,11 @@ pub(crate) mod ffi {
binding: extern "C" fn(user_data: *mut c_void, pointer_to_value: *mut c_void),
user_data: *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);
}
@ -1334,6 +1360,7 @@ pub(crate) mod ffi {
binding,
user_data,
drop_user_data,
None,
)) as usize)
| 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"));
```
```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"));
```
*/