Skip to main content

freya_html/
component.rs

1use std::{
2    cell::RefCell,
3    rc::Rc,
4};
5
6use freya_core::prelude::*;
7use futures_lite::StreamExt;
8
9use crate::{
10    cache::ResourceCache,
11    element::Html,
12    handle::HtmlHandle,
13    net::FetchRequest,
14    state::BlitzState,
15};
16
17/// Embeds an HTML + CSS document rendered by [Blitz](https://github.com/DioxusLabs/blitz), driven by an [HtmlHandle].
18///
19/// ```rust, no_run
20/// # use freya::prelude::*;
21/// # use freya_html::prelude::*;
22/// # fn app() -> impl IntoElement {
23/// // Inline HTML
24/// let inline = use_html(|| HtmlSource::html("<p>Hello <b>world</b></p>"));
25/// HtmlViewer::new(inline);
26/// // Or a remote document, sized explicitly
27/// let remote = use_html(|| HtmlSource::url("https://example.com"));
28/// HtmlViewer::new(remote)
29///     .width(Size::px(400.))
30///     .height(Size::px(300.))
31/// # }
32/// ```
33#[derive(PartialEq)]
34pub struct HtmlViewer {
35    handle: HtmlHandle,
36    layout: LayoutData,
37    fonts: Vec<Bytes>,
38}
39
40impl HtmlViewer {
41    /// Render the document navigated by `handle`, fills its parent unless sized explicitly.
42    pub fn new(handle: HtmlHandle) -> Self {
43        Self {
44            handle,
45            layout: LayoutData::default(),
46            fonts: Vec::new(),
47        }
48        .expanded()
49    }
50
51    /// Adds a font to the document's font collection.
52    pub fn font(mut self, font: impl Into<Bytes>) -> Self {
53        self.fonts.push(font.into());
54        self
55    }
56}
57
58impl LayoutExt for HtmlViewer {
59    fn layout_mut(&mut self) -> &mut LayoutData {
60        &mut self.layout
61    }
62}
63
64impl ContainerExt for HtmlViewer {}
65
66impl Component for HtmlViewer {
67    fn render(&self) -> impl IntoElement {
68        let platform = Platform::get().current_window();
69        let mut handle = self.handle;
70        let fonts = self.fonts.clone();
71
72        let state = use_hook(move || {
73            let platform = Platform::get().current_window();
74            let resource_cache = ResourceCache::get();
75            let (wake_tx, mut wake_rx) = futures_channel::mpsc::unbounded::<()>();
76            let (nav_tx, mut nav_rx) = futures_channel::mpsc::unbounded::<String>();
77            let (fetch_tx, mut fetch_rx) = futures_channel::mpsc::unbounded::<FetchRequest>();
78
79            let state = Rc::new(RefCell::new(BlitzState::new(
80                wake_tx,
81                nav_tx,
82                fetch_tx,
83                resource_cache.clone(),
84                fonts,
85            )));
86            handle.attach(state.clone());
87
88            spawn(async move {
89                while wake_rx.next().await.is_some() {
90                    platform.send(UserEvent::RequestRedraw);
91                }
92            });
93
94            spawn(async move {
95                while let Some((url, handler)) = fetch_rx.next().await {
96                    let resource_cache = resource_cache.clone();
97
98                    spawn(async move {
99                        match freya_components::http::fetch(url.clone()).await {
100                            Ok(bytes) => {
101                                let resolved_url = url.to_string();
102                                resource_cache.set(url, bytes.clone());
103                                handler.bytes(resolved_url, bytes);
104                            }
105                            Err(err) => tracing::warn!("Failed to fetch resource {url}: {err}"),
106                        }
107                    });
108                }
109            });
110
111            spawn(async move {
112                while let Some(url) = nav_rx.next().await {
113                    handle.navigate(url);
114                }
115            });
116
117            state
118        });
119
120        let a11y_id = use_hook(AccessibilityId::new_unique);
121
122        Html::new(state.clone())
123            .layout(self.layout.clone())
124            .a11y_id(a11y_id)
125            .a11y_focusable(true)
126            .on_mouse_move({
127                let state = state.clone();
128                move |e: Event<MouseEventData>| {
129                    state
130                        .borrow_mut()
131                        .mouse_move(e.element_location.x as f32, e.element_location.y as f32);
132                }
133            })
134            .on_mouse_down({
135                let state = state.clone();
136                move |e: Event<MouseEventData>| {
137                    a11y_id.request_focus();
138                    state.borrow_mut().mouse_button(
139                        e.element_location.x as f32,
140                        e.element_location.y as f32,
141                        e.button,
142                        true,
143                    );
144                }
145            })
146            .on_mouse_up({
147                let state = state.clone();
148                move |e: Event<MouseEventData>| {
149                    state.borrow_mut().mouse_button(
150                        e.element_location.x as f32,
151                        e.element_location.y as f32,
152                        e.button,
153                        false,
154                    );
155                }
156            })
157            .on_touch_start({
158                let state = state.clone();
159                move |event: Event<TouchEventData>| {
160                    a11y_id.request_focus();
161                    state.borrow_mut().touch(&event, TouchPhase::Started);
162                }
163            })
164            .on_touch_move({
165                let state = state.clone();
166                move |event: Event<TouchEventData>| {
167                    state.borrow_mut().touch(&event, TouchPhase::Moved);
168                }
169            })
170            .on_touch_end({
171                let state = state.clone();
172                move |event: Event<TouchEventData>| {
173                    state.borrow_mut().touch(&event, TouchPhase::Ended);
174                }
175            })
176            .on_touch_cancel({
177                let state = state.clone();
178                move |event: Event<TouchEventData>| {
179                    state.borrow_mut().touch(&event, TouchPhase::Cancelled);
180                }
181            })
182            .on_wheel({
183                let state = state.clone();
184                move |e: Event<WheelEventData>| {
185                    let scale = *platform.scale_factor.read();
186                    state.borrow_mut().wheel(
187                        e.element_location.x as f32,
188                        e.element_location.y as f32,
189                        e.delta_x / scale,
190                        e.delta_y / scale,
191                    );
192                }
193            })
194            .on_key_down({
195                let state = state.clone();
196                move |e: Event<KeyboardEventData>| {
197                    state.borrow_mut().key(&e.key, &e.code, e.modifiers, true);
198                }
199            })
200            .on_key_up(move |e: Event<KeyboardEventData>| {
201                state.borrow_mut().key(&e.key, &e.code, e.modifiers, false);
202            })
203    }
204}