mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-02 14:51:15 +00:00
C++ two way bindings
This commit is contained in:
parent
cc3ac5fbed
commit
abe24e2e9e
5 changed files with 149 additions and 46 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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"));
|
||||
```
|
||||
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue