mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-28 21:04:47 +00:00
runtime: Add a Signal class
Not very functional yet
This commit is contained in:
parent
4f22e2c341
commit
83d206ea39
5 changed files with 158 additions and 2 deletions
33
api/sixtyfps-cpp/include/sixtyfps_signals.h
Normal file
33
api/sixtyfps-cpp/include/sixtyfps_signals.h
Normal 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;
|
||||
};
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
111
sixtyfps_runtime/corelib/abi/signals.rs
Normal file
111
sixtyfps_runtime/corelib/abi/signals.rs
Normal 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<()>);
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue