// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 /*! This module enable runtime type information for the builtin items and property so that the viewer can handle them */ #![allow(clippy::result_unit_err)] // We have nothing better to report pub type FieldOffset = const_field_offset::FieldOffset; use crate::Property; use crate::items::PropertyAnimation; use crate::properties::InterpolatedPropertyValue; use alloc::boxed::Box; use alloc::rc::Rc; use alloc::vec::Vec; use core::convert::{TryFrom, TryInto}; use core::ffi::c_void; use core::pin::Pin; macro_rules! declare_ValueType { ($($ty:ty,)*) => { pub trait ValueType: 'static + PartialEq + Default + Clone $(+ TryInto<$ty> + TryFrom<$ty>)* {} }; } macro_rules! declare_ValueType_2 { ($( $(#[$enum_doc:meta])* $vis:vis enum $Name:ident { $($body:tt)* })*) => { declare_ValueType![ (), bool, u32, u64, i32, i64, f32, f64, crate::SharedString, crate::graphics::Image, crate::Color, crate::PathData, crate::animations::EasingCurve, crate::items::StandardListViewItem, crate::items::TableColumn, crate::input::KeyEvent, crate::Brush, crate::graphics::Point, crate::items::PointerEvent, crate::items::PointerScrollEvent, crate::lengths::LogicalLength, crate::lengths::LogicalPoint, crate::lengths::LogicalSize, crate::lengths::LogicalEdges, crate::component_factory::ComponentFactory, crate::api::LogicalPosition, crate::items::FontMetrics, crate::items::InputMethodHints, crate::items::MenuEntry, crate::items::DropEvent, crate::model::ModelRc, crate::styled_text::StyledText, crate::input::Keys, crate::data_transfer::DataTransfer, crate::cursor::MouseCursorInner, $(crate::items::$Name,)* ]; }; } i_slint_common::for_each_enums!(declare_ValueType_2); /// What kind of animation is on a binding pub enum AnimatedBindingKind { /// No animation is on the binding NotAnimated, /// Single animation Animation(Box PropertyAnimation>), /// Transition Transition(Box (PropertyAnimation, crate::animations::Instant)>), } impl AnimatedBindingKind { /// return a PropertyAnimation if self contains AnimatedBindingKind::Animation pub fn as_animation(self) -> Option { match self { AnimatedBindingKind::NotAnimated => None, AnimatedBindingKind::Animation(a) => Some(a()), AnimatedBindingKind::Transition(_) => None, } } } pub trait TwoWayBindingMapping { fn map_to(&self, value: &Value) -> Value; fn map_from(&self, value: &mut Value, from: &Value); } impl Value, F2: Fn(&mut Value, &Value)> TwoWayBindingMapping for (F1, F2) { fn map_to(&self, value: &Value) -> Value { (self.0)(value) } fn map_from(&self, value: &mut Value, from: &Value) { (self.1)(value, from) } } pub trait PropertyInfo { fn get(&self, item: Pin<&Item>) -> Result; fn set( &self, item: Pin<&Item>, value: Value, animation: Option, ) -> Result<(), ()>; fn set_binding( &self, item: Pin<&Item>, binding: Box Value>, animation: AnimatedBindingKind, ) -> Result<(), ()>; /// The offset of the property in the item. /// The use of this is unsafe fn offset(&self) -> usize; /// Returns self. This is just a trick to get auto-deref specialization of /// MaybeAnimatedPropertyInfoWrapper working. fn as_property_info(&'static self) -> &'static dyn PropertyInfo where Self: Sized, { self } /// Calls Property::link_two_ways with the property represented here and the property pointer /// /// # Safety /// the property2 must be a pinned pointer to a Property of the same type #[allow(unsafe_code)] unsafe fn link_two_ways(&self, item: Pin<&Item>, property2: *const c_void); /// Set the debug name of this property (only effective with `cfg(slint_debug_property)`) #[cfg(slint_debug_property)] fn set_debug_name(&self, _item: Pin<&Item>, _name: alloc::string::String) {} /// Prepare the property for two way binding and return the "common" shared property in the TwoWayBinding /// /// Every call for the same property must hand out a property backed by that one /// common property, otherwise a later call detaches what an earlier one linked. fn prepare_for_two_way_binding(&self, item: Pin<&Item>) -> Pin>>; /// Link another property to this property with a mapping function /// /// if the mapper is None, it uses the identity mapping fn link_two_way_with_map( &self, item: Pin<&Item>, property2: Pin>>, mapper: Option>>, ); /// Install a two-way binding between this property and a row of a model. /// `getter` reads the current row value (or `None` if the row no longer /// exists); `setter` writes a new value back into the row. fn link_two_way_to_model_data( &self, item: Pin<&Item>, getter: Box Option>, setter: Box, ); } // The two helpers below only depend on `Value`, so they are kept out of the // generic `PropertyInfo` impl: there they would be instantiated once per // item property type instead of once. /// Returns the common property of `p`, installing a two-way binding on it first if needed. /// /// link_two_way installs a TwoWayBinding on p (moving any /// existing binding into the shared common property), which /// check_common_property finds on subsequent calls. This keeps /// prepare_for_two_way_binding idempotent: when several fields of /// the same struct property are two-way bound, they must all share /// one common property instead of each creating its own. fn common_property( p: Pin<&Property>, ) -> Pin>> { if let Some(cp) = Property::check_common_property(p) { return cp; } let anchor = Rc::pin(Property::::default()); Property::link_two_way(anchor.as_ref(), p); Property::check_common_property(p).unwrap() } /// Links `prop2` to the common property `prop1`, through `mapper` when given. fn link_common_properties( prop1: Pin>>, prop2: Pin>>, mapper: Option>>, ) { match mapper { Some(m1) => { let m2 = m1.clone(); Property::link_two_way_with_map_to_common_property( prop2, prop1.as_ref(), move |value| m1.map_to(value), move |value, value2| m2.map_from(value, value2), true, ); } None => { Property::link_two_way_with_map_to_common_property( prop2, prop1.as_ref(), |value| value.clone(), |value, value2| *value = value2.clone(), true, ); } } } impl PropertyInfo for FieldOffset> where Value: TryInto + Clone + PartialEq + Default + 'static, T: TryInto + Clone + PartialEq + Default + 'static, { fn get(&self, item: Pin<&Item>) -> Result { self.apply_pin(item).get().try_into().map_err(|_| ()) } fn set( &self, item: Pin<&Item>, value: Value, animation: Option, ) -> Result<(), ()> { if animation.is_some() { Err(()) } else { self.apply_pin(item).set(value.try_into().map_err(|_| ())?); Ok(()) } } fn set_binding( &self, item: Pin<&Item>, binding: Box Value>, animation: AnimatedBindingKind, ) -> Result<(), ()> { if !matches!(animation, AnimatedBindingKind::NotAnimated) { Err(()) } else { self.apply_pin(item).set_binding(move || { binding().try_into().map_err(|_| ()).expect("binding was of the wrong type") }); Ok(()) } } fn offset(&self) -> usize { self.get_byte_offset() } #[cfg(slint_debug_property)] fn set_debug_name(&self, item: Pin<&Item>, name: alloc::string::String) { self.apply_pin(item).debug_name.replace(name); } #[allow(unsafe_code)] unsafe fn link_two_ways(&self, item: Pin<&Item>, property2: *const c_void) { let p1 = self.apply_pin(item); // Safety: that's the invariant of this function let p2 = unsafe { Pin::new_unchecked((property2 as *const Property).as_ref().unwrap()) }; Property::link_two_way(p1, p2); } fn prepare_for_two_way_binding(&self, item: Pin<&Item>) -> Pin>> { if let Some(self_) = (self as &dyn core::any::Any).downcast_ref::>>() { return common_property(self_.apply_pin(item)); } let shared_property = Rc::pin(Property::::default()); Property::link_two_way_with_map( self.apply_pin(item), shared_property.as_ref(), |v| v.clone().try_into().unwrap_or_default(), |v, v2| *v = v2.clone().try_into().unwrap_or_default(), ); shared_property } fn link_two_way_with_map( &self, item: Pin<&Item>, prop2: Pin>>, mapper: Option>>, ) { let prop1 = self.prepare_for_two_way_binding(item); link_common_properties(prop1, prop2, mapper); } fn link_two_way_to_model_data( &self, item: Pin<&Item>, getter: Box Option>, setter: Box, ) { self.apply_pin(item).link_two_way_to_model_data( (), move |_| getter().and_then(|v| v.try_into().ok()), move |_, v: &T| setter(&v.clone().try_into().unwrap_or_default()), ); } } /// Wrapper for a field offset that optionally implement PropertyInfo and uses /// the auto deref specialization trick #[derive(derive_more::Deref)] pub struct MaybeAnimatedPropertyInfoWrapper(pub FieldOffset); impl PropertyInfo for MaybeAnimatedPropertyInfoWrapper> where Value: TryInto + Clone + PartialEq + Default + 'static, T: TryInto + Clone + PartialEq + Default + 'static, T: InterpolatedPropertyValue, { fn get(&self, item: Pin<&Item>) -> Result { self.0.get(item) } fn set( &self, item: Pin<&Item>, value: Value, animation: Option, ) -> Result<(), ()> { if let Some(animation) = animation { self.apply_pin(item).set_animated_value(value.try_into().map_err(|_| ())?, animation); Ok(()) } else { self.0.set(item, value, None) } } fn set_binding( &self, item: Pin<&Item>, binding: Box Value>, animation: AnimatedBindingKind, ) -> Result<(), ()> { // Put in a function that does not depends on Item to avoid code bloat fn set_binding_impl( p: Pin<&Property>, binding: Box Value>, animation: AnimatedBindingKind, ) -> Result<(), ()> where T: Clone + TryInto + InterpolatedPropertyValue + 'static, Value: TryInto + 'static, { match animation { AnimatedBindingKind::NotAnimated => { p.set_binding(move || { binding().try_into().map_err(|_| ()).expect("binding was of the wrong type") }); Ok(()) } AnimatedBindingKind::Animation(animation) => { p.set_animated_binding( move || { binding() .try_into() .map_err(|_| ()) .expect("binding was of the wrong type") }, move || (animation(), None), ); Ok(()) } AnimatedBindingKind::Transition(tr) => { p.set_animated_binding( move || { binding() .try_into() .map_err(|_| ()) .expect("binding was of the wrong type") }, move || { let (animation, start_time) = tr(); (animation, Some(start_time)) }, ); Ok(()) } } } set_binding_impl(self.apply_pin(item), binding, animation) } fn offset(&self) -> usize { self.get_byte_offset() } #[cfg(slint_debug_property)] fn set_debug_name(&self, item: Pin<&Item>, name: alloc::string::String) { self.0.set_debug_name(item, name); } #[allow(unsafe_code)] unsafe fn link_two_ways(&self, item: Pin<&Item>, property2: *const c_void) { let p1 = self.apply_pin(item); // Safety: that's the invariant of this function let p2 = unsafe { Pin::new_unchecked((property2 as *const Property).as_ref().unwrap()) }; Property::link_two_way(p1, p2); } fn prepare_for_two_way_binding(&self, item: Pin<&Item>) -> Pin>> { self.0.prepare_for_two_way_binding(item) } fn link_two_way_with_map( &self, item: Pin<&Item>, property2: Pin>>, mapper: Option>>, ) { self.0.link_two_way_with_map(item, property2, mapper) } fn link_two_way_to_model_data( &self, item: Pin<&Item>, getter: Box Option>, setter: Box, ) { self.0.link_two_way_to_model_data(item, getter, setter) } } pub trait CallbackInfo { fn call(&self, item: Pin<&Item>, args: &[Value]) -> Result; fn set_handler( &self, item: Pin<&Item>, handler: Box Value>, ) -> Result<(), ()>; } impl CallbackInfo for FieldOffset> where Value: TryInto, Ret: TryInto, { fn call(&self, item: Pin<&Item>, _args: &[Value]) -> Result { self.apply_pin(item).call(&()).try_into().map_err(|_| ()) } fn set_handler( &self, item: Pin<&Item>, handler: Box Value>, ) -> Result<(), ()> { self.apply_pin(item).set_handler(move |()| handler(&[]).try_into().ok().unwrap()); Ok(()) } } impl CallbackInfo for FieldOffset> where Value: TryInto, T: TryInto, Value: TryInto, Ret: TryInto, { fn call(&self, item: Pin<&Item>, args: &[Value]) -> Result { let value = args.first().ok_or(())?; let value = value.clone().try_into().map_err(|_| ())?; self.apply_pin(item).call(&(value,)).try_into().map_err(|_| ()) } fn set_handler( &self, item: Pin<&Item>, handler: Box Value>, ) -> Result<(), ()> { self.apply_pin(item).set_handler(move |(val,)| { let val: Value = val.clone().try_into().ok().unwrap(); handler(&[val]).try_into().ok().unwrap() }); Ok(()) } } pub trait FieldInfo { fn set_field(&self, item: &mut Item, value: Value) -> Result<(), ()>; } impl FieldInfo for FieldOffset where Value: TryInto, T: TryInto, { fn set_field(&self, item: &mut Item, value: Value) -> Result<(), ()> { *self.apply_mut(item) = value.try_into().map_err(|_| ())?; Ok(()) } } pub trait BuiltinItem: Sized { fn name() -> &'static str; fn properties() -> Vec<(&'static str, &'static dyn PropertyInfo)>; fn fields() -> Vec<(&'static str, &'static dyn FieldInfo)>; fn callbacks() -> Vec<(&'static str, &'static dyn CallbackInfo)>; } /// Trait implemented by builtin globals pub trait BuiltinGlobal: BuiltinItem { fn new() -> Pin>; }