aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/animation/clock.rs41
-rw-r--r--src/animation/mod.rs80
-rw-r--r--src/input/mod.rs3
-rw-r--r--src/layout/closing_window.rs3
-rw-r--r--src/layout/mod.rs120
-rw-r--r--src/layout/monitor.rs20
-rw-r--r--src/layout/tile.rs25
-rw-r--r--src/layout/workspace.rs46
-rw-r--r--src/niri.rs15
-rw-r--r--src/ui/config_error_notification.rs14
-rw-r--r--src/ui/screenshot_ui.rs17
11 files changed, 306 insertions, 78 deletions
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<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 {}
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<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),
@@ -5753,6 +5810,37 @@ mod tests {
}
#[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 = [
Op::AddOutput(1),
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<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,
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<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,
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<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());
diff --git a/src/niri.rs b/src/niri.rs
index 7a579761..57050d6d 100644
--- a/src/niri.rs
+++ b/