From c234ae5308826415002a1fb0d71e2449a825428f Mon Sep 17 00:00:00 2001 From: jess Date: Thu, 23 Apr 2026 09:05:41 -0700 Subject: [PATCH] winbats transp --- src/bin/layers_shell.rs | 91 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 9 deletions(-) diff --git a/src/bin/layers_shell.rs b/src/bin/layers_shell.rs index e89e220..4234728 100644 --- a/src/bin/layers_shell.rs +++ b/src/bin/layers_shell.rs @@ -1,7 +1,7 @@ #![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")] -use std::num::NonZeroU32; use std::path::PathBuf; +use std::time::Instant; use layers::ffi::ViewportHandle; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; @@ -14,6 +14,7 @@ use winit::window::{Window, WindowAttributes, WindowId, WindowLevel}; const DEFAULT_LOGICAL_SIZE: (u32, u32) = (480, 640); const MIN_LOGICAL_SIZE: (u32, u32) = (380, 220); +const ALPHA_FADE_MS: u128 = 150; fn main() { let plugin_root = discover_plugin_root(); @@ -45,7 +46,6 @@ fn run() { } } -#[derive(Default)] struct ShellApp { window: Option>, handle: Option, @@ -53,6 +53,27 @@ struct ShellApp { is_hovered: bool, modifiers: ModifiersState, last_cursor: PhysicalPosition, + current_alpha: f32, + target_alpha: f32, + tween_from: f32, + tween_started: Option, +} + +impl Default for ShellApp { + fn default() -> Self { + Self { + window: None, + handle: None, + is_focused: false, + is_hovered: false, + modifiers: ModifiersState::empty(), + last_cursor: PhysicalPosition::new(0.0, 0.0), + current_alpha: 1.0, + target_alpha: 1.0, + tween_from: 1.0, + tween_started: None, + } + } } impl ApplicationHandler for ShellApp { @@ -61,7 +82,6 @@ impl ApplicationHandler for ShellApp { return; } tracing::info!("resumed: creating window"); - let colors = layers::ui::colors::get(); let attrs = WindowAttributes::default() .with_title("Layers") .with_inner_size(LogicalSize::new(DEFAULT_LOGICAL_SIZE.0, DEFAULT_LOGICAL_SIZE.1)) @@ -84,6 +104,7 @@ impl ApplicationHandler for ShellApp { #[cfg(target_os = "windows")] apply_win11_chrome(&raw_window); + set_window_alpha(&window, self.current_alpha); let raw_display = window .display_handle() .expect("winit: display handle") @@ -113,7 +134,7 @@ impl ApplicationHandler for ShellApp { self.window = Some(window); self.handle = Some(handle); - self.apply_window_alpha(colors); + self.refresh_target_alpha(); } fn window_event( @@ -146,14 +167,17 @@ impl ApplicationHandler for ShellApp { } WindowEvent::Focused(focused) => { self.is_focused = focused; + self.refresh_target_alpha(); } WindowEvent::CursorEntered { .. } => { self.is_hovered = true; + self.refresh_target_alpha(); } WindowEvent::CursorLeft { .. } => { self.is_hovered = false; handle.push_mouse_left(); window.request_redraw(); + self.refresh_target_alpha(); } WindowEvent::CursorMoved { position, .. } => { self.last_cursor = position; @@ -211,6 +235,7 @@ impl ApplicationHandler for ShellApp { } fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + self.step_alpha_tween(); if let Some(window) = self.window.as_ref() { window.request_redraw(); } @@ -218,17 +243,38 @@ impl ApplicationHandler for ShellApp { } impl ShellApp { - fn apply_window_alpha(&self, colors: &layers::ui::colors::Colors) { - let Some(_window) = self.window.as_ref() else { return; }; - let alpha = if self.is_focused && self.is_hovered { + fn refresh_target_alpha(&mut self) { + let colors = layers::ui::colors::get(); + let target = if self.is_focused && self.is_hovered { colors.window.alpha_focused_hovered } else if self.is_focused || self.is_hovered { colors.window.alpha_partial } else { colors.window.alpha_idle }; - let _ = alpha; - let _ = NonZeroU32::new(1); + if (target - self.target_alpha).abs() < 0.001 { + return; + } + self.tween_from = self.current_alpha; + self.target_alpha = target; + self.tween_started = Some(Instant::now()); + } + + fn step_alpha_tween(&mut self) { + let Some(started) = self.tween_started else { return; }; + let Some(window) = self.window.as_ref() else { return; }; + let prev = self.current_alpha; + let elapsed = started.elapsed().as_millis(); + if elapsed >= ALPHA_FADE_MS { + self.current_alpha = self.target_alpha; + self.tween_started = None; + } else { + let t = elapsed as f32 / ALPHA_FADE_MS as f32; + self.current_alpha = self.tween_from + (self.target_alpha - self.tween_from) * t; + } + if (self.current_alpha - prev).abs() > 0.001 || self.tween_started.is_none() { + set_window_alpha(window, self.current_alpha); + } } } @@ -258,6 +304,33 @@ fn encode_modifiers(mods: ModifiersState) -> u32 { bits } +#[cfg(target_os = "windows")] +fn set_window_alpha(window: &Window, alpha: f32) { + use raw_window_handle::RawWindowHandle; + use windows::Win32::Foundation::{COLORREF, HWND}; + use windows::Win32::UI::WindowsAndMessaging::{ + GetWindowLongPtrW, SetLayeredWindowAttributes, SetWindowLongPtrW, + GWL_EXSTYLE, LWA_ALPHA, WS_EX_LAYERED, + }; + + let Ok(handle) = window.window_handle() else { return; }; + let RawWindowHandle::Win32(win32) = handle.as_raw() else { return; }; + let hwnd = HWND(win32.hwnd.get() as *mut _); + + let a = (alpha.clamp(0.0, 1.0) * 255.0).round() as u8; + unsafe { + let ex = GetWindowLongPtrW(hwnd, GWL_EXSTYLE); + let layered = WS_EX_LAYERED.0 as isize; + if ex & layered == 0 { + SetWindowLongPtrW(hwnd, GWL_EXSTYLE, ex | layered); + } + let _ = SetLayeredWindowAttributes(hwnd, COLORREF(0), a, LWA_ALPHA); + } +} + +#[cfg(not(target_os = "windows"))] +fn set_window_alpha(_window: &Window, _alpha: f32) {} + #[cfg(target_os = "windows")] fn apply_win11_chrome(raw_window: &raw_window_handle::RawWindowHandle) { use raw_window_handle::RawWindowHandle;