| 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 | |
| 11 | pub type FieldOffset<T, U> = const_field_offset::FieldOffset<T, U, const_field_offset::AllowPin>; |
| 12 | use crate::items::PropertyAnimation; |
| 13 | use alloc::boxed::Box; |
| 14 | use alloc::rc::Rc; |
| 15 | use alloc::vec::Vec; |
| 16 | use core::convert::{TryFrom, TryInto}; |
| 17 | use core::pin::Pin; |
| 18 | |
| 19 | macro_rules! declare_ValueType { |
| 20 | ($($ty:ty,)*) => { |
| 21 | pub trait ValueType: 'static + Default + Clone $(+ TryInto<$ty> + TryFrom<$ty>)* {} |
| 22 | }; |
| 23 | } |
| 24 | |
| 25 | macro_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 | |
| 59 | i_slint_common::for_each_enums!(declare_ValueType_2); |
| 60 | |
| 61 | /// What kind of animation is on a binding |
| 62 | pub 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 | |
| 71 | impl 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 | |
| 82 | pub 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 | |
| 118 | impl<Item, T: PartialEq + Clone + 'static, Value: 'static> PropertyInfo<Item, Value> |
| 119 | for FieldOffset<Item, crate::Property<T>> |
| 120 | where |
| 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)] |
| 171 | pub struct MaybeAnimatedPropertyInfoWrapper<T, U>(pub FieldOffset<T, U>); |
| 172 | |
| 173 | impl<Item, T: Clone + 'static, Value: 'static> PropertyInfo<Item, Value> |
| 174 | for MaybeAnimatedPropertyInfoWrapper<Item, crate::Property<T>> |
| 175 | where |
| 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 | |
| 260 | pub 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 | |
| 269 | impl<Item, Value: Default + 'static, Ret: Default> CallbackInfo<Item, Value> |
| 270 | for FieldOffset<Item, crate::Callback<(), Ret>> |
| 271 | where |
| 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 | |
| 289 | impl<Item, Value: Clone + Default + 'static, T: Clone, Ret: Default> CallbackInfo<Item, Value> |
| 290 | for FieldOffset<Item, crate::Callback<(T,), Ret>> |
| 291 | where |
| 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 | |
| 316 | pub trait FieldInfo<Item, Value> { |
| 317 | fn set_field(&self, item: &mut Item, value: Value) -> Result<(), ()>; |
| 318 | } |
| 319 | |
| 320 | impl<Item, T, Value: 'static> FieldInfo<Item, Value> for FieldOffset<Item, T> |
| 321 | where |
| 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 | |
| 331 | pub 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 |
| 339 | pub trait BuiltinGlobal: BuiltinItem { |
| 340 | fn new() -> Pin<Rc<Self>>; |
| 341 | } |
| 342 | |