mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-30 23:27:22 +00:00
458 lines
15 KiB
Rust
458 lines
15 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.0 OR LicenseRef-Slint-commercial
|
|
|
|
use super::*;
|
|
use crate::graphics::{Brush, Color};
|
|
use crate::items::PropertyAnimation;
|
|
use core::pin::Pin;
|
|
|
|
#[allow(non_camel_case_types)]
|
|
type c_void = ();
|
|
#[repr(C)]
|
|
/// Has the same layout as PropertyHandle
|
|
pub struct PropertyHandleOpaque(PropertyHandle);
|
|
|
|
/// Initialize the first pointer of the Property. Does not initialize the content.
|
|
/// `out` is assumed to be uninitialized
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_init(out: *mut PropertyHandleOpaque) {
|
|
core::ptr::write(out, PropertyHandleOpaque(PropertyHandle::default()));
|
|
}
|
|
|
|
/// To be called before accessing the value
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_update(handle: &PropertyHandleOpaque, val: *mut c_void) {
|
|
let handle = Pin::new_unchecked(&handle.0);
|
|
handle.update(val);
|
|
handle.register_as_dependency_to_current_binding();
|
|
}
|
|
|
|
/// Mark the fact that the property was changed and that its binding need to be removed, and
|
|
/// the dependencies marked dirty.
|
|
/// To be called after the `value` has been changed
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_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();
|
|
}
|
|
|
|
fn make_c_function_binding(
|
|
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)>,
|
|
intercept_set: Option<
|
|
extern "C" fn(user_data: *mut c_void, pointer_to_value: *const c_void) -> bool,
|
|
>,
|
|
intercept_set_binding: Option<
|
|
extern "C" fn(user_data: *mut c_void, new_binding: *mut 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>,
|
|
intercept_set_binding:
|
|
Option<extern "C" fn(user_data: *mut c_void, new_binding: *mut c_void) -> bool>,
|
|
}
|
|
|
|
impl<T> Drop for CFunctionBinding<T> {
|
|
fn drop(&mut self) {
|
|
if let Some(x) = self.drop_user_data {
|
|
x(self.user_data)
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe 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),
|
|
}
|
|
}
|
|
unsafe fn intercept_set_binding(self: Pin<&Self>, new_binding: *mut BindingHolder) -> bool {
|
|
match self.intercept_set_binding {
|
|
None => false,
|
|
Some(intercept_set_b) => intercept_set_b(self.user_data, new_binding.cast()),
|
|
}
|
|
}
|
|
}
|
|
|
|
CFunctionBinding {
|
|
binding_function: binding,
|
|
user_data,
|
|
drop_user_data,
|
|
intercept_set,
|
|
intercept_set_binding,
|
|
}
|
|
}
|
|
|
|
/// Set a binding
|
|
///
|
|
/// The current implementation will do usually two memory allocation:
|
|
/// 1. the allocation from the calling code to allocate user_data
|
|
/// 2. the box allocation within this binding
|
|
/// It might be possible to reduce that by passing something with a
|
|
/// vtable, so there is the need for less memory allocation.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_set_binding(
|
|
handle: &PropertyHandleOpaque,
|
|
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,
|
|
>,
|
|
intercept_set_binding: Option<
|
|
extern "C" fn(user_data: *mut c_void, new_binding: *mut c_void) -> bool,
|
|
>,
|
|
) {
|
|
let binding = make_c_function_binding(
|
|
binding,
|
|
user_data,
|
|
drop_user_data,
|
|
intercept_set,
|
|
intercept_set_binding,
|
|
);
|
|
handle.0.set_binding(binding);
|
|
}
|
|
|
|
/// Set a binding using an already allocated building holder
|
|
///
|
|
//// (take ownership of the binding)
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_set_binding_internal(
|
|
handle: &PropertyHandleOpaque,
|
|
binding: *mut c_void,
|
|
) {
|
|
handle.0.set_binding_impl(binding.cast());
|
|
}
|
|
|
|
/// Returns whether the property behind this handle is marked as dirty
|
|
#[no_mangle]
|
|
pub extern "C" fn slint_property_is_dirty(handle: &PropertyHandleOpaque) -> bool {
|
|
handle.0.access(|binding| binding.map_or(false, |b| b.dirty.get()))
|
|
}
|
|
|
|
/// Marks the property as dirty and notifies dependencies.
|
|
#[no_mangle]
|
|
pub extern "C" fn slint_property_mark_dirty(handle: &PropertyHandleOpaque) {
|
|
handle.0.mark_dirty()
|
|
}
|
|
|
|
/// Destroy handle
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_drop(handle: *mut PropertyHandleOpaque) {
|
|
core::ptr::drop_in_place(handle);
|
|
}
|
|
|
|
fn c_set_animated_value<T: InterpolatedPropertyValue + Clone>(
|
|
handle: &PropertyHandleOpaque,
|
|
from: T,
|
|
to: T,
|
|
animation_data: &PropertyAnimation,
|
|
) {
|
|
let d = RefCell::new(properties_animations::PropertyValueAnimationData::new(
|
|
from,
|
|
to,
|
|
animation_data.clone(),
|
|
));
|
|
// Safety: The BindingCallable is for type T
|
|
unsafe {
|
|
handle.0.set_binding(move |val: *mut ()| {
|
|
let (value, finished) = d.borrow_mut().compute_interpolated_value();
|
|
*(val as *mut T) = value;
|
|
if finished {
|
|
BindingResult::RemoveBinding
|
|
} else {
|
|
crate::animations::CURRENT_ANIMATION_DRIVER
|
|
.with(|driver| driver.set_has_active_animations());
|
|
BindingResult::KeepBinding
|
|
}
|
|
})
|
|
};
|
|
handle.0.mark_dirty();
|
|
}
|
|
|
|
/// Internal function to set up a property animation to the specified target value for an integer property.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_set_animated_value_int(
|
|
handle: &PropertyHandleOpaque,
|
|
from: i32,
|
|
to: i32,
|
|
animation_data: &PropertyAnimation,
|
|
) {
|
|
c_set_animated_value(handle, from, to, animation_data)
|
|
}
|
|
|
|
/// Internal function to set up a property animation to the specified target value for a float property.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_set_animated_value_float(
|
|
handle: &PropertyHandleOpaque,
|
|
from: f32,
|
|
to: f32,
|
|
animation_data: &PropertyAnimation,
|
|
) {
|
|
c_set_animated_value(handle, from, to, animation_data)
|
|
}
|
|
|
|
/// Internal function to set up a property animation to the specified target value for a color property.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_set_animated_value_color(
|
|
handle: &PropertyHandleOpaque,
|
|
from: Color,
|
|
to: Color,
|
|
animation_data: &PropertyAnimation,
|
|
) {
|
|
c_set_animated_value(handle, from, to, animation_data);
|
|
}
|
|
|
|
unsafe fn c_set_animated_binding<T: InterpolatedPropertyValue + Clone>(
|
|
handle: &PropertyHandleOpaque,
|
|
binding: extern "C" fn(*mut c_void, *mut T),
|
|
user_data: *mut c_void,
|
|
drop_user_data: Option<extern "C" fn(*mut c_void)>,
|
|
animation_data: Option<&PropertyAnimation>,
|
|
transition_data: Option<
|
|
extern "C" fn(user_data: *mut c_void, start_instant: &mut u64) -> PropertyAnimation,
|
|
>,
|
|
) {
|
|
let binding = core::mem::transmute::<
|
|
extern "C" fn(*mut c_void, *mut T),
|
|
extern "C" fn(*mut c_void, *mut ()),
|
|
>(binding);
|
|
let original_binding = PropertyHandle {
|
|
handle: Cell::new(
|
|
(alloc_binding_holder(make_c_function_binding(
|
|
binding,
|
|
user_data,
|
|
drop_user_data,
|
|
None,
|
|
None,
|
|
)) as usize)
|
|
| 0b10,
|
|
),
|
|
};
|
|
let animation_data = RefCell::new(properties_animations::PropertyValueAnimationData::new(
|
|
T::default(),
|
|
T::default(),
|
|
animation_data.cloned().unwrap_or_default(),
|
|
));
|
|
if let Some(transition_data) = transition_data {
|
|
handle.0.set_binding(properties_animations::AnimatedBindingCallable::<T, _> {
|
|
original_binding,
|
|
state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating),
|
|
animation_data,
|
|
compute_animation_details: move || -> properties_animations::AnimationDetail {
|
|
let mut start_instant = 0;
|
|
let anim = transition_data(user_data, &mut start_instant);
|
|
Some((anim, crate::animations::Instant(start_instant)))
|
|
},
|
|
});
|
|
} else {
|
|
handle.0.set_binding(properties_animations::AnimatedBindingCallable::<T, _> {
|
|
original_binding,
|
|
state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating),
|
|
animation_data,
|
|
compute_animation_details: || -> properties_animations::AnimationDetail { None },
|
|
});
|
|
}
|
|
handle.0.mark_dirty();
|
|
}
|
|
|
|
/// Internal function to set up a property animation between values produced by the specified binding for an integer property.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_set_animated_binding_int(
|
|
handle: &PropertyHandleOpaque,
|
|
binding: extern "C" fn(*mut c_void, *mut i32),
|
|
user_data: *mut c_void,
|
|
drop_user_data: Option<extern "C" fn(*mut c_void)>,
|
|
animation_data: Option<&PropertyAnimation>,
|
|
transition_data: Option<
|
|
extern "C" fn(user_data: *mut c_void, start_instant: &mut u64) -> PropertyAnimation,
|
|
>,
|
|
) {
|
|
c_set_animated_binding(
|
|
handle,
|
|
binding,
|
|
user_data,
|
|
drop_user_data,
|
|
animation_data,
|
|
transition_data,
|
|
);
|
|
}
|
|
|
|
/// Internal function to set up a property animation between values produced by the specified binding for a float property.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_set_animated_binding_float(
|
|
handle: &PropertyHandleOpaque,
|
|
binding: extern "C" fn(*mut c_void, *mut f32),
|
|
user_data: *mut c_void,
|
|
drop_user_data: Option<extern "C" fn(*mut c_void)>,
|
|
animation_data: Option<&PropertyAnimation>,
|
|
transition_data: Option<
|
|
extern "C" fn(user_data: *mut c_void, start_instant: &mut u64) -> PropertyAnimation,
|
|
>,
|
|
) {
|
|
c_set_animated_binding(
|
|
handle,
|
|
binding,
|
|
user_data,
|
|
drop_user_data,
|
|
animation_data,
|
|
transition_data,
|
|
);
|
|
}
|
|
|
|
/// Internal function to set up a property animation between values produced by the specified binding for a color property.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_set_animated_binding_color(
|
|
handle: &PropertyHandleOpaque,
|
|
binding: extern "C" fn(*mut c_void, *mut Color),
|
|
user_data: *mut c_void,
|
|
drop_user_data: Option<extern "C" fn(*mut c_void)>,
|
|
animation_data: Option<&PropertyAnimation>,
|
|
transition_data: Option<
|
|
extern "C" fn(user_data: *mut c_void, start_instant: &mut u64) -> PropertyAnimation,
|
|
>,
|
|
) {
|
|
c_set_animated_binding(
|
|
handle,
|
|
binding,
|
|
user_data,
|
|
drop_user_data,
|
|
animation_data,
|
|
transition_data,
|
|
);
|
|
}
|
|
|
|
/// Internal function to set up a property animation between values produced by the specified binding for a brush property.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_set_animated_binding_brush(
|
|
handle: &PropertyHandleOpaque,
|
|
binding: extern "C" fn(*mut c_void, *mut Brush),
|
|
user_data: *mut c_void,
|
|
drop_user_data: Option<extern "C" fn(*mut c_void)>,
|
|
animation_data: Option<&PropertyAnimation>,
|
|
transition_data: Option<
|
|
extern "C" fn(user_data: *mut c_void, start_instant: &mut u64) -> PropertyAnimation,
|
|
>,
|
|
) {
|
|
c_set_animated_binding(
|
|
handle,
|
|
binding,
|
|
user_data,
|
|
drop_user_data,
|
|
animation_data,
|
|
transition_data,
|
|
);
|
|
}
|
|
|
|
/// Internal function to set up a state binding on a Property<StateInfo>.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_set_state_binding(
|
|
handle: &PropertyHandleOpaque,
|
|
binding: extern "C" fn(*mut c_void) -> i32,
|
|
user_data: *mut c_void,
|
|
drop_user_data: Option<extern "C" fn(*mut c_void)>,
|
|
) {
|
|
struct CStateBinding {
|
|
binding: extern "C" fn(*mut c_void) -> i32,
|
|
user_data: *mut c_void,
|
|
drop_user_data: Option<extern "C" fn(*mut c_void)>,
|
|
}
|
|
|
|
impl Drop for CStateBinding {
|
|
fn drop(&mut self) {
|
|
if let Some(x) = self.drop_user_data {
|
|
x(self.user_data)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CStateBinding {
|
|
fn call(&self) -> i32 {
|
|
(self.binding)(self.user_data)
|
|
}
|
|
}
|
|
|
|
let c_state_binding = CStateBinding { binding, user_data, drop_user_data };
|
|
let bind_callable =
|
|
StateInfoBinding { dirty_time: Cell::new(None), binding: move || c_state_binding.call() };
|
|
handle.0.set_binding(bind_callable)
|
|
}
|
|
|
|
#[repr(C)]
|
|
/// Opaque type representing the PropertyTracker
|
|
pub struct PropertyTrackerOpaque {
|
|
dependencies: usize,
|
|
dep_nodes: usize,
|
|
vtable: usize,
|
|
dirty: bool,
|
|
}
|
|
|
|
static_assertions::assert_eq_align!(PropertyTrackerOpaque, PropertyTracker);
|
|
static_assertions::assert_eq_size!(PropertyTrackerOpaque, PropertyTracker);
|
|
|
|
/// Initialize the first pointer of the PropertyTracker.
|
|
/// `out` is assumed to be uninitialized
|
|
/// slint_property_tracker_drop need to be called after that
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_tracker_init(out: *mut PropertyTrackerOpaque) {
|
|
core::ptr::write(out as *mut PropertyTracker, PropertyTracker::default());
|
|
}
|
|
|
|
/// Call the callback with the user data. Any properties access within the callback will be registered.
|
|
/// Any currently evaluated bindings or property trackers will be notified if accessed properties are changed.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_tracker_evaluate(
|
|
handle: *const PropertyTrackerOpaque,
|
|
callback: extern "C" fn(user_data: *mut c_void),
|
|
user_data: *mut c_void,
|
|
) {
|
|
Pin::new_unchecked(&*(handle as *const PropertyTracker)).evaluate(|| callback(user_data))
|
|
}
|
|
|
|
/// Call the callback with the user data. Any properties access within the callback will be registered.
|
|
/// Any currently evaluated bindings or property trackers will be not notified if accessed properties are changed.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_tracker_evaluate_as_dependency_root(
|
|
handle: *const PropertyTrackerOpaque,
|
|
callback: extern "C" fn(user_data: *mut c_void),
|
|
user_data: *mut c_void,
|
|
) {
|
|
Pin::new_unchecked(&*(handle as *const PropertyTracker))
|
|
.evaluate_as_dependency_root(|| callback(user_data))
|
|
}
|
|
/// Query if the property tracker is dirty
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_tracker_is_dirty(
|
|
handle: *const PropertyTrackerOpaque,
|
|
) -> bool {
|
|
(*(handle as *const PropertyTracker)).is_dirty()
|
|
}
|
|
|
|
/// Destroy handle
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn slint_property_tracker_drop(handle: *mut PropertyTrackerOpaque) {
|
|
core::ptr::drop_in_place(handle as *mut PropertyTracker);
|
|
}
|
|
|
|
/// return the current animation tick for the `animation-tick` function
|
|
#[no_mangle]
|
|
pub extern "C" fn slint_animation_tick() -> u64 {
|
|
crate::animations::animation_tick()
|
|
}
|