use std::sync::Arc;
use platform_web::{WebClipboard, WebPlatform, WebPlatformConfig};
#[cfg(feature = "web")]
use renderer_web::{WebGpuRendererFactory, canvas_in};
use services_core::{AppPathsProvider, NoPaths};
use crate::app::App;
use crate::app_config::AppConfig;
#[derive(Clone, Debug, Default)]
pub struct WebOptions {
pub host: Option<String>,
pub focus_and_gestures: Option<(bool, bool)>,
pub renderer: WebRenderer,
pub owns_context_menu: Option<bool>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum WebRenderer {
#[default]
Auto,
Canvas,
Document,
}
impl WebRenderer {
fn resolved(self, host: &web_sys::HtmlElement) -> Self {
if self != Self::Auto {
return self;
}
match platform_web::page_setting(host, "renderer")
.as_deref()
.map(str::trim)
{
Some("dom" | "document") => Self::Document,
Some("canvas" | "gpu" | "webgpu") => Self::Canvas,
Some(other) if !other.is_empty() && other != "auto" => {
tracing::warn!(
"`{other}` is not a renderer this build has; the choice is `dom`, `canvas` or `auto`"
);
Self::Auto
}
_ => Self::Auto,
}
}
}
pub fn run_web_app_with_name<A: App>(
config: AppConfig,
options: WebOptions,
app: A,
app_name: &str,
) {
platform_web::install_console_logging();
let app_name = app_name.to_string();
let host = match platform_web::host_element(options.host.as_deref()) {
Ok(host) => host,
Err(e) => {
tracing::error!("telar could not find the element to mount on: {e}");
return;
}
};
services_core::set_clipboard(Arc::new(WebClipboard::new()));
let wanted = options.renderer.resolved(&host);
let host_for_start = host.clone();
wasm_bindgen_futures::spawn_local(async move {
let document = draws_as_document(wanted).await;
start(config, options, host_for_start, document, app, &app_name);
});
}
#[cfg(feature = "web")]
async fn draws_as_document(wanted: WebRenderer) -> bool {
match wanted {
WebRenderer::Document => true,
WebRenderer::Canvas => false,
WebRenderer::Auto => match renderer_web::webgpu_available().await {
Ok(()) => false,
Err(reason) => {
tracing::info!("drawing as a document: {}", reason.message());
true
}
},
}
}
#[cfg(not(feature = "web"))]
async fn draws_as_document(wanted: WebRenderer) -> bool {
if wanted == WebRenderer::Canvas {
tracing::warn!(
"this build draws as a document; `canvas` needs the `web` feature, where `web-dom` is the document alone"
);
}
true
}
fn start<A: App>(
config: AppConfig,
options: WebOptions,
host: web_sys::HtmlElement,
document: bool,
app: A,
app_name: &str,
) {
let (autofocus, owns_gestures) = options.focus_and_gestures.unwrap_or((true, true));
let paths: Arc<dyn AppPathsProvider> = Arc::new(NoPaths);
let platform_config = WebPlatformConfig {
host: options.host,
autofocus,
owns_gestures,
owns_scroll: document,
owns_context_menu: options.owns_context_menu.unwrap_or(!document),
};
let platform = WebPlatform::with_host(host.clone(), platform_config);
let result = if document {
renderer_core::set_text_metrics(renderer_dom::CanvasTextMetrics);
ui_tree::set_element_capture(true);
crate::runner::run_with_platform_and_renderer::<_, _, A, ()>(
platform,
renderer_dom::DomRendererFactory::new(host),
config,
paths,
app,
app_name,
)
} else {
#[cfg(feature = "web")]
{
let canvas = match canvas_in(&host) {
Ok(canvas) => canvas,
Err(e) => {
tracing::error!("telar could not create a canvas to draw on: {e}");
return;
}
};
crate::runner::run_with_platform_and_renderer::<_, _, A, ()>(
platform,
WebGpuRendererFactory::new(canvas),
config,
paths,
app,
app_name,
)
}
#[cfg(not(feature = "web"))]
unreachable!("a build without the canvas renderer draws as a document")
};
if let Err(e) = result {
tracing::error!("telar could not start: {e}");
}
}