cranpose 0.9.22

Build desktop, mobile and web apps with composable Rust functions
Documentation
use std::sync::mpsc::{self, Receiver, Sender};

use cranpose_ui::{LayoutTree, Rect};

use crate::native_view::NativeViewHost;

pub(crate) type Events = Sender<(u64, String)>;

pub(crate) trait BrowserView {
    fn navigate(&mut self, url: &str) -> Result<(), String>;
    fn place(&mut self, bounds: Rect) -> Result<(), String>;
}

struct Child<V> {
    id: u64,
    url: String,
    bounds: Rect,
    view: Option<V>,
    seen: bool,
}

pub(crate) struct WebViews<V> {
    children: Vec<Child<V>>,
    revision: u64,
    pub(crate) events: Events,
    incoming: Receiver<(u64, String)>,
}

impl<V> Default for WebViews<V> {
    fn default() -> Self {
        let (events, incoming) = mpsc::channel();
        Self {
            children: Vec::new(),
            revision: 0,
            events,
            incoming,
        }
    }
}

impl<V: BrowserView> WebViews<V> {
    #[cfg(target_os = "ios")]
    pub(crate) fn revision(&self) -> u64 {
        self.revision
    }

    #[cfg(any(target_os = "ios", target_os = "linux"))]
    pub(crate) fn views(&self) -> impl Iterator<Item = &V> {
        self.children.iter().filter_map(|child| child.view.as_ref())
    }
    pub(crate) fn dispatch(&self, host: &NativeViewHost) {
        for (id, event) in self.incoming.try_iter() {
            host.dispatch(id, &event);
        }
    }

    pub(crate) fn is_empty(&self) -> bool {
        self.children.is_empty()
    }

    pub(crate) fn sync(
        &mut self,
        host: &NativeViewHost,
        tree: Option<&LayoutTree>,
        mut create: impl FnMut(u64, &str, Rect, &Events) -> Result<V, String>,
    ) {
        if host.is_empty() && self.is_empty() {
            return;
        }
        for child in &mut self.children {
            child.seen = false;
        }
        host.for_each_layout(tree, |id, kind, url, bounds| {
            if kind != "web" {
                return;
            }
            if let Some(child) = self.children.iter_mut().find(|child| child.id == id) {
                child.seen = true;
                if let Some(view) = &mut child.view {
                    if child.url != url {
                        if let Err(error) = view.navigate(url) {
                            let _ = self.events.send((id, format!("error:{error}")));
                        }
                        url.clone_into(&mut child.url);
                    }
                    if child.bounds != bounds {
                        if let Err(error) = view.place(bounds) {
                            let _ = self.events.send((id, format!("error:{error}")));
                        }
                        child.bounds = bounds;
                    }
                }
                return;
            }
            let view = match create(id, url, bounds, &self.events) {
                Ok(view) => Some(view),
                Err(error) => {
                    let _ = self.events.send((id, format!("error:{error}")));
                    None
                }
            };
            self.children.push(Child {
                id,
                url: url.to_owned(),
                bounds,
                view,
                seen: true,
            });
            self.revision = self.revision.wrapping_add(1);
        });
        let before = self.children.len();
        self.children.retain(|child| child.seen);
        if before != self.children.len() {
            self.revision = self.revision.wrapping_add(1);
        }
        self.dispatch(host);
    }
}