backdrop_blur_core/liveness.rs
1//! Backdrop freshness as a *typed adapter obligation*, not a footnote. Whether a blurred
2//! backdrop stays correct as content behind it changes does **not** generalize across toolkits
3//! (egui is reactive-by-default: it does not repaint when behind-surface content changes, and
4//! has no region invalidation — "fresh as long as the host repaints" can be *zero* frames). So
5//! the adapter must state its intent with a domain type rather than assume the host repaints
6//! (DESIGN §4.6).
7
8use std::time::Duration;
9
10/// How often the frosted surface's backdrop must be re-grabbed and re-blurred. The adapter —
11/// not core — drives the host's `request_repaint` from this; core only names the obligation.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum RepaintPolicy {
14 /// The backdrop is over still content (dialog, tooltip): grab once, never refresh. The
15 /// default — cheapest, and correct whenever the content behind the surface is static.
16 ///
17 /// `Static` means the adapter does not *drive* repaints for this surface — **not** that the
18 /// surface stops compositing. If the host repaints for any other reason (input, animation
19 /// elsewhere, another surface's `Live`), the frost still re-grabs and re-composites that frame.
20 /// It is "don't request frames," not "don't paint."
21 Static,
22 /// The backdrop is over animating content: refresh every frame. Names an idle-power cost
23 /// and is **required** for glass over moving content (otherwise the blur goes stale).
24 Live,
25 /// Refresh periodically — for content that changes on a known cadence.
26 Bounded(Duration),
27}
28
29impl Default for RepaintPolicy {
30 /// `Static` — the safe, cheap default for the common dialog/tooltip-over-still-content case.
31 fn default() -> Self {
32 Self::Static
33 }
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39
40 #[test]
41 fn default_repaint_policy_is_static() {
42 assert_eq!(RepaintPolicy::default(), RepaintPolicy::Static);
43 }
44
45 #[test]
46 fn bounded_policy_carries_its_interval() {
47 let policy = RepaintPolicy::Bounded(Duration::from_millis(250));
48 assert_eq!(policy, RepaintPolicy::Bounded(Duration::from_millis(250)));
49 assert_ne!(policy, RepaintPolicy::Bounded(Duration::from_millis(500)));
50 }
51}