runtime: Add a Signal class

Not very functional yet
This commit is contained in:
Olivier Goffart 2020-05-20 15:22:07 +02:00
parent 4f22e2c341
commit 83d206ea39
5 changed files with 158 additions and 2 deletions

View file

@ -0,0 +1,33 @@
#pragma once
#include <string_view>
#include "sixtyfps_signals_internal.h"
namespace sixtyfps {
// template<typename... Args>
struct Signal
{
Signal() { internal::sixtyfps_signal_init(&inner); }
~Signal() { internal::sixtyfps_signal_drop(&inner); }
Signal(const Signal &) = delete;
Signal(Signal &&) = delete;
Signal &operator=(const Signal &) = delete;
template<typename F>
void set_handler(F binding)
{
internal::sixtyfps_signal_set_handler(
&inner,
[](const void *user_data, const void *value) {
(*reinterpret_cast<F *>(user_data))(value);
},
new F(binding),
[](const void *user_data) { delete reinterpret_cast<F *>(user_data); });
}
void emit(void *data) const { internal::sixtyfps_signal_emit(&inner, data); }
private:
internal::SignalOpaque inner;
};
}

View file

@ -17,7 +17,7 @@ When adding an item or a property, it needs to be kept in sync with different pl
use super::datastructures::{
CachedRenderingData, Item, ItemConsts, ItemVTable, LayoutInfo, Rect, RenderingInfo,
};
use crate::{Property, SharedString};
use crate::{Property, SharedString, Signal};
use vtable::HasStaticVTable;
#[repr(C)]
@ -143,6 +143,7 @@ pub struct TouchArea {
pub height: Property<f32>,
/// FIXME: We should anotate this as an "output" property
pub pressed: Property<bool>,
pub clicked: Signal<()>,
/// FIXME: remove this
pub cached_rendering_data: CachedRenderingData,
}

View file

@ -0,0 +1,111 @@
/*!
Signal that can be connected to one sigle handler.
TODO: reconsider if we should rename that to `Event`
but then it should also be renamed everywhere, including in the language grammar
*/
use core::any::Any;
/// A Signal that can be connected to a handler.
///
/// The Arg represents the argument. It should always be a tuple
///
#[derive(Default)]
#[repr(C)]
pub struct Signal<Arg> {
/// FIXME: Box<dyn> is a fat object and we probaly want to put an erased type in there
handler: Option<Box<dyn Fn(&dyn Any, Arg)>>,
}
impl<Arg> Signal<Arg> {
pub fn emit(&self, context: &dyn Any, a: Arg) {
if let Some(h) = &self.handler {
h(context, a);
}
}
pub fn set_handler(&mut self, f: impl Fn(&dyn Any, Arg) + 'static) {
self.handler = Some(Box::new(f));
}
}
#[test]
fn signal_simple_test() {
#[derive(Default)]
struct Component {
pressed: core::cell::Cell<bool>,
clicked: Signal<()>,
}
let mut c = Component::default();
c.clicked.set_handler(|c, ()| {
c.downcast_ref::<Component>().unwrap().pressed.set(true);
});
c.clicked.emit(&c, ());
assert_eq!(c.pressed.get(), true);
}
#[allow(non_camel_case_types)]
type c_void = ();
#[repr(C)]
/// Has the same layout as Signal<()>
pub struct SignalOpaque(*const c_void, *const c_void);
/// Initialize the signal.
/// sixtyfps_signal_drop must be called.
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_signal_init(out: *mut SignalOpaque) {
assert_eq!(core::mem::size_of::<SignalOpaque>(), core::mem::size_of::<Signal<()>>());
core::ptr::write(out as *mut Signal<()>, Default::default());
}
/// Emit the signal
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_signal_emit(sig: *const SignalOpaque, component: *const c_void) {
let sig = &*(sig as *const Signal<()>);
let context = &*component;
sig.emit(context, ());
}
/// Set signal handler.
///
/// The binding has signature fn(user_data, component_data)
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_signal_set_handler(
sig: *mut SignalOpaque,
binding: extern "C" fn(*mut c_void, *const c_void),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
) {
let sig = &mut *(sig as *mut Signal<()>);
struct UserData {
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
}
impl Drop for UserData {
fn drop(&mut self) {
if let Some(x) = self.drop_user_data {
x(self.user_data)
}
}
}
let ud = UserData { user_data, drop_user_data };
#[repr(C)]
struct TraitObject(*const c_void, *const c_void);
let real_binding = move |compo: &dyn Any, ()| {
let to = core::mem::transmute::<&dyn Any, TraitObject>(compo);
binding(ud.user_data, to.0);
};
sig.set_handler(real_binding);
}
/// Destroy signal
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_signal_drop(handle: *mut SignalOpaque) {
core::ptr::read(handle as *mut Signal<()>);
}

View file

@ -36,11 +36,17 @@ fn main() {
cbindgen::Builder::new()
.with_config(config.clone())
.with_src(format!("{}/abi/properties.rs", crate_dir))
// .with_after_include("namespace sixtyfps { struct SharedString; }")
.generate()
.expect("Unable to generate bindings")
.write_to_file(env::var("OUT_DIR").unwrap() + "/sixtyfps_properties_internal.h");
cbindgen::Builder::new()
.with_config(config.clone())
.with_src(format!("{}/abi/signals.rs", crate_dir))
.generate()
.expect("Unable to generate bindings")
.write_to_file(env::var("OUT_DIR").unwrap() + "/sixtyfps_signals_internal.h");
cbindgen::Builder::new()
.with_config(config)
.with_src(format!("{}/abi/datastructures.rs", crate_dir))
@ -49,6 +55,7 @@ fn main() {
.with_include("vtable.h")
.with_include("sixtyfps_string.h")
.with_include("sixtyfps_properties.h")
.with_include("sixtyfps_signals.h")
.generate()
.expect("Unable to generate bindings")
.write_to_file(env::var("OUT_DIR").unwrap() + "/sixtyfps_internal.h");

View file

@ -7,6 +7,7 @@ pub mod abi {
pub mod model;
pub mod primitives;
pub mod properties;
pub mod signals;
pub mod string;
}
@ -16,6 +17,9 @@ pub use abi::string::SharedString;
#[doc(inline)]
pub use abi::properties::Property;
#[doc(inline)]
pub use abi::signals::Signal;
mod item_rendering;
pub struct MainWindow<GraphicsBackend>