mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-29 13:24:48 +00:00
471 lines
19 KiB
Rust
471 lines
19 KiB
Rust
use core::convert::{TryFrom, TryInto};
|
|
use core::pin::Pin;
|
|
use sixtyfps_compilerlib::expression_tree::{
|
|
Expression, NamedReference, Path as ExprPath, PathElement as ExprPathElement,
|
|
};
|
|
use sixtyfps_compilerlib::{object_tree::ElementRc, typeregister::Type};
|
|
use sixtyfps_corelib as corelib;
|
|
use sixtyfps_corelib::{
|
|
abi::datastructures::ItemRef, abi::datastructures::PathElement,
|
|
abi::primitives::PropertyAnimation, Color, ComponentRefPin, PathData, Resource, SharedArray,
|
|
SharedString,
|
|
};
|
|
use std::{collections::HashMap, rc::Rc};
|
|
|
|
pub trait ErasedPropertyInfo {
|
|
fn get(&self, item: Pin<ItemRef>) -> Value;
|
|
fn set(&self, item: Pin<ItemRef>, value: Value, animation: Option<PropertyAnimation>);
|
|
fn set_binding(
|
|
&self,
|
|
item: Pin<ItemRef>,
|
|
binding: Box<dyn Fn() -> Value>,
|
|
animation: Option<PropertyAnimation>,
|
|
);
|
|
fn offset(&self) -> usize;
|
|
}
|
|
|
|
impl<Item: vtable::HasStaticVTable<corelib::abi::datastructures::ItemVTable>> ErasedPropertyInfo
|
|
for &'static dyn corelib::rtti::PropertyInfo<Item, Value>
|
|
{
|
|
fn get(&self, item: Pin<ItemRef>) -> Value {
|
|
(*self).get(ItemRef::downcast_pin(item).unwrap()).unwrap()
|
|
}
|
|
fn set(&self, item: Pin<ItemRef>, value: Value, animation: Option<PropertyAnimation>) {
|
|
(*self).set(ItemRef::downcast_pin(item).unwrap(), value, animation).unwrap()
|
|
}
|
|
fn set_binding(
|
|
&self,
|
|
item: Pin<ItemRef>,
|
|
binding: Box<dyn Fn() -> Value>,
|
|
animation: Option<PropertyAnimation>,
|
|
) {
|
|
(*self).set_binding(ItemRef::downcast_pin(item).unwrap(), binding, animation).unwrap();
|
|
}
|
|
fn offset(&self) -> usize {
|
|
(*self).offset()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
/// This is a dynamically typed Value used in the interpreter, it need to be able
|
|
/// to be converted from and to anything that can be stored in a Property
|
|
pub enum Value {
|
|
/// There is nothing in this value. That's the default.
|
|
/// For example, a function that do not return a result would return a Value::Void
|
|
Void,
|
|
/// An i32 or a float
|
|
Number(f64),
|
|
/// String
|
|
String(SharedString),
|
|
/// Bool
|
|
Bool(bool),
|
|
/// A resource (typically an image)
|
|
Resource(Resource),
|
|
/// An Array
|
|
Array(Vec<Value>),
|
|
/// An object
|
|
Object(HashMap<String, Value>),
|
|
/// A color
|
|
Color(Color),
|
|
/// The elements of a path
|
|
PathElements(PathData),
|
|
}
|
|
|
|
impl Default for Value {
|
|
fn default() -> Self {
|
|
Value::Void
|
|
}
|
|
}
|
|
|
|
impl corelib::rtti::ValueType for Value {}
|
|
|
|
/// Helper macro to implement the TryFrom / TryInto for Value
|
|
///
|
|
/// For example
|
|
/// `declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64] );`
|
|
/// means that Value::Number can be converted to / from each of the said rust types
|
|
macro_rules! declare_value_conversion {
|
|
( $value:ident => [$($ty:ty),*] ) => {
|
|
$(
|
|
impl TryFrom<$ty> for Value {
|
|
type Error = ();
|
|
fn try_from(v: $ty) -> Result<Self, ()> {
|
|
//Ok(Value::$value(v.try_into().map_err(|_|())?))
|
|
Ok(Value::$value(v as _))
|
|
}
|
|
}
|
|
impl TryInto<$ty> for Value {
|
|
type Error = ();
|
|
fn try_into(self) -> Result<$ty, ()> {
|
|
match self {
|
|
//Self::$value(x) => x.try_into().map_err(|_|()),
|
|
Self::$value(x) => Ok(x as _),
|
|
_ => Err(())
|
|
}
|
|
}
|
|
}
|
|
)*
|
|
};
|
|
}
|
|
declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64, usize, isize] );
|
|
declare_value_conversion!(String => [SharedString] );
|
|
declare_value_conversion!(Bool => [bool] );
|
|
declare_value_conversion!(Resource => [Resource] );
|
|
declare_value_conversion!(Object => [HashMap<String, Value>] );
|
|
declare_value_conversion!(Color => [Color] );
|
|
declare_value_conversion!(PathElements => [PathData]);
|
|
|
|
/// Evaluate an expression and return a Value as the result of this expression
|
|
pub fn eval_expression(
|
|
e: &Expression,
|
|
component_type: &crate::ComponentDescription,
|
|
component_ref: ComponentRefPin,
|
|
) -> Value {
|
|
match e {
|
|
Expression::Invalid => panic!("invalid expression while evaluating"),
|
|
Expression::Uncompiled(_) => panic!("uncompiled expression while evaluating"),
|
|
Expression::StringLiteral(s) => Value::String(s.as_str().into()),
|
|
Expression::NumberLiteral(n, unit) => Value::Number(unit.normalize(*n)),
|
|
Expression::BoolLiteral(b) => Value::Bool(*b),
|
|
Expression::SignalReference { .. } => panic!("signal in expression"),
|
|
Expression::PropertyReference(NamedReference { element, name }) => {
|
|
let element = element.upgrade().unwrap();
|
|
let (component_mem, component_type, _) =
|
|
enclosing_component_for_element(&element, component_type, component_ref);
|
|
let element = element.borrow();
|
|
if element.id == element.enclosing_component.upgrade().unwrap().root_element.borrow().id
|
|
{
|
|
if let Some(x) = component_type.custom_properties.get(name) {
|
|
return unsafe {
|
|
x.prop.get(Pin::new_unchecked(&*component_mem.add(x.offset))).unwrap()
|
|
};
|
|
}
|
|
};
|
|
let item_info = &component_type.items[element.id.as_str()];
|
|
core::mem::drop(element);
|
|
let item = unsafe { item_info.item_from_component(component_mem) };
|
|
item_info.rtti.properties[name.as_str()].get(item)
|
|
}
|
|
Expression::RepeaterIndexReference { element } => {
|
|
if element.upgrade().unwrap().borrow().base_type
|
|
== Type::Component(component_type.original.clone())
|
|
{
|
|
let x = &component_type.custom_properties["index"];
|
|
unsafe {
|
|
x.prop.get(Pin::new_unchecked(&*component_ref.as_ptr().add(x.offset))).unwrap()
|
|
}
|
|
} else {
|
|
todo!();
|
|
}
|
|
}
|
|
Expression::RepeaterModelReference { element } => {
|
|
if element.upgrade().unwrap().borrow().base_type
|
|
== Type::Component(component_type.original.clone())
|
|
{
|
|
let x = &component_type.custom_properties["model_data"];
|
|
unsafe {
|
|
x.prop.get(Pin::new_unchecked(&*component_ref.as_ptr().add(x.offset))).unwrap()
|
|
}
|
|
} else {
|
|
todo!();
|
|
}
|
|
}
|
|
Expression::ObjectAccess { base, name } => {
|
|
if let Value::Object(mut o) = eval_expression(base, component_type, component_ref) {
|
|
o.remove(name).unwrap_or(Value::Void)
|
|
} else {
|
|
Value::Void
|
|
}
|
|
}
|
|
Expression::Cast { from, to } => {
|
|
let v = eval_expression(&*from, component_type, component_ref);
|
|
match (v, to) {
|
|
(Value::Number(n), Type::Int32) => Value::Number(n.round()),
|
|
(Value::Number(n), Type::String) => {
|
|
Value::String(SharedString::from(format!("{}", n).as_str()))
|
|
}
|
|
(Value::Number(n), Type::Color) => Value::Color(Color::from(n as u32)),
|
|
(Value::Number(n), Type::Length) => {
|
|
Value::Number(n * dpi_value(component_type, component_ref))
|
|
}
|
|
(Value::Number(n), Type::LogicalLength) => {
|
|
Value::Number(n / dpi_value(component_type, component_ref))
|
|
}
|
|
(v, _) => v,
|
|
}
|
|
}
|
|
Expression::CodeBlock(sub) => {
|
|
let mut v = Value::Void;
|
|
for e in sub {
|
|
v = eval_expression(e, component_type, component_ref);
|
|
}
|
|
v
|
|
}
|
|
Expression::FunctionCall { function, .. } => {
|
|
if let Expression::SignalReference(NamedReference { element, name }) = &**function {
|
|
let element = element.upgrade().unwrap();
|
|
let (component_mem, component_type, _) =
|
|
enclosing_component_for_element(&element, component_type, component_ref);
|
|
|
|
let item_info = &component_type.items[element.borrow().id.as_str()];
|
|
let item = unsafe { item_info.item_from_component(component_mem) };
|
|
let signal = unsafe {
|
|
&mut *(item_info
|
|
.rtti
|
|
.signals
|
|
.get(name.as_str())
|
|
.map(|o| item.as_ptr().add(*o) as *const u8)
|
|
.or_else(|| {
|
|
component_type
|
|
.custom_signals
|
|
.get(name.as_str())
|
|
.map(|o| component_mem.add(*o))
|
|
})
|
|
.unwrap_or_else(|| panic!("unkown signal {}", name))
|
|
as *mut corelib::Signal<()>)
|
|
};
|
|
signal.emit(());
|
|
Value::Void
|
|
} else {
|
|
panic!("call of something not a signal")
|
|
}
|
|
}
|
|
Expression::SelfAssignment { lhs, rhs, op } => match &**lhs {
|
|
Expression::PropertyReference(NamedReference { element, name }) => {
|
|
let eval = |lhs| {
|
|
let rhs = eval_expression(&**rhs, component_type, component_ref);
|
|
match (lhs, rhs, op) {
|
|
(Value::Number(a), Value::Number(b), '+') => Value::Number(a + b),
|
|
(Value::Number(a), Value::Number(b), '-') => Value::Number(a - b),
|
|
(Value::Number(a), Value::Number(b), '/') => Value::Number(a / b),
|
|
(Value::Number(a), Value::Number(b), '*') => Value::Number(a * b),
|
|
(lhs, rhs, op) => panic!("unsupported {:?} {} {:?}", lhs, op, rhs),
|
|
}
|
|
};
|
|
|
|
let element = element.upgrade().unwrap();
|
|
let (component_mem, component_type, _) =
|
|
enclosing_component_for_element(&element, component_type, component_ref);
|
|
|
|
let component = element.borrow().enclosing_component.upgrade().unwrap();
|
|
if element.borrow().id == component.root_element.borrow().id {
|
|
if let Some(x) = component_type.custom_properties.get(name) {
|
|
unsafe {
|
|
let p = Pin::new_unchecked(&*component_mem.add(x.offset));
|
|
x.prop.set(p, eval(x.prop.get(p).unwrap()), None).unwrap();
|
|
}
|
|
return Value::Void;
|
|
}
|
|
};
|
|
let item_info = &component_type.items[element.borrow().id.as_str()];
|
|
let item = unsafe { item_info.item_from_component(component_mem) };
|
|
let p = &item_info.rtti.properties[name.as_str()];
|
|
p.set(item, eval(p.get(item)), None);
|
|
Value::Void
|
|
}
|
|
_ => panic!("typechecking should make sure this was a PropertyReference"),
|
|
},
|
|
Expression::BinaryExpression { lhs, rhs, op } => {
|
|
let lhs = eval_expression(&**lhs, component_type, component_ref);
|
|
let rhs = eval_expression(&**rhs, component_type, component_ref);
|
|
|
|
match (op, lhs, rhs) {
|
|
('+', Value::Number(a), Value::Number(b)) => Value::Number(a + b),
|
|
('-', Value::Number(a), Value::Number(b)) => Value::Number(a - b),
|
|
('/', Value::Number(a), Value::Number(b)) => Value::Number(a / b),
|
|
('*', Value::Number(a), Value::Number(b)) => Value::Number(a * b),
|
|
('<', Value::Number(a), Value::Number(b)) => Value::Bool(a < b),
|
|
('>', Value::Number(a), Value::Number(b)) => Value::Bool(a > b),
|
|
('≤', Value::Number(a), Value::Number(b)) => Value::Bool(a <= b),
|
|
('≥', Value::Number(a), Value::Number(b)) => Value::Bool(a >= b),
|
|
('=', a, b) => Value::Bool(a == b),
|
|
('!', a, b) => Value::Bool(a != b),
|
|
('&', Value::Bool(a), Value::Bool(b)) => Value::Bool(a && b),
|
|
('|', Value::Bool(a), Value::Bool(b)) => Value::Bool(a || b),
|
|
(op, lhs, rhs) => panic!("unsupported {:?} {} {:?}", lhs, op, rhs),
|
|
}
|
|
}
|
|
Expression::UnaryOp { sub, op } => {
|
|
let sub = eval_expression(&**sub, component_type, component_ref);
|
|
match (sub, op) {
|
|
(Value::Number(a), '+') => Value::Number(a),
|
|
(Value::Number(a), '-') => Value::Number(-a),
|
|
(Value::Bool(a), '!') => Value::Bool(!a),
|
|
(sub, op) => panic!("unsupported {} {:?}", op, sub),
|
|
}
|
|
}
|
|
Expression::ResourceReference { absolute_source_path } => {
|
|
Value::Resource(Resource::AbsoluteFilePath(absolute_source_path.as_str().into()))
|
|
}
|
|
Expression::Condition { condition, true_expr, false_expr } => {
|
|
match eval_expression(&**condition, component_type, component_ref).try_into()
|
|
as Result<bool, _>
|
|
{
|
|
Ok(true) => eval_expression(&**true_expr, component_type, component_ref),
|
|
Ok(false) => eval_expression(&**false_expr, component_type, component_ref),
|
|
_ => panic!("conditional expression did not evaluate to boolean"),
|
|
}
|
|
}
|
|
Expression::Array { values, .. } => Value::Array(
|
|
values.iter().map(|e| eval_expression(e, component_type, component_ref)).collect(),
|
|
),
|
|
Expression::Object { values, .. } => Value::Object(
|
|
values
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), eval_expression(v, component_type, component_ref)))
|
|
.collect(),
|
|
),
|
|
Expression::PathElements { elements } => {
|
|
Value::PathElements(convert_path(elements, component_type, component_ref))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn dpi_value(component_type: &crate::ComponentDescription, component_ref: ComponentRefPin) -> f64 {
|
|
if let Some(parent_offset) = component_type.parent_component_offset {
|
|
let mem = component_ref.as_ptr();
|
|
let parent_component =
|
|
unsafe { *(mem.add(parent_offset) as *const Option<corelib::ComponentRefPin>) }
|
|
.unwrap();
|
|
let parent_component_type =
|
|
unsafe { crate::dynamic_component::get_component_type(parent_component) };
|
|
dpi_value(parent_component_type, parent_component)
|
|
} else {
|
|
let x = &component_type.custom_properties["dpi"];
|
|
let val = unsafe {
|
|
x.prop.get(Pin::new_unchecked(&*component_ref.as_ptr().add(x.offset))).unwrap()
|
|
};
|
|
val.try_into().unwrap()
|
|
}
|
|
}
|
|
|
|
fn enclosing_component_for_element<'a>(
|
|
element: &ElementRc,
|
|
component_type: &'a crate::ComponentDescription,
|
|
component_ref: ComponentRefPin<'a>,
|
|
) -> (*const u8, &'a crate::ComponentDescription, ComponentRefPin<'a>) {
|
|
if Rc::ptr_eq(
|
|
&element.borrow().enclosing_component.upgrade().unwrap(),
|
|
&component_type.original,
|
|
) {
|
|
let mem = component_ref.as_ptr();
|
|
(mem, component_type, component_ref)
|
|
} else {
|
|
let mem = component_ref.as_ptr();
|
|
let parent_component = unsafe {
|
|
*(mem.add(component_type.parent_component_offset.unwrap())
|
|
as *const Option<corelib::ComponentRefPin>)
|
|
}
|
|
.unwrap();
|
|
let parent_component_type =
|
|
unsafe { crate::dynamic_component::get_component_type(parent_component) };
|
|
enclosing_component_for_element(element, parent_component_type, parent_component)
|
|
}
|
|
}
|
|
|
|
pub fn new_struct_with_bindings<
|
|
ElementType: 'static + Default + sixtyfps_corelib::rtti::BuiltinItem,
|
|
>(
|
|
bindings: &HashMap<String, Expression>,
|
|
component_type: &crate::ComponentDescription,
|
|
component_ref: ComponentRefPin,
|
|
) -> ElementType {
|
|
let mut element = ElementType::default();
|
|
for (prop, info) in ElementType::fields::<Value>().into_iter() {
|
|
if let Some(binding) = &bindings.get(prop) {
|
|
let value = eval_expression(&binding, &*component_type, component_ref);
|
|
info.set_field(&mut element, value).unwrap();
|
|
}
|
|
}
|
|
element
|
|
}
|
|
|
|
fn convert_from_lyon_path<'a>(
|
|
it: impl IntoIterator<Item = &'a lyon::path::Event<lyon::math::Point, lyon::math::Point>>,
|
|
) -> PathData {
|
|
use lyon::path::Event;
|
|
use sixtyfps_corelib::abi::datastructures::PathEvent;
|
|
|
|
let mut coordinates = Vec::new();
|
|
|
|
let events = it
|
|
.into_iter()
|
|
.map(|event| match event {
|
|
Event::Begin { at } => {
|
|
coordinates.push(at);
|
|
PathEvent::Begin
|
|
}
|
|
Event::Line { from, to } => {
|
|
coordinates.push(from);
|
|
coordinates.push(to);
|
|
PathEvent::Line
|
|
}
|
|
Event::Quadratic { from, ctrl, to } => {
|
|
coordinates.push(from);
|
|
coordinates.push(ctrl);
|
|
coordinates.push(to);
|
|
PathEvent::Quadratic
|
|
}
|
|
Event::Cubic { from, ctrl1, ctrl2, to } => {
|
|
coordinates.push(from);
|
|
coordinates.push(ctrl1);
|
|
coordinates.push(ctrl2);
|
|
coordinates.push(to);
|
|
PathEvent::Cubic
|
|
}
|
|
Event::End { last, first, close } => {
|
|
debug_assert_eq!(coordinates.first(), Some(&first));
|
|
debug_assert_eq!(coordinates.last(), Some(&last));
|
|
if *close {
|
|
PathEvent::EndClosed
|
|
} else {
|
|
PathEvent::EndOpen
|
|
}
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
PathData::Events(
|
|
SharedArray::from(&events),
|
|
SharedArray::from_iter(coordinates.into_iter().cloned()),
|
|
)
|
|
}
|
|
|
|
pub fn convert_path(
|
|
path: &ExprPath,
|
|
component_type: &crate::ComponentDescription,
|
|
eval_context: ComponentRefPin,
|
|
) -> PathData {
|
|
match path {
|
|
ExprPath::Elements(elements) => PathData::Elements(SharedArray::<PathElement>::from_iter(
|
|
elements
|
|
.iter()
|
|
.map(|element| convert_path_element(element, component_type, eval_context)),
|
|
)),
|
|
ExprPath::Events(events) => convert_from_lyon_path(events.iter()),
|
|
}
|
|
}
|
|
|
|
fn convert_path_element(
|
|
expr_element: &ExprPathElement,
|
|
component_type: &crate::ComponentDescription,
|
|
eval_context: ComponentRefPin,
|
|
) -> PathElement {
|
|
match expr_element.element_type.class_name.as_str() {
|
|
"LineTo" => PathElement::LineTo(new_struct_with_bindings(
|
|
&expr_element.bindings,
|
|
component_type,
|
|
eval_context,
|
|
)),
|
|
"ArcTo" => PathElement::ArcTo(new_struct_with_bindings(
|
|
&expr_element.bindings,
|
|
component_type,
|
|
eval_context,
|
|
)),
|
|
"Close" => PathElement::Close,
|
|
_ => panic!(
|
|
"Cannot create unsupported path element {}",
|
|
expr_element.element_type.class_name
|
|
),
|
|
}
|
|
}
|