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,
};
#[derive(PartialEq)]
pub struct HtmlViewer {
handle: HtmlHandle,
layout: LayoutData,
fonts: Vec<Bytes>,
}
impl HtmlViewer {
pub fn new(handle: HtmlHandle) -> Self {
Self {
handle,
layout: LayoutData::default(),
fonts: Vec::new(),
}
.expanded()
}
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);
})
}
}