/* LICENSE BEGIN This file is part of the SixtyFPS Project -- https://sixtyfps.io Copyright (c) 2020 Olivier Goffart Copyright (c) 2020 Simon Hausmann SPDX-License-Identifier: GPL-3.0-only This file is also available under commercial licensing terms. Please contact info@sixtyfps.io for more information. LICENSE END */ #![warn(missing_docs)] use core::convert::TryInto; use std::collections::HashMap; use std::iter::FromIterator; use std::path::Path; use std::rc::Rc; use crate::eval; #[doc(inline)] pub use sixtyfps_compilerlib::diagnostics::{Diagnostic, DiagnosticLevel}; /// This is a dynamically typed value used in the SixtyFPS interpreter. /// It can hold a value of different types, and you should use the /// [`From`] or [`TryInto`] traits to access the value. /// /// ``` /// # use sixtyfps_interpreter::api::*; /// use core::convert::TryInto; /// // create a value containing an integer /// let v = Value::from(100u32); /// assert_eq!(v.try_into(), Ok(100u32)); /// ``` #[derive(Clone, PartialEq, Debug, Default)] pub struct Value(pub eval::Value); // FIXME: Make inner pub(crate) once everything is ported /// A dummy structure that can be converted to and from [`Value`]. /// /// A default constructed Value holds this value. /// ``` /// # use sixtyfps_interpreter::api::*; /// let value = Value::default(); /// assert_eq!(Value::default(), VoidValue.into()); /// ``` #[derive(Clone, PartialEq, Debug, Default)] pub struct VoidValue; impl From for Value { fn from(_: VoidValue) -> Self { Self(eval::Value::Void) } } impl TryInto for Value { type Error = Value; fn try_into(self) -> Result { if self.0 == eval::Value::Void { Ok(VoidValue) } else { Err(self) } } } macro_rules! pub_value_conversion { ($($ty:ty,)*) => { $( impl From<$ty> for Value { fn from(v: $ty) -> Self { Self(v.try_into().unwrap()) } } impl TryInto<$ty> for Value { type Error = Value; fn try_into(self) -> Result<$ty, Value> { self.0.try_into().map_err(Value) } } )* }; } pub_value_conversion!( f32, f64, i32, u32, bool, sixtyfps_corelib::SharedString, sixtyfps_corelib::Color, //sixtyfps_corelib::Brush, ); // TODO! model #[derive(Clone, PartialEq, Debug, Default)] /// This type represent a runtime instance of structure in `.60`. /// /// This can either be an instance of a name structure introduced /// with the `struct` keywrod in the .60 file, or an annonymous struct /// writen with the `{ key: value, }` notation. /// /// It can be constructed with the [`FromIterator`] trait, and converted /// into or from a [`Value`] with the [`From`] and [`TryInto`] trait /// /// /// ``` /// # use sixtyfps_interpreter::api::*; /// use core::convert::TryInto; /// // Construct a value from a key/value iterator /// let value : Value = [("foo".into(), 45u32.into()), ("bar".into(), true.into())] /// .iter().cloned().collect::().into(); /// /// // get the properties of a `{ foo: 45, bar: true }` /// let s : Struct = value.try_into().unwrap(); /// assert_eq!(s.get_property("foo").unwrap().try_into(), Ok(45u32)); /// ``` /// FIXME: the documentation of langref.md uses "Object" and we probably should make that uniform. /// also, is "property" the right term here? pub struct Struct(HashMap); impl Struct { /// Get the value for a given struct property pub fn get_property(&self, name: &str) -> Option { self.0.get(name).cloned().map(Value) } /// Set the value of a given struct property pub fn set_property(&mut self, name: String, value: Value) { self.0.insert(name, value.0); } /// Iterate over all the property in this struct pub fn iter(&self) -> impl Iterator { self.0.iter().map(|(a, b)| (a.as_str(), Value(b.clone()))) } } impl From for Value { fn from(s: Struct) -> Self { Self(eval::Value::Struct(s)) } } impl TryInto for Value { type Error = Value; fn try_into(self) -> Result { if let eval::Value::Struct(o) = self.0 { Ok(o) } else { Err(self) } } } impl FromIterator<(String, Value)> for Struct { fn from_iter>(iter: T) -> Self { Self(iter.into_iter().map(|(a, b)| (a, b.0)).collect()) } } /// FIXME: use SharedArray instead? impl From> for Value { fn from(a: Vec) -> Self { Self(eval::Value::Array(a.into_iter().map(|v| v.0).collect())) } } impl TryInto> for Value { type Error = Value; fn try_into(self) -> Result, Value> { if let eval::Value::Array(a) = self.0 { Ok(a.into_iter().map(Value).collect()) } else { Err(self) } } } /// ComponentDescription is a representation of a compiled component from .60 /// /// It can be constructed from a .60 file using the [`Self::from_path`] or [`Self::from_string`] functions. /// And then it can be instentiated with the [`Self::create`] function #[derive(Clone)] pub struct ComponentDefinition { inner: Rc>, } impl ComponentDefinition { /// Compile a .60 file into a ComponentDefinition /// /// The first element of the returned tuple is going to be the compiled /// ComponentDefinition if there was no errors. This function also return /// a vector if diagnostics with errors and/or warnings pub async fn from_path>( path: P, config: CompilerConfiguration, ) -> (Option, Vec) { let path = path.as_ref(); let source = match sixtyfps_compilerlib::diagnostics::load_from_path(path) { Ok(s) => s, Err(d) => return (None, vec![d]), }; let (c, diag) = crate::load(source, path.into(), config.config).await; (c.ok().map(|inner| Self { inner }), diag.into_iter().collect()) } /// Compile some .60 code into a ComponentDefinition /// /// The first element of the returned tuple is going to be the compiled /// ComponentDefinition if there was no errors. This function also return /// a vector if diagnostics with errors and/or warnings pub async fn from_string( source_code: &str, config: CompilerConfiguration, ) -> (Option, Vec) { let (c, diag) = crate::load(source_code.into(), Default::default(), config.config).await; (c.ok().map(|inner| Self { inner }), diag.into_iter().collect()) } /// Instantiate the component pub fn create(&self) -> ComponentInstance { ComponentInstance { inner: self.inner.clone().create( #[cfg(target_arch = "wasm32")] "canvas", ), } } /// Instantiate the component for wasm using the given canvas id #[cfg(target_arch = "wasm32")] pub fn create_with_canvas_id(&self, canvas_id: &str) -> ComponentInstance { ComponentInstance { inner: self.inner.clone().create(canvas_id) } } /// List of publicly declared properties or callback. /// /// This is internal because it exposes the `Type` from compilerlib. /// In the future this should probably return an iterator instead. #[doc(hidden)] pub fn properties(&self) -> HashMap { self.inner.properties() } /// The name of this Component as written in the .60 file pub fn name(&self) -> &str { self.inner.id() } } /// This represent an instance of a dynamic component pub struct ComponentInstance { inner: vtable::VRc< sixtyfps_corelib::component::ComponentVTable, crate::dynamic_component::ErasedComponentBox, >, } impl ComponentInstance { /// Return the value for a public property of this component pub fn get_property(&self, name: &str) -> Result { generativity::make_guard!(guard); let comp = self.inner.unerase(guard); comp.description() .get_property(comp.borrow(), name) .map(|v| Value(v)) .map_err(|()| GetPropertyError::NoSuchProperty) } /// Return the value for a public property of this component pub fn set_property(&self, name: &str, value: Value) -> Result<(), SetPropertyError> { generativity::make_guard!(guard); let comp = self.inner.unerase(guard); comp.description() .set_property(comp.borrow(), name, value.0) .map_err(|()| todo!("set_property don't return the right error type")) } /// Set a handler for the callback with the given name. A callback with that /// name must be defined in the document otherwise an error will be returned. pub fn set_callback( &self, name: &str, callback: impl Fn(&[Value]) -> Value + 'static, ) -> Result<(), SetCallbackError> { generativity::make_guard!(guard); let comp = self.inner.unerase(guard); comp.description() .set_callback_handler( comp.borrow(), name, Box::new(move |args| { // TODO: avoid allocation in common case by puting that on the stack or find a way (transmute?) let args = args.iter().map(|v| Value(v.clone())).collect::>(); callback(&args).0 }), ) .map_err(|()| SetCallbackError::NoSuchCallback) } /// Call the given callback with the arguments pub fn call_callback(&self, name: &str, args: &[Value]) -> Result { generativity::make_guard!(guard); let comp = self.inner.unerase(guard); // TODO: avoid allocation in common case by puting that on the stack or find a way (transmute?) let args = args.iter().map(|v| v.0.clone()).collect::>(); Ok(Value( comp.description().call_callback(comp.borrow(), name, &args).map_err(|()| todo!())?, )) } /// Marks the window of this component to be shown on the screen. This registers /// the window with the windowing system. In order to react to events from the windowing system, /// such as draw requests or mouse/touch input, it is still necessary to spin the event loop, /// using [`crate::run_event_loop`]. pub fn show(&self) { generativity::make_guard!(guard); let comp = self.inner.unerase(guard); comp.window().show(); } /// Marks the window of this component to be hidden on the screen. This de-registers /// the window from the windowing system and it will not receive any further events. pub fn hide(&self) { generativity::make_guard!(guard); let comp = self.inner.unerase(guard); comp.window().hide(); } /// This is a convenience function that first calls [`Self::show`], followed by [`crate::run_event_loop()`] /// and [`Self::hide`]. pub fn run(&self) { self.show(); crate::run_event_loop(); self.hide(); } /// Clone this `ComponentInstance`. /// /// A `ComponentInstance` is in fact a handle to a reference counted instance. /// This function is semanticallt the same as the one from `Clone::clone`, but /// Clone is not implemented because of the danger of circular reference: /// If you want to use this instance in a callback, you should capture a weak /// reference given by [`Self::as_weak`]. pub fn clone_strong(&self) -> Self { Self { inner: self.inner.clone() } } /// Create a weak pointer to this component pub fn as_weak(&self) -> WeakComponentInstance { WeakComponentInstance { inner: vtable::VRc::downgrade(&self.inner) } } } /// A Weak references to a dynamic SixtyFPS components. #[derive(Clone)] pub struct WeakComponentInstance { inner: vtable::VWeak< sixtyfps_corelib::component::ComponentVTable, crate::dynamic_component::ErasedComponentBox, >, } impl WeakComponentInstance { /// Returns a new strongly referenced component if some other instance still /// holds a strong reference. Otherwise, returns None. pub fn upgrade(&self) -> Option { self.inner.upgrade().map(|inner| ComponentInstance { inner }) } /// Convenience function that returns a new stronlyg referenced component if /// some other instance still holds a strong reference. Otherwise, this function /// panics. pub fn unwrap(&self) -> ComponentInstance { self.upgrade().unwrap() } } /// Error returned by [`ComponentInstance::get_property`] #[derive(Debug)] pub enum GetPropertyError { /// There is no property with the given name NoSuchProperty, } /// Error returned by [`ComponentInstance::set_property`] #[derive(Debug)] pub enum SetPropertyError { /// There is no property with the given name NoSuchProperty, /// The property exist but does not have a type matching the dynamic value WrongType, } /// Error returned by [`ComponentInstance::set_callback`] #[derive(Debug)] pub enum SetCallbackError { /// There is no callback with the given name NoSuchCallback, } /// Error returned by [`ComponentInstance::call_callback`] pub enum CallCallbackError { /// There is no callback with the given name NoSuchCallback, } /// The structure for configuring aspects of the compilation of `.60` markup files to Rust. pub struct CompilerConfiguration { config: sixtyfps_compilerlib::CompilerConfiguration, } impl CompilerConfiguration { /// Creates a new default configuration. pub fn new() -> Self { Self { config: sixtyfps_compilerlib::CompilerConfiguration::new( sixtyfps_compilerlib::generator::OutputFormat::Interpreter, ), } } /// Create a new configuration that includes sets the include paths used for looking up /// `.60` imports to the specified vector of paths. pub fn with_include_paths(self, include_paths: Vec) -> Self { let mut config = self.config; config.include_paths = include_paths; Self { config } } /// Create a new configuration that selects the style to be used for widgets. pub fn with_style(self, style: String) -> Self { let mut config = self.config; config.style = Some(style); Self { config } } /// Create a new configuration that will use the provided callback for loading. pub fn with_file_loader( _file_loader_fallback: Box< dyn Fn( &Path, ) -> core::pin::Pin< Box>>, >, >, ) -> Self { todo!(); } }