rework animation to be smoother and drop different animation types
This commit is contained in:
parent
652f2218e6
commit
12dc85e928
3 changed files with 59 additions and 207 deletions
|
@ -56,8 +56,7 @@ layerrule = blur, worf
|
||||||
### New config / command line options
|
### New config / command line options
|
||||||
* fuzzy-length: Defines how long a string must be to be considered for fuzzy match
|
* 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
|
* row-box-orientation: Allows aligning values vertically to place the label below the icon
|
||||||
* text wrapping
|
* configurable animation time
|
||||||
* configurable animations
|
|
||||||
|
|
||||||
### New Styling options
|
### New Styling options
|
||||||
* `label`: Allows styling the label
|
* `label`: Allows styling the label
|
||||||
|
|
|
@ -50,14 +50,6 @@ pub enum Align {
|
||||||
Center,
|
Center,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Serialize, Deserialize)]
|
|
||||||
pub enum Animation {
|
|
||||||
None,
|
|
||||||
Expand,
|
|
||||||
ExpandVertical,
|
|
||||||
ExpandHorizontal,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum WrapMode {
|
pub enum WrapMode {
|
||||||
None,
|
None,
|
||||||
|
@ -323,11 +315,6 @@ pub struct Config {
|
||||||
// #[serde(default = "default_text_wrap_length")]
|
// #[serde(default = "default_text_wrap_length")]
|
||||||
// #[clap(long = "text-wrap-length")]
|
// #[clap(long = "text-wrap-length")]
|
||||||
// pub text_wrap_length: Option<usize>,
|
// pub text_wrap_length: Option<usize>,
|
||||||
/// Defines the animation when the window is show.
|
|
||||||
/// Defaults to Expand
|
|
||||||
#[serde(default = "default_show_animation")]
|
|
||||||
#[clap(long = "show-animation")]
|
|
||||||
pub show_animation: Option<Animation>,
|
|
||||||
|
|
||||||
/// Defines how long it takes for the show animation to finish
|
/// Defines how long it takes for the show animation to finish
|
||||||
/// Defaults to 70ms
|
/// Defaults to 70ms
|
||||||
|
@ -335,13 +322,6 @@ pub struct Config {
|
||||||
#[clap(long = "show-animation-time")]
|
#[clap(long = "show-animation-time")]
|
||||||
pub show_animation_time: Option<u64>,
|
pub show_animation_time: Option<u64>,
|
||||||
|
|
||||||
/// 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<Animation>,
|
|
||||||
|
|
||||||
/// Defines how long it takes for the hide animation to finish
|
/// Defines how long it takes for the hide animation to finish
|
||||||
/// Defaults to 100ms
|
/// Defaults to 100ms
|
||||||
#[serde(default = "default_hide_animation_time")]
|
#[serde(default = "default_hide_animation_time")]
|
||||||
|
@ -416,9 +396,7 @@ impl Default for Config {
|
||||||
pre_display_exec: None,
|
pre_display_exec: None,
|
||||||
fuzzy_min_score: default_fuzzy_min_score(),
|
fuzzy_min_score: default_fuzzy_min_score(),
|
||||||
row_bow_orientation: default_row_box_orientation(),
|
row_bow_orientation: default_row_box_orientation(),
|
||||||
show_animation: default_show_animation(),
|
|
||||||
show_animation_time: default_show_animation_time(),
|
show_animation_time: default_show_animation_time(),
|
||||||
hide_animation: default_hide_animation(),
|
|
||||||
hide_animation_time: default_hide_animation_time(),
|
hide_animation_time: default_hide_animation_time(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -428,28 +406,14 @@ impl Default for Config {
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn default_show_animation_time() -> Option<u64> {
|
pub fn default_show_animation_time() -> Option<u64> {
|
||||||
Some(30)
|
Some(50)
|
||||||
}
|
|
||||||
|
|
||||||
// allowed because option is needed for serde macro
|
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
|
||||||
#[must_use]
|
|
||||||
pub fn default_show_animation() -> Option<Animation> {
|
|
||||||
Some(Animation::Expand)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// allowed because option is needed for serde macro
|
// allowed because option is needed for serde macro
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn default_hide_animation_time() -> Option<u64> {
|
pub fn default_hide_animation_time() -> Option<u64> {
|
||||||
Some(100)
|
Some(0)
|
||||||
}
|
|
||||||
|
|
||||||
// allowed because option is needed for serde macro
|
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
|
||||||
#[must_use]
|
|
||||||
pub fn default_hide_animation() -> Option<Animation> {
|
|
||||||
Some(Animation::None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// allowed because option is needed for serde macro
|
// allowed because option is needed for serde macro
|
||||||
|
|
223
src/lib/gui.rs
223
src/lib/gui.rs
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
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::desktop::known_image_extension_regex_pattern;
|
||||||
use crate::{config, desktop};
|
use crate::{config, desktop};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
@ -14,7 +14,7 @@ use gdk4::prelude::{Cast, DisplayExt, MonitorExt};
|
||||||
use gdk4::{Display, Key};
|
use gdk4::{Display, Key};
|
||||||
use gtk4::glib::ControlFlow;
|
use gtk4::glib::ControlFlow;
|
||||||
use gtk4::prelude::{
|
use gtk4::prelude::{
|
||||||
AppChooserExt, ApplicationExt, ApplicationExtManual, BoxExt, EditableExt, FlowBoxChildExt,
|
ApplicationExt, ApplicationExtManual, BoxExt, EditableExt, FlowBoxChildExt,
|
||||||
GestureSingleExt, GtkWindowExt, ListBoxRowExt, NativeExt, WidgetExt,
|
GestureSingleExt, GtkWindowExt, ListBoxRowExt, NativeExt, WidgetExt,
|
||||||
};
|
};
|
||||||
use gtk4::{
|
use gtk4::{
|
||||||
|
@ -97,8 +97,6 @@ impl<T: Clone> AsRef<MenuItem<T>> for MenuItem<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type IconCache = Arc<HashMap<String, Image>>;
|
|
||||||
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Will return Err when the channel between the UI and this is broken
|
/// Will return Err when the channel between the UI and this is broken
|
||||||
|
@ -212,7 +210,6 @@ fn build_ui<T, P>(
|
||||||
|
|
||||||
let item_provider = Arc::new(Mutex::new(item_provider));
|
let item_provider = Arc::new(Mutex::new(item_provider));
|
||||||
let list_items: ArcMenuMap<T> = Arc::new(Mutex::new(HashMap::new()));
|
let list_items: ArcMenuMap<T> = Arc::new(Mutex::new(HashMap::new()));
|
||||||
// let icon_cache: IconCache = Default::default();
|
|
||||||
let elements = item_provider.lock().unwrap().get_elements(None);
|
let elements = item_provider.lock().unwrap().get_elements(None);
|
||||||
|
|
||||||
build_ui_from_menu_items(
|
build_ui_from_menu_items(
|
||||||
|
@ -257,13 +254,14 @@ fn build_ui<T, P>(
|
||||||
let window_done = Instant::now();
|
let window_done = Instant::now();
|
||||||
|
|
||||||
window.show();
|
window.show();
|
||||||
animate_window_show(config, window.clone(), outer_box);
|
animate_window_show(config, window.clone());
|
||||||
|
let animation_done = Instant::now();
|
||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Building UI took {:?}, window creation {:?}, animation {:?}",
|
"Building UI took {:?}, window creation {:?}, animation {:?}",
|
||||||
start.elapsed(),
|
start.elapsed(),
|
||||||
window_done - start,
|
window_done - start,
|
||||||
window_done.elapsed()
|
animation_done - start
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,7 +475,7 @@ fn sort_menu_items_by_score<T: std::clone::Clone>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn animate_window_show(config: &Config, window: ApplicationWindow, outer_box: gtk4::Box) {
|
fn animate_window_show(config: &Config, window: ApplicationWindow) {
|
||||||
let display = window.display();
|
let display = window.display();
|
||||||
if let Some(surface) = window.surface() {
|
if let Some(surface) = window.surface() {
|
||||||
// todo this does not work for multi monitor systems
|
// 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(
|
animate_window(
|
||||||
window.clone(),
|
window.clone(),
|
||||||
config.show_animation.unwrap_or(Animation::None),
|
|
||||||
config.show_animation_time.unwrap_or(0),
|
config.show_animation_time.unwrap_or(0),
|
||||||
target_height,
|
target_height,
|
||||||
target_width,
|
target_width,
|
||||||
|
@ -513,36 +510,26 @@ where
|
||||||
// todo the target size might not work for higher dpi displays or bigger resolutions
|
// todo the target size might not work for higher dpi displays or bigger resolutions
|
||||||
window.set_child(Widget::NONE);
|
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(
|
animate_window(
|
||||||
window,
|
window,
|
||||||
config.hide_animation.unwrap_or(Animation::None),
|
|
||||||
config.hide_animation_time.unwrap_or(0),
|
config.hide_animation_time.unwrap_or(0),
|
||||||
target_h,
|
10,
|
||||||
target_w,
|
10,
|
||||||
on_done_func,
|
on_done_func,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// both warnings are disabled because
|
fn ease_in_out_cubic(t: f32) -> f32 {
|
||||||
// we can deal with truncation and precission loss
|
if t < 0.5 {
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
4.0 * t * t * t
|
||||||
#[allow(clippy::cast_precision_loss)]
|
} else {
|
||||||
|
1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn animate_window<Func>(
|
fn animate_window<Func>(
|
||||||
window: ApplicationWindow,
|
window: ApplicationWindow,
|
||||||
animation_type: Animation,
|
|
||||||
animation_time: u64,
|
animation_time: u64,
|
||||||
target_height: i32,
|
target_height: i32,
|
||||||
target_width: i32,
|
target_width: i32,
|
||||||
|
@ -552,155 +539,57 @@ fn animate_window<Func>(
|
||||||
{
|
{
|
||||||
let allocation = window.allocation();
|
let allocation = window.allocation();
|
||||||
|
|
||||||
let animation_step_length = Duration::from_millis(10);
|
// Define animation parameters
|
||||||
let animation_speed = Duration::from_millis(animation_time);
|
let animation_step_length = Duration::from_millis(16); // ~60 FPS
|
||||||
|
|
||||||
let animation_steps =
|
// Start positions (initial window dimensions)
|
||||||
((animation_speed.as_millis() / animation_step_length.as_millis()) as f32).max(1.0);
|
let start_width = allocation.width() as f32;
|
||||||
|
let start_height = allocation.height() as f32;
|
||||||
|
|
||||||
let width = allocation.width();
|
// Calculate the change in width and height
|
||||||
let height = allocation.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)
|
// Start the animation timer
|
||||||
let mut width_step = ((target_width as f32 - width as f32) / animation_steps).round() as i32;
|
let start_time = Instant::now();
|
||||||
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 loop (runs at ~60 FPS)
|
||||||
timeout_add_local(animation_step_length, move || {
|
timeout_add_local(animation_step_length, move || {
|
||||||
let result = match animation_type {
|
// Get the elapsed time in milliseconds since the animation started
|
||||||
Animation::None => animation_none(&window, target_width, target_height),
|
let elapsed_ms = start_time.elapsed().as_millis() as f32; // Elapsed time in milliseconds
|
||||||
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)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
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) {
|
fn close_gui(app: Application, window: ApplicationWindow, config: &Config) {
|
||||||
animate_window_close(config, window, move || app.quit());
|
animate_window_close(config, window, move || app.quit());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue