freya-html 0.5.0

HTML + CSS rendering for Freya using Blitz
Documentation
use std::{
    cell::RefCell,
    rc::Rc,
};

use freya_core::prelude::*;
use futures_lite::StreamExt;

use crate::{
    cache::ResourceCache,
    element::Html,
    handle::HtmlHandle,
    net::FetchRequest,
    state::BlitzState,
};

/// Embeds an HTML + CSS document rendered by [Blitz](https://github.com/DioxusLabs/blitz), driven by an [HtmlHandle].
///
/// ```rust, no_run
/// # use freya::prelude::*;
/// # use freya_html::prelude::*;
/// # fn app() -> impl IntoElement {
/// // Inline HTML
/// let inline = use_html(|| HtmlSource::html("<p>Hello <b>world</b></p>"));
/// HtmlViewer::new(inline);
/// // Or a remote document, sized explicitly
/// let remote = use_html(|| HtmlSource::url("https://example.com"));
/// HtmlViewer::new(remote)
///     .width(Size::px(400.))
///     .height(Size::px(300.))
/// # }
/// ```
#[derive(PartialEq)]
pub struct HtmlViewer {
    handle: HtmlHandle,
    layout: LayoutData,
    fonts: Vec<Bytes>,
}

impl HtmlViewer {
    /// Render the document navigated by `handle`, fills its parent unless sized explicitly.
    pub fn new(handle: HtmlHandle) -> Self {
        Self {
            handle,
            layout: LayoutData::default(),
            fonts: Vec::new(),
        }
        .expanded()
    }

    /// Adds a font to the document's font collection.
    pub fn font(mut self, font: impl Into<Bytes>) -> Self {
        self.fonts.push(font.into());
        self
    }
}

impl LayoutExt for HtmlViewer {
    fn layout_mut(&mut self) -> &mut LayoutData {
        &mut self.layout
    }
}

impl ContainerExt for HtmlViewer {}

impl Component for HtmlViewer {
    fn render(&self) -> impl IntoElement {
        let platform = Platform::get().current_window();
        let mut handle = self.handle;
        let fonts = self.fonts.clone();

        let state = use_hook(move || {
            let platform = Platform::get().current_window();
            let resource_cache = ResourceCache::get();
            let (wake_tx, mut wake_rx) = futures_channel::mpsc::unbounded::<()>();
            let (nav_tx, mut nav_rx) = futures_channel::mpsc::unbounded::<String>();
            let (fetch_tx, mut fetch_rx) = futures_channel::mpsc::unbounded::<FetchRequest>();

            let state = Rc::new(RefCell::new(BlitzState::new(
                wake_tx,
                nav_tx,
                fetch_tx,
                resource_cache.clone(),
                fonts,
            )));
            handle.attach(state.clone());

            spawn(async move {
                while wake_rx.next().await.is_some() {
                    platform.send(UserEvent::RequestRedraw);
                }
            });

            spawn(async move {
                while let Some((url, handler)) = fetch_rx.next().await {
                    let resource_cache = resource_cache.clone();

                    spawn(async move {
                        match freya_components::http::fetch(url.clone()).await {
                            Ok(bytes) => {
                                let resolved_url = url.to_string();
                                resource_cache.set(url, bytes.clone());
                                handler.bytes(resolved_url, bytes);
                            }
                            Err(err) => tracing::warn!("Failed to fetch resource {url}: {err}"),
                        }
                    });
                }
            });

            spawn(async move {
                while let Some(url) = nav_rx.next().await {
                    handle.navigate(url);
                }
            });

            state
        });

        let a11y_id = use_hook(AccessibilityId::new_unique);

        Html::new(state.clone())
            .layout(self.layout.clone())
            .a11y_id(a11y_id)
            .a11y_focusable(true)
            .on_mouse_move({
                let state = state.clone();
                move |e: Event<MouseEventData>| {
                    state
                        .borrow_mut()
                        .mouse_move(e.element_location.x as f32, e.element_location.y as f32);
                }
            })
            .on_mouse_down({
                let state = state.clone();
                move |e: Event<MouseEventData>| {
                    a11y_id.request_focus();
                    state.borrow_mut().mouse_button(
                        e.element_location.x as f32,
                        e.element_location.y as f32,
                        e.button,
                        true,
                    );
                }
            })
            .on_mouse_up({
                let state = state.clone();
                move |e: Event<MouseEventData>| {
                    state.borrow_mut().mouse_button(
                        e.element_location.x as f32,
                        e.element_location.y as f32,
                        e.button,
                        false,
                    );
                }
            })
            .on_touch_start({
                let state = state.clone();
                move |event: Event<TouchEventData>| {
                    a11y_id.request_focus();
                    state.borrow_mut().touch(&event, TouchPhase::Started);
                }
            })
            .on_touch_move({
                let state = state.clone();
                move |event: Event<TouchEventData>| {
                    state.borrow_mut().touch(&event, TouchPhase::Moved);
                }
            })
            .on_touch_end({
                let state = state.clone();
                move |event: Event<TouchEventData>| {
                    state.borrow_mut().touch(&event, TouchPhase::Ended);
                }
            })
            .on_touch_cancel({
                let state = state.clone();
                move |event: Event<TouchEventData>| {
                    state.borrow_mut().touch(&event, TouchPhase::Cancelled);
                }
            })
            .on_wheel({
                let state = state.clone();
                move |e: Event<WheelEventData>| {
                    let scale = *platform.scale_factor.read();
                    state.borrow_mut().wheel(
                        e.element_location.x as f32,
                        e.element_location.y as f32,
                        e.delta_x / scale,
                        e.delta_y / scale,
                    );
                }
            })
            .on_key_down({
                let state = state.clone();
                move |e: Event<KeyboardEventData>| {
                    state.borrow_mut().key(&e.key, &e.code, e.modifiers, true);
                }
            })
            .on_key_up(move |e: Event<KeyboardEventData>| {
                state.borrow_mut().key(&e.key, &e.code, e.modifiers, false);
            })
    }
}