From 12dc85e9288eb07bbfdebd537008939f56412d46 Mon Sep 17 00:00:00 2001 From: Alexander Mohr Date: Wed, 23 Apr 2025 22:13:53 +0200 Subject: [PATCH] rework animation to be smoother and drop different animation types --- README.md | 3 +- src/lib/config.rs | 40 +-------- src/lib/gui.rs | 223 ++++++++++++---------------------------------- 3 files changed, 59 insertions(+), 207 deletions(-) diff --git a/README.md b/README.md index 0e33770..b63754d 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,7 @@ layerrule = blur, worf ### New config / command line options * fuzzy-length: Defines how long a string must be to be considered for fuzzy match * row-box-orientation: Allows aligning values vertically to place the label below the icon -* text wrapping -* configurable animations +* configurable animation time ### New Styling options * `label`: Allows styling the label diff --git a/src/lib/config.rs b/src/lib/config.rs index fbb994b..63b1bb7 100644 --- a/src/lib/config.rs +++ b/src/lib/config.rs @@ -50,14 +50,6 @@ pub enum Align { Center, } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Serialize, Deserialize)] -pub enum Animation { - None, - Expand, - ExpandVertical, - ExpandHorizontal, -} - #[derive(Clone, Debug, Serialize, Deserialize)] pub enum WrapMode { None, @@ -323,11 +315,6 @@ pub struct Config { // #[serde(default = "default_text_wrap_length")] // #[clap(long = "text-wrap-length")] // pub text_wrap_length: Option, - /// Defines the animation when the window is show. - /// Defaults to Expand - #[serde(default = "default_show_animation")] - #[clap(long = "show-animation")] - pub show_animation: Option, /// Defines how long it takes for the show animation to finish /// Defaults to 70ms @@ -335,13 +322,6 @@ pub struct Config { #[clap(long = "show-animation-time")] pub show_animation_time: Option, - /// Defines the animation when the window is hidden. - /// Defaults to None, because it is a bit buggy with - /// gtk layer shell. works fine with normal window though - #[serde(default = "default_hide_animation")] - #[clap(long = "hide-animation")] - pub hide_animation: Option, - /// Defines how long it takes for the hide animation to finish /// Defaults to 100ms #[serde(default = "default_hide_animation_time")] @@ -416,9 +396,7 @@ impl Default for Config { pre_display_exec: None, fuzzy_min_score: default_fuzzy_min_score(), row_bow_orientation: default_row_box_orientation(), - show_animation: default_show_animation(), show_animation_time: default_show_animation_time(), - hide_animation: default_hide_animation(), hide_animation_time: default_hide_animation_time(), } } @@ -428,28 +406,14 @@ impl Default for Config { #[allow(clippy::unnecessary_wraps)] #[must_use] pub fn default_show_animation_time() -> Option { - Some(30) -} - -// allowed because option is needed for serde macro -#[allow(clippy::unnecessary_wraps)] -#[must_use] -pub fn default_show_animation() -> Option { - Some(Animation::Expand) + Some(50) } // allowed because option is needed for serde macro #[allow(clippy::unnecessary_wraps)] #[must_use] pub fn default_hide_animation_time() -> Option { - Some(100) -} - -// allowed because option is needed for serde macro -#[allow(clippy::unnecessary_wraps)] -#[must_use] -pub fn default_hide_animation() -> Option { - Some(Animation::None) + Some(0) } // allowed because option is needed for serde macro diff --git a/src/lib/gui.rs b/src/lib/gui.rs index e77dc1c..67a74a9 100644 --- a/src/lib/gui.rs +++ b/src/lib/gui.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; -use crate::config::{Anchor, Animation, Config, MatchMethod, WrapMode}; +use crate::config::{Anchor, Config, MatchMethod, WrapMode}; use crate::desktop::known_image_extension_regex_pattern; use crate::{config, desktop}; use anyhow::anyhow; @@ -14,7 +14,7 @@ use gdk4::prelude::{Cast, DisplayExt, MonitorExt}; use gdk4::{Display, Key}; use gtk4::glib::ControlFlow; use gtk4::prelude::{ - AppChooserExt, ApplicationExt, ApplicationExtManual, BoxExt, EditableExt, FlowBoxChildExt, + ApplicationExt, ApplicationExtManual, BoxExt, EditableExt, FlowBoxChildExt, GestureSingleExt, GtkWindowExt, ListBoxRowExt, NativeExt, WidgetExt, }; use gtk4::{ @@ -97,8 +97,6 @@ impl AsRef> for MenuItem { } } -type IconCache = Arc>; - /// # Errors /// /// Will return Err when the channel between the UI and this is broken @@ -212,7 +210,6 @@ fn build_ui( let item_provider = Arc::new(Mutex::new(item_provider)); let list_items: ArcMenuMap = Arc::new(Mutex::new(HashMap::new())); - // let icon_cache: IconCache = Default::default(); let elements = item_provider.lock().unwrap().get_elements(None); build_ui_from_menu_items( @@ -257,13 +254,14 @@ fn build_ui( let window_done = Instant::now(); window.show(); - animate_window_show(config, window.clone(), outer_box); + animate_window_show(config, window.clone()); + let animation_done = Instant::now(); log::debug!( "Building UI took {:?}, window creation {:?}, animation {:?}", start.elapsed(), window_done - start, - window_done.elapsed() + animation_done - start ); } @@ -477,7 +475,7 @@ fn sort_menu_items_by_score( } } -fn animate_window_show(config: &Config, window: ApplicationWindow, outer_box: gtk4::Box) { +fn animate_window_show(config: &Config, window: ApplicationWindow) { let display = window.display(); if let Some(surface) = window.surface() { // todo this does not work for multi monitor systems @@ -497,7 +495,6 @@ fn animate_window_show(config: &Config, window: ApplicationWindow, outer_box: gt animate_window( window.clone(), - config.show_animation.unwrap_or(Animation::None), config.show_animation_time.unwrap_or(0), target_height, target_width, @@ -513,36 +510,26 @@ where // todo the target size might not work for higher dpi displays or bigger resolutions window.set_child(Widget::NONE); - let (target_h, target_w) = { - if let Some(animation) = config.hide_animation { - let allocation = window.allocation(); - match animation { - Animation::None | Animation::Expand => (10, 10), - Animation::ExpandVertical => (allocation.height(), 0), - Animation::ExpandHorizontal => (0, allocation.width()), - } - } else { - (0, 0) - } - }; - animate_window( window, - config.hide_animation.unwrap_or(Animation::None), config.hide_animation_time.unwrap_or(0), - target_h, - target_w, + 10, + 10, on_done_func, ); } -// both warnings are disabled because -// we can deal with truncation and precission loss -#[allow(clippy::cast_possible_truncation)] -#[allow(clippy::cast_precision_loss)] +fn ease_in_out_cubic(t: f32) -> f32 { + if t < 0.5 { + 4.0 * t * t * t + } else { + 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0 + } +} + + fn animate_window( window: ApplicationWindow, - animation_type: Animation, animation_time: u64, target_height: i32, target_width: i32, @@ -552,155 +539,57 @@ fn animate_window( { let allocation = window.allocation(); - let animation_step_length = Duration::from_millis(10); - let animation_speed = Duration::from_millis(animation_time); + // Define animation parameters + let animation_step_length = Duration::from_millis(16); // ~60 FPS - let animation_steps = - ((animation_speed.as_millis() / animation_step_length.as_millis()) as f32).max(1.0); + // Start positions (initial window dimensions) + let start_width = allocation.width() as f32; + let start_height = allocation.height() as f32; - let width = allocation.width(); - let height = allocation.height(); + // Calculate the change in width and height + let delta_width = target_width as f32 - start_width; + let delta_height = target_height as f32 - start_height; - // Calculate signed steps (can be negative) - let mut width_step = ((target_width as f32 - width as f32) / animation_steps).round() as i32; - let mut height_step = ((target_height as f32 - height as f32) / animation_steps).round() as i32; - - // Ensure we move at least 1 pixel per step in the correct direction - if width_step == 0 && target_width != width { - width_step = if target_width < width { -1 } else { 1 }; - } - if height_step == 0 && target_height != height { - height_step = if target_height < height { -1 } else { 1 }; - } + // Start the animation timer + let start_time = Instant::now(); + // Start the animation loop (runs at ~60 FPS) timeout_add_local(animation_step_length, move || { - let result = match animation_type { - Animation::None => animation_none(&window, target_width, target_height), - Animation::Expand => animation_expand( - &window, - target_width, - target_height, - width_step, - height_step, - ), - Animation::ExpandVertical => { - animation_expand_vertical(&window, target_width, target_height, width_step) - } - Animation::ExpandHorizontal => { - animation_expand_horizontal(&window, target_width, target_height, height_step) - } - }; + // Get the elapsed time in milliseconds since the animation started + let elapsed_ms = start_time.elapsed().as_millis() as f32; // Elapsed time in milliseconds - window.queue_draw(); + // Calculate the progress (t) from 0.0 to 1.0 based on elapsed time and animation duration + let t = (elapsed_ms / animation_time as f32).min(1.0); - if result == ControlFlow::Break { + // Apply easing function for smoother transition + // could be other easing function, but this looked best + // todo make other easing types configurable? + let eased_t = ease_in_out_cubic(t); + + // Calculate the new width and height based on easing + let current_width = start_width + delta_width * eased_t; + let current_height = start_height + delta_height * eased_t; + + // Round the dimensions to nearest integers + let rounded_width = current_width.round() as i32; + let rounded_height = current_height.round() as i32; + + // Perform the resizing of the window based on the current width and height + window.set_width_request(rounded_width); + window.set_height_request(rounded_height); + + // If the animation is complete (t >= 1.0), set final size and break the loop + if t >= 1.0 { + window.set_width_request(target_width); + window.set_height_request(target_height); on_done_func(); + ControlFlow::Break + } else { + ControlFlow::Continue } - result }); } -fn animation_none( - window: &ApplicationWindow, - target_width: i32, - target_height: i32, -) -> ControlFlow { - window.set_height_request(target_height); - window.set_width_request(target_width); - ControlFlow::Break -} - -fn animation_expand( - window: &ApplicationWindow, - target_width: i32, - target_height: i32, - width_step: i32, - height_step: i32, -) -> ControlFlow { - let allocation = window.allocation(); - let mut done = true; - let height = allocation.height(); - let width = allocation.width(); - - if resize_height_needed(window, target_height, height_step, height) { - window.set_height_request(height + height_step); - done = false; - } - - if resize_width_needed(window, target_width, width_step, width) { - window.set_width_request(width + width_step); - done = false; - } - - if done { - window.set_height_request(target_height); - window.set_width_request(target_width); - ControlFlow::Break - } else { - ControlFlow::Continue - } -} - -fn animation_expand_horizontal( - window: &ApplicationWindow, - target_width: i32, - target_height: i32, - height_step: i32, -) -> ControlFlow { - let allocation = window.allocation(); - let height = allocation.height(); - window.set_width_request(target_width); - - if resize_height_needed(window, target_height, height_step, height) { - window.set_height_request(height + height_step); - ControlFlow::Continue - } else { - window.set_height_request(target_height); - window.set_width_request(target_width); - ControlFlow::Break - } -} - -fn animation_expand_vertical( - window: &ApplicationWindow, - target_width: i32, - target_height: i32, - width_step: i32, -) -> ControlFlow { - let allocation = window.allocation(); - let width = allocation.width(); - window.set_height_request(target_height); - - if resize_width_needed(window, target_width, width_step, width) { - window.set_width_request(allocation.width() + width_step); - ControlFlow::Continue - } else { - window.set_height_request(target_height); - window.set_width_request(target_width); - ControlFlow::Break - } -} - -fn resize_height_needed( - window: &ApplicationWindow, - target_height: i32, - height_step: i32, - current_height: i32, -) -> bool { - (height_step > 0 && window.height() < target_height) - || (height_step < 0 && window.height() > target_height && current_height + height_step > 0) -} - -fn resize_width_needed( - window: &ApplicationWindow, - target_width: i32, - width_step: i32, - current_width: i32, -) -> bool { - (width_step > 0 && window.width() < target_width) - || (width_step < 0 && window.width() > target_width && current_width + width_step > 0) -} - fn close_gui(app: Application, window: ApplicationWindow, config: &Config) { animate_window_close(config, window, move || app.quit()); }