Refactor animations to take explicit current time

This commit is contained in:
Ivan Molodetskikh
2024-11-23 11:27:27 +03:00
parent 9c7e8d04d2
commit 93cee2994a
30 changed files with 426 additions and 187 deletions

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
angle: 0.,
prev_time: Duration::ZERO,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
let border = FocusRing::new(niri_config::FocusRing {
off: false,
width: FloatOrInt(1.),

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklab,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklab,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklch,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklch,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklch,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklch,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Oklch,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Srgb,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::Srgb,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::SrgbLinear,

View File

@@ -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<i32, Logical>) -> Self {
pub fn new(_args: Args) -> Self {
Self {
gradient_format: GradientInterpolation {
color_space: GradientColorSpace::SrgbLinear,

View File

@@ -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<dyn FnOnce(&mut Layout)>;
@@ -20,13 +20,16 @@ type DynStepFn = Box<dyn FnOnce(&mut Layout)>;
pub struct Layout {
output: Output,
windows: Vec<TestWindow>,
clock: Clock,
layout: niri::layout::Layout<TestWindow>,
start_time: Duration,
steps: HashMap<Duration, DynStepFn>,
}
impl Layout {
pub fn new(size: Size<i32, Logical>) -> 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<i32, Logical>) -> 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<i32, Logical>) -> 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<i32, Logical>) -> 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<i32, Logical>) -> 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<i32, Logical>) -> 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::<Vec<_>>();
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);
}

View File

@@ -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<i32, Logical>,
pub clock: Clock,
}
pub trait TestCase {
fn resize(&mut self, _width: i32, _height: i32) {}
fn are_animations_ongoing(&self) -> bool {

View File

@@ -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<i32, Logical>) -> 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<i32, Logical>) -> 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<i32, Logical>) -> 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<i32, Logical>) -> 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<i32, Logical>) -> 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<i32, Logical>) -> 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 }
}
}

View File

@@ -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<i32, Logical>) -> 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<i32, Logical>) -> 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<i32, Logical>) -> 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 }
}

View File

@@ -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<T: TestCase + 'static>(
&self,
make: impl Fn(Size<i32, Logical>) -> T + 'static,
title: &str,
) {
fn add<T: TestCase + 'static>(&self, make: impl Fn(Args) -> T + 'static, title: &str) {
let view = SmithayView::new(make);
self.stack.add_titled(&view, None, title);
}

View File

@@ -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<dyn Fn(Size<i32, Logical>) -> Box<dyn TestCase>>;
type DynMakeTestCase = Box<dyn Fn(Args) -> Box<dyn TestCase>>;
#[derive(Default)]
pub struct SmithayView {
@@ -30,6 +31,7 @@ mod imp {
renderer: RefCell<Option<Result<GlesRenderer, ()>>>,
pub make_test_case: OnceCell<DynMakeTestCase>,
test_case: RefCell<Option<Box<dyn TestCase>>>,
clock: RefCell<Clock>,
}
#[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<T: TestCase + 'static>(
make_test_case: impl Fn(Size<i32, Logical>) -> T + 'static,
) -> Self {
pub fn new<T: TestCase + 'static>(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<dyn TestCase>;
let make = move |args| Box::new(make_test_case(args)) as Box<dyn TestCase>;
let make_test_case = Box::new(make) as _;
let _ = obj.imp().make_test_case.set(make_test_case);

41
src/animation/clock.rs Normal file
View File

@@ -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<Cell<Option<Duration>>>,
}
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<Duration>) {
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 {}

View File

@@ -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,
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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<W: LayoutElement> {
last_active_workspace_id: HashMap<String, WorkspaceId>,
/// Ongoing interactive move.
interactive_move: Option<InteractiveMoveState<W>>,
/// Clock for driving animations.
clock: Clock,
/// Configurable properties of the layout.
options: Rc<Options>,
}
@@ -433,27 +436,30 @@ impl Options {
}
impl<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Layout<W> {
is_active: true,
last_active_workspace_id: HashMap::new(),
interactive_move: None,
clock,
options: opts,
}
}
@@ -522,13 +529,18 @@ impl<W: LayoutElement> Layout<W> {
}
// 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<W: LayoutElement> Layout<W> {
}
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<W: LayoutElement> Layout<W> {
}
}
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<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Layout<W> {
"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<W: LayoutElement> Layout<W> {
);
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<W: LayoutElement> Layout<W> {
// 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<W: LayoutElement> Layout<W> {
return;
}
let clock = self.clock.clone();
let options = self.options.clone();
match &mut self.monitor_set {
@@ -2349,6 +2377,7 @@ impl<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Default for Layout<W> {
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<Value = i32> {
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 = [

View File

@@ -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<W: LayoutElement> {
pub(super) previous_workspace_id: Option<WorkspaceId>,
/// In-progress switch between workspaces.
pub(super) workspace_switch: Option<WorkspaceSwitch>,
/// Clock for driving animations.
pub(super) clock: Clock,
/// Configurable properties of the layout.
pub(super) options: Rc<Options>,
}
@@ -94,7 +96,12 @@ impl WorkspaceSwitch {
}
impl<W: LayoutElement> Monitor<W> {
pub fn new(output: Output, workspaces: Vec<Workspace<W>>, options: Rc<Options>) -> Self {
pub fn new(
output: Output,
workspaces: Vec<Workspace<W>>,
clock: Clock,
options: Rc<Options>,
) -> Self {
Self {
output_name: output.name(),
output,
@@ -102,6 +109,7 @@ impl<W: LayoutElement> Monitor<W> {
active_workspace_idx: 0,
previous_workspace_id: None,
workspace_switch: None,
clock,
options,
}
}
@@ -151,7 +159,11 @@ impl<W: LayoutElement> Monitor<W> {
}
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<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Monitor<W> {
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,

View File

@@ -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<W: LayoutElement> {
/// 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<Options>,
}
@@ -110,7 +113,7 @@ struct MoveAnimation {
}
impl<W: LayoutElement> Tile<W> {
pub fn new(window: W, scale: f64, options: Rc<Options>) -> Self {
pub fn new(window: W, scale: f64, clock: Clock, options: Rc<Options>) -> 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<W: LayoutElement> Tile<W> {
unmap_snapshot: None,
rounded_corner_damage: Default::default(),
scale,
clock,
options,
}
}
@@ -180,7 +184,13 @@ impl<W: LayoutElement> Tile<W> {
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<W: LayoutElement> Tile<W> {
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<W: LayoutElement> Tile<W> {
// 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<W: LayoutElement> Tile<W> {
// 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,

View File

@@ -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<W: LayoutElement> {
/// 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<Options>,
@@ -293,6 +296,9 @@ pub struct Column<W: LayoutElement> {
/// 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<Options>,
}
@@ -403,13 +409,14 @@ impl TileData {
}
impl<W: LayoutElement> Workspace<W> {
pub fn new(output: Output, options: Rc<Options>) -> Self {
Self::new_with_config(output, None, options)
pub fn new(output: Output, clock: Clock, options: Rc<Options>) -> Self {
Self::new_with_config(output, None, clock, options)
}
pub fn new_with_config(
output: Output,
config: Option<WorkspaceConfig>,
clock: Clock,
base_options: Rc<Options>,
) -> Self {
let original_output = config
@@ -442,6 +449,7 @@ impl<W: LayoutElement> Workspace<W> {
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<W: LayoutElement> Workspace<W> {
pub fn new_with_config_no_outputs(
config: Option<WorkspaceConfig>,
clock: Clock,
base_options: Rc<Options>,
) -> Self {
let original_output = OutputId(
@@ -482,6 +491,7 @@ impl<W: LayoutElement> Workspace<W> {
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<W: LayoutElement> Workspace<W> {
}
}
pub fn new_no_outputs(options: Rc<Options>) -> Self {
Self::new_with_config_no_outputs(None, options)
pub fn new_no_outputs(clock: Clock, options: Rc<Options>) -> Self {
Self::new_with_config_no_outputs(None, clock, options)
}
pub fn id(&self) -> WorkspaceId {
@@ -941,6 +951,7 @@ impl<W: LayoutElement> Workspace<W> {
// 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<W: LayoutElement> Workspace<W> {
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<W: LayoutElement> Workspace<W> {
self.view_size,
self.working_area,
self.scale.fractional_scale(),
self.options.clone(),
width,
is_full_width,
true,
@@ -1682,7 +1697,13 @@ impl<W: LayoutElement> Workspace<W> {
) {
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<W: LayoutElement> Workspace<W> {
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<W: LayoutElement> Workspace<W> {
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<W: LayoutElement> Workspace<W> {
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<W: LayoutElement> Column<W> {
view_size: Size<f64, Logical>,
working_area: Rectangle<f64, Logical>,
scale: f64,
options: Rc<Options>,
width: ColumnWidth,
is_full_width: bool,
animate_resize: bool,
@@ -3190,7 +3211,8 @@ impl<W: LayoutElement> Column<W> {
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<W: LayoutElement> Column<W> {
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<W: LayoutElement> Column<W> {
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());

View File

@@ -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<Mapped>,
@@ -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,
clock,
layout,
global_space: Space::default(),
@@ -3044,6 +3050,7 @@ impl Niri {
for state in self.output_state.values_mut() {
if let Some(transition) = &mut state.screen_transition {
// Screen transition uses real time so that it's not affected by animation slowdown.
transition.advance_animations(target_time);
if transition.is_done() {
state.screen_transition = None;
@@ -4819,6 +4826,8 @@ impl Niri {
let delay = delay_ms.map_or(screen_transition::DELAY, |d| {
Duration::from_millis(u64::from(d))
});
// Screen transition uses real time so that it's not affected by animation slowdown.
let start_at = get_monotonic_time() + delay;
for (output, from_texture) in textures {
let state = self.output_state.get_mut(&output).unwrap();

View File

@@ -14,7 +14,7 @@ use smithay::output::Output;
use smithay::reexports::gbm::Format as Fourcc;
use smithay::utils::{Point, Transform};
use crate::animation::Animation;
use crate::animation::{Animation, Clock};
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
@@ -35,6 +35,7 @@ pub struct ConfigErrorNotification {
// notification.
created_path: Option<PathBuf>,
clock: Clock,
config: Rc<RefCell<Config>>,
}
@@ -46,18 +47,25 @@ enum State {
}
impl ConfigErrorNotification {
pub fn new(config: Rc<RefCell<Config>>) -> Self {
pub fn new(clock: Clock, config: Rc<RefCell<Config>>) -> Self {
Self {
state: State::Hidden,
buffers: RefCell::new(HashMap::new()),
created_path: None,
clock,
config,
}
}
fn animation(&self, from: f64, to: f64) -> Animation {
let c = self.config.borrow();
Animation::new(from, to, 0., c.animations.config_notification_open_close.0)
Animation::new(
self.clock.now(),
from,
to,
0.,
c.animations.config_notification_open_close.0,
)
}
pub fn show_created(&mut self, created_path: PathBuf) {

View File

@@ -20,7 +20,7 @@ use smithay::input::keyboard::{Keysym, ModifiersState};
use smithay::output::{Output, WeakOutput};
use smithay::utils::{Physical, Point, Rectangle, Scale, Size, Transform};
use crate::animation::Animation;
use crate::animation::{Animation, Clock};
use crate::niri_render_elements;
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
@@ -49,6 +49,7 @@ const TEXT_SHOW_P: &str =
pub enum ScreenshotUi {
Closed {
last_selection: Option<(WeakOutput, Rectangle<i32, Physical>)>,
clock: Clock,
config: Rc<RefCell<Config>>,
},
Open {
@@ -57,6 +58,7 @@ pub enum ScreenshotUi {
mouse_down: bool,
show_pointer: bool,
open_anim: Animation,
clock: Clock,
config: Rc<RefCell<Config>>,
},
}
@@ -86,9 +88,10 @@ niri_render_elements! {
}
impl ScreenshotUi {
pub fn new(config: Rc<RefCell<Config>>) -> Self {
pub fn new(clock: Clock, config: Rc<RefCell<Config>>) -> Self {
Self::Closed {
last_selection: None,
clock,
config,
}
}
@@ -106,6 +109,7 @@ impl ScreenshotUi {
let Self::Closed {
last_selection,
clock,
config,
} = self
else {
@@ -181,7 +185,7 @@ impl ScreenshotUi {
let open_anim = {
let c = config.borrow();
Animation::new(0., 1., 0., c.animations.screenshot_ui_open.0)
Animation::new(clock.now(), 0., 1., 0., c.animations.screenshot_ui_open.0)
};
*self = Self::Open {
@@ -190,6 +194,7 @@ impl ScreenshotUi {
mouse_down: false,
show_pointer: true,
open_anim,
clock: clock.clone(),
config: config.clone(),
};
@@ -200,7 +205,10 @@ impl ScreenshotUi {
pub fn close(&mut self) -> bool {
let Self::Open {
selection, config, ..
selection,
clock,
config,
..
} = self
else {
return false;
@@ -213,6 +221,7 @@ impl ScreenshotUi {
*self = Self::Closed {
last_selection,
clock: clock.clone(),
config: config.clone(),
};