mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-07-11 15:42:43 +02:00
feat(animation): introduce RenderDispatcher trait
This commit is contained in:
@@ -8,23 +8,20 @@ use std::sync::atomic::Ordering;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use super::RenderDispatcher;
|
||||||
use super::ANIMATION_DURATION;
|
use super::ANIMATION_DURATION;
|
||||||
use super::ANIMATION_FPS;
|
use super::ANIMATION_FPS;
|
||||||
use super::ANIMATION_MANAGER;
|
use super::ANIMATION_MANAGER;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||||
pub struct Animation {}
|
pub struct Animation;
|
||||||
|
|
||||||
impl Animation {
|
impl Animation {
|
||||||
/// Returns true if the animation needs to continue
|
/// Returns true if the animation needs to continue
|
||||||
pub fn cancel(animation_key: &str) -> bool {
|
pub fn cancel(animation_key: &str) -> bool {
|
||||||
if !ANIMATION_MANAGER.lock().in_progress(animation_key) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// should be more than 0
|
// should be more than 0
|
||||||
let cancel_idx = ANIMATION_MANAGER.lock().init_cancel(animation_key);
|
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();
|
let spent_duration = Instant::now();
|
||||||
|
|
||||||
while ANIMATION_MANAGER.lock().in_progress(animation_key) {
|
while ANIMATION_MANAGER.lock().in_progress(animation_key) {
|
||||||
@@ -46,54 +43,67 @@ impl Animation {
|
|||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
pub fn animate(
|
pub fn animate(
|
||||||
animation_key: &str,
|
animation_key: String,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
mut render_callback: impl FnMut(f64) -> Result<()>,
|
render_dispatcher: (impl RenderDispatcher + Send + 'static),
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if ANIMATION_MANAGER.lock().in_progress(animation_key) {
|
std::thread::spawn(move || {
|
||||||
let should_animate = Self::cancel(animation_key);
|
if ANIMATION_MANAGER.lock().in_progress(animation_key.as_str()) {
|
||||||
|
let should_animate = Self::cancel(animation_key.as_str());
|
||||||
|
|
||||||
if !should_animate {
|
if !should_animate {
|
||||||
return Ok(());
|
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(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame_start = Instant::now();
|
render_dispatcher.pre_render()?;
|
||||||
// calculate progress
|
|
||||||
progress = animation_start.elapsed().as_millis() as f64 / duration.as_millis() as f64;
|
|
||||||
render_callback(progress).ok();
|
|
||||||
|
|
||||||
// sleep until next frame
|
ANIMATION_MANAGER.lock().start(animation_key.as_str());
|
||||||
let frame_time_elapsed = frame_start.elapsed();
|
|
||||||
|
|
||||||
if frame_time_elapsed < target_frame_time {
|
let target_frame_time =
|
||||||
std::thread::sleep(target_frame_time - frame_time_elapsed);
|
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
|
// limit progress to 1.0 if animation took longer
|
||||||
if progress > 1.0 {
|
if progress != 1.0 {
|
||||||
progress = 1.0;
|
progress = 1.0;
|
||||||
}
|
|
||||||
|
|
||||||
// process animation for 1.0 to set target position
|
// process animation for 1.0 to set target position
|
||||||
render_callback(progress)
|
render_dispatcher.render(progress).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
render_dispatcher.post_render()
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::prefix::AnimationPrefix;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct AnimationState {
|
struct AnimationState {
|
||||||
pub in_progress: bool,
|
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
|
self.animations
|
||||||
.keys()
|
.keys()
|
||||||
.filter(|key| key.starts_with(animation_key_prefix))
|
.filter(|key| key.starts_with(animation_key_prefix.to_string().as_str()))
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ pub use animation::Animation;
|
|||||||
pub mod animation_manager;
|
pub mod animation_manager;
|
||||||
pub mod lerp;
|
pub mod lerp;
|
||||||
pub mod prefix;
|
pub mod prefix;
|
||||||
|
pub mod render_dispatcher;
|
||||||
|
pub use render_dispatcher::RenderDispatcher;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|||||||
@@ -13,7 +13,5 @@ pub enum AnimationPrefix {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_animation_key(prefix: AnimationPrefix, key: String) -> String {
|
pub fn new_animation_key(prefix: AnimationPrefix, key: String) -> String {
|
||||||
match prefix {
|
format!("{}:{}", prefix, key)
|
||||||
AnimationPrefix::WindowMove => format!("window_move:{}", key),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<()>;
|
||||||
|
}
|
||||||
+90
-57
@@ -1,6 +1,8 @@
|
|||||||
use crate::animation::lerp::Lerp;
|
use crate::animation::lerp::Lerp;
|
||||||
use crate::animation::prefix::new_animation_key;
|
use crate::animation::prefix::new_animation_key;
|
||||||
use crate::animation::prefix::AnimationPrefix;
|
use crate::animation::prefix::AnimationPrefix;
|
||||||
|
use crate::animation::RenderDispatcher;
|
||||||
|
// use crate::animation::renderer::Renderer;
|
||||||
use crate::animation::ANIMATION_DURATION;
|
use crate::animation::ANIMATION_DURATION;
|
||||||
use crate::animation::ANIMATION_ENABLED;
|
use crate::animation::ANIMATION_ENABLED;
|
||||||
use crate::animation::ANIMATION_MANAGER;
|
use crate::animation::ANIMATION_MANAGER;
|
||||||
@@ -10,6 +12,7 @@ use crate::com::SetCloak;
|
|||||||
use crate::focus_manager;
|
use crate::focus_manager;
|
||||||
use crate::stackbar_manager;
|
use crate::stackbar_manager;
|
||||||
use crate::windows_api;
|
use crate::windows_api;
|
||||||
|
use crate::AnimationStyle;
|
||||||
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
|
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
|
||||||
use crate::SLOW_APPLICATION_IDENTIFIERS;
|
use crate::SLOW_APPLICATION_IDENTIFIERS;
|
||||||
use std::collections::HashMap;
|
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 {
|
impl Window {
|
||||||
pub const fn hwnd(self) -> HWND {
|
pub const fn hwnd(self) -> HWND {
|
||||||
HWND(windows_api::as_ptr!(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<()> {
|
pub fn set_position(&self, layout: &Rect, top: bool) -> Result<()> {
|
||||||
let window_rect = WindowsApi::window_rect(self.hwnd)?;
|
let window_rect = WindowsApi::window_rect(self.hwnd)?;
|
||||||
|
|
||||||
@@ -262,7 +279,23 @@ impl Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ANIMATION_ENABLED.load(Ordering::SeqCst) {
|
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 {
|
} else {
|
||||||
WindowsApi::position_window(self.hwnd, layout, top)
|
WindowsApi::position_window(self.hwnd, layout, top)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user