1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4/*!
5 This module enable runtime type information for the builtin items and
6 property so that the viewer can handle them
7*/
8
9#![allow(clippy::result_unit_err)] // We have nothing better to report
10
11pub type FieldOffset<T, U> = const_field_offset::FieldOffset<T, U, const_field_offset::AllowPin>;
12use crate::items::PropertyAnimation;
13use alloc::boxed::Box;
14use alloc::rc::Rc;
15use alloc::vec::Vec;
16use core::convert::{TryFrom, TryInto};
17use core::pin::Pin;
18
19macro_rules! declare_ValueType {
20 ($($ty:ty,)*) => {
21 pub trait ValueType: 'static + Default + Clone $(+ TryInto<$ty> + TryFrom<$ty>)* {}
22 };
23}
24
25macro_rules! declare_ValueType_2 {
26 ($( $(#[$enum_doc:meta])* enum $Name:ident { $($body:tt)* })*) => {
27 declare_ValueType![
28 (),
29 bool,
30 u32,
31 u64,
32 i32,
33 i64,
34 f32,
35 f64,
36 crate::SharedString,
37 crate::graphics::Image,
38 crate::Color,
39 crate::PathData,
40 crate::animations::EasingCurve,
41 crate::model::StandardListViewItem,
42 crate::model::TableColumn,
43 crate::input::KeyEvent,
44 crate::Brush,
45 crate::graphics::Point,
46 crate::items::PointerEvent,
47 crate::items::PointerScrollEvent,
48 crate::lengths::LogicalLength,
49 crate::component_factory::ComponentFactory,
50 crate::api::LogicalPosition,
51 crate::items::FontMetrics,
52 crate::items::MenuEntry,
53 crate::model::ModelRc<crate::items::MenuEntry>,
54 $(crate::items::$Name,)*
55 ];
56 };
57}
58
59i_slint_common::for_each_enums!(declare_ValueType_2);
60
61/// What kind of animation is on a binding
62pub enum AnimatedBindingKind {
63 /// No animation is on the binding
64 NotAnimated,
65 /// Single animation
66 Animation(PropertyAnimation),
67 /// Transition
68 Transition(Box<dyn Fn() -> (PropertyAnimation, crate::animations::Instant)>),
69}
70
71impl AnimatedBindingKind {
72 /// return a PropertyAnimation if self contains AnimatedBindingKind::Animation
73 pub fn as_animation(self) -> Option<PropertyAnimation> {
74 match self {
75 AnimatedBindingKind::NotAnimated => None,
76 AnimatedBindingKind::Animation(a: PropertyAnimation) => Some(a),
77 AnimatedBindingKind::Transition(_) => None,
78 }
79 }
80}
81
82pub trait PropertyInfo<Item, Value> {
83 fn get(&self, item: Pin<&Item>) -> Result<Value, ()>;
84 fn set(
85 &self,
86 item: Pin<&Item>,
87 value: Value,
88 animation: Option<PropertyAnimation>,
89 ) -> Result<(), ()>;
90 fn set_binding(
91 &self,
92 item: Pin<&Item>,
93 binding: Box<dyn Fn() -> Value>,
94 animation: AnimatedBindingKind,
95 ) -> Result<(), ()>;
96
97 /// The offset of the property in the item.
98 /// The use of this is unsafe
99 fn offset(&self) -> usize;
100
101 /// Returns self. This is just a trick to get auto-deref specialization of
102 /// MaybeAnimatedPropertyInfoWrapper working.
103 fn as_property_info(&'static self) -> &'static dyn PropertyInfo<Item, Value>
104 where
105 Self: Sized,
106 {
107 self
108 }
109
110 /// Calls Property::link_two_ways with the property represented here and the property pointer
111 ///
112 /// # Safety
113 /// the property2 must be a pinned pointer to a Property of the same type
114 #[allow(unsafe_code)]
115 unsafe fn link_two_ways(&self, item: Pin<&Item>, property2: *const ());
116}
117
118impl<Item, T: PartialEq + Clone + 'static, Value: 'static> PropertyInfo<Item, Value>
119 for FieldOffset<Item, crate::Property<T>>
120where
121 Value: TryInto<T>,
122 T: TryInto<Value>,
123{
124 fn get(&self, item: Pin<&Item>) -> Result<Value, ()> {
125 self.apply_pin(item).get().try_into().map_err(|_| ())
126 }
127 fn set(
128 &self,
129 item: Pin<&Item>,
130 value: Value,
131 animation: Option<PropertyAnimation>,
132 ) -> Result<(), ()> {
133 if animation.is_some() {
134 Err(())
135 } else {
136 self.apply_pin(item).set(value.try_into().map_err(|_| ())?);
137 Ok(())
138 }
139 }
140 fn set_binding(
141 &self,
142 item: Pin<&Item>,
143 binding: Box<dyn Fn() -> Value>,
144 animation: AnimatedBindingKind,
145 ) -> Result<(), ()> {
146 if !matches!(animation, AnimatedBindingKind::NotAnimated) {
147 Err(())
148 } else {
149 self.apply_pin(item).set_binding(move || {
150 binding().try_into().map_err(|_| ()).expect("binding was of the wrong type")
151 });
152 Ok(())
153 }
154 }
155 fn offset(&self) -> usize {
156 self.get_byte_offset()
157 }
158
159 #[allow(unsafe_code)]
160 unsafe fn link_two_ways(&self, item: Pin<&Item>, property2: *const ()) {
161 let p1 = self.apply_pin(item);
162 // Safety: that's the invariant of this function
163 let p2 = Pin::new_unchecked((property2 as *const crate::Property<T>).as_ref().unwrap());
164 crate::Property::link_two_way(p1, p2);
165 }
166}
167
168/// Wrapper for a field offset that optionally implement PropertyInfo and uses
169/// the auto deref specialization trick
170#[derive(derive_more::Deref)]
171pub struct MaybeAnimatedPropertyInfoWrapper<T, U>(pub FieldOffset<T, U>);
172
173impl<Item, T: Clone + 'static, Value: 'static> PropertyInfo<Item, Value>
174 for MaybeAnimatedPropertyInfoWrapper<Item, crate::Property<T>>
175where
176 Value: TryInto<T>,
177 T: TryInto<Value>,
178 T: crate::properties::InterpolatedPropertyValue,
179{
180 fn get(&self, item: Pin<&Item>) -> Result<Value, ()> {
181 self.0.get(item)
182 }
183 fn set(
184 &self,
185 item: Pin<&Item>,
186 value: Value,
187 animation: Option<PropertyAnimation>,
188 ) -> Result<(), ()> {
189 if let Some(animation) = animation {
190 self.apply_pin(item).set_animated_value(value.try_into().map_err(|_| ())?, animation);
191 Ok(())
192 } else {
193 self.0.set(item, value, None)
194 }
195 }
196 fn set_binding(
197 &self,
198 item: Pin<&Item>,
199 binding: Box<dyn Fn() -> Value>,
200 animation: AnimatedBindingKind,
201 ) -> Result<(), ()> {
202 // Put in a function that does not depends on Item to avoid code bloat
203 fn set_binding_impl<T, Value>(
204 p: Pin<&crate::Property<T>>,
205 binding: Box<dyn Fn() -> Value>,
206 animation: AnimatedBindingKind,
207 ) -> Result<(), ()>
208 where
209 T: Clone + TryInto<Value> + crate::properties::InterpolatedPropertyValue + 'static,
210 Value: TryInto<T> + 'static,
211 {
212 match animation {
213 AnimatedBindingKind::NotAnimated => {
214 p.set_binding(move || {
215 binding().try_into().map_err(|_| ()).expect("binding was of the wrong type")
216 });
217 Ok(())
218 }
219 AnimatedBindingKind::Animation(animation) => {
220 p.set_animated_binding(
221 move || {
222 binding()
223 .try_into()
224 .map_err(|_| ())
225 .expect("binding was of the wrong type")
226 },
227 animation,
228 );
229 Ok(())
230 }
231 AnimatedBindingKind::Transition(tr) => {
232 p.set_animated_binding_for_transition(
233 move || {
234 binding()
235 .try_into()
236 .map_err(|_| ())
237 .expect("binding was of the wrong type")
238 },
239 tr,
240 );
241 Ok(())
242 }
243 }
244 }
245 set_binding_impl(self.apply_pin(item), binding, animation)
246 }
247 fn offset(&self) -> usize {
248 self.get_byte_offset()
249 }
250
251 #[allow(unsafe_code)]
252 unsafe fn link_two_ways(&self, item: Pin<&Item>, property2: *const ()) {
253 let p1 = self.apply_pin(item);
254 // Safety: that's the invariant of this function
255 let p2 = Pin::new_unchecked((property2 as *const crate::Property<T>).as_ref().unwrap());
256 crate::Property::link_two_way(p1, p2);
257 }
258}
259
260pub trait CallbackInfo<Item, Value> {
261 fn call(&self, item: Pin<&Item>, args: &[Value]) -> Result<Value, ()>;
262 fn set_handler(
263 &self,
264 item: Pin<&Item>,
265 handler: Box<dyn Fn(&[Value]) -> Value>,
266 ) -> Result<(), ()>;
267}
268
269impl<Item, Value: Default + 'static, Ret: Default> CallbackInfo<Item, Value>
270 for FieldOffset<Item, crate::Callback<(), Ret>>
271where
272 Value: TryInto<Ret>,
273 Ret: TryInto<Value>,
274{
275 fn call(&self, item: Pin<&Item>, _args: &[Value]) -> Result<Value, ()> {
276 self.apply_pin(item).call(&()).try_into().map_err(|_| ())
277 }
278
279 fn set_handler(
280 &self,
281 item: Pin<&Item>,
282 handler: Box<dyn Fn(&[Value]) -> Value>,
283 ) -> Result<(), ()> {
284 self.apply_pin(item).set_handler(move |()| handler(&[]).try_into().ok().unwrap());
285 Ok(())
286 }
287}
288
289impl<Item, Value: Clone + Default + 'static, T: Clone, Ret: Default> CallbackInfo<Item, Value>
290 for FieldOffset<Item, crate::Callback<(T,), Ret>>
291where
292 Value: TryInto<T>,
293 T: TryInto<Value>,
294 Value: TryInto<Ret>,
295 Ret: TryInto<Value>,
296{
297 fn call(&self, item: Pin<&Item>, args: &[Value]) -> Result<Value, ()> {
298 let value: &Value = args.first().ok_or(())?;
299 let value: T = value.clone().try_into().map_err(|_| ())?;
300 self.apply_pin(item).call(&(value,)).try_into().map_err(|_| ())
301 }
302
303 fn set_handler(
304 &self,
305 item: Pin<&Item>,
306 handler: Box<dyn Fn(&[Value]) -> Value>,
307 ) -> Result<(), ()> {
308 self.apply_pin(item).set_handler(move |(val: &T,)| {
309 let val: Value = val.clone().try_into().ok().unwrap();
310 handler(&[val]).try_into().ok().unwrap()
311 });
312 Ok(())
313 }
314}
315
316pub trait FieldInfo<Item, Value> {
317 fn set_field(&self, item: &mut Item, value: Value) -> Result<(), ()>;
318}
319
320impl<Item, T, Value: 'static> FieldInfo<Item, Value> for FieldOffset<Item, T>
321where
322 Value: TryInto<T>,
323 T: TryInto<Value>,
324{
325 fn set_field(&self, item: &mut Item, value: Value) -> Result<(), ()> {
326 *self.apply_mut(item) = value.try_into().map_err(|_| ())?;
327 Ok(())
328 }
329}
330
331pub trait BuiltinItem: Sized {
332 fn name() -> &'static str;
333 fn properties<Value: ValueType>() -> Vec<(&'static str, &'static dyn PropertyInfo<Self, Value>)>;
334 fn fields<Value: ValueType>() -> Vec<(&'static str, &'static dyn FieldInfo<Self, Value>)>;
335 fn callbacks<Value: ValueType>() -> Vec<(&'static str, &'static dyn CallbackInfo<Self, Value>)>;
336}
337
338/// Trait implemented by builtin globals
339pub trait BuiltinGlobal: BuiltinItem {
340 fn new() -> Pin<Rc<Self>>;
341}
342