diff --git a/CHANGELOG.md b/CHANGELOG.md index 2be60d4830b..6387c6cb2b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ All notable changes to this project will be documented in this file. ### Added + - One can now set an alias from the root to a global callback + ### Fixed ## [0.1.1] - 2021-08-19 @@ -25,7 +27,7 @@ All notable changes to this project will be documented in this file. preserved. For fields in structures they are normalized. - Show a compilation error when there are duplicated element ids. - The `clip` property can now be any expression. - + ### Added - `ComboBox` now has a `selected` callback. diff --git a/docs/langref.md b/docs/langref.md index 46b7c308f64..bd6bd53ffb7 100644 --- a/docs/langref.md +++ b/docs/langref.md @@ -726,6 +726,31 @@ Example := Rectangle { } ``` +It is possible to re-expose a callback or properties from a global using the two way binding syntax. + +```60 +global Logic := { + property the-value; + callback magic-operation(int) -> int; +} + +SomeComponent := Text { + // use the global in any component + text: "The magic value is:" + Logic.magic-operation(42); +} + +MainWindow := Window { + // re-expose the global properties such that the native code + // can access or modify them + property the-value <=> Logic.the-value; + callback magic-operation <=> Logic.magic-operation; + + SomeComponent {} +} +``` + +A global can be declared in another module file, and imported from many files. + ## Modules Components declared in a .60 file can be shared with components in other .60 files, by means of exporting and importing them. diff --git a/sixtyfps_compiler/generator/cpp.rs b/sixtyfps_compiler/generator/cpp.rs index 5a245818307..18bd1032ff7 100644 --- a/sixtyfps_compiler/generator/cpp.rs +++ b/sixtyfps_compiler/generator/cpp.rs @@ -372,6 +372,9 @@ fn handle_property_binding( }; let prop_type = item.lookup_property(prop_name).property_type; if let Type::Callback { args, .. } = &prop_type { + if matches!(binding_expression.expression, Expression::Invalid) { + return; + } let mut params = args.iter().enumerate().map(|(i, ty)| { format!("[[maybe_unused]] {} arg_{}", ty.cpp_type().unwrap_or_default(), i) }); diff --git a/sixtyfps_compiler/generator/rust.rs b/sixtyfps_compiler/generator/rust.rs index 66120ec4a5e..58cb0ee3831 100644 --- a/sixtyfps_compiler/generator/rust.rs +++ b/sixtyfps_compiler/generator/rust.rs @@ -189,6 +189,9 @@ fn handle_property_binding( }; if matches!(prop_type, Type::Callback { .. }) { + if matches!(binding_expression.expression, Expression::Invalid) { + return; + } let tokens_for_expression = compile_expression(binding_expression, component); init.push(quote!({ sixtyfps::internal::set_callback_handler(#rust_property, &self_rc, { diff --git a/sixtyfps_compiler/passes/remove_aliases.rs b/sixtyfps_compiler/passes/remove_aliases.rs index 1f3fdc04a52..1d7a8c1f23d 100644 --- a/sixtyfps_compiler/passes/remove_aliases.rs +++ b/sixtyfps_compiler/passes/remove_aliases.rs @@ -29,13 +29,14 @@ impl PropertySets { if !std::rc::Weak::ptr_eq( &p1.element().borrow().enclosing_component, &p2.element().borrow().enclosing_component, - ) { - // We can only merge aliases if they are in the same Component. + ) && (p1.element().borrow().enclosing_component.upgrade().unwrap().is_global() + == p2.element().borrow().enclosing_component.upgrade().unwrap().is_global()) + { + // We can only merge aliases if they are in the same Component. (unless one of them is global) // TODO: actually we could still merge two alias in a component pointing to the same // property in a parent component return; } - if let Some(s1) = self.map.get(&p1).cloned() { if let Some(s2) = self.map.get(&p2).cloned() { if Rc::ptr_eq(&s1, &s2) { diff --git a/sixtyfps_runtime/interpreter/global_component.rs b/sixtyfps_runtime/interpreter/global_component.rs index 313caa7aefa..140759c3386 100644 --- a/sixtyfps_runtime/interpreter/global_component.rs +++ b/sixtyfps_runtime/interpreter/global_component.rs @@ -24,9 +24,7 @@ pub enum CompiledGlobal { } pub trait GlobalComponent { - fn invoke_callback(self: Pin<&Self>, _callback_name: &str, _args: &[Value]) -> Value { - todo!("call callback") - } + fn invoke_callback(self: Pin<&Self>, callback_name: &str, args: &[Value]) -> Value; fn set_property(self: Pin<&Self>, prop_name: &str, value: Value); fn get_property(self: Pin<&Self>, prop_name: &str) -> Value; @@ -101,6 +99,12 @@ impl GlobalComponent for GlobalComponentInstance { comp.borrow_instance(), ) } + + fn invoke_callback(self: Pin<&Self>, callback_name: &str, args: &[Value]) -> Value { + generativity::make_guard!(guard); + let comp = self.0.unerase(guard); + comp.description().invoke_callback(comp.borrow(), callback_name, args).unwrap() + } } impl GlobalComponent for T { @@ -119,6 +123,11 @@ impl GlobalComponent for T { Self::properties().into_iter().find(|(k, _)| *k == prop_name).unwrap().1; unsafe { (self.get_ref() as *const Self as *const u8).add(prop.offset()) as *const () } } + + fn invoke_callback(self: Pin<&Self>, callback_name: &str, args: &[Value]) -> Value { + let cb = Self::callbacks().into_iter().find(|(k, _)| *k == callback_name).unwrap().1; + cb.call(self, args).unwrap() + } } pub(crate) fn generate(component: &Rc) -> CompiledGlobal { diff --git a/tests/cases/globals/global_callback.60 b/tests/cases/globals/global_callback.60 new file mode 100644 index 00000000000..c4ce331c46f --- /dev/null +++ b/tests/cases/globals/global_callback.60 @@ -0,0 +1,63 @@ +/* LICENSE BEGIN + This file is part of the SixtyFPS Project -- https://sixtyfps.io + Copyright (c) 2021 Olivier Goffart + Copyright (c) 2021 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 */ + +global Glo := { + property hello: 42; + callback sum(int, int) -> int; + callback mul(int, int) -> int; +} + +ExtraComp := Rectangle { + property five: Glo.sum(3, 2); + property six: Glo.mul(3, 2); +} + + +TestCase := Window { + callback sum <=> Glo.sum; + callback mul <=> Glo.mul; + + x := ExtraComp {} + property five: x.five; + property six: x.six; + + //mul(α, β) => { return α * β; } + property test: five == 0; // because the callback is not set + +} + +/* +```rust +let instance = TestCase::new(); +instance.on_sum(|a, b| a + b); +instance.on_mul(|a, b| a * b); +assert_eq!(instance.get_five(), 5); +assert_eq!(instance.get_six(), 6); +``` + +```cpp +auto handle = TestCase::create(); +const TestCase &instance = *handle; +instance.on_sum([](int a, int b) { return a + b; }); +instance.on_mul([](int a, int b) { return a * b; }); +assert_eq(instance.get_five(), 5); +assert_eq(instance.get_six(), 6); +``` + +```js +let instance = new sixtyfps.TestCase({ + sum: function(a, b) { return a + b; }, + mul: function(a, b) { return a * b; } +}); +assert.equal(instance.five, 5); +assert.equal(instance.six, 6); +``` + +*/