From 2400d757fec9305c1f0d6b022443f091f123bd7b Mon Sep 17 00:00:00 2001 From: thearturca Date: Sat, 28 Sep 2024 20:26:34 +0300 Subject: [PATCH] feat(animation): implement window transparency animation this commit also fixes graceful shutdown of animations by disabling them before exit and wait for all remaining animations for 20 seconds --- komorebi/src/animation/animation.rs | 19 ++- komorebi/src/animation/animation_manager.rs | 4 + komorebi/src/animation/lerp.rs | 6 + komorebi/src/animation/prefix.rs | 1 + komorebi/src/main.rs | 4 + komorebi/src/process_command.rs | 4 + komorebi/src/window.rs | 141 +++++++++++++++++--- komorebi/src/windows_api.rs | 16 +++ 8 files changed, 171 insertions(+), 24 deletions(-) diff --git a/komorebi/src/animation/animation.rs b/komorebi/src/animation/animation.rs index 992312f9..5a17e9ec 100644 --- a/komorebi/src/animation/animation.rs +++ b/komorebi/src/animation/animation.rs @@ -17,6 +17,21 @@ use super::ANIMATION_MANAGER; pub struct Animation; impl Animation { + pub fn wait_for_all_animations() { + let max_duration = Duration::from_secs(20); + let spent_duration = Instant::now(); + + while ANIMATION_MANAGER.lock().count() > 0 { + if spent_duration.elapsed() >= max_duration { + break; + } + + std::thread::sleep(Duration::from_millis( + ANIMATION_DURATION.load(Ordering::SeqCst), + )); + } + } + /// Returns true if the animation needs to continue pub fn cancel(animation_key: &str) -> bool { // should be more than 0 @@ -29,9 +44,7 @@ impl Animation { ANIMATION_MANAGER.lock().end(animation_key); } - std::thread::sleep(Duration::from_millis( - ANIMATION_DURATION.load(Ordering::SeqCst) / 2, - )); + std::thread::sleep(Duration::from_millis(250 / 2)); } let latest_cancel_idx = ANIMATION_MANAGER.lock().latest_cancel_idx(animation_key); diff --git a/komorebi/src/animation/animation_manager.rs b/komorebi/src/animation/animation_manager.rs index a1448031..5eb1587c 100644 --- a/komorebi/src/animation/animation_manager.rs +++ b/komorebi/src/animation/animation_manager.rs @@ -108,4 +108,8 @@ impl AnimationManager { .filter(|key| key.starts_with(animation_key_prefix.to_string().as_str())) .count() } + + pub fn count(&self) -> usize { + self.animations.len() + } } diff --git a/komorebi/src/animation/lerp.rs b/komorebi/src/animation/lerp.rs index 46afd89f..4044341c 100644 --- a/komorebi/src/animation/lerp.rs +++ b/komorebi/src/animation/lerp.rs @@ -24,6 +24,12 @@ impl Lerp for f64 { } } +impl Lerp for u8 { + fn lerp(self, end: u8, time: f64, style: AnimationStyle) -> u8 { + (self as f64).lerp(end as f64, time, style) as u8 + } +} + impl Lerp for Rect { fn lerp(self, end: Rect, time: f64, style: AnimationStyle) -> Rect { Rect { diff --git a/komorebi/src/animation/prefix.rs b/komorebi/src/animation/prefix.rs index f7ff5886..86eadfba 100644 --- a/komorebi/src/animation/prefix.rs +++ b/komorebi/src/animation/prefix.rs @@ -10,6 +10,7 @@ use strum::EnumString; )] pub enum AnimationPrefix { WindowMove, + WindowTransparency, } pub fn new_animation_key(prefix: AnimationPrefix, key: String) -> String { diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 7b608e7b..e6cfbb99 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -17,6 +17,8 @@ use std::time::Duration; use clap::Parser; use color_eyre::Result; use crossbeam_utils::Backoff; +use komorebi::animation::Animation; +use komorebi::animation::ANIMATION_ENABLED; #[cfg(feature = "deadlock_detection")] use parking_lot::deadlock; use parking_lot::Mutex; @@ -287,7 +289,9 @@ fn main() -> Result<()> { tracing::error!("received ctrl-c, restoring all hidden windows and terminating process"); + ANIMATION_ENABLED.store(false, Ordering::SeqCst); wm.lock().restore_all_windows()?; + Animation::wait_for_all_animations(); if WindowsApi::focus_follows_mouse()? { WindowsApi::disable_focus_follows_mouse()?; diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index cf8c704c..35822c1a 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -22,6 +22,7 @@ use schemars::gen::SchemaSettings; use schemars::schema_for; use uds_windows::UnixStream; +use crate::animation::Animation; use crate::core::config_generation::ApplicationConfiguration; use crate::core::config_generation::IdWithIdentifier; use crate::core::config_generation::MatchingRule; @@ -876,7 +877,10 @@ impl WindowManager { tracing::info!( "received stop command, restoring all hidden windows and terminating process" ); + + ANIMATION_ENABLED.store(false, Ordering::SeqCst); self.restore_all_windows()?; + Animation::wait_for_all_animations(); if WindowsApi::focus_follows_mouse()? { WindowsApi::disable_focus_follows_mouse()?; diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 0723bdb3..1382ad61 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -167,7 +167,6 @@ struct WindowMoveRenderDispatcher { impl WindowMoveRenderDispatcher { pub fn new( - prefix: AnimationPrefix, hwnd: isize, start_rect: Rect, target_rect: Rect, @@ -175,7 +174,7 @@ impl WindowMoveRenderDispatcher { style: AnimationStyle, ) -> Self { Self { - prefix, + prefix: AnimationPrefix::WindowMove, hwnd, start_rect, target_rect, @@ -226,6 +225,71 @@ impl RenderDispatcher for WindowMoveRenderDispatcher { } } +struct WindowTransparencyRenderDispatcher { + prefix: AnimationPrefix, + hwnd: isize, + start_opacity: u8, + target_opacity: u8, + style: AnimationStyle, + is_opaque: bool, + is_transparent: bool, +} + +impl WindowTransparencyRenderDispatcher { + pub fn new( + hwnd: isize, + is_opaque: bool, + is_transparent: bool, + start_opacity: u8, + target_opacity: u8, + style: AnimationStyle, + ) -> Self { + Self { + prefix: AnimationPrefix::WindowTransparency, + hwnd, + start_opacity, + target_opacity, + style, + is_opaque, + is_transparent, + } + } +} + +impl RenderDispatcher for WindowTransparencyRenderDispatcher { + fn pre_render(&self) -> Result<()> { + //transparent + if self.is_transparent { + let window = Window::from(self.hwnd); + let mut ex_style = window.ex_style()?; + ex_style.insert(ExtendedWindowStyle::LAYERED); + window.update_ex_style(&ex_style)?; + } + + Ok(()) + } + + fn render(&self, progress: f64) -> Result<()> { + WindowsApi::set_transparent( + self.hwnd, + self.start_opacity + .lerp(self.target_opacity, progress, self.style), + ) + } + + fn post_render(&self) -> Result<()> { + //opaque + if self.is_opaque { + let window = Window::from(self.hwnd); + let mut ex_style = window.ex_style()?; + ex_style.remove(ExtendedWindowStyle::LAYERED); + window.update_ex_style(&ex_style)?; + } + + Ok(()) + } +} + impl Window { pub const fn hwnd(self) -> HWND { HWND(windows_api::as_ptr!(self.hwnd)) @@ -282,17 +346,11 @@ impl Window { let duration = Duration::from_millis(ANIMATION_DURATION.load(Ordering::SeqCst)); let style = *ANIMATION_STYLE.lock(); - let render_dispatcher = WindowMoveRenderDispatcher::new( - AnimationPrefix::WindowMove, - self.hwnd, - window_rect, - *layout, - top, - style, - ); + let render_dispatcher = + WindowMoveRenderDispatcher::new(self.hwnd, window_rect, *layout, top, style); Animation::animate( - new_animation_key(AnimationPrefix::WindowMove, self.hwnd.to_string()), + new_animation_key(render_dispatcher.prefix, self.hwnd.to_string()), duration, render_dispatcher, ) @@ -404,19 +462,60 @@ impl Window { } pub fn transparent(self) -> Result<()> { - let mut ex_style = self.ex_style()?; - ex_style.insert(ExtendedWindowStyle::LAYERED); - self.update_ex_style(&ex_style)?; - WindowsApi::set_transparent( - self.hwnd, - transparency_manager::TRANSPARENCY_ALPHA.load_consume(), - ) + if ANIMATION_ENABLED.load(Ordering::SeqCst) { + let duration = Duration::from_millis(ANIMATION_DURATION.load(Ordering::SeqCst)); + let style = *ANIMATION_STYLE.lock(); + + let render_dispatcher = WindowTransparencyRenderDispatcher::new( + self.hwnd, + false, + true, + WindowsApi::get_transparent(self.hwnd).unwrap_or(255), + transparency_manager::TRANSPARENCY_ALPHA.load_consume(), + style, + ); + + Animation::animate( + new_animation_key(render_dispatcher.prefix, self.hwnd.to_string()), + duration, + render_dispatcher, + ) + } else { + let mut ex_style = self.ex_style()?; + ex_style.insert(ExtendedWindowStyle::LAYERED); + self.update_ex_style(&ex_style)?; + WindowsApi::set_transparent( + self.hwnd, + transparency_manager::TRANSPARENCY_ALPHA.load_consume(), + ) + } } pub fn opaque(self) -> Result<()> { - let mut ex_style = self.ex_style()?; - ex_style.remove(ExtendedWindowStyle::LAYERED); - self.update_ex_style(&ex_style) + if ANIMATION_ENABLED.load(Ordering::SeqCst) { + let duration = Duration::from_millis(ANIMATION_DURATION.load(Ordering::SeqCst)); + let style = *ANIMATION_STYLE.lock(); + + let render_dispatcher = WindowTransparencyRenderDispatcher::new( + self.hwnd, + true, + false, + WindowsApi::get_transparent(self.hwnd) + .unwrap_or(transparency_manager::TRANSPARENCY_ALPHA.load_consume()), + 255, + style, + ); + + Animation::animate( + new_animation_key(render_dispatcher.prefix, self.hwnd.to_string()), + duration, + render_dispatcher, + ) + } else { + let mut ex_style = self.ex_style()?; + ex_style.remove(ExtendedWindowStyle::LAYERED); + self.update_ex_style(&ex_style) + } } pub fn set_accent(self, colour: u32) -> Result<()> { diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index 6548c0d4..be9e8ef3 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -77,6 +77,7 @@ use windows::Win32::UI::WindowsAndMessaging::EnumWindows; use windows::Win32::UI::WindowsAndMessaging::GetCursorPos; use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow; use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow; +use windows::Win32::UI::WindowsAndMessaging::GetLayeredWindowAttributes; use windows::Win32::UI::WindowsAndMessaging::GetTopWindow; use windows::Win32::UI::WindowsAndMessaging::GetWindow; use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW; @@ -1127,6 +1128,21 @@ impl WindowsApi { Ok(()) } + pub fn get_transparent(hwnd: isize) -> Result { + unsafe { + let mut alpha: u8 = u8::default(); + let mut color_ref = COLORREF(-1i32 as u32); + let mut flags = LWA_ALPHA; + GetLayeredWindowAttributes( + HWND(as_ptr!(hwnd)), + Some(std::ptr::addr_of_mut!(color_ref)), + Some(std::ptr::addr_of_mut!(alpha)), + Some(std::ptr::addr_of_mut!(flags)), + )?; + Ok(alpha) + } + } + pub fn create_hidden_window(name: PCWSTR, instance: isize) -> Result { unsafe { CreateWindowExW(