Files
Mike-Solar af5482d774 style: cargo fmt with the workspace tab policy (hard_tabs)
Whitespace only; the fork has no own rustfmt.toml so the Oak root
policy applies.
2026-08-11 23:06:13 +08:00

316 lines
8.5 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Blur Filters Example
//!
//! Demonstrates the two CSS-style blur filters:
//!
//! 1. `backdrop_blur` — frosted glass: blurs whatever is rendered behind the element.
//! Shown as a translucent panel, and again inside a `deferred()` popover (to prove the
//! backdrop snapshot includes everything beneath an overlay, and that the overlay sorts on top).
//! 2. `blur` — content blur: blurs the element and its own children as a group.
//! Shown with text and again with a row of colored chips.
//!
//! It also stresses two content-blur edge cases:
//!
//! 3. Nested content blur — a `blur()` element inside another `blur()` element, so the inner
//! subtree is blurred twice (its own filter, then again as part of the outer group). This
//! exercises the renderer's per-nesting-level group textures.
//! 4. Adjacent blocks with no gap on a dark parent, shown two ways: `blur` on each block (every
//! block is its own group, so the parent shows through each seam — exactly like CSS `filter`
//! on each sibling) versus `blur` on the parent (one group covering all blocks, so the blur is
//! continuous and the seams are clean — the CSS "blur the wrapper" idiom).
use gpui::{
App, Bounds, Context, Render, Window, WindowBounds, WindowOptions, deferred, div, point,
prelude::*, px, rgb, rgba, size,
};
struct BlurExample;
/// A vivid, gap-free background so blur is obvious: a grid of saturated tiles filling the window.
fn busy_background() -> impl IntoElement {
let palette = [
0xef4444, 0xf97316, 0xeab308, 0x22c55e, 0x06b6d4, 0x3b82f6, 0x8b5cf6, 0xec4899,
];
let mut next = 0usize;
div()
.absolute()
.inset_0()
.flex()
.flex_col()
.children((0..9).map(|_| {
div().flex().flex_1().children(
(0..8)
.map(|_| {
let hex = palette[next % palette.len()];
next += 1;
div()
.flex_1()
.h_full()
.bg(rgb(hex))
.flex()
.items_center()
.justify_center()
.text_color(rgb(0xffffff))
.text_xl()
.child("◆")
})
.collect::<Vec<_>>(),
)
}))
}
/// A translucent rounded panel that frosts the content behind it.
fn frosted_panel() -> impl IntoElement {
div()
.absolute()
.left(px(60.))
.top(px(120.))
.w(px(360.))
.h(px(200.))
.rounded_xl()
.bg(rgba(0xffffff30))
.backdrop_blur(px(24.))
.border_1()
.border_color(rgba(0xffffff60))
.flex()
.items_center()
.justify_center()
.text_color(rgb(0x111111))
.text_2xl()
.child("backdrop_blur(24px)")
}
/// A popover painted via `deferred()` so it sits above everything; its backdrop blur must
/// still pick up the panel and background beneath it.
fn deferred_popover() -> impl IntoElement {
deferred(
div()
.absolute()
.left(px(260.))
.top(px(260.))
.w(px(300.))
.h(px(150.))
.rounded_lg()
.bg(rgba(0x1e293b66))
.backdrop_blur(px(12.))
.border_1()
.border_color(rgba(0xffffffaa))
.flex()
.items_center()
.justify_center()
.text_color(rgb(0xffffff))
.text_xl()
.child("deferred + backdrop_blur"),
)
}
/// A self-blurred element (CSS `filter: blur`) — its own content is blurred as a group.
fn content_blurred() -> impl IntoElement {
div()
.absolute()
.left(px(120.))
.top(px(420.))
.w(px(280.))
.h(px(120.))
.blur(px(5.))
.bg(rgb(0x0f172a))
.rounded_lg()
.flex()
.items_center()
.justify_center()
.text_color(rgb(0xfacc15))
.text_3xl()
.child("blur(5px) content")
}
/// A content-blurred element with richer content (a row of colored chips), so the `filter: blur`
/// effect — the element and its children blurred as one group — is clearly visible.
fn content_blurred_rich() -> impl IntoElement {
div()
.absolute()
.left(px(120.))
.top(px(580.))
.w(px(280.))
.h(px(120.))
.blur(px(6.))
.bg(rgb(0x1e293b))
.rounded_lg()
.flex()
.items_center()
.justify_center()
.gap_3()
.children(
[0xef4444, 0x22c55e, 0x3b82f6]
.into_iter()
.map(|hex| div().w(px(48.)).h(px(48.)).rounded_md().bg(rgb(hex))),
)
}
/// Nested content blur: a `blur()` element inside another `blur()` element. The inner block is
/// blurred by its own filter and then again as part of the outer group, exercising the renderer's
/// per-nesting-level isolated group textures (up to `MAX_FILTER_DEPTH`). The inner content should
/// read as markedly softer than the outer block's own text.
fn nested_content_blurred() -> impl IntoElement {
div()
.absolute()
.left(px(740.))
.top(px(80.))
.w(px(290.))
.h(px(220.))
.blur(px(3.))
.bg(rgb(0x1e293b))
.rounded_xl()
.flex()
.flex_col()
.items_center()
.justify_center()
.gap_4()
.text_color(rgb(0xe2e8f0))
.text_xl()
.child("outer blur(3px)")
.child(
div()
.w(px(190.))
.h(px(100.))
.blur(px(8.))
.bg(rgb(0xf59e0b))
.rounded_lg()
.flex()
.items_center()
.justify_center()
.text_color(rgb(0x111111))
.text_2xl()
.child("inner blur(8px)"),
)
}
const SEAM_COLORS: [u32; 4] = [0xef4444, 0x22c55e, 0x3b82f6, 0xeab308];
/// One numbered, brightly-coloured block of the seam row. With `blur_each` it becomes its own
/// content-filter group; otherwise it is a plain block (relying on a blurred parent, if any).
/// Square corners on purpose: gpui content masks are axis-aligned rectangles, so a *rounded*
/// parent would not clip the blurred children to its radius and the busy background would leak
/// through the corner triangles — a separate concern from the seam blending under test here.
fn seam_block(i: usize, hex: u32, blur_each: bool) -> impl IntoElement {
let block = div().flex_1().h_full();
let block = if blur_each { block.blur(px(5.)) } else { block };
block
.bg(rgb(hex))
.flex()
.items_center()
.justify_center()
.text_color(rgb(0xffffff))
.text_2xl()
.child(format!("{}", i + 1))
}
/// Adjacent blocks, each its OWN content-filter group (`blur` on every block), no gap, dark parent.
/// This is CSS `filter: blur()` on each sibling: every block fades to transparent at its edges and
/// composites independently, so the dark parent shows through each seam by roughly `α_left · α_right`
/// (peaking at ~25% right on the seam). This matches the web — the clean alternative is the next panel.
fn adjacent_per_block_blur() -> impl IntoElement {
div()
.absolute()
.left(px(740.))
.top(px(350.))
.w(px(290.))
.h(px(110.))
.bg(rgb(0x050505))
.flex()
.children(
SEAM_COLORS
.into_iter()
.enumerate()
.map(|(i, hex)| seam_block(i, hex, true)),
)
}
/// The same adjacent blocks, but `blur` is on the PARENT — one content-filter group covering all
/// four. The blocks are opaque and touching, so the group's interior has no transparency: the blur
/// is continuous across the seams and only the group's outer edge fades. This is the CSS "blur the
/// wrapper, not each child" idiom, and the seams come out clean.
fn adjacent_group_blur() -> impl IntoElement {
div()
.absolute()
.left(px(740.))
.top(px(510.))
.w(px(290.))
.h(px(110.))
.bg(rgb(0x050505))
.blur(px(5.))
.flex()
.children(
SEAM_COLORS
.into_iter()
.enumerate()
.map(|(i, hex)| seam_block(i, hex, false)),
)
}
/// A small dark pill label so the two new test sections are identifiable over the busy background.
fn caption(text: &'static str, left: f32, top: f32) -> impl IntoElement {
div()
.absolute()
.left(px(left))
.top(px(top))
.px_2()
.py_1()
.rounded_md()
.bg(rgba(0x000000cc))
.text_color(rgb(0xffffff))
.text_sm()
.child(text)
}
impl Render for BlurExample {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.relative()
.size_full()
.bg(rgb(0x000000))
.child(busy_background())
.child(frosted_panel())
.child(content_blurred())
.child(content_blurred_rich())
.child(nested_content_blurred())
.child(adjacent_per_block_blur())
.child(adjacent_group_blur())
.child(caption("nested content blur", 740., 50.))
.child(caption(
"adjacent — blur each block (seams, = CSS)",
740.,
322.,
))
.child(caption(
"adjacent — blur the parent (one group, clean)",
740.,
482.,
))
.child(deferred_popover())
}
}
fn main() {
gpui_platform::application().run(|cx: &mut App| {
cx.activate(true);
cx.on_window_closed(|cx, _| {
if cx.windows().is_empty() {
cx.quit();
}
})
.detach();
let bounds = Bounds {
origin: point(px(100.), px(100.)),
size: size(px(1060.), px(760.)),
};
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|_, cx| cx.new(|_| BlurExample),
)
.expect("failed to open window");
});
}