phlow 3.0.0

An engine for scripting reactive browsers in Rust by adding custom views to structures
Documentation
use std::any::type_name;
use std::borrow::Cow;
use std::fmt::{Debug, Formatter};
use std::marker::PhantomData;
use std::sync::Arc;

use crate::{AnySendObject, BaseView, DefiningMethod, ObjectRef, PhlowView, ViewInstance};

type ItemsComputation<T, Item> = dyn Fn(&T) -> Vec<Item> + Send + Sync + 'static;
type ItemTextComputation<Item> = dyn for<'a> Fn(&'a Item) -> Cow<'a, str> + Send + Sync + 'static;
type InstanceItemTextComputation = dyn for<'a> Fn(ObjectRef<'a>) -> String + Send + Sync + 'static;
type InstanceSendComputation =
    dyn for<'a> Fn(ObjectRef<'a>) -> Option<AnySendObject> + Send + Sync + 'static;
type SendComputation<Item, SendItem> = dyn Fn(&Item) -> SendItem + Send + Sync + 'static;

#[repr(transparent)]
pub struct ProtoListView<T: ?Sized>(BaseView, PhantomData<fn() -> T>);

impl<T: ?Sized> ProtoListView<T> {
    pub fn new(defining_method: Option<DefiningMethod>) -> Self {
        Self(BaseView::new(defining_method), PhantomData)
    }

    pub fn title(mut self, title: impl Into<String>) -> Self {
        self.0.title = title.into();
        self
    }

    pub fn priority(mut self, priority: usize) -> Self {
        self.0.priority = priority;
        self
    }

    pub fn items<Item: Send>(
        self,
        items_block: impl Fn(&T) -> Vec<Item> + Send + Sync + 'static,
    ) -> ListViewWithoutSend<T, Item> {
        ListViewWithoutSend::new(self, items_block)
    }
}

#[allow(unused)]
pub struct ListViewWithoutSend<T: ?Sized, Item> {
    proto_list: ProtoListView<T>,
    items_computation: Arc<ItemsComputation<T, Item>>,
    item_text_computation: Arc<ItemTextComputation<Item>>,
}

impl<T: ?Sized, Item> ListViewWithoutSend<T, Item> {
    fn new(
        proto_list: ProtoListView<T>,
        items_computation: impl Fn(&T) -> Vec<Item> + Send + Sync + 'static,
    ) -> Self {
        Self {
            proto_list,
            items_computation: Arc::new(items_computation),
            item_text_computation: Arc::new(|_item| type_name::<Item>().into()),
        }
    }

    fn compute_items_sync(&self, object: &T) -> Vec<Item> {
        (self.items_computation)(object)
    }
}

impl<T: 'static, Item: Send + 'static> ListViewWithoutSend<T, Item> {
    fn create_list_instance(&self, object: ObjectRef<'_>) -> ListViewInstance {
        // SAFETY: the view is only instantiated with the receiver type it was defined for.
        let object = unsafe { object.cast::<T>() };
        let items = self
            .compute_items_sync(object)
            .into_iter()
            .map(|item| AnySendObject::new(item))
            .collect();

        let item_text_computation = self.item_text_computation.clone();

        ListViewInstance {
            base_view: self.proto_list.0.clone(),
            items,
            item_text_computation: Arc::new(move |item| {
                (unsafe { item_text_computation(item.cast()) }).to_string()
            }),
            send_computation: Arc::new(move |_| None),
        }
    }
}

impl<T: ?Sized, Item: Send> ListViewWithoutSend<T, Item> {
    pub fn item_text(
        mut self,
        item_text: impl Fn(&Item) -> String + Send + Sync + 'static,
    ) -> Self {
        self.item_text_computation = Arc::new(move |item| item_text(item).into());
        self
    }

    pub fn send<SendItem: Send>(
        self,
        item_send_block: impl Fn(&Item) -> SendItem + Send + Sync + 'static,
    ) -> ListViewWithSend<T, Item, SendItem> {
        ListViewWithSend::new(self, item_send_block)
    }
}

impl<T: ?Sized, Item: Send + Clone> ListViewWithoutSend<T, Item> {
    /// Sends a cloned item.
    ///
    /// Prefer this over `send(|item| item.clone())` when the list item itself should be sent.
    pub fn send_clone(self) -> ListViewWithSend<T, Item, Item> {
        self.send(|item| item.clone())
    }
}

#[allow(unused)]
pub struct ListViewWithSend<T: ?Sized, Item, SendItem> {
    proto_list: ListViewWithoutSend<T, Item>,
    send_computation: Arc<SendComputation<Item, SendItem>>,
}

impl<T: ?Sized, Item, SendItem> ListViewWithSend<T, Item, SendItem> {
    fn new(
        proto_list: ListViewWithoutSend<T, Item>,
        send_computation: impl Fn(&Item) -> SendItem + Send + Sync + 'static,
    ) -> Self {
        Self {
            proto_list,
            send_computation: Arc::new(send_computation),
        }
    }
}

impl<T: 'static, Item: Send + 'static> PhlowView for ListViewWithoutSend<T, Item> {
    fn get_title(&self) -> &str {
        self.proto_list.0.title.as_str()
    }

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

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

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

    fn create_instance(&self, object: ObjectRef<'_>) -> Box<dyn ViewInstance> {
        Box::new(self.create_list_instance(object))
    }

    fn view_type() -> &'static str
    where
        Self: Sized,
    {
        "list_view"
    }
}

impl<T: 'static, Item: Send + 'static, SendItem: Send + 'static> PhlowView
    for ListViewWithSend<T, Item, SendItem>
{
    fn get_title(&self) -> &str {
        self.proto_list.proto_list.0.title.as_str()
    }

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

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

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

    fn create_instance(&self, object: ObjectRef<'_>) -> Box<dyn ViewInstance> {
        let send_computation = self.send_computation.clone();

        let mut instance = self.proto_list.create_list_instance(object);
        instance.send_computation = Arc::new(move |item| {
            Some(AnySendObject::new(unsafe { send_computation(item.cast()) }))
        });
        Box::new(instance)
    }

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

pub struct ListViewInstance {
    base_view: BaseView,
    items: Vec<AnySendObject>,
    item_text_computation: Arc<InstanceItemTextComputation>,
    send_computation: Arc<InstanceSendComputation>,
}

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

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

    fn get_view_type(&self) -> &str {
        "list_view"
    }

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

impl ListViewInstance {
    pub fn items_count(&self) -> usize {
        self.items.len()
    }

    pub fn get_item_text(&self, item_index: usize) -> Option<String> {
        self.items.get(item_index).map(|item| {
            ObjectRef::with_any(item.as_any(), |item| {
                ((self.item_text_computation)(item)).to_string()
            })
        })
    }

    pub fn get_item_to_send(&self, item_index: usize) -> Option<AnySendObject> {
        self.items.get(item_index).and_then(|item| {
            ObjectRef::with_any(item.as_any(), |item| (self.send_computation)(item))
        })
    }
}

impl<T: ?Sized, Item> Debug for ListViewWithoutSend<T, Item> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct(type_name::<Self>()).finish()
    }
}

impl<T: ?Sized, Item, SendItem> Debug for ListViewWithSend<T, Item, SendItem> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct(type_name::<Self>()).finish()
    }
}

impl<T: ?Sized> Clone for ProtoListView<T> {
    fn clone(&self) -> Self {
        Self(self.0.clone(), PhantomData)
    }
}

impl<T: ?Sized, Item> Clone for ListViewWithoutSend<T, Item> {
    fn clone(&self) -> Self {
        Self {
            proto_list: self.proto_list.clone(),
            items_computation: self.items_computation.clone(),
            item_text_computation: self.item_text_computation.clone(),
        }
    }
}

impl<T: ?Sized, Item, SendItem> Clone for ListViewWithSend<T, Item, SendItem> {
    fn clone(&self) -> Self {
        Self {
            proto_list: self.proto_list.clone(),
            send_computation: self.send_computation.clone(),
        }
    }
}

// impl<T: 'static, Item: Send + 'static> IntoView for ListViewWithoutSend<T, Item> {
//     fn into_view(self) -> Box<dyn PhlowView> {
//         Box::new(self)
//     }
// }

// impl<T: 'static, Item: Send + 'static, SentItem: Send + 'static> IntoView
//     for ListViewWithSend<T, Item, SentItem>
// {
//     fn into_view(self) -> Box<dyn PhlowView> {
//         Box::new(self)
//     }
// }