Skip to main content

teksilo_core/styles/
web_view_style.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Tier-3 style protocol for `WebView`. See `docs/styling-system.md`.
5//!
6//! A `WebView` paints almost nothing itself — the actual page is drawn by a
7//! native OS engine subview (WKWebView / WebView2 / WebKitGTK / Servo) that
8//! sits *on top of* the wgpu surface. What Teksilo owns is the **overlay
9//! chrome** the engine surface sits behind/under: the loading shimmer shown
10//! before the first paint arrives, an error banner when navigation fails, and
11//! the focus ring. This trait themes that chrome.
12//!
13//! Because the chrome reacts to the page's lifecycle, the config carries a
14//! `Signal<WebViewVisualState>` (the same reactive pattern `DropZone` uses)
15//! rather than a static state — `make_body` binds the overlay's appearance to
16//! it so it updates without a rebuild.
17
18use std::rc::Rc;
19
20use teksilo_tokens::{BorderRole, SurfaceRole};
21
22use crate::build_context::BuildContext;
23use crate::signal::Signal;
24use crate::widget_id::WidgetId;
25
26/// Lifecycle/visual state of a web view, driving the overlay chrome. Defined
27/// here (not in `teksilo-webview`) so the core style trait and the default
28/// recipe can both name it — mirroring `DropZoneVisualState`.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum WebViewVisualState {
31    /// The engine is loading the current page — show the loading shimmer.
32    Loading,
33    /// The page has finished loading and is displayed by the engine subview.
34    /// The overlay is fully transparent; only the focus ring may show.
35    Ready,
36    /// Navigation or engine init failed — show the error chrome. Also the
37    /// state the Wayland-without-Servo / unsupported-platform path lands in.
38    Error,
39}
40
41impl WebViewVisualState {
42    /// Background surface-tint role for the overlay in this state.
43    pub fn surface_role(self) -> SurfaceRole {
44        match self {
45            Self::Loading => SurfaceRole::Sunken,
46            Self::Ready => SurfaceRole::Transparent,
47            Self::Error => SurfaceRole::StatusError,
48        }
49    }
50
51    /// Border role for the overlay/focus chrome in this state.
52    pub fn border_role(self) -> BorderRole {
53        match self {
54            Self::Loading => BorderRole::Default,
55            Self::Ready => BorderRole::Default,
56            Self::Error => BorderRole::Error,
57        }
58    }
59}
60
61/// Inputs handed to a [`WebViewStyle`] to build the web view's overlay chrome.
62#[derive(Clone)]
63pub struct WebViewStyleConfig {
64    /// Reactive lifecycle state — bind overlay surface/border/opacity to it.
65    pub state: Signal<WebViewVisualState>,
66    /// Whether the Teksilo-side frame holds keyboard focus. The web view paints
67    /// no content of its own — the page is the engine's native subview — so the
68    /// focus ring drawn from this is the *only* thing telling a keyboard user
69    /// that Tab landed here and Enter will enter the page. Draw it.
70    pub focused: Signal<bool>,
71    /// Pre-built overlay content (e.g. a spinner + status label) the chrome
72    /// centers. The native engine surface is composited by the OS on top of
73    /// this; the overlay is what shows through before/around the page.
74    pub content: WidgetId,
75}
76
77/// Tier-3 style protocol for [`WebView`](../../teksilo_webview/struct.WebView.html).
78/// Produces the overlay body shown behind/around the native engine surface.
79pub trait WebViewStyle: 'static {
80    fn make_body(&self, cfg: &WebViewStyleConfig, ctx: &mut BuildContext) -> WidgetId;
81}
82
83/// Shared, theme-installable handle to a [`WebViewStyle`].
84pub type SharedWebViewStyle = Rc<dyn WebViewStyle>;