Pitch: Bely Launcher — Como implementei Glassmorphism com Acrylic Blur no Tauri 2 (Windows)
Implementar acrylic blur no Tauri 2 não foi nada trivial. Envolve configuração de janela, chamadas diretas à DWM API do Windows, e um workaround para quando a janela perde o foco. Vou mostrar o passo a passo com código.
O resultado
A janela da Bely tem um fundo translúcido que borra o que está atrás dela — o famoso acrylic blur do Windows. Quando a janela perde o foco, o fundo vira opaco escuro. E tudo isso sem barra de título, com bordas arredondadas nativas.
Passo 1: Configurar a janela no Tauri
No tauri.conf.json, a janela precisa ser transparente e sem decoração:
{
"label": "main",
"width": 660,
"height": 480,
"transparent": true,
"decorations": false,
"shadow": false,
"alwaysOnTop": true,
"skipTaskbar": true
}
O transparent: true é obrigatório — sem isso o acrylic não funciona. O decorations: false remove a barra de título nativa do Windows.
Passo 2: Aplicar o Acrylic via Rust
No Cargo.toml, adicionei a crate window-vibrancy:
window-vibrancy = "0.7"
No setup do app, aplico o efeito acrylic com uma cor escura semitransparente:
use tauri::webview::Color;
if let Some(win) = app.get_webview_window("main") {
// Fundo totalmente transparente
let _ = win.set_background_color(Some(Color(0, 0, 0, 0)));
// Acrylic com tint escuro: RGB(9, 9, 11) com alpha 180/255
let _ = window_vibrancy::apply_acrylic(&win, Some((9, 9, 11, 180)));
}
O apply_acrylic recebe uma tupla (r, g, b, a) onde o alpha controla quanto do blur você vê. Valores mais baixos = mais transparente e mais blur visível. Eu uso 180 que dá um escuro com blur sutil.
Passo 3: Bordas arredondadas via DWM
O Tauri não expõe uma API para bordas arredondadas quando decorations: false. Precisei chamar a DWM API do Windows diretamente:
use winapi::shared::minwindef::DWORD;
use winapi::shared::windef::HWND;
extern "system" {
fn DwmSetWindowAttribute(
hwnd: HWND,
dwAttribute: DWORD,
pvAttribute: *const std::ffi::c_void,
cbAttribute: DWORD,
) -> i32;
}
const DWMWA_WINDOW_CORNER_PREFERENCE: DWORD = 33;
const DWMWCP_ROUND: DWORD = 2;
const DWMWA_BORDER_COLOR: DWORD = 34;
const DWMWA_COLOR_NONE: u32 = 0xFFFFFFFE;
if let Ok(hwnd) = win.hwnd() {
unsafe {
// Bordas arredondadas
DwmSetWindowAttribute(
hwnd.0 as HWND,
DWMWA_WINDOW_CORNER_PREFERENCE,
&DWMWCP_ROUND as *const DWORD as *const std::ffi::c_void,
std::mem::size_of::<DWORD>() as DWORD,
);
// Remove borda nativa
let none_color: u32 = DWMWA_COLOR_NONE;
DwmSetWindowAttribute(
hwnd.0 as HWND,
DWMWA_BORDER_COLOR,
&none_color as *const u32 as *const std::ffi::c_void,
std::mem::size_of::<u32>() as DWORD,
);
}
}
O DWMWA_WINDOW_CORNER_PREFERENCE = 33 com valor DWMWCP_ROUND = 2 força cantos arredondados no Windows 11. O DWMWA_BORDER_COLOR com COLOR_NONE remove aquela borda fina que o Windows coloca por padrão.
Importante: Isso só funciona no Windows 11 22H2+. No Windows 10 os cantos ficam retos.
Passo 4: O problema do foco
Esse foi o detalhe mais chato de resolver. O Windows desabilita o acrylic blur quando a janela perde o foco. É uma limitação do sistema operacional — não tem como contornar via API.
O resultado é horrível: quando você clica fora da janela, o fundo transparente vira um cinza feio sem blur nenhum.
A solução foi detectar foco/desfoco no frontend e trocar o background dinamicamente:
const [focused, setFocused] = useState(true);
useEffect(() => {
const onFocus = () => setFocused(true);
const onBlur = () => setFocused(false);
window.addEventListener("focus", onFocus);
window.addEventListener("blur", onBlur);
return () => {
window.removeEventListener("focus", onFocus);
window.removeEventListener("blur", onBlur);
};
}, []);
E no JSX, aplico o background condicional:
<div style={{
background: focused
? "rgba(9, 9, 11, 0.5)" // translúcido — mostra o blur
: "#09090B", // opaco escuro — esconde o cinza feio
}}>
Quando focado: fundo com 50% de opacidade, você vê o blur atrás.
Quando desfocado: fundo sólido escuro, transição suave, sem aquele cinza horroroso.
Passo 5: CSS Glass System
Para manter consistência visual, criei um sistema de variáveis CSS para o tema glass:
:root {
--glass-bg: rgba(9, 9, 11, 0.5);
--glass-bg-solid: #09090B;
--glass-border: rgba(255,255,255,0.08);
--glass-divider: rgba(255,255,255,0.06);
--glass-text: rgba(255,255,255,0.95);
--glass-text-dim: rgba(255,255,255,0.45);
--glass-text-muted: rgba(255,255,255,0.3);
--glass-btn-bg: rgba(255,255,255,0.08);
--glass-btn-border: rgba(255,255,255,0.1);
--glass-selected: rgba(255,255,255,0.1);
}
A regra principal: nunca usar cores sólidas em elementos internos. Todos os fundos, bordas, textos e divisores usam rgba com branco em diferentes opacidades. Isso garante que o blur do fundo continue visível através dos componentes.
Se você colocar um background: #18181B sólido em um painel filho, ele cobre o blur e mata o efeito. Use rgba(255,255,255,0.08) no lugar.
Gotchas que aprendi
-
Acrylic vs Mica: O acrylic borra o que está atrás da janela. O Mica (Windows 11) borra o wallpaper. São efeitos diferentes — eu uso acrylic porque quero que o conteúdo da tela atrás fique visível.
-
Windows 10 vs 11: No Windows 10 o blur é mais sutil e não tem cantos arredondados nativos. A crate
window-vibrancylida com isso internamente, mas o resultado visual é diferente. -
Performance: O acrylic tem um custo de GPU. Em máquinas fracas, o Windows pode desabilitar automaticamente. Sempre tenha um fallback opaco.
-
Alpha do acrylic vs alpha do CSS: São duas camadas de transparência. O acrylic com alpha 180 já é escuro. O CSS com
rgba(9,9,11,0.5)adiciona mais escurecimento por cima. Ajuste os dois juntos até achar o balanço certo.
Resultado
A stack completa: Tauri 2 + window-vibrancy + DWM API + estado de foco no React + variáveis CSS com rgba.
Se quiserem ver o efeito funcionando, a Bely está em fase de whitelist. É só se cadastrar no site e eu aprovo rápido: https://bely.my
Fonte: https://bely.my