1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! `backdrop-blur-egui` — the egui adapter for frosted glass, over **two paths** sharing one
//! [`Surface`] vocabulary.
//!
//! - **`grab-pass`** (the mainstream path: `eframe`-on-glow and the `cage` Wayland kiosk). The host
//! owns the GL loop; `GrabPassRenderer` rides an egui **paint callback** that grabs the live
//! framebuffer behind a surface, blurs it, and composites the frosted surface back. Build it once
//! from `eframe::CreationContext::gl`, call `.frost(ui, surface)` per frame, and `.destroy(gl)` in
//! `eframe::App::on_exit`. Pulls glow, never wgpu.
//! - **`own-loop`** (default feature). For a host driving `egui-winit` + `egui-wgpu` directly (not
//! eframe), [`OwnLoopRenderer`] renders the UI into an offscreen intermediate, blurs a region of
//! it, and composites a frosted [`Surface`] over the display target — one encoder, one submit, in
//! the order that does not panic (DESIGN §6). Pulls the wgpu stack.
//!
//! Pick the path with a feature: a kiosk build is `--no-default-features --features grab-pass` and
//! compiles neither wgpu nor egui-wgpu; an own-loop build is the default. The backends themselves are
//! the separate `backdrop-blur-glow` / `backdrop-blur-wgpu` crates.
//!
//! The crate owns only a surface's *background*. The surface's content, foreground, and
//! accessibility stay the host's: a frosted [`Surface`] is a post-render composite, never an egui
//! widget, so it adds nothing to the AccessKit tree.
//!
//! # The three dials: blur, tint, opacity
//!
//! A frosted [`Surface`] mixes three **independent** knobs — conflating them is the most common
//! "my glass looks wrong":
//!
//! - **[`BlurStrength`]** — the blur *radius* in logical points. How smeared the backdrop is.
//! `0` = no blur (a plain tinted pane).
//! - **[`Tint`]** — the glass *film* painted over the blur, a linear-light color whose **alpha is
//! the film mix** (how much tint shows vs. how much blurred backdrop shows through). A *colored*
//! tint composites in color, not black — author it as sRGB with [`Tint::from_srgb_unmultiplied`]
//! so the linear decode is done for you. Alpha `0` = pure blur, no film; alpha `1` = the film is
//! opaque and the blur is invisible under it.
//! - **[`Opacity`]** — the surface-global *presence* in `[0, 1]`, the whole frosted result blended
//! over the destination. This is the **fade dial**: drive it per frame to dissolve glass in/out.
//! Default `1.0`.
//!
//! Rule of thumb: blur sets the *texture*, tint-alpha sets the *material*, opacity sets the
//! *presence*. A barely-tinted heavy blur is clear vibrancy; a high tint-alpha is frosted/opaque
//! glass; opacity below `1` fades the entire thing.
//!
//! # Grab-pass contracts (read before calling `frost`)
//!
//! The grab-pass path samples the **live framebuffer** mid-frame, which makes draw order and fade
//! load-bearing in ways the types cannot enforce:
//!
//! 1. **Enqueue the frost *before* the surface's foreground.** The callback grabs whatever is in the
//! framebuffer at its position — content drawn *before* it. Call `frost(ui, surface)` first, then
//! paint the surface's own content (text, controls) **after**, so the foreground lands on top of
//! the blur. Enqueue it too late and it grabs — and blurs away — your own content. There is no
//! runtime guard for this; it is a hard ordering contract.
//! 2. **Fade with [`Opacity`], not `multiply_opacity`.** egui's `Ui::multiply_opacity` (and the
//! `Opacity` style) **do not reach paint callbacks** — the standard fade silently no-ops on the
//! blur. To dissolve frost in/out, drive the surface's `opacity` field ([`Opacity`]) per frame
//! instead. This is the one egui trap that bites everyone; the [`Opacity`] dial is the supported
//! escape hatch.
//! 3. **A dynamically-sized rect needs *last frame's* rect.** In immediate mode the surface's rect
//! is only known *after* its content lays out, but the frost must be enqueued *before* the content
//! paints (contract 1) — a chicken-and-egg. The worked pattern: stash the rect in egui temp memory
//! keyed by an `Id`, frost **last frame's** rect at the top of this frame, then lay out the content
//! and write back the rect for next frame. It is stable while the surface is open; the only
//! artifact is one frame of staleness on a resize. (A first-class reserved-slot API that returns
//! the callback `Shape` for `painter.set()` is planned; until then this is the recommendation.)
//! 4. **`GrabPassRenderer::took_effect` reports *ran*, not *composited*.** egui skips a
//! fully-clipped callback, so a frosted surface is not guaranteed to paint; `took_effect` lets the
//! host observe that the callback **fired**. It is set even when the region clipped to nothing or
//! the frost errored — it answers "did egui invoke my callback this frame", not "did pixels
//! change". Useful to confirm wiring; not a success signal.
// Neutral spine — available on both paths: the glass material vocabulary (used in `Surface`) and
// the shared `Surface` type itself.
pub use ;
pub use Surface;
// Own-loop path re-exports: the wgpu backend (`render_frame` drives it), the egui-wgpu screen
// descriptor (`FrameInput` carries it), and the renderer. Gated so a grab-pass-only build pulls
// none of the wgpu stack.
pub use ;
pub use ScreenDescriptor;
pub use ;
// Grab-pass path: the eframe-on-glow adapter. Gated so an own-loop-only build pulls no glow/egui_glow.
pub use GrabPassRenderer;
// Re-export the exact `glow` this crate's public API ([`GrabPassRenderer::new`]/`destroy`) is typed
// against, so a consumer writes `backdrop_blur_egui::glow::Context` and is structurally pinned to the
// same `glow` as the adapter. Without this a consumer picks its own `glow` version; a skew from the
// one eframe hands back at `new` surfaces as a baffling "expected `glow::Context`, found
// `glow::Context`" with no breadcrumb. Re-exporting the crate (the eframe-ecosystem norm) turns the
// footgun into a compile-time guarantee.
pub use glow;