-
Notifications
You must be signed in to change notification settings - Fork 303
feat: add window animation system #1199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…ations Adds a new animation engine with functions for calculating animation progress, applying easing functions, and interpolating between rectangles and opacity values. Introduces `WindowAnimationState` to manage individual window animations, including movement, opening, and closing animations. Integrates animation management into the `WindowManager` and updates the configuration to support animation settings.
Introduces a new `window_target_positions` HashMap in `WmState` to track target positions for each window, preventing animation restart loops. Updates the `redraw_containers` function to utilize previous target positions for smoother animations and cleans up tracking data when a window is unmanaged.
Enhances the window animation system by introducing adaptive timing based on the size change of the target window. The animation duration is now adjusted for expansions and shrinkages, with specific easing functions applied to improve visual fluidity. This update aims to create smoother transitions and reduce visual artifacts during window movements.
…ance during workspace switches Introduces a new `skip_animations` field in `PendingSync` to control animation behavior during sync operations. Updates the `redraw_containers` function to respect this setting, allowing for smoother transitions when switching workspaces. This enhancement aims to optimize performance by reducing unnecessary animations.
…uration Adds a new command to toggle animations on and off, updating the configuration and state management accordingly. Introduces an animation profile system to control timing values for window animations, allowing users to choose between different profiles (fast, balanced, smooth, custom). Updates the IPC server to respond with the current animation state and modifies the redraw logic to respect the new configuration settings.
When multiple movement commands were issued rapidly for the same window, animations would queue up and slow down significantly. Each new animation was starting from the previous target position rather than the current animated position, causing a buildup of slow back-and-forth movements. This fix implements a "cancel and replace" approach: - When a new movement command arrives during an active animation, the current animation is immediately cancelled - A new animation starts from the window's current animated position (not the previous target) to the new target position - Threshold for detecting target changes kept at 10 pixels to avoid excessive restarts from minor position adjustments The window now feels responsive and snappy when toggling layouts or resizing rapidly, with no animation buildup or slowdown. Changes: - Store existing animation state before checking if new animation needed - Use existing_animation.current_rect() as start position when cancelling - Calculate adaptive timing based on current position, not previous target
Addresses inconsistent animation timing when rapidly toggling window layouts. Previously, interrupted animations would calculate duration based on the current animated position (which varies frame-by-frame), causing wildly inconsistent speeds - sometimes fast, sometimes slow. Now uses a fixed 100ms duration for all cancel-and-replace animations (when interrupting an existing animation), while preserving adaptive timing for new animations starting from stable positions. This ensures: - Rapid toggling feels smooth and consistent - No more random slowdowns or speed variations - Predictable animation behavior regardless of when toggle occurs - Adaptive timing benefits preserved for initial movements The fix splits animation config logic in platform_sync.rs to detect cancel-and-replace scenarios and apply fixed timing only when needed.
Implements Issues glzr-io#1, glzr-io#2, and glzr-io#4 from animation system review to address performance inefficiencies and add user configurability. Also reduces excessive logging during normal operation. Changes: 1. Fix Timer Management (Issue glzr-io#1) - Add animation_timer_running atomic flag to WindowManager - Replace task-per-tick spawning with proper tokio::time::interval - Prevents multiple concurrent timer tasks from running - Timer exits cleanly when channel closes - Significantly reduces task overhead during animations 2. Fix Redundant Redraw Queuing (Issue glzr-io#2) - Filter completed animations from active window list - Ensures windows are only queued once for final redraw - Removes duplicate redraw operations that could impact performance - Simplifies animation completion logic 3. Add Configurable Movement Threshold (Issue glzr-io#4) - Add movement_threshold_px field to AnimationsConfig (default: 10) - Replace hardcoded pixel thresholds with configurable value - Allows users to adjust sensitivity based on DPI and preference - Document in sample config with guidance for high-DPI displays 4. Reduce Excessive Logging - Change window position update log from info! to debug! level - Prevents log spam during animation frames - Keeps information available when debug logging is enabled Issue glzr-io#3 (window cleanup) was already properly implemented in unmanage_window.rs, so no changes were needed. The animation system is now more efficient, prevents unnecessary work, and provides better configurability for end users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really great! However, there are some issues I've found after the initial testing:
-
Full screen video in a browser doesn't work properly when another app tiled in a workspace with the browser. Same with
toggle-fullscreencommand in GlazeWM ifmaximized: false. -
When moving floating windows using hotkeys it animates as well and sort of bounces weirdly. Also has a really hard time getting to the edge of the screen for some reason 😅
-
Floating windows reset their size when trying to move them with hotkeys. For example a window was resized and then moved using GlazeWM hotkeys and it will snap back to the size it was originally before resizing.
-
On multi-monitor setup using
move-workspace --direction left/rightwill move the windows to another monitor. If that target monitor already had a workspace with windows, they will become semi-transparent when you then switch back to their workspace. This only happens with animations enabled. Same can happen with another apps in workspaces in the original monitor. Somove-workspace --directionshould be tested more.
Amazing job so far!
Thank you so much for taking a look! I will be on vacation for a few days, but will take a look at those issues mentioned! Question for you as well, I didn't see any testing unless I missed it (again, new to Rust). Any harm in adding some basic unit tests? The math on the animation engine is pretty precise 😅 |
|
@sagezaugg No idea about the tests to be honest, it's better to ask core maintainers about that. I'm just a GlazeWM user and very new to rust too. Just to add one additional thing:
pZrDHXEt7r.mp4 |
|
Whoa, that's insane. Would not have guessed this was written by someone pretty new to Rust. Thanks for the PR 🙏 I've got a partial list of feedback noted so far but will have to continue with the review tomorrow.
Nope, not at all! Actually just started adding tests to some Also ty @Video-Nomad for the testing and PR feedback! |
| /// Outputs whether the window manager is paused. | ||
| Paused, | ||
| /// Outputs whether animations are enabled. | ||
| AnimationsEnabled, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reckon we should leave the IPC event, query, and command as a separate change. especially since I think a lot of people would confuse it with whether system animations are enabled:
there's also use cases for getting/setting the specific animation config rather than just enabled state. could potentially do something what this PR did but i'd rather that be tackled in a separate PR
| } else { | ||
| 60 | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove the 60Hz default value in the case that it's invalid. instead return an err, and default to 60Hz where it's consumed
|
|
||
| /// Interpolates between two rectangles. | ||
| #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] | ||
| pub fn interpolate_rect(start: &Rect, end: &Rect, progress: f32) -> Rect { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would recommend moving interpolate_rect and the other fns below as methods on Rect/OpacityValue
e.g.
impl Rect {
/// Linearly interpolates between this rect and another rect.
/// `t` should be in the range [0.0, 1.0] where:
/// - 0.0 returns `self`
/// - 1.0 returns `other`
/// - 0.5 returns the midpoint
#[must_use]
pub fn interpolate(&self, other: &Rect, t: f32) -> Self {
let t = t.clamp(0.0, 1.0);
Self::from_ltrb(
self.left + ((other.left - self.left) as f32 * t) as i32,
self.top + ((other.top - self.top) as f32 * t) as i32,
self.right + ((other.right - self.right) as f32 * t) as i32,
self.bottom + ((other.bottom - self.bottom) as f32 * t) as i32,
)
}
}| } | ||
|
|
||
| /// Updates all active animations and redraws windows that are animating. | ||
| pub fn update_animations( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the business logic for animations is spread out a bit too much atm (wm.rs, platform_sync.rs, and animation_state.rs) - wdyt of moving these functions (and possibly animation_tick_rx, animation_tick_tx, animation_timer_running fields) into AnimationManager?
| let existing_animation_clone = existing_animation.clone(); | ||
|
|
||
| // Decide whether to start a new animation | ||
| let should_start_new_animation = if is_opening && config.value.animations.effective_window_open().enabled { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similar to in wm.rs, would suggest moving out parts of the animation logic here to AnimationManager - e.g. could have a should_start_new_animation getter
| # - 'balanced': Default, smooth but responsive (150-200ms) | ||
| # - 'smooth': Slower, more fluid animations (200-250ms) | ||
| # - 'custom': Use the detailed settings below | ||
| profile: "balanced" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think the "animation profiles" add more complexity than they're worth - would you be on board with removing this? since we're already specifying explicit duration and easings in the sample config anyways, animation profiles seem unnecessary
| animations: | ||
| # Whether to enable window animations (opt-in feature). | ||
| # Default: false | ||
| enabled: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a bit overkill with both the top-level enabled flag as well as the individual enabled flags under window_open etc.
we can remove this top-level one and let the individual flags determine whether an animation is enabled. plus it'd be more inline with how the window_effects config is set up
| # Helps prevent animations from starting on very small position changes. | ||
| # Increase this value on high-DPI displays to reduce sensitivity. | ||
| # Default: 10 | ||
| movement_threshold_px: 10 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could be under the window_movement config
|
|
||
| # Animation settings for window movement (tiling, resizing, etc). | ||
| # Only used when profile is set to 'custom'. | ||
| window_movement: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should we rename this to window_move?
| # Whether to slide windows into position. | ||
| slide: true | ||
| # Whether to scale windows from a smaller size. | ||
| scale: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
regarding:
# Whether to fade in windows.
fade: true
# Whether to slide windows into position.
slide: true
# Whether to scale windows from a smaller size.
scale: truedm'd you on discord about some thoughts on this. it'd be nice if there was a flexible enough way to represent combinations of opacity, positional, and scale transforms similar to how it's done in CSS. but after a lot of experimenting with syntax, i unfortunately don't think there's a user-friendly way to allow for this.
i'm thinking the best approach would be similar to hyprland's config where they have preset animations with customizable easing curves, something like:
window_move:
enabled: true
animation_type: "slide"
duration_ms: 200
easing: "ease_out"lmk what you think 👍
(also should every animation type be allowed for window_move + window_open? might make sense to have an enum of window move animations and separate window open animations. will leave that to you)
Add Window Animation System
Preamble
Hey all!! Very excited to introduce my first PR here. I am a huge fan of GlazeWM and use it daily. However, I wanted to add a bit of visual magic to window management. So, I cracked open my IDE and implemented an Animation Engine with many different configuration possibilities! I am new to Rust, so critiques are definitely welcome.
Additionally, I tried to see if this feature was purposefully omitted or not, but couldn't find anything. If this is against GlazeWM's philosophy, more than happy to close.
Anyways, thanks for reviewing!
📝 Description
This PR introduces a comprehensive animation system for GlazeWM, bringing smooth, polished visual feedback to window management operations. The system provides fluid transitions for window movements, resizing, opening, and closing with extensive configuration options and intelligent adaptive behavior.
🎥 Demo
GlazeWM.Animations2.mp4
✨ Features
Core Capabilities
Configuration
IPC Integration
query animations-enabledto check animation statewm-toggle-animationsto enable/disableanimations-changedevent for monitoring animation state🏗️ Architecture
Component Structure
Key Design Decisions
Arc<AtomicBool>to prevent multiple concurrent timers⚙️ Configuration Options
Basic Configuration
Animation Profiles
Fast Profile
Balanced Profile (Default)
Smooth Profile
Custom Configuration
Keybinding Example
IPC Commands
🎯 Technical Highlights
Performance Optimizations
Efficient Timer Management
tokio::intervalper animation loop (not one per frame)MissedTickBehavior::Skipprevents tick accumulationSmart Redraw Queuing
Adaptive Frame Rate
Context-Aware Timing
Memory Management
Thread Safety
Arc<AtomicBool>for timer coordinationmpsc::unbounded_channel🧪 Testing
Manual Testing Performed
🔧 Implementation Details
Key Files Added
packages/wm/src/animation_engine.rs- Core math and interpolation functionspackages/wm/src/animation_state.rs- Animation state managementpackages/wm/src/commands/general/toggle_animations.rs- Toggle command implementationKey Files Modified
packages/wm/src/wm.rs- Timer management and animation updatespackages/wm/src/wm_state.rs- Animation manager and target position trackingpackages/wm/src/commands/general/platform_sync.rs- Animation integration and logicpackages/wm/src/commands/window/unmanage_window.rs- Animation cleanuppackages/wm-common/src/parsed_config.rs- Animation configuration structspackages/wm-common/src/app_command.rs- IPC commands and queriespackages/wm-common/src/wm_event.rs- Animation eventspackages/wm-platform/src/native_monitor.rs- Refresh rate detection📋 Breaking Changes
None - This is a purely additive feature with sensible defaults. Existing configurations will work without modification.
🔄 Migration Guide
No migration needed! Animations are disabled by default. To enable:
🐛 Known Issues
🎯 For Reviewers
Focus Areas
platform_sync.rs:268-430) - Target position tracking prevents restart loopswm.rs:189-223) - Thread-safe interval with atomic coordinationunmanage_window.rs:27-29) - Proper resource cleanupparsed_config.rs:393-610) - Profile system and easing functionsWhat to Test
Expected Behavior
Ready for review! ✨