Skip to main content

freya_webview/
component.rs

1use freya_core::{
2    events::data::{
3        Event,
4        SizedEventData,
5    },
6    prelude::*,
7};
8
9use crate::{
10    element::webview,
11    lifecycle::WebViewLifecycleEvent,
12    prelude::{
13        WebViewConfig,
14        WebViewId,
15    },
16    registry::WebViewCallback,
17};
18
19/// Embeds a native WebView pointing to a given URL as a regular element in your UI.
20///
21/// # Requirements
22///
23/// The [`WebViewPlugin`](crate::plugin::WebViewPlugin) must be registered in your `LaunchConfig`
24/// for this component to work, otherwise it will panic:
25///
26/// ```rust,no_run
27/// # use freya::prelude::*;
28/// # use freya_webview::prelude::*;
29/// launch(
30///     LaunchConfig::new()
31///         .with_plugin(WebViewPlugin::new())
32///         .with_window(WindowConfig::new(app)),
33/// );
34/// # fn app() -> impl IntoElement { rect() }
35/// ```
36///
37/// WebViews are not supported on every platform, see the [crate-level docs](crate#platform-compatibility)
38/// for details.
39///
40/// # Example
41///
42/// ```rust,no_run
43/// # use freya::prelude::*;
44/// # use freya_webview::prelude::*;
45/// fn app() -> impl IntoElement {
46///     WebView::new("https://example.com").expanded()
47/// }
48/// ```
49#[derive(Clone)]
50pub struct WebView {
51    webview_id: WebViewId,
52    url: String,
53    close_on_drop: bool,
54    on_created: Option<WebViewCallback>,
55    layout: LayoutData,
56}
57
58impl PartialEq for WebView {
59    fn eq(&self, other: &Self) -> bool {
60        self.webview_id == other.webview_id
61            && self.url == other.url
62            && self.close_on_drop == other.close_on_drop
63            && match (&self.on_created, &other.on_created) {
64                (None, None) => true,
65                (Some(a), Some(b)) => std::sync::Arc::ptr_eq(a, b),
66                _ => false,
67            }
68            && self.layout == other.layout
69    }
70}
71
72impl WebView {
73    pub fn new(url: impl Into<String>) -> Self {
74        Self {
75            webview_id: WebViewId::new(),
76            url: url.into(),
77            close_on_drop: true,
78            on_created: None,
79            layout: LayoutData::default(),
80        }
81    }
82
83    /// Set the URL the WebView will load.
84    pub fn url(mut self, url: impl Into<String>) -> Self {
85        self.url = url.into();
86        self
87    }
88
89    /// Set a custom [`WebViewId`] so the WebView can be referenced later, e.g. with
90    /// [`WebViewManager::close`](crate::lifecycle::WebViewManager::close).
91    pub fn id(mut self, id: WebViewId) -> Self {
92        self.webview_id = id;
93        self
94    }
95
96    /// If you decide to not close the webview on drop you will need to manually close it with [WebViewManager::close](crate::lifecycle::WebViewManager::close).
97    pub fn close_on_drop(mut self, close: bool) -> Self {
98        self.close_on_drop = close;
99        self
100    }
101
102    /// Customize the underlying [`wry::WebViewBuilder`] right before the WebView is built, for
103    /// example to set a custom user agent or inject initialization scripts.
104    pub fn on_created(
105        mut self,
106        on_created: impl Fn(wry::WebViewBuilder) -> wry::WebViewBuilder + Send + 'static,
107    ) -> Self {
108        self.on_created = Some(WebViewCallback::new(Box::new(on_created)));
109        self
110    }
111}
112
113impl LayoutExt for WebView {
114    fn get_layout(&mut self) -> &mut LayoutData {
115        &mut self.layout
116    }
117}
118impl ContainerExt for WebView {}
119
120const WEBVIEW_CONTEXT_ERROR: &str = "
121Error: Make sure to register the WebViewPlugin in your LaunchConfig:
122
123LaunchConfig::new()
124    .with_plugin(WebViewPlugin::new())
125";
126
127impl Component for WebView {
128    fn render(&self) -> impl IntoElement {
129        let events = try_consume_root_context::<crate::lifecycle::WebViewEvents>()
130            .expect(WEBVIEW_CONTEXT_ERROR);
131
132        let webview_id = self.webview_id;
133        let url = self.url.clone();
134        let close_on_drop = self.close_on_drop;
135        let on_created = self.on_created.clone();
136
137        let config = WebViewConfig {
138            url: url.clone(),
139            transparent: false,
140            user_agent: None,
141            on_created,
142        };
143
144        use_drop({
145            let events = events.clone();
146            move || {
147                events.lock().unwrap().push(if close_on_drop {
148                    WebViewLifecycleEvent::Close { id: webview_id }
149                } else {
150                    WebViewLifecycleEvent::Hide { id: webview_id }
151                });
152            }
153        });
154
155        webview(&url)
156            .layout(self.layout.clone())
157            .on_sized(move |event: Event<SizedEventData>| {
158                events.lock().unwrap().push(WebViewLifecycleEvent::Resized {
159                    id: webview_id,
160                    area: event.area,
161                    config: config.clone(),
162                });
163                Platform::get().send(UserEvent::RequestRedraw);
164            })
165    }
166
167    fn render_key(&self) -> DiffKey {
168        DiffKey::from(&self.webview_id)
169    }
170}