glass survives the window gestures: the accent rides the modal loop and the maximize animation gets a bracket
Kyle reported the blur "being masked by just going opeque" on maximize, and separately the desktop punching through a maximized window. Both were the same transition, and the old guard covered neither honestly. THE GUARD'S SIGNAL IS NOW THE MODAL LOOP ITSELF. thumbbar.rs already subclasses the main window for the taskbar transport, so the size loop is three arms on that existing proc: WM_NCLBUTTONDOWN carrying a sizing hit-test (or SC_SIZE, the keyboard path) latches "this loop is a RESIZE", WM_ENTERSIZEMOVE arms, WM_EXITSIZEMOVE releases, and WM_SIZING arms as a second net because this window is undecorated and tao does its own NCHITTEST. Emitted as soulseek:resize-drag, POSTED to ourselves rather than emitted inline: emitting from inside DefWindowProc's modal loop reenters the wry runtime's window store while that loop holds it. The signal and the failure are then the same object, which is what the two previous attempts were not. The first grounded on any DOM `resize` — one event fires for a maximize, so the settled window sat flat black for ~70ms hiding glass that was already back. The second discriminated on event CADENCE, and that is the one worth remembering: on this 4-core laptop, re-rasterising every backdrop-filter at each new size pushes `resize` deliveries far apart, and measured 2026-08-28 events 150ms apart never grounded AT ALL — a real drag stayed see-through, shipping the exact artifact the guard exists to prevent. Cadence is not a proxy for the loop. DWM SUSPENDS THE ACRYLIC INSIDE THE LOOP but keeps compositing the plain BLURBEHIND, so the accent swaps down to Blur for the gesture and back on release (window_glass refactored into apply_accent + an Accent enum; GLASS_ON tracks the main window so the subclass can re-assert). The window stays glassy through a drag instead of going black — which is what Kyle actually wanted, and GROUND_DRAG is off on his verification by hand. THE MAXIMIZE ANIMATION SCALES A SNAPSHOT the accent cannot ride: measured at 43ms/frame, desktop icons read sharply THROUGH the maximized window until ~613ms after the call. Disabling the DWM zoom animation removed the punch-through outright and every frame of a ground-suppressed maximize showed the blur soft and present — but that also removed the animation, and Kyle chose the animation back with the canvas grounded across the recomposition. So transitions are forced ON, the subclass brackets the jump (active at WM_SIZE, a re-assert immediately and again at 650ms, release with the second), and GROUND_DISCRETE paints the canvas for exactly that bracket. Both policies are one-line JS constants in glass.js — flipping between "animation + dark bracket" and "instant snap + live glass" needs no rebuild. The DOM resize listener survives as the fallback for a build with no subclass, reverting to the original behaviour; it stands down for good the moment one native event is heard. Measured clean: 3 maximize/restore gestures, 3 brackets, canvas never opaque outside them; a 9-second modal loop held with zero resize events in it. 98/98 tests.
This commit is contained in:
1 parent
93271aceec
commit
385b598170
3 files changed
+356
-23
No files matched your search
@@ -4907,12 +4907,28 @@ pub async fn mini_close(app: tauri::AppHandle) -> Result<(), String> {
|
||||
/// scroll blinked the glass dark for a beat (Kyle, 2026-08-26). The legacy
|
||||
/// accent-policy acrylic below is composed with the window itself and holds
|
||||
/// through interaction.
|
||||
#[tauri::command]
|
||||
pub fn window_glass(window: tauri::WebviewWindow, on: bool) -> Result<(), String> {
|
||||
// Clear any system backdrop a previous toggle/build may have applied.
|
||||
let _ = window.set_effects(None::<tauri::utils::config::WindowEffectsConfig>);
|
||||
let hwnd = window.hwnd().map_err(|e| e.to_string())?.0 as *mut std::ffi::c_void;
|
||||
/// Whether dark-glass is currently ON for the MAIN window. Written by
|
||||
/// window_glass, read by the window subclass in thumbbar.rs so it can
|
||||
/// re-assert the accent after the gestures where Windows drops it: the
|
||||
/// modal move/size loop, and a maximize/restore (Kyle photographed the
|
||||
/// desktop punching through a maximized window on 2026-08-28 — the accent
|
||||
/// did not survive the transition on its own).
|
||||
pub(crate) static GLASS_ON: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
/// The accent states this window uses. `Blur` is the pre-acrylic
|
||||
/// ACCENT_ENABLE_BLURBEHIND: visually close, but composited by DWM through
|
||||
/// the modal move/size loop where the acrylic is SUSPENDED — the subclass
|
||||
/// swaps down to it for the length of a gesture and back up on release
|
||||
/// (see thumbbar.rs). Same struct, one field different.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub(crate) enum Accent {
|
||||
Off,
|
||||
Blur,
|
||||
Acrylic,
|
||||
}
|
||||
|
||||
pub(crate) fn apply_accent(hwnd: *mut std::ffi::c_void, accent: Accent) -> Result<(), String> {
|
||||
#[repr(C)]
|
||||
struct AccentPolicy {
|
||||
state: u32,
|
||||
@@ -4949,11 +4965,15 @@ pub fn window_glass(window: tauri::WebviewWindow, on: bool) -> Result<(), String
|
||||
}
|
||||
let f: Swca = std::mem::transmute(f);
|
||||
let mut policy = AccentPolicy {
|
||||
state: if on { 4 } else { 0 }, // ACCENT_ENABLE_ACRYLICBLURBEHIND / ACCENT_DISABLED
|
||||
state: match accent {
|
||||
Accent::Off => 0, // ACCENT_DISABLED
|
||||
Accent::Blur => 3, // ACCENT_ENABLE_BLURBEHIND
|
||||
Accent::Acrylic => 4, // ACCENT_ENABLE_ACRYLICBLURBEHIND
|
||||
},
|
||||
flags: 0,
|
||||
// A whisper of black in the OS layer (acrylic treats alpha 0 as
|
||||
// "don't draw"); the real darkening is the CSS canvas tint.
|
||||
gradient: if on { 0x2E00_0000 } else { 0 },
|
||||
gradient: if matches!(accent, Accent::Off) { 0 } else { 0x2E00_0000 },
|
||||
animation: 0,
|
||||
};
|
||||
let mut data = AttribData {
|
||||
@@ -4966,6 +4986,58 @@ pub fn window_glass(window: tauri::WebviewWindow, on: bool) -> Result<(), String
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The DWM maximize/restore zoom animation. The accent visual detaches for
|
||||
/// its length and takes until ~650ms after the call to recompose (measured
|
||||
/// 2026-08-28 at 43ms/frame: desktop icons legible straight through the
|
||||
/// maximized window). Disabling it removed the punch-through outright, but
|
||||
/// also the animation — the window snapped to size — and Kyle chose the
|
||||
/// animation back with the canvas grounded across the recomposition instead
|
||||
/// (2026-08-29). So transitions are now explicitly forced ON (clearing any
|
||||
/// disable a previous build left on this window), and the discrete bracket
|
||||
/// in thumbbar.rs + GROUND_DISCRETE in glass.js covers the detach window.
|
||||
fn set_transitions_disabled(hwnd: *mut std::ffi::c_void, disabled: bool) {
|
||||
#[allow(non_snake_case)]
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
fn LoadLibraryA(name: *const u8) -> *mut std::ffi::c_void;
|
||||
fn GetProcAddress(
|
||||
module: *mut std::ffi::c_void,
|
||||
name: *const u8,
|
||||
) -> *mut std::ffi::c_void;
|
||||
}
|
||||
type DwmSet =
|
||||
unsafe extern "system" fn(*mut std::ffi::c_void, u32, *const std::ffi::c_void, u32) -> i32;
|
||||
unsafe {
|
||||
let dwm = LoadLibraryA(b"dwmapi.dll | ||||