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>;