Skip to content

Glass and effects

A transparent Sabine window can show the desktop’s own material behind its page: a blur on Wayland, acrylic or Mica on Windows, and the materials of NSVisualEffectView on macOS. The page decides where the material shows by leaving those parts transparent.

.glass() gives a window each desktop’s standard material:

use sabine::prelude::*;
fn main() {
SabineWindow::main(|window| {
Ok(window
.app()
.glass()
.app_chrome(AppChrome::default()))
});
}

.glass() turns on transparency, so call it after .app(), which turns transparency off. In a transparent window Sabine keeps the page’s html and body backgrounds transparent; give the page’s panels translucent backgrounds of their own and the material shows through them:

.sidebar {
background: rgb(20 20 24 / 0.35);
}
.content {
background: #18181b;
}

.transparent(false) turns transparency off and removes the effect with it.

.background_effect(WindowBackgroundEffect) picks a material. Glass exists everywhere; the others belong to one desktop and exist only when building for it:

Effect Desktop Material
None All No material. A transparent window shows the desktop unblurred
Glass Linux A blur of what is behind the window, through ext_background_effect_v1
Glass Windows Acrylic: a blur with a light or dark tint
Glass macOS The under-window background material
Blur Windows A plain blur, without acrylic’s tint
Mica Windows 11 The material tinted by the desktop background, as main windows use it
MicaAlt Windows 11 Mica tinted more strongly, as tabbed windows use it
HudWindow macOS The dark material of heads-up panels
Sidebar macOS The material of sidebars such as Finder’s

Pick a desktop’s own material with cfg:

use sabine::WindowBackgroundEffect;
fn material() -> WindowBackgroundEffect {
#[cfg(target_os = "windows")]
return WindowBackgroundEffect::Mica;
#[cfg(target_os = "macos")]
return WindowBackgroundEffect::Sidebar;
#[cfg(target_os = "linux")]
return WindowBackgroundEffect::Glass;
}
// ...
window.app().background_effect(material())

Any effect other than None turns on transparency, as .glass() does.

Wayland has one effect, a blur, which compositors offer through ext_background_effect_v1. Compositors without that protocol, such as GNOME’s, show the page over a clear window instead, so design glass layouts to stay readable without the blur.

A transparent window has three regions, which follow its size as it resizes:

Region Means Default
Blur Where the material shows The whole window
Opaque Where the page covers everything, so the material and desktop needn’t be drawn Nothing
Input Where the window takes pointer input. Outside it, clicks pass through to whatever is beneath The whole window

Sabine paints the opaque region with the window’s background color beneath the page, so content there never shows the desktop even while the page repaints.

Most apps get their regions from AppChrome, which sets them for a glass sidebar layout. Set them yourself for other layouts:

use sabine::{WindowRegion, WindowRegions};
window
.frameless()
.glass()
.regions(
WindowRegions::new()
.blur(WindowRegion::rounded_left(280, 14))
.opaque(WindowRegion::content_after_sidebar(280, 0))
.input(WindowRegion::rounded_rect(14)),
)

.blur_region, .opaque_region and .input_region set one region each.

Regions are in logical pixels. Every shape except a rectangle is computed from the window’s size every time it changes, and has the same name in Rust and JavaScript.

Rust JavaScript Shape
WindowRegion::empty() region.empty() Nothing
WindowRegion::rect(x, y, w, h) region.rect(x, y, w, h) A fixed rectangle. Add more with .add_rect(x, y, w, h) in Rust
WindowRegion::full() region.full() The whole window
WindowRegion::rounded_rect(radius) region.roundedRect(radius) The whole window with rounded corners
WindowRegion::rounded_left(width, radius) region.roundedLeft(width, radius) A full-height column along the left edge, rounded on its left corners
WindowRegion::titlebar_and_sidebar(sidebar, titlebar, radius) region.titlebarAndSidebar(sidebar, titlebar, radius) A titlebar strip across the top and a sidebar column below it
WindowRegion::content_after_sidebar(sidebar, titlebar) region.contentAfterSidebar(sidebar, titlebar) Everything right of the sidebar and below the titlebar
WindowRegion::content_after_sidebar_rounded_right(sidebar, titlebar, radius) region.contentAfterSidebarRoundedRight(sidebar, titlebar, radius) The same, rounded on its right corners

Regions can change at any time, for example to drop a sidebar’s blur while it is collapsed. A page replaces its window’s regions with appWindow.setRegions:

import { appWindow, region } from "@lantharos/sabine";
async function showSidebar(open: boolean) {
await appWindow.setRegions(
open
? {
blur: region.titlebarAndSidebar(260, 38, 14),
opaque: region.contentAfterSidebar(260, 38),
input: region.roundedRect(14),
}
: {
blur: region.empty(),
opaque: region.contentAfterSidebarRoundedRight(0, 0, 14),
input: region.roundedRect(14),
},
);
}

Rust does the same with the app’s BridgeEventEmitter: set_regions(&regions) changes every window, and set_regions_of(window, &regions) one. A region left out of the new set returns to its default.

A window that is maximized, fullscreen or tiled against a screen edge meets the edges of the screen, so Sabine squares the corners of its adaptive regions in those states. Desktops can also let people turn rounded window corners off. Follow such a setting with BridgeEventEmitter::set_square_corners(true), which squares every window’s regions while it floats too, including windows opened later.

Each desktop applies regions with its own compositor, and they differ in what they allow:

Linux Windows macOS
Blur part of the window Yes, the blur region No: the material is behind the whole window Yes, the blur region
Remove the material An empty blur region An empty blur region An empty blur region
Opaque region Passed to the compositor Not needed Taken out of the material
Click-through outside the input region Yes Frameless windows only Yes

Sabine sets the surface’s input and opaque regions through wl_compositor, and the blur region through ext_background_effect_v1. Compositors without that protocol show no blur, but still honor the input and opaque regions.

Native video surfaces sit beneath the page, and Sabine leaves their area out of the blur and opaque regions by itself so the video shows through.