phlow 3.0.0

An engine for scripting reactive browsers in Rust by adding custom views to structures
Documentation
use std::any::Any;
use std::ffi::c_void;
use std::fmt::{Debug, Display, Formatter};
use std::marker::PhantomData;
use std::ptr::NonNull;

use crate::{DefiningMethod, PhlowTextView, ProtoInfoView, ProtoListView};

#[derive(Clone, Copy)]
pub enum ObjectRef<'a> {
    Erased(ErasedObjectRef<'a>),
    Any(&'a dyn Any),
}

impl<'a> ObjectRef<'a> {
    pub fn new<T>(value: &'a T) -> Self {
        Self::Erased(ErasedObjectRef::new(value))
    }

    /// Creates an [`ObjectRef`] from a type-erased raw pointer for the duration of `f`.
    ///
    /// This is intended for FFI callers that can only pass a type-erased non-null pointer.
    ///
    /// # Safety
    ///
    /// `ptr` must point to a live value for the entire duration of `f`, and any subsequent call to
    /// [`ObjectRef::cast`] must use the pointee's concrete type.
    pub unsafe fn with_erased_ptr<R>(
        ptr: NonNull<c_void>,
        f: impl for<'b> FnOnce(ObjectRef<'b>) -> R,
    ) -> R {
        f(ObjectRef::Erased(ErasedObjectRef {
            ptr: ptr.as_ptr().cast(),
            phantom_data: PhantomData,
        }))
    }

    pub fn with_any<R>(any: &dyn Any, f: impl for<'b> FnOnce(ObjectRef<'b>) -> R) -> R {
        f(ObjectRef::Any(any))
    }

    /// Casts this object reference back to its concrete type.
    ///
    /// # Safety
    ///
    /// The caller must ensure `T` matches the concrete type behind this reference. For
    /// [`ObjectRef::Any`], a mismatched `T` will panic due to the underlying `downcast_ref`.
    pub unsafe fn cast<T: 'static>(self) -> &'a T {
        match self {
            Self::Erased(obj) => unsafe { obj.cast() },
            Self::Any(any) => any.downcast_ref().unwrap(),
        }
    }
}

#[derive(Clone, Copy)]
pub struct ErasedObjectRef<'a> {
    ptr: *const (),
    phantom_data: PhantomData<&'a ()>,
}

impl<'a> ErasedObjectRef<'a> {
    pub fn new<T>(value: &'a T) -> Self {
        Self {
            ptr: value as *const T as *const (),
            phantom_data: PhantomData,
        }
    }

    /// Creates an [`ErasedObjectRef`] from a type-erased raw pointer for the duration of `f`.
    ///
    /// This is intended for FFI callers that can only pass a type-erased non-null pointer.
    ///
    /// # Safety
    ///
    /// `ptr` must point to a live value for the entire duration of `f`, and the eventual
    /// [`ErasedObjectRef::cast`] target type must match the concrete pointee type.
    pub unsafe fn with_erased_ptr<R>(
        ptr: NonNull<c_void>,
        f: impl for<'b> FnOnce(ErasedObjectRef<'b>) -> R,
    ) -> R {
        f(ErasedObjectRef {
            ptr: ptr.as_ptr().cast(),
            phantom_data: PhantomData,
        })
    }

    /// Casts this erased reference back to its concrete type.
    ///
    /// # Safety
    ///
    /// `T` must be the same concrete type used to construct this reference.
    pub unsafe fn cast<T>(&self) -> &'a T {
        unsafe { &*(self.ptr as *const T) }
    }
}

pub trait ViewInstance: Send {
    fn get_title(&self) -> &str;
    fn get_priority(&self) -> usize;
    fn get_view_type(&self) -> &str;
    fn as_any(&self) -> &dyn Any;
}

pub trait PhlowView: Debug + Send + Sync {
    fn get_title(&self) -> &str;
    fn get_priority(&self) -> usize;
    fn get_view_type(&self) -> &str;
    fn get_defining_method(&self) -> Option<&DefiningMethod>;
    fn create_instance(&self, object: ObjectRef<'_>) -> Box<dyn ViewInstance>;

    fn view_type() -> &'static str
    where
        Self: Sized;
}

pub trait ProtoView<T: ?Sized>: PhlowView {
    fn list(&self) -> ProtoListView<T> {
        ProtoListView::new(self.get_defining_method().cloned())
    }

    fn info(&self) -> ProtoInfoView<T> {
        ProtoInfoView::new(self.get_defining_method().cloned())
    }
    // fn columned_list(&self) -> PhlowColumnedListView<T> {
    //     PhlowColumnedListView::new(self.object().clone(), self.get_defining_method().clone())
    // }
    fn text(&self) -> PhlowTextView<T> {
        PhlowTextView::new(self.get_defining_method().cloned())
    }
    // fn bitmap(&self) -> PhlowBitmapView<T> {
    //     PhlowBitmapView::new(self.object().clone(), self.get_defining_method().clone())
    // }
}

pub struct PhlowProtoView<T: ?Sized> {
    defining_method: Option<DefiningMethod>,
    phantom_data: PhantomData<fn() -> T>,
}

impl<T: 'static + ?Sized> PhlowProtoView<T> {
    pub fn new() -> Self {
        Self {
            defining_method: None,
            phantom_data: PhantomData,
        }
    }

    pub fn compiled(defining_method: DefiningMethod) -> Self {
        Self {
            defining_method: Some(defining_method),
            phantom_data: PhantomData,
        }
    }
}

impl<T: 'static + ?Sized> Default for PhlowProtoView<T> {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug)]
pub struct BaseView {
    pub defining_method: Option<DefiningMethod>,
    pub title: String,
    pub priority: usize,
}

impl BaseView {
    pub fn new(defining_method: Option<DefiningMethod>) -> Self {
        Self {
            defining_method,
            title: "".to_string(),
            priority: 10,
        }
    }
}

impl Clone for BaseView {
    fn clone(&self) -> Self {
        Self {
            defining_method: self.defining_method.clone(),
            title: self.title.clone(),
            priority: self.priority,
        }
    }
}

impl<T: ?Sized> Display for PhlowProtoView<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "ProtoView")
    }
}

impl<T: ?Sized> Debug for PhlowProtoView<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "ProtoView")
    }
}

#[derive(Debug)]
struct ProtoViewInstance {
    title: String,
    priority: usize,
    view_type: &'static str,
}

impl ViewInstance for ProtoViewInstance {
    fn get_title(&self) -> &str {
        self.title.as_str()
    }

    fn get_priority(&self) -> usize {
        self.priority
    }

    fn get_view_type(&self) -> &str {
        self.view_type
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

impl<T: ?Sized> PhlowView for PhlowProtoView<T> {
    fn get_title(&self) -> &str {
        "Untitled"
    }

    fn get_priority(&self) -> usize {
        10
    }

    fn get_view_type(&self) -> &str {
        Self::view_type()
    }

    fn get_defining_method(&self) -> Option<&DefiningMethod> {
        self.defining_method.as_ref()
    }

    fn create_instance(&self, _object: ObjectRef<'_>) -> Box<dyn ViewInstance> {
        Box::new(ProtoViewInstance {
            title: self.get_title().to_string(),
            priority: self.get_priority(),
            view_type: Self::view_type(),
        })
    }

    fn view_type() -> &'static str {
        "proto_view"
    }
}

impl<T: ?Sized> ProtoView<T> for PhlowProtoView<T> {}

impl<V: PhlowView + 'static> From<Box<V>> for Box<dyn PhlowView> {
    fn from(value: Box<V>) -> Self {
        value
    }
}

pub trait IntoView {
    fn into_view(self) -> Box<dyn PhlowView>;
}

impl<V: PhlowView + 'static> IntoView for V {
    fn into_view(self) -> Box<dyn PhlowView> {
        Box::new(self)
    }
}