feat(animation): introduce RenderDispatcher trait

This commit is contained in:
thearturca
2024-10-14 20:51:33 +03:00
parent 67b2a7a284
commit 8290f143a6
6 changed files with 158 additions and 106 deletions

View File

@@ -8,23 +8,20 @@ use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use super::RenderDispatcher;
use super::ANIMATION_DURATION;
use super::ANIMATION_FPS;
use super::ANIMATION_MANAGER;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct Animation {}
pub struct Animation;
impl Animation {
/// Returns true if the animation needs to continue
pub fn cancel(animation_key: &str) -> bool {
if !ANIMATION_MANAGER.lock().in_progress(animation_key) {
return true;
}
// should be more than 0
let cancel_idx = ANIMATION_MANAGER.lock().init_cancel(animation_key);
let max_duration = Duration::from_secs(1);
let max_duration = Duration::from_secs(5);
let spent_duration = Instant::now();
while ANIMATION_MANAGER.lock().in_progress(animation_key) {
@@ -46,54 +43,67 @@ impl Animation {
#[allow(clippy::cast_precision_loss)]
pub fn animate(
animation_key: &str,
animation_key: String,
duration: Duration,
mut render_callback: impl FnMut(f64) -> Result<()>,
render_dispatcher: (impl RenderDispatcher + Send + 'static),
) -> Result<()> {
if ANIMATION_MANAGER.lock().in_progress(animation_key) {
let should_animate = Self::cancel(animation_key);
std::thread::spawn(move || {
if ANIMATION_MANAGER.lock().in_progress(animation_key.as_str()) {
let should_animate = Self::cancel(animation_key.as_str());
if !should_animate {
return Ok(());
}
}
ANIMATION_MANAGER.lock().start(animation_key);
let target_frame_time = Duration::from_millis(1000 / ANIMATION_FPS.load(Ordering::Relaxed));
let mut progress = 0.0;
let animation_start = Instant::now();
// start animation
while progress < 1.0 {
// check if animation is cancelled
if ANIMATION_MANAGER.lock().is_cancelled(animation_key) {
// cancel animation
ANIMATION_MANAGER.lock().cancel(animation_key);
return Ok(());
if !should_animate {
return Ok(());
}
}
let frame_start = Instant::now();
// calculate progress
progress = animation_start.elapsed().as_millis() as f64 / duration.as_millis() as f64;
render_callback(progress).ok();
render_dispatcher.pre_render()?;
// sleep until next frame
let frame_time_elapsed = frame_start.elapsed();
ANIMATION_MANAGER.lock().start(animation_key.as_str());
if frame_time_elapsed < target_frame_time {
std::thread::sleep(target_frame_time - frame_time_elapsed);
let target_frame_time =
Duration::from_millis(1000 / ANIMATION_FPS.load(Ordering::Relaxed));
let mut progress = 0.0;
let animation_start = Instant::now();
// start animation
while progress < 1.0 {
// check if animation is cancelled
if ANIMATION_MANAGER
.lock()
.is_cancelled(animation_key.as_str())
{
// cancel animation
ANIMATION_MANAGER.lock().cancel(animation_key.as_str());
return Ok(());
}
let frame_start = Instant::now();
// calculate progress
progress =
animation_start.elapsed().as_millis() as f64 / duration.as_millis() as f64;
render_dispatcher.render(progress).ok();
// sleep until next frame
let frame_time_elapsed = frame_start.elapsed();
if frame_time_elapsed < target_frame_time {
std::thread::sleep(target_frame_time - frame_time_elapsed);
}
}
}
ANIMATION_MANAGER.lock().end(animation_key);
ANIMATION_MANAGER.lock().end(animation_key.as_str());
// limit progress to 1.0 if animation took longer
if progress > 1.0 {
progress = 1.0;
}
// limit progress to 1.0 if animation took longer
if progress != 1.0 {
progress = 1.0;
// process animation for 1.0 to set target position
render_callback(progress)
// process animation for 1.0 to set target position
render_dispatcher.render(progress).ok();
}
render_dispatcher.post_render()
});
Ok(())
}
}

View File

@@ -1,6 +1,8 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use super::prefix::AnimationPrefix;
#[derive(Debug, Clone, Copy)]
struct AnimationState {
pub in_progress: bool,
@@ -100,10 +102,10 @@ impl AnimationManager {
}
}
pub fn animations_in_progress(&self, animation_key_prefix: &str) -> usize {
pub fn count_in_progress(&self, animation_key_prefix: AnimationPrefix) -> usize {
self.animations
.keys()
.filter(|key| key.starts_with(animation_key_prefix))
.filter(|key| key.starts_with(animation_key_prefix.to_string().as_str()))
.count()
}
}

View File

@@ -13,6 +13,8 @@ pub use animation::Animation;
pub mod animation_manager;
pub mod lerp;
pub mod prefix;
pub mod render_dispatcher;
pub use render_dispatcher::RenderDispatcher;
pub mod style;
lazy_static! {

View File

@@ -13,7 +13,5 @@ pub enum AnimationPrefix {
}
pub fn new_animation_key(prefix: AnimationPrefix, key: String) -> String {
match prefix {
AnimationPrefix::WindowMove => format!("window_move:{}", key),
}
format!("{}:{}", prefix, key)
}

View File

@@ -0,0 +1,7 @@
use color_eyre::Result;
pub trait RenderDispatcher {
fn pre_render(&self) -> Result<()>;
fn render(&self, delta: f64) -> Result<()>;
fn post_render(&self) -> Result<()>;
}

View File

@@ -1,6 +1,8 @@
use crate::animation::lerp::Lerp;
use crate::animation::prefix::new_animation_key;
use crate::animation::prefix::AnimationPrefix;
use crate::animation::RenderDispatcher;
// use crate::animation::renderer::Renderer;
use crate::animation::ANIMATION_DURATION;
use crate::animation::ANIMATION_ENABLED;
use crate::animation::ANIMATION_MANAGER;
@@ -10,6 +12,7 @@ use crate::com::SetCloak;
use crate::focus_manager;
use crate::stackbar_manager;
use crate::windows_api;
use crate::AnimationStyle;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use std::collections::HashMap;
@@ -153,6 +156,76 @@ impl Serialize for Window {
}
}
struct WindowMoveRenderDispatcher {
prefix: AnimationPrefix,
hwnd: isize,
start_rect: Rect,
target_rect: Rect,
top: bool,
style: AnimationStyle,
}
impl WindowMoveRenderDispatcher {
pub fn new(
prefix: AnimationPrefix,
hwnd: isize,
start_rect: Rect,
target_rect: Rect,
top: bool,
style: AnimationStyle,
) -> Self {
Self {
prefix,
hwnd,
start_rect,
target_rect,
top,
style,
}
}
}
impl RenderDispatcher for WindowMoveRenderDispatcher {
fn pre_render(&self) -> Result<()> {
border_manager::BORDER_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst);
border_manager::send_notification(Some(self.hwnd));
stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst);
stackbar_manager::send_notification();
Ok(())
}
fn render(&self, progress: f64) -> Result<()> {
let new_rect = self.start_rect.lerp(self.target_rect, progress, self.style);
// using MoveWindow because it runs faster than SetWindowPos
// so animation have more fps and feel smoother
WindowsApi::move_window(self.hwnd, &new_rect, false)?;
WindowsApi::invalidate_rect(self.hwnd, None, false);
Ok(())
}
fn post_render(&self) -> Result<()> {
WindowsApi::position_window(self.hwnd, &self.target_rect, self.top)?;
if ANIMATION_MANAGER.lock().count_in_progress(self.prefix) == 0 {
if WindowsApi::foreground_window().unwrap_or_default() == self.hwnd {
focus_manager::send_notification(self.hwnd)
}
border_manager::BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);
stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);
border_manager::send_notification(Some(self.hwnd));
stackbar_manager::send_notification();
transparency_manager::send_notification();
}
Ok(())
}
}
impl Window {
pub const fn hwnd(self) -> HWND {
HWND(windows_api::as_ptr!(self.hwnd))
@@ -198,62 +271,6 @@ impl Window {
)
}
pub fn animate_position(&self, start_rect: &Rect, target_rect: &Rect, top: bool) -> Result<()> {
let start_rect = *start_rect;
let target_rect = *target_rect;
let duration = Duration::from_millis(ANIMATION_DURATION.load(Ordering::SeqCst));
let style = *ANIMATION_STYLE.lock();
border_manager::BORDER_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst);
border_manager::send_notification(Some(self.hwnd));
stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst);
stackbar_manager::send_notification();
let hwnd = self.hwnd;
std::thread::spawn(move || {
Animation::animate(
new_animation_key(AnimationPrefix::WindowMove, hwnd.to_string()).as_str(),
duration,
|progress: f64| {
let new_rect = start_rect.lerp(target_rect, progress, style);
if progress == 1.0 {
WindowsApi::position_window(hwnd, &new_rect, top)?;
if WindowsApi::foreground_window().unwrap_or_default() == hwnd {
focus_manager::send_notification(hwnd)
}
if ANIMATION_MANAGER
.lock()
.animations_in_progress("window_move")
== 0
{
border_manager::BORDER_TEMPORARILY_DISABLED
.store(false, Ordering::SeqCst);
stackbar_manager::STACKBAR_TEMPORARILY_DISABLED
.store(false, Ordering::SeqCst);
border_manager::send_notification(Some(hwnd));
stackbar_manager::send_notification();
transparency_manager::send_notification();
}
} else {
// using MoveWindow because it runs faster than SetWindowPos
// so animation have more fps and feel smoother
WindowsApi::move_window(hwnd, &new_rect, false)?;
WindowsApi::invalidate_rect(hwnd, None, false);
}
Ok(())
},
)
});
Ok(())
}
pub fn set_position(&self, layout: &Rect, top: bool) -> Result<()> {
let window_rect = WindowsApi::window_rect(self.hwnd)?;
@@ -262,7 +279,23 @@ impl Window {
}
if ANIMATION_ENABLED.load(Ordering::SeqCst) {
self.animate_position(&window_rect, layout, top)
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,
);
Animation::animate(
new_animation_key(AnimationPrefix::WindowMove, self.hwnd.to_string()),
duration,
render_dispatcher,
)
} else {
WindowsApi::position_window(self.hwnd, layout, top)
}