use std::sync::Arc;
use teksilo_canvas::Rect;
use teksilo_core::AppEventPoster;
use teksilo_core::raw_handle::ParentHandle;
use teksilo_core::window::TeksiloWindowId;
use wry::{PageLoadEvent, WebView, WebViewBuilder};
#[cfg(not(target_os = "linux"))]
use wry::dpi::{LogicalPosition, LogicalSize};
use crate::backend::{
ConsoleLevel, NoopWebViewHandle, WebSource, WebViewAttributes, WebViewBackend, WebViewEvent,
WebViewHandle, WebViewId, js_string, post_event,
};
#[derive(Debug, Default)]
pub struct WryBackend {
_private: (),
}
impl WryBackend {
pub fn new() -> Self {
Self { _private: () }
}
}
fn fail(
poster: &Option<Arc<dyn AppEventPoster>>,
window_id: TeksiloWindowId,
web_view_id: WebViewId,
text: String,
) -> Box<dyn WebViewHandle> {
post_event(
poster,
window_id,
web_view_id,
WebViewEvent::ConsoleMessage {
level: ConsoleLevel::Error,
text,
},
);
post_event(
poster,
window_id,
web_view_id,
WebViewEvent::NavigationFinished {
url: String::new(),
success: false,
},
);
Box::new(NoopWebViewHandle)
}
impl WebViewBackend for WryBackend {
fn open(
&mut self,
web_view_id: WebViewId,
window_id: TeksiloWindowId,
parent: Option<ParentHandle>,
attrs: WebViewAttributes,
poster: Option<Arc<dyn AppEventPoster>>,
) -> Box<dyn WebViewHandle> {
#[cfg(target_os = "linux")]
if gtk::init().is_err() {
return fail(
&poster,
window_id,
web_view_id,
"WryBackend: gtk::init() failed — WebKitGTK needs GTK initialised".to_string(),
);
}
let Some(parent) = parent else {
return fail(
&poster,
window_id,
web_view_id,
"WryBackend: no parent window handle available; webview not created".to_string(),
);
};
let mut builder = WebViewBuilder::new();
match &attrs.source {
Some(WebSource::Url(url)) => builder = builder.with_url(url.clone()),
Some(WebSource::Html { html, .. }) => builder = builder.with_html(html.clone()),
None => {}
}
if let Some(ua) = &attrs.user_agent {
builder = builder.with_user_agent(ua.clone());
}
builder = builder
.with_transparent(attrs.transparent)
.with_devtools(attrs.devtools);
{
let poster = poster.clone();
builder = builder.with_ipc_handler(move |req| {
post_event(
&poster,
window_id,
web_view_id,
WebViewEvent::Message(req.body().clone()),
);
});
}
{
let poster = poster.clone();
builder = builder.with_on_page_load_handler(move |event, url| {
let ev = match event {
PageLoadEvent::Started => WebViewEvent::PageLoadStarted,
PageLoadEvent::Finished => WebViewEvent::PageLoadFinished,
};
post_event(&poster, window_id, web_view_id, ev);
if matches!(event, PageLoadEvent::Finished) {
post_event(
&poster,
window_id,
web_view_id,
WebViewEvent::NavigationFinished { url, success: true },
);
}
});
}
{
let poster = poster.clone();
builder = builder.with_navigation_handler(move |url| {
post_event(
&poster,
window_id,
web_view_id,
WebViewEvent::NavigationStarted {
url,
can_cancel: false,
},
);
true });
}
{
let poster = poster.clone();
builder = builder.with_document_title_changed_handler(move |title| {
post_event(
&poster,
window_id,
web_view_id,
WebViewEvent::TitleChanged(title),
);
});
}
{
let poster = poster.clone();
builder = builder.with_download_started_handler(move |url, path| {
post_event(
&poster,
window_id,
web_view_id,
WebViewEvent::DownloadStarted {
url,
suggested_path: path.clone(),
},
);
true
});
}
{
let poster = poster.clone();
builder = builder.with_download_completed_handler(move |_url, path, success| {
post_event(
&poster,
window_id,
web_view_id,
WebViewEvent::DownloadFinished {
path: path.unwrap_or_default(),
success,
},
);
});
}
match builder.build_as_child(&parent) {
Ok(webview) => Box::new(WryHandle { webview }),
Err(e) => fail(
&poster,
window_id,
web_view_id,
format!("WryBackend: failed to create webview: {e}"),
),
}
}
}
struct WryHandle {
webview: WebView,
}
impl WebViewHandle for WryHandle {
fn set_bounds(&self, bounds: Rect, scale_factor: f32) {
#[cfg(target_os = "linux")]
let rect = wry::Rect {
position: wry::dpi::PhysicalPosition::new(
(bounds.x * scale_factor) as f64,
(bounds.y * scale_factor) as f64,
)
.into(),
size: wry::dpi::PhysicalSize::new(
(bounds.width * scale_factor) as f64,
(bounds.height * scale_factor) as f64,
)
.into(),
};
#[cfg(not(target_os = "linux"))]
let rect = {
let _ = scale_factor;
wry::Rect {
position: LogicalPosition::new(bounds.x, bounds.y).into(),
size: LogicalSize::new(bounds.width, bounds.height).into(),
}
};
let _ = self.webview.set_bounds(rect);
}
fn load_url(&self, url: &str) {
let _ = self.webview.load_url(url);
}
fn load_html(&self, html: &str, _base_url: Option<&str>) {
let _ = self.webview.evaluate_script(&format!(
"document.open();document.write({});document.close();",
js_string(html)
));
}
fn eval(&self, script: &str) {
let _ = self.webview.evaluate_script(script);
}
fn post_message(&self, msg: &str) {
let _ = self.webview.evaluate_script(&format!(
"window.dispatchEvent(new MessageEvent('teksilo-message',{{data:{}}}))",
js_string(msg)
));
}
fn reload(&self) {
let _ = self.webview.reload();
}
fn go_back(&self) {
let _ = self.webview.evaluate_script("history.back()");
}
fn go_forward(&self) {
let _ = self.webview.evaluate_script("history.forward()");
}
fn stop(&self) {
let _ = self.webview.evaluate_script("window.stop()");
}
fn set_visible(&self, visible: bool) {
let _ = self.webview.set_visible(visible);
}
fn set_focus(&self) {
let _ = self.webview.focus();
}
fn open_devtools(&self) {
self.webview.open_devtools();
}
fn close_devtools(&self) {
self.webview.close_devtools();
}
}