From 93cee2994ab9ccf59a09f61d5b8acf6cd937d654 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Sat, 23 Nov 2024 11:27:27 +0300 Subject: Refactor animations to take explicit current time --- niri-visual-tests/src/cases/gradient_angle.rs | 6 +- niri-visual-tests/src/cases/gradient_area.rs | 6 +- niri-visual-tests/src/cases/gradient_oklab.rs | 6 +- .../src/cases/gradient_oklab_alpha.rs | 6 +- .../src/cases/gradient_oklch_alpha.rs | 6 +- .../src/cases/gradient_oklch_decreasing.rs | 6 +- .../src/cases/gradient_oklch_increasing.rs | 6 +- .../src/cases/gradient_oklch_longer.rs | 6 +- .../src/cases/gradient_oklch_shorter.rs | 6 +- niri-visual-tests/src/cases/gradient_srgb.rs | 6 +- niri-visual-tests/src/cases/gradient_srgb_alpha.rs | 6 +- niri-visual-tests/src/cases/gradient_srgblinear.rs | 6 +- .../src/cases/gradient_srgblinear_alpha.rs | 6 +- niri-visual-tests/src/cases/layout.rs | 52 +++++---- niri-visual-tests/src/cases/mod.rs | 8 +- niri-visual-tests/src/cases/tile.rs | 47 ++++---- niri-visual-tests/src/cases/window.rs | 16 +-- niri-visual-tests/src/main.rs | 8 +- niri-visual-tests/src/smithay_view.rs | 20 ++-- src/animation/clock.rs | 41 +++++++ src/animation/mod.rs | 80 +++++++++----- src/input/mod.rs | 3 +- src/layout/closing_window.rs | 3 +- src/layout/mod.rs | 120 ++++++++++++++++++--- src/layout/monitor.rs | 20 +++- src/layout/tile.rs | 25 +++-- src/layout/workspace.rs | 46 ++++++-- src/niri.rs | 15 ++- src/ui/config_error_notification.rs | 14 ++- src/ui/screenshot_ui.rs | 17 ++- 30 files changed, 426 insertions(+), 187 deletions(-) create mode 100644 src/animation/clock.rs diff --git a/niri-visual-tests/src/cases/gradient_angle.rs b/niri-visual-tests/src/cases/gradient_angle.rs index fa70dc73..64c953c4 100644 --- a/niri-visual-tests/src/cases/gradient_angle.rs +++ b/niri-visual-tests/src/cases/gradient_angle.rs @@ -7,9 +7,9 @@ use niri::render_helpers::border::BorderRenderElement; use niri_config::{Color, CornerRadius, GradientInterpolation}; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientAngle { angle: f32, @@ -17,7 +17,7 @@ pub struct GradientAngle { } impl GradientAngle { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { angle: 0., prev_time: Duration::ZERO, diff --git a/niri-visual-tests/src/cases/gradient_area.rs b/niri-visual-tests/src/cases/gradient_area.rs index 5463cfc4..2fa275f2 100644 --- a/niri-visual-tests/src/cases/gradient_area.rs +++ b/niri-visual-tests/src/cases/gradient_area.rs @@ -8,9 +8,9 @@ use niri::render_helpers::border::BorderRenderElement; use niri_config::{Color, CornerRadius, FloatOrInt, GradientInterpolation}; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Point, Rectangle, Size}; +use smithay::utils::{Physical, Point, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientArea { progress: f32, @@ -19,7 +19,7 @@ pub struct GradientArea { } impl GradientArea { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { let border = FocusRing::new(niri_config::FocusRing { off: false, width: FloatOrInt(1.), diff --git a/niri-visual-tests/src/cases/gradient_oklab.rs b/niri-visual-tests/src/cases/gradient_oklab.rs index abebb213..eac2aaf0 100644 --- a/niri-visual-tests/src/cases/gradient_oklab.rs +++ b/niri-visual-tests/src/cases/gradient_oklab.rs @@ -4,16 +4,16 @@ use niri_config::{ }; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientOklab { gradient_format: GradientInterpolation, } impl GradientOklab { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::Oklab, diff --git a/niri-visual-tests/src/cases/gradient_oklab_alpha.rs b/niri-visual-tests/src/cases/gradient_oklab_alpha.rs index 31ae59ef..e9bdcf17 100644 --- a/niri-visual-tests/src/cases/gradient_oklab_alpha.rs +++ b/niri-visual-tests/src/cases/gradient_oklab_alpha.rs @@ -2,16 +2,16 @@ use niri::render_helpers::border::BorderRenderElement; use niri_config::{Color, CornerRadius, GradientColorSpace, GradientInterpolation}; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientOklabAlpha { gradient_format: GradientInterpolation, } impl GradientOklabAlpha { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::Oklab, diff --git a/niri-visual-tests/src/cases/gradient_oklch_alpha.rs b/niri-visual-tests/src/cases/gradient_oklch_alpha.rs index 022f59ec..695dc2b8 100644 --- a/niri-visual-tests/src/cases/gradient_oklch_alpha.rs +++ b/niri-visual-tests/src/cases/gradient_oklch_alpha.rs @@ -4,16 +4,16 @@ use niri_config::{ }; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientOklchAlpha { gradient_format: GradientInterpolation, } impl GradientOklchAlpha { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::Oklch, diff --git a/niri-visual-tests/src/cases/gradient_oklch_decreasing.rs b/niri-visual-tests/src/cases/gradient_oklch_decreasing.rs index 7039f2c8..208dd9b0 100644 --- a/niri-visual-tests/src/cases/gradient_oklch_decreasing.rs +++ b/niri-visual-tests/src/cases/gradient_oklch_decreasing.rs @@ -4,16 +4,16 @@ use niri_config::{ }; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientOklchDecreasing { gradient_format: GradientInterpolation, } impl GradientOklchDecreasing { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::Oklch, diff --git a/niri-visual-tests/src/cases/gradient_oklch_increasing.rs b/niri-visual-tests/src/cases/gradient_oklch_increasing.rs index 2a020923..92beca2c 100644 --- a/niri-visual-tests/src/cases/gradient_oklch_increasing.rs +++ b/niri-visual-tests/src/cases/gradient_oklch_increasing.rs @@ -4,16 +4,16 @@ use niri_config::{ }; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientOklchIncreasing { gradient_format: GradientInterpolation, } impl GradientOklchIncreasing { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::Oklch, diff --git a/niri-visual-tests/src/cases/gradient_oklch_longer.rs b/niri-visual-tests/src/cases/gradient_oklch_longer.rs index d63259fd..924d56ce 100644 --- a/niri-visual-tests/src/cases/gradient_oklch_longer.rs +++ b/niri-visual-tests/src/cases/gradient_oklch_longer.rs @@ -4,16 +4,16 @@ use niri_config::{ }; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientOklchLonger { gradient_format: GradientInterpolation, } impl GradientOklchLonger { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::Oklch, diff --git a/niri-visual-tests/src/cases/gradient_oklch_shorter.rs b/niri-visual-tests/src/cases/gradient_oklch_shorter.rs index 7cd412ab..fec62af3 100644 --- a/niri-visual-tests/src/cases/gradient_oklch_shorter.rs +++ b/niri-visual-tests/src/cases/gradient_oklch_shorter.rs @@ -4,16 +4,16 @@ use niri_config::{ }; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientOklchShorter { gradient_format: GradientInterpolation, } impl GradientOklchShorter { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::Oklch, diff --git a/niri-visual-tests/src/cases/gradient_srgb.rs b/niri-visual-tests/src/cases/gradient_srgb.rs index d0b847c0..a3182b80 100644 --- a/niri-visual-tests/src/cases/gradient_srgb.rs +++ b/niri-visual-tests/src/cases/gradient_srgb.rs @@ -4,16 +4,16 @@ use niri_config::{ }; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientSrgb { gradient_format: GradientInterpolation, } impl GradientSrgb { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::Srgb, diff --git a/niri-visual-tests/src/cases/gradient_srgb_alpha.rs b/niri-visual-tests/src/cases/gradient_srgb_alpha.rs index c1c3c75c..f4542e79 100644 --- a/niri-visual-tests/src/cases/gradient_srgb_alpha.rs +++ b/niri-visual-tests/src/cases/gradient_srgb_alpha.rs @@ -2,16 +2,16 @@ use niri::render_helpers::border::BorderRenderElement; use niri_config::{Color, CornerRadius, GradientColorSpace, GradientInterpolation}; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientSrgbAlpha { gradient_format: GradientInterpolation, } impl GradientSrgbAlpha { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::Srgb, diff --git a/niri-visual-tests/src/cases/gradient_srgblinear.rs b/niri-visual-tests/src/cases/gradient_srgblinear.rs index b8d0ebba..dc18ce82 100644 --- a/niri-visual-tests/src/cases/gradient_srgblinear.rs +++ b/niri-visual-tests/src/cases/gradient_srgblinear.rs @@ -4,16 +4,16 @@ use niri_config::{ }; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientSrgbLinear { gradient_format: GradientInterpolation, } impl GradientSrgbLinear { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::SrgbLinear, diff --git a/niri-visual-tests/src/cases/gradient_srgblinear_alpha.rs b/niri-visual-tests/src/cases/gradient_srgblinear_alpha.rs index 2a22388f..3c73dc7c 100644 --- a/niri-visual-tests/src/cases/gradient_srgblinear_alpha.rs +++ b/niri-visual-tests/src/cases/gradient_srgblinear_alpha.rs @@ -2,16 +2,16 @@ use niri::render_helpers::border::BorderRenderElement; use niri_config::{Color, CornerRadius, GradientColorSpace, GradientInterpolation}; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Rectangle, Size}; +use smithay::utils::{Physical, Rectangle, Size}; -use super::TestCase; +use super::{Args, TestCase}; pub struct GradientSrgbLinearAlpha { gradient_format: GradientInterpolation, } impl GradientSrgbLinearAlpha { - pub fn new(_size: Size) -> Self { + pub fn new(_args: Args) -> Self { Self { gradient_format: GradientInterpolation { color_space: GradientColorSpace::SrgbLinear, diff --git a/niri-visual-tests/src/cases/layout.rs b/niri-visual-tests/src/cases/layout.rs index c661185e..f979debb 100644 --- a/niri-visual-tests/src/cases/layout.rs +++ b/niri-visual-tests/src/cases/layout.rs @@ -1,18 +1,18 @@ use std::collections::HashMap; use std::time::Duration; +use niri::animation::Clock; use niri::layout::workspace::ColumnWidth; use niri::layout::{LayoutElement as _, Options}; use niri::render_helpers::RenderTarget; -use niri::utils::get_monotonic_time; use niri_config::{Color, FloatOrInt, OutputName}; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; use smithay::desktop::layer_map_for_output; use smithay::output::{Mode, Output, PhysicalProperties, Subpixel}; -use smithay::utils::{Logical, Physical, Size}; +use smithay::utils::{Physical, Size}; -use super::TestCase; +use super::{Args, TestCase}; use crate::test_window::TestWindow; type DynStepFn = Box; @@ -20,13 +20,16 @@ type DynStepFn = Box; pub struct Layout { output: Output, windows: Vec, + clock: Clock, layout: niri::layout::Layout, start_time: Duration, steps: HashMap, } impl Layout { - pub fn new(size: Size) -> Self { + pub fn new(args: Args) -> Self { + let Args { size, clock } = args; + let output = Output::new( String::new(), PhysicalProperties { @@ -63,20 +66,23 @@ impl Layout { }, ..Default::default() }; - let mut layout = niri::layout::Layout::with_options(options); + let mut layout = niri::layout::Layout::with_options(clock.clone(), options); layout.add_output(output.clone()); + let start_time = clock.now(); + Self { output, windows: Vec::new(), + clock, layout, - start_time: get_monotonic_time(), + start_time, steps: HashMap::new(), } } - pub fn open_in_between(size: Size) -> Self { - let mut rv = Self::new(size); + pub fn open_in_between(args: Args) -> Self { + let mut rv = Self::new(args); rv.add_window(TestWindow::freeform(0), Some(ColumnWidth::Proportion(0.3))); rv.add_window(TestWindow::freeform(1), Some(ColumnWidth::Proportion(0.3))); @@ -91,8 +97,8 @@ impl Layout { rv } - pub fn open_multiple_quickly(size: Size) -> Self { - let mut rv = Self::new(size); + pub fn open_multiple_quickly(args: Args) -> Self { + let mut rv = Self::new(args); for delay in [100, 200, 300] { rv.add_step(delay, move |l| { @@ -105,8 +111,8 @@ impl Layout { rv } - pub fn open_multiple_quickly_big(size: Size) -> Self { - let mut rv = Self::new(size); + pub fn open_multiple_quickly_big(args: Args) -> Self { + let mut rv = Self::new(args); for delay in [100, 200, 300] { rv.add_step(delay, move |l| { @@ -119,8 +125,8 @@ impl Layout { rv } - pub fn open_to_the_left(size: Size) -> Self { - let mut rv = Self::new(size); + pub fn open_to_the_left(args: Args) -> Self { + let mut rv = Self::new(args); rv.add_window(TestWindow::freeform(0), Some(ColumnWidth::Proportion(0.3))); rv.add_window(TestWindow::freeform(1), Some(ColumnWidth::Proportion(0.3))); @@ -135,8 +141,8 @@ impl Layout { rv } - pub fn open_to_the_left_big(size: Size) -> Self { - let mut rv = Self::new(size); + pub fn open_to_the_left_big(args: Args) -> Self { + let mut rv = Self::new(args); rv.add_window(TestWindow::freeform(0), Some(ColumnWidth::Proportion(0.3))); rv.add_window(TestWindow::freeform(1), Some(ColumnWidth::Proportion(0.8))); @@ -201,21 +207,23 @@ impl TestCase for Layout { self.layout.are_animations_ongoing(Some(&self.output)) || !self.steps.is_empty() } - fn advance_animations(&mut self, mut current_time: Duration) { + fn advance_animations(&mut self, current_time: Duration) { let run = self .steps .keys() .copied() .filter(|delay| self.start_time + *delay <= current_time) .collect::>(); - for key in &run { - let f = self.steps.remove(key).unwrap(); + for delay in &run { + let now = self.start_time + *delay; + self.clock.set_time_override(Some(now)); + self.layout.advance_animations(now); + + let f = self.steps.remove(delay).unwrap(); f(self); } - if !run.is_empty() { - current_time = get_monotonic_time(); - } + self.clock.set_time_override(None); self.layout.advance_animations(current_time); } diff --git a/niri-visual-tests/src/cases/mod.rs b/niri-visual-tests/src/cases/mod.rs index 07a41ba4..bc43d189 100644 --- a/niri-visual-tests/src/cases/mod.rs +++ b/niri-visual-tests/src/cases/mod.rs @@ -1,8 +1,9 @@ use std::time::Duration; +use niri::animation::Clock; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Physical, Size}; +use smithay::utils::{Logical, Physical, Size}; pub mod gradient_angle; pub mod gradient_area; @@ -21,6 +22,11 @@ pub mod layout; pub mod tile; pub mod window; +pub struct Args { + pub size: Size, + pub clock: Clock, +} + pub trait TestCase { fn resize(&mut self, _width: i32, _height: i32) {} fn are_animations_ongoing(&self) -> bool { diff --git a/niri-visual-tests/src/cases/tile.rs b/niri-visual-tests/src/cases/tile.rs index 1b542f58..e2637698 100644 --- a/niri-visual-tests/src/cases/tile.rs +++ b/niri-visual-tests/src/cases/tile.rs @@ -6,9 +6,9 @@ use niri::render_helpers::RenderTarget; use niri_config::{Color, FloatOrInt}; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size}; +use smithay::utils::{Physical, Point, Rectangle, Scale, Size}; -use super::TestCase; +use super::{Args, TestCase}; use crate::test_window::TestWindow; pub struct Tile { @@ -17,53 +17,44 @@ pub struct Tile { } impl Tile { - pub fn freeform(size: Size) -> Self { + pub fn freeform(args: Args) -> Self { let window = TestWindow::freeform(0); - let mut rv = Self::with_window(window); - rv.tile.request_tile_size(size.to_f64(), false, None); - rv.window.communicate(); - rv + Self::with_window(args, window) } - pub fn fixed_size(size: Size) -> Self { + pub fn fixed_size(args: Args) -> Self { let window = TestWindow::fixed_size(0); - let mut rv = Self::with_window(window); - rv.tile.request_tile_size(size.to_f64(), false, None); - rv.window.communicate(); - rv + Self::with_window(args, window) } - pub fn fixed_size_with_csd_shadow(size: Size) -> Self { + pub fn fixed_size_with_csd_shadow(args: Args) -> Self { let window = TestWindow::fixed_size(0); window.set_csd_shadow_width(64); - let mut rv = Self::with_window(window); - rv.tile.request_tile_size(size.to_f64(), false, None); - rv.window.communicate(); - rv + Self::with_window(args, window) } - pub fn freeform_open(size: Size) -> Self { - let mut rv = Self::freeform(size); + pub fn freeform_open(args: Args) -> Self { + let mut rv = Self::freeform(args); rv.window.set_color([0.1, 0.1, 0.1, 1.]); rv.tile.start_open_animation(); rv } - pub fn fixed_size_open(size: Size) -> Self { - let mut rv = Self::fixed_size(size); + pub fn fixed_size_open(args: Args) -> Self { + let mut rv = Self::fixed_size(args); rv.window.set_color([0.1, 0.1, 0.1, 1.]); rv.tile.start_open_animation(); rv } - pub fn fixed_size_with_csd_shadow_open(size: Size) -> Self { - let mut rv = Self::fixed_size_with_csd_shadow(size); + pub fn fixed_size_with_csd_shadow_open(args: Args) -> Self { + let mut rv = Self::fixed_size_with_csd_shadow(args); rv.window.set_color([0.1, 0.1, 0.1, 1.]); rv.tile.start_open_animation(); rv } - pub fn with_window(window: TestWindow) -> Self { + pub fn with_window(args: Args, window: TestWindow) -> Self { let options = Options { focus_ring: niri_config::FocusRing { off: true, @@ -77,7 +68,13 @@ impl Tile { }, ..Default::default() }; - let tile = niri::layout::tile::Tile::new(window.clone(), 1., Rc::new(options)); + + let mut tile = + niri::layout::tile::Tile::new(window.clone(), 1., args.clock, Rc::new(options)); + + tile.request_tile_size(args.size.to_f64(), false, None); + window.communicate(); + Self { window, tile } } } diff --git a/niri-visual-tests/src/cases/window.rs b/niri-visual-tests/src/cases/window.rs index 23f90041..26c7fbce 100644 --- a/niri-visual-tests/src/cases/window.rs +++ b/niri-visual-tests/src/cases/window.rs @@ -2,9 +2,9 @@ use niri::layout::LayoutElement; use niri::render_helpers::RenderTarget; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; -use smithay::utils::{Logical, Physical, Point, Scale, Size}; +use smithay::utils::{Physical, Point, Scale, Size}; -use super::TestCase; +use super::{Args, TestCase}; use crate::test_window::TestWindow; pub struct Window { @@ -12,24 +12,24 @@ pub struct Window { } impl Window { - pub fn freeform(size: Size) -> Self { + pub fn freeform(args: Args) -> Self { let mut window = TestWindow::freeform(0); - window.request_size(size, false, None); + window.request_size(args.size, false, None); window.communicate(); Self { window } } - pub fn fixed_size(size: Size) -> Self { + pub fn fixed_size(args: Args) -> Self { let mut window = TestWindow::fixed_size(0); - window.request_size(size, false, None); + window.request_size(args.size, false, None); window.communicate(); Self { window } } - pub fn fixed_size_with_csd_shadow(size: Size) -> Self { + pub fn fixed_size_with_csd_shadow(args: Args) -> Self { let mut window = TestWindow::fixed_size(0); window.set_csd_shadow_width(64); - window.request_size(size, false, None); + window.request_size(args.size, false, None); window.communicate(); Self { window } } diff --git a/niri-visual-tests/src/main.rs b/niri-visual-tests/src/main.rs index 2b46f4f5..fa035c75 100644 --- a/niri-visual-tests/src/main.rs +++ b/niri-visual-tests/src/main.rs @@ -5,12 +5,12 @@ use std::env; use std::sync::atomic::Ordering; use adw::prelude::{AdwApplicationWindowExt, NavigationPageExt}; +use cases::Args; use gtk::prelude::{ AdjustmentExt, ApplicationExt, ApplicationExtManual, BoxExt, GtkWindowExt, WidgetExt, }; use gtk::{gdk, gio, glib}; use niri::animation::ANIMATION_SLOWDOWN; -use smithay::utils::{Logical, Size}; use smithay_view::SmithayView; use tracing_subscriber::EnvFilter; @@ -72,11 +72,7 @@ fn build_ui(app: &adw::Application) { } impl S { - fn add( - &self, - make: impl Fn(Size) -> T + 'static, - title: &str, - ) { + fn add(&self, make: impl Fn(Args) -> T + 'static, title: &str) { let view = SmithayView::new(make); self.stack.add_titled(&view, None, title); } diff --git a/niri-visual-tests/src/smithay_view.rs b/niri-visual-tests/src/smithay_view.rs index df514df1..68c5d67d 100644 --- a/niri-visual-tests/src/smithay_view.rs +++ b/niri-visual-tests/src/smithay_view.rs @@ -1,8 +1,8 @@ use gtk::glib; use gtk::subclass::prelude::*; -use smithay::utils::{Logical, Size}; +use smithay::utils::Size; -use crate::cases::TestCase; +use crate::cases::{Args, TestCase}; mod imp { use std::cell::{Cell, OnceCell, RefCell}; @@ -11,6 +11,7 @@ mod imp { use anyhow::{ensure, Context}; use gtk::gdk; use gtk::prelude::*; + use niri::animation::Clock; use niri::render_helpers::{resources, shaders}; use niri::utils::get_monotonic_time; use smithay::backend::egl::ffi::egl; @@ -21,7 +22,7 @@ mod imp { use super::*; - type DynMakeTestCase = Box) -> Box>; + type DynMakeTestCase = Box Box>; #[derive(Default)] pub struct SmithayView { @@ -30,6 +31,7 @@ mod imp { renderer: RefCell>>, pub make_test_case: OnceCell, test_case: RefCell>>, + clock: RefCell, } #[glib::object_subclass] @@ -129,7 +131,11 @@ mod imp { let mut case = self.test_case.borrow_mut(); let case = case.get_or_insert_with(|| { let make = self.make_test_case.get().unwrap(); - make(Size::from(size)) + let args = Args { + size: Size::from(size), + clock: self.clock.borrow().clone(), + }; + make(args) }); case.advance_animations(get_monotonic_time()); @@ -232,12 +238,10 @@ glib::wrapper! { } impl SmithayView { - pub fn new( - make_test_case: impl Fn(Size) -> T + 'static, - ) -> Self { + pub fn new(make_test_case: impl Fn(Args) -> T + 'static) -> Self { let obj: Self = glib::Object::builder().build(); - let make = move |size| Box::new(make_test_case(size)) as Box; + let make = move |args| Box::new(make_test_case(args)) as Box; let make_test_case = Box::new(make) as _; let _ = obj.imp().make_test_case.set(make_test_case); diff --git a/src/animation/clock.rs b/src/animation/clock.rs new file mode 100644 index 00000000..3cfa727b --- /dev/null +++ b/src/animation/clock.rs @@ -0,0 +1,41 @@ +use std::cell::Cell; +use std::rc::Rc; +use std::time::Duration; + +use crate::utils::get_monotonic_time; + +/// Clock that can have its time value overridden. +/// +/// Can be cloned to share the same clock. +#[derive(Debug, Default, Clone)] +pub struct Clock { + time_override: Rc>>, +} + +impl Clock { + /// Creates a new [`Clock`] with time override in place. + pub fn with_override(time: Duration) -> Self { + Self { + time_override: Rc::new(Cell::new(Some(time))), + } + } + + /// Sets the current time override. + pub fn set_time_override(&mut self, time: Option) { + self.time_override.set(time); + } + + /// Gets the current time. + #[inline] + pub fn now(&self) -> Duration { + self.time_override.get().unwrap_or_else(get_monotonic_time) + } +} + +impl PartialEq for Clock { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.time_override, &other.time_override) + } +} + +impl Eq for Clock {} diff --git a/src/animation/mod.rs b/src/animation/mod.rs index 780cb3a8..5efb0f16 100644 --- a/src/animation/mod.rs +++ b/src/animation/mod.rs @@ -4,11 +4,12 @@ use keyframe::functions::{EaseOutCubic, EaseOutQuad}; use keyframe::EasingFunction; use portable_atomic::{AtomicF64, Ordering}; -use crate::utils::get_monotonic_time; - mod spring; pub use spring::{Spring, SpringParams}; +mod clock; +pub use clock::Clock; + pub static ANIMATION_SLOWDOWN: AtomicF64 = AtomicF64::new(1.); #[derive(Debug, Clone)] @@ -48,11 +49,24 @@ pub enum Curve { } impl Animation { - pub fn new(from: f64, to: f64, initial_velocity: f64, config: niri_config::Animation) -> Self { + pub fn new( + current_time: Duration, + from: f64, + to: f64, + initial_velocity: f64, + config: niri_config::Animation, + ) -> Self { // Scale the velocity by slowdown to keep the touchpad gestures feeling right. let initial_velocity = initial_velocity * ANIMATION_SLOWDOWN.load(Ordering::Relaxed); - let mut rv = Self::ease(from, to, initial_velocity, 0, Curve::EaseOutCubic); + let mut rv = Self::ease( + current_time, + from, + to, + initial_velocity, + 0, + Curve::EaseOutCubic, + ); if config.off { rv.is_off = true; return rv; @@ -83,10 +97,11 @@ impl Animation { initial_velocity: self.initial_velocity, params, }; - *self = Self::spring(spring); + *self = Self::spring(current_time, spring); } niri_config::AnimationKind::Easing(p) => { *self = Self::ease( + current_time, self.from, self.to, self.initial_velocity, @@ -101,7 +116,13 @@ impl Animation { } /// Restarts the animation using the previous config. - pub fn restarted(&self, from: f64, to: f64, initial_velocity: f64) -> Self { + pub fn restarted( + &self, + current_time: Duration, + from: f64, + to: f64, + initial_velocity: f64, + ) -> Self { if self.is_off { return self.clone(); } @@ -111,6 +132,7 @@ impl Animation { match self.kind { Kind::Easing { curve } => Self::ease( + current_time, from, to, initial_velocity, @@ -124,23 +146,32 @@ impl Animation { initial_velocity: self.initial_velocity, params: spring.params, }; - Self::spring(spring) + Self::spring(current_time, spring) } Kind::Deceleration { initial_velocity, deceleration_rate, } => { let threshold = 0.001; // FIXME - Self::decelerate(from, initial_velocity, deceleration_rate, threshold) + Self::decelerate( + current_time, + from, + initial_velocity, + deceleration_rate, + threshold, + ) } } } - pub fn ease(from: f64, to: f64, initial_velocity: f64, duration_ms: u64, curve: Curve) -> Self { - // FIXME: ideally we shouldn't use current time here because animations started within the - // same frame cycle should have the same start time to be synchronized. - let now = get_monotonic_time(); - + pub fn ease( + current_time: Duration, + from: f64, + to: f64, + initial_velocity: f64, + duration_ms: u64, + curve: Curve, + ) -> Self { let duration = Duration::from_millis(duration_ms); let kind = Kind::Easing { curve }; @@ -152,19 +183,15 @@ impl Animation { duration, // Our current curves never overshoot. clamped_duration: duration, - start_time: now, - current_time: now, + start_time: current_time, + current_time, kind, } } - pub fn spring(spring: Spring) -> Self { + pub fn spring(current_time: Duration, spring: Spring) -> Self { let _span = tracy_client::span!("Animation::spring"); - // FIXME: ideally we shouldn't use current time here because animations started within the - // same frame cycle should have the same start time to be synchronized. - let now = get_monotonic_time(); - let duration = spring.duration(); let clamped_duration = spring.clamped_duration().unwrap_or(duration); let kind = Kind::Spring(spring); @@ -176,22 +203,19 @@ impl Animation { is_off: false, duration, clamped_duration, - start_time: now, - current_time: now, + start_time: current_time, + current_time, kind, } } pub fn decelerate( + current_time: Duration, from: f64, initial_velocity: f64, deceleration_rate: f64, threshold: f64, ) -> Self { - // FIXME: ideally we shouldn't use current time here because animations started within the - // same frame cycle should have the same start time to be synchronized. - let now = get_monotonic_time(); - let duration_s = if initial_velocity == 0. { 0. } else { @@ -214,8 +238,8 @@ impl Animation { is_off: false, duration, clamped_duration: duration, - start_time: now, - current_time: now, + start_time: current_time, + current_time, kind, } } diff --git a/src/input/mod.rs b/src/input/mod.rs index 976f51db..eec8fbe4 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -3024,6 +3024,7 @@ pub fn mods_with_finger_scroll_binds(comp_mod: CompositorMod, binds: &Binds) -> #[cfg(test)] mod tests { use super::*; + use crate::animation::Clock; #[test] fn bindings_suppress_keys() { @@ -3042,7 +3043,7 @@ mod tests { let comp_mod = CompositorMod::Super; let mut suppressed_keys = HashSet::new(); - let screenshot_ui = ScreenshotUi::new(Default::default()); + let screenshot_ui = ScreenshotUi::new(Clock::default(), Default::default()); let disable_power_key_handling = false; // The key_code we pick is arbitrary, the only thing diff --git a/src/layout/closing_window.rs b/src/layout/closing_window.rs index 8bbf0258..744c099e 100644 --- a/src/layout/closing_window.rs +++ b/src/layout/closing_window.rs @@ -142,8 +142,7 @@ impl ClosingWindow { match &mut self.anim_state { AnimationState::Waiting { blocker, anim } => { if blocker.state() != BlockerState::Pending { - let mut anim = anim.restarted(0., 1., 0.); - anim.set_current_time(current_time); + let anim = anim.restarted(current_time, 0., 1., 0.); self.anim_state = AnimationState::Animating(anim); } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index e5cfea67..f4e59af3 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -52,6 +52,7 @@ use workspace::WorkspaceId; pub use self::monitor::MonitorRenderElement; use self::monitor::{Monitor, WorkspaceSwitch}; use self::workspace::{compute_working_area, Column, ColumnWidth, InsertHint, OutputId, Workspace}; +use crate::animation::Clock; use crate::layout::workspace::InsertPosition; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; @@ -216,6 +217,8 @@ pub struct Layout { last_active_workspace_id: HashMap, /// Ongoing interactive move. interactive_move: Option>, + /// Clock for driving animations. + clock: Clock, /// Configurable properties of the layout. options: Rc, } @@ -433,27 +436,30 @@ impl Options { } impl Layout { - pub fn new(config: &Config) -> Self { - Self::with_options_and_workspaces(config, Options::from_config(config)) + pub fn new(clock: Clock, config: &Config) -> Self { + Self::with_options_and_workspaces(clock, config, Options::from_config(config)) } - pub fn with_options(options: Options) -> Self { + pub fn with_options(clock: Clock, options: Options) -> Self { Self { monitor_set: MonitorSet::NoOutputs { workspaces: vec![] }, is_active: true, last_active_workspace_id: HashMap::new(), interactive_move: None, + clock, options: Rc::new(options), } } - fn with_options_and_workspaces(config: &Config, options: Options) -> Self { + fn with_options_and_workspaces(clock: Clock, config: &Config, options: Options) -> Self { let opts = Rc::new(options); let workspaces = config .workspaces .iter() - .map(|ws| Workspace::new_with_config_no_outputs(Some(ws.clone()), opts.clone())) + .map(|ws| { + Workspace::new_with_config_no_outputs(Some(ws.clone()), clock.clone(), opts.clone()) + }) .collect(); Self { @@ -461,6 +467,7 @@ impl Layout { is_active: true, last_active_workspace_id: HashMap::new(), interactive_move: None, + clock, options: opts, } } @@ -522,13 +529,18 @@ impl Layout { } // Make sure there's always an empty workspace. - workspaces.push(Workspace::new(output.clone(), self.options.clone())); + workspaces.push(Workspace::new( + output.clone(), + self.clock.clone(), + self.options.clone(), + )); for ws in &mut workspaces { ws.set_output(Some(output.clone())); } - let mut monitor = Monitor::new(output, workspaces, self.options.clone()); + let mut monitor = + Monitor::new(output, workspaces, self.clock.clone(), self.options.clone()); monitor.active_workspace_idx = active_workspace_idx.unwrap_or(0); monitors.push(monitor); @@ -540,7 +552,11 @@ impl Layout { } MonitorSet::NoOutputs { mut workspaces } => { // We know there are no empty workspaces there, so add one. - workspaces.push(Workspace::new(output.clone(), self.options.clone())); + workspaces.push(Workspace::new( + output.clone(), + self.clock.clone(), + self.options.clone(), + )); let ws_id_to_activate = self.last_active_workspace_id.remove(&output.name()); let mut active_workspace_idx = 0; @@ -553,7 +569,8 @@ impl Layout { } } - let mut monitor = Monitor::new(output, workspaces, self.options.clone()); + let mut monitor = + Monitor::new(output, workspaces, self.clock.clone(), self.options.clone()); monitor.active_workspace_idx = active_workspace_idx; MonitorSet::Normal { @@ -785,7 +802,10 @@ impl Layout { let ws = if let Some(ws) = workspaces.get_mut(0) { ws } else { - workspaces.push(Workspace::new_no_outputs(self.options.clone())); + workspaces.push(Workspace::new_no_outputs( + self.clock.clone(), + self.options.clone(), + )); &mut workspaces[0] }; ws.add_window(None, window, true, width, is_full_width); @@ -1992,6 +2012,8 @@ impl Layout { move_win_id = Some(window_id.clone()); } InteractiveMoveState::Moving(move_) => { + assert_eq!(self.clock, move_.tile.clock); + let scale = move_.output.current_scale().fractional_scale(); let options = Options::clone(&self.options).adjusted_for_scale(scale); assert_eq!( @@ -2026,6 +2048,8 @@ impl Layout { "with no outputs there cannot be empty unnamed workspaces" ); + assert_eq!(self.clock, workspace.clock); + assert_eq!( workspace.base_options, self.options, "workspace base options must be synchronized with layout" @@ -2070,6 +2094,7 @@ impl Layout { ); assert!(monitor.active_workspace_idx < monitor.workspaces.len()); + assert_eq!(self.clock, monitor.clock); assert_eq!( monitor.options, self.options, "monitor options must be synchronized with layout" @@ -2135,6 +2160,8 @@ impl Layout { // exists. for workspace in &monitor.workspaces { + assert_eq!(self.clock, workspace.clock); + assert_eq!( workspace.base_options, self.options, "workspace options must be synchronized with layout" @@ -2326,6 +2353,7 @@ impl Layout { return; } + let clock = self.clock.clone(); let options = self.options.clone(); match &mut self.monitor_set { @@ -2349,6 +2377,7 @@ impl Layout { let ws = Workspace::new_with_config( mon.output.clone(), Some(ws_config.clone()), + clock, options, ); mon.workspaces.insert(0, ws); @@ -2357,7 +2386,8 @@ impl Layout { mon.clean_up_workspaces(); } MonitorSet::NoOutputs { workspaces } => { - let ws = Workspace::new_with_config_no_outputs(Some(ws_config.clone()), options); + let ws = + Workspace::new_with_config_no_outputs(Some(ws_config.clone()), clock, options); workspaces.insert(0, ws); } } @@ -3161,7 +3191,10 @@ impl Layout { let ws = if let Some(ws) = workspaces.get_mut(0) { ws } else { - workspaces.push(Workspace::new_no_outputs(self.options.clone())); + workspaces.push(Workspace::new_no_outputs( + self.clock.clone(), + self.options.clone(), + )); &mut workspaces[0] }; @@ -3620,7 +3653,7 @@ mod tests { impl Default for Layout { fn default() -> Self { - Self::with_options(Default::default()) + Self::with_options(Clock::with_override(Duration::ZERO), Default::default()) } } @@ -3854,6 +3887,16 @@ mod tests { prop_oneof![Just(1.), Just(1.5), Just(2.),] } + fn arbitrary_msec_delta() -> impl Strategy { + prop_oneof![ + 1 => Just(-1000), + 2 => Just(-10), + 1 => Just(0), + 2 => Just(10), + 6 => Just(1000), + ] + } + #[derive(Debug, Clone, Copy, Arbitrary)] enum Op { AddOutput(#[proptest(strategy = "1..=5usize")] usize), @@ -3997,6 +4040,10 @@ mod tests { Refresh { is_active: bool, }, + AdvanceAnimations { + #[proptest(strategy = "arbitrary_msec_delta()")] + msec_delta: i32, + }, MoveWorkspaceToOutput(#[proptest(strategy = "1..=5u8")] u8), ViewOffsetGestureBegin { #[proptest(strategy = "1..=5usize")] @@ -4505,6 +4552,16 @@ mod tests { Op::Refresh { is_active } => { layout.refresh(is_active); } + Op::AdvanceAnimations { msec_delta } => { + let mut now = layout.clock.now(); + if msec_delta >= 0 { + now = now.saturating_add(Duration::from_millis(msec_delta as u64)); + } else { + now = now.saturating_sub(Duration::from_millis(-msec_delta as u64)); + } + layout.clock.set_time_override(Some(now)); + layout.advance_animations(now); + } Op::MoveWorkspaceToOutput(id) => { let name = format!("output{id}"); let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { @@ -4617,7 +4674,7 @@ mod tests { #[track_caller] fn check_ops_with_options(options: Options, ops: &[Op]) { - let mut layout = Layout::with_options(options); + let mut layout = Layout::with_options(Clock::with_override(Duration::ZERO), options); for op in ops { op.apply(&mut layout); @@ -5440,7 +5497,7 @@ mod tests { config.layout.border.off = false; config.layout.border.width = FloatOrInt(2.); - let mut layout = Layout::new(&config); + let mut layout = Layout::new(Clock::default(), &config); Op::AddWindow { id: 1, @@ -5460,7 +5517,7 @@ mod tests { let mut config = Config::default(); config.layout.preset_window_heights = vec![PresetSize::Fixed(1), PresetSize::Fixed(2)]; - let mut layout = Layout::new(&config); + let mut layout = Layout::new(Clock::default(), &config); let ops = [ Op::AddOutput(1), @@ -5752,6 +5809,37 @@ mod tests { check_ops(&ops); } + #[test] + fn interactive_move_onto_last_workspace() { + let ops = [ + Op::AddOutput(1), + Op::AddWindow { + id: 0, + bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)), + min_max_size: Default::default(), + }, + Op::InteractiveMoveBegin { + window: 0, + output_idx: 1, + px: 0., + py: 0., + }, + Op::InteractiveMoveUpdate { + window: 0, + dx: 1000., + dy: 0., + output_idx: 1, + px: 0., + py: 0., + }, + Op::FocusWorkspaceDown, + Op::AdvanceAnimations { msec_delta: 1000 }, + Op::InteractiveMoveEnd { window: 0 }, + ]; + + check_ops(&ops); + } + #[test] fn output_active_workspace_is_preserved() { let ops = [ diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index aecad889..aad921bb 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -15,7 +15,7 @@ use super::workspace::{ WorkspaceRenderElement, }; use super::{LayoutElement, Options}; -use crate::animation::Animation; +use crate::animation::{Animation, Clock}; use crate::input::swipe_tracker::SwipeTracker; use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::RenderTarget; @@ -45,6 +45,8 @@ pub struct Monitor { pub(super) previous_workspace_id: Option, /// In-progress switch between workspaces. pub(super) workspace_switch: Option, + /// Clock for driving animations. + pub(super) clock: Clock, /// Configurable properties of the layout. pub(super) options: Rc, } @@ -94,7 +96,12 @@ impl WorkspaceSwitch { } impl Monitor { - pub fn new(output: Output, workspaces: Vec>, options: Rc) -> Self { + pub fn new( + output: Output, + workspaces: Vec>, + clock: Clock, + options: Rc, + ) -> Self { Self { output_name: output.name(), output, @@ -102,6 +109,7 @@ impl Monitor { active_workspace_idx: 0, previous_workspace_id: None, workspace_switch: None, + clock, options, } } @@ -151,7 +159,11 @@ impl Monitor { } pub fn add_workspace_bottom(&mut self) { - let ws = Workspace::new(self.output.clone(), self.options.clone()); + let ws = Workspace::new( + self.output.clone(), + self.clock.clone(), + self.options.clone(), + ); self.workspaces.push(ws); } @@ -172,6 +184,7 @@ impl Monitor { self.active_workspace_idx = idx; self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new( + self.clock.now(), current_idx, idx as f64, 0., @@ -1099,6 +1112,7 @@ impl Monitor { self.active_workspace_idx = new_idx; self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new( + self.clock.now(), gesture.current_idx, new_idx as f64, velocity, diff --git a/src/layout/tile.rs b/src/layout/tile.rs index ddb9bd8c..dd3d50af 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -13,7 +13,7 @@ use super::{ LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot, Options, RESIZE_ANIMATION_THRESHOLD, }; -use crate::animation::Animation; +use crate::animation::{Animation, Clock}; use crate::niri_render_elements; use crate::render_helpers::border::BorderRenderElement; use crate::render_helpers::clipped_surface::{ClippedSurfaceRenderElement, RoundedCornerDamage}; @@ -76,6 +76,9 @@ pub struct Tile { /// Scale of the output the tile is on (and rounds its sizes to). scale: f64, + /// Clock for driving animations. + pub(super) clock: Clock, + /// Configurable properties of the layout. pub(super) options: Rc, } @@ -110,7 +113,7 @@ struct MoveAnimation { } impl Tile { - pub fn new(window: W, scale: f64, options: Rc) -> Self { + pub fn new(window: W, scale: f64, clock: Clock, options: Rc) -> Self { let rules = window.rules(); let border_config = rules.border.resolve_against(options.border); let focus_ring_config = rules.focus_ring.resolve_against(options.focus_ring.into()); @@ -130,6 +133,7 @@ impl Tile { unmap_snapshot: None, rounded_corner_damage: Default::default(), scale, + clock, options, } } @@ -180,7 +184,13 @@ impl Tile { let change = self.window.size().to_f64().to_point() - size_from.to_point(); let change = f64::max(change.x.abs(), change.y.abs()); if change > RESIZE_ANIMATION_THRESHOLD { - let anim = Animation::new(0., 1., 0., self.options.animations.window_resize.anim); + let anim = Animation::new( + self.clock.now(), + 0., + 1., + 0., + self.options.animations.window_resize.anim, + ); self.resize_animation = Some(ResizeAnimation { anim, size_from, @@ -316,6 +326,7 @@ impl Tile { pub fn start_open_animation(&mut self) { self.open_animation = Some(OpenAnimation::new(Animation::new( + self.clock.now(), 0., 1., 0., @@ -342,8 +353,8 @@ impl Tile { // Preserve the previous config if ongoing. let anim = self.move_x_animation.take().map(|move_| move_.anim); let anim = anim - .map(|anim| anim.restarted(1., 0., 0.)) - .unwrap_or_else(|| Animation::new(1., 0., 0., config)); + .map(|anim| anim.restarted(self.clock.now(), 1., 0., 0.)) + .unwrap_or_else(|| Animation::new(self.clock.now(), 1., 0., 0., config)); self.move_x_animation = Some(MoveAnimation { anim, @@ -361,8 +372,8 @@ impl Tile { // Preserve the previous config if ongoing. let anim = self.move_y_animation.take().map(|move_| move_.anim); let anim = anim - .map(|anim| anim.restarted(1., 0., 0.)) - .unwrap_or_else(|| Animation::new(1., 0., 0., config)); + .map(|anim| anim.restarted(self.clock.now(), 1., 0., 0.)) + .unwrap_or_else(|| Animation::new(self.clock.now(), 1., 0., 0., config)); self.move_y_animation = Some(MoveAnimation { anim, diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index b26dfcc1..6432ca8b 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -19,7 +19,7 @@ use super::closing_window::{ClosingWindow, ClosingWindowRenderElement}; use super::insert_hint_element::{InsertHintElement, InsertHintRenderElement}; use super::tile::{Tile, TileRenderElement, TileRenderSnapshot}; use super::{ConfigureIntent, InteractiveResizeData, LayoutElement, Options, RemovedTile}; -use crate::animation::Animation; +use crate::animation::{Animation, Clock}; use crate::input::swipe_tracker::SwipeTracker; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; @@ -113,6 +113,9 @@ pub struct Workspace { /// Insert hint element for rendering. insert_hint_element: InsertHintElement, + /// Clock for driving animations. + pub(super) clock: Clock, + /// Configurable properties of the layout as received from the parent monitor. pub(super) base_options: Rc, @@ -293,6 +296,9 @@ pub struct Column { /// Scale of the output the column is on (and rounds its sizes to). scale: f64, + /// Clock for driving animations. + clock: Clock, + /// Configurable properties of the layout. options: Rc, } @@ -403,13 +409,14 @@ impl TileData { } impl Workspace { - pub fn new(output: Output, options: Rc) -> Self { - Self::new_with_config(output, None, options) + pub fn new(output: Output, clock: Clock, options: Rc) -> Self { + Self::new_with_config(output, None, clock, options) } pub fn new_with_config( output: Output, config: Option, + clock: Clock, base_options: Rc, ) -> Self { let original_output = config @@ -442,6 +449,7 @@ impl Workspace { closing_windows: vec![], insert_hint: None, insert_hint_element: InsertHintElement::new(options.insert_hint), + clock, base_options, options, name: config.map(|c| c.name.0), @@ -451,6 +459,7 @@ impl Workspace { pub fn new_with_config_no_outputs( config: Option, + clock: Clock, base_options: Rc, ) -> Self { let original_output = OutputId( @@ -482,6 +491,7 @@ impl Workspace { closing_windows: vec![], insert_hint: None, insert_hint_element: InsertHintElement::new(options.insert_hint), + clock, base_options, options, name: config.map(|c| c.name.0), @@ -489,8 +499,8 @@ impl Workspace { } } - pub fn new_no_outputs(options: Rc) -> Self { - Self::new_with_config_no_outputs(None, options) + pub fn new_no_outputs(clock: Clock, options: Rc) -> Self { + Self::new_with_config_no_outputs(None, clock, options) } pub fn id(&self) -> WorkspaceId { @@ -941,6 +951,7 @@ impl Workspace { // FIXME: also compute and use current velocity. self.view_offset_adj = Some(ViewOffsetAdjustment::Animation(Animation::new( + self.clock.now(), self.view_offset, new_view_offset, 0., @@ -1100,7 +1111,12 @@ impl Workspace { width: ColumnWidth, is_full_width: bool, ) { - let tile = Tile::new(window, self.scale.fractional_scale(), self.options.clone()); + let tile = Tile::new( + window, + self.scale.fractional_scale(), + self.clock.clone(), + self.options.clone(), + ); self.add_tile(col_idx, tile, activate, width, is_full_width, None); } @@ -1118,7 +1134,6 @@ impl Workspace { self.view_size, self.working_area, self.scale.fractional_scale(), - self.options.clone(), width, is_full_width, true, @@ -1682,7 +1697,13 @@ impl Workspace { ) { let output_scale = Scale::from(self.scale.fractional_scale()); - let anim = Animation::new(0., 1., 0., self.options.animations.window_close.anim); + let anim = Animation::new( + self.clock.now(), + 0., + 1., + 0., + self.options.animations.window_close.anim, + ); let blocker = if self.options.disable_transactions { TransactionBlocker::completed() @@ -1725,6 +1746,7 @@ impl Workspace { for (column, data) in zip(&self.columns, &self.data) { assert!(Rc::ptr_eq(&self.options, &column.options)); + assert_eq!(self.clock, column.clock); assert_eq!(self.scale.fractional_scale(), column.scale); column.verify_invariants(); @@ -2619,7 +2641,6 @@ impl Workspace { self.view_size, self.working_area, self.scale.fractional_scale(), - self.options.clone(), removed.width, removed.is_full_width, false, @@ -2969,6 +2990,7 @@ impl Workspace { let target_view_offset = target_snap.view_pos - new_col_x; self.view_offset_adj = Some(ViewOffsetAdjustment::Animation(Animation::new( + self.clock.now(), current_view_offset + delta, target_view_offset, velocity, @@ -3174,7 +3196,6 @@ impl Column { view_size: Size, working_area: Rectangle, scale: f64, - options: Rc, width: ColumnWidth, is_full_width: bool, animate_resize: bool, @@ -3190,7 +3211,8 @@ impl Column { view_size, working_area, scale, - options, + clock: tile.clock.clone(), + options: tile.options.clone(), }; let is_pending_fullscreen = tile.window().is_pending_fullscreen(); @@ -3313,6 +3335,7 @@ impl Column { let current_offset = self.move_animation.as_ref().map_or(0., Animation::value); self.move_animation = Some(Animation::new( + self.clock.now(), from_x_offset + current_offset, 0., 0., @@ -3716,6 +3739,7 @@ impl Column { let mut total_min_height = 0.; for (tile, data) in zip(&self.tiles, &self.data) { assert!(Rc::ptr_eq(&self.options, &tile.options)); + assert_eq!(self.clock, tile.clock); assert_eq!(self.scale, tile.scale()); assert_eq!(self.is_fullscreen, tile.window().is_pending_fullscreen()); diff --git a/src/niri.rs b/src/niri.rs index 7a579761..57050d6d 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -100,6 +100,7 @@ use smithay::wayland::virtual_keyboard::VirtualKeyboardManagerState; use smithay::wayland::xdg_activation::XdgActivationState; use smithay::wayland::xdg_foreign::XdgForeignState; +use crate::animation::Clock; use crate::backend::tty::SurfaceDmabufFeedback; use crate::backend::{Backend, RenderResult, Tty, Winit}; use crate::cursor::{CursorManager, CursorTextureCache, RenderCursor, XCursor}; @@ -179,6 +180,9 @@ pub struct Niri { /// Whether the at-startup=true window rules are active. pub is_at_startup: bool, + /// Clock for driving animations. + pub clock: Clock, + // Each workspace corresponds to a Space. Each workspace generally has one Output mapped to it, // however it may have none (when there are no outputs connected) or multiple (when mirroring). pub layout: Layout, @@ -1671,7 +1675,8 @@ impl Niri { let config_ = config.borrow(); let config_file_output_config = config_.outputs.clone(); - let layout = Layout::new(&config_); + let clock = Clock::default(); + let layout = Layout::new(clock.clone(), &config_); let (blocker_cleared_tx, blocker_cleared_rx) = mpsc::channel(); @@ -1799,8 +1804,8 @@ impl Niri { let mods_with_finger_scroll_binds = mods_with_finger_scroll_binds(backend.mod_key(), &config_.binds); - let screenshot_ui = ScreenshotUi::new(config.clone()); - let config_error_notification = ConfigErrorNotification::new(config.clone()); + let screenshot_ui = ScreenshotUi::new(clock.clone(), config.clone()); + let config_error_notification = ConfigErrorNotification::new(clock.clone(), config.clone()); let mut hotkey_overlay = HotkeyOverlay::new(config.clone(), backend.mod_key()); if !config_.hotkey_overlay.skip_at_startup { @@ -1895,6 +1900,7 @@ impl Niri { display_handle, start_time: Instant::now(), is_at_startup: true, +