aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/animation/clock.rs191
-rw-r--r--src/animation/mod.rs133
-rw-r--r--src/input/mod.rs7
-rw-r--r--src/ipc/server.rs3
-rw-r--r--src/layout/closing_window.rs7
-rw-r--r--src/layout/mod.rs28
-rw-r--r--src/layout/monitor.rs9
-rw-r--r--src/layout/opening_window.rs5
-rw-r--r--src/layout/tile.rs19
-rw-r--r--src/layout/workspace.rs339
-rw-r--r--src/main.rs8
-rw-r--r--src/niri.rs69
-rw-r--r--src/ui/config_error_notification.rs10
-rw-r--r--src/ui/screen_transition.rs40
-rw-r--r--src/ui/screenshot_ui.rs11
15 files changed, 488 insertions, 391 deletions
diff --git a/src/animation/clock.rs b/src/animation/clock.rs
index 3cfa727b..30f8bd45 100644
--- a/src/animation/clock.rs
+++ b/src/animation/clock.rs
@@ -1,41 +1,202 @@
-use std::cell::Cell;
+use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use crate::utils::get_monotonic_time;
-/// Clock that can have its time value overridden.
+/// Shareable lazy clock that can change rate.
///
-/// Can be cloned to share the same clock.
+/// The clock will fetch the time once and then retain it until explicitly cleared with
+/// [`Clock::clear`].
#[derive(Debug, Default, Clone)]
pub struct Clock {
- time_override: Rc<Cell<Option<Duration>>>,
+ inner: Rc<RefCell<AdjustableClock>>,
+}
+
+#[derive(Debug, Default)]
+struct LazyClock {
+ time: Option<Duration>,
+}
+
+/// Clock that can adjust its rate.
+#[derive(Debug)]
+struct AdjustableClock {
+ inner: LazyClock,
+ current_time: Duration,
+ last_seen_time: Duration,
+ rate: f64,
+ complete_instantly: bool,
}
impl Clock {
- /// Creates a new [`Clock`] with time override in place.
- pub fn with_override(time: Duration) -> Self {
+ /// Creates a new clock with the given time.
+ pub fn with_time(time: Duration) -> Self {
+ let clock = AdjustableClock::new(LazyClock::with_time(time));
Self {
- time_override: Rc::new(Cell::new(Some(time))),
+ inner: Rc::new(RefCell::new(clock)),
}
}
- /// Sets the current time override.
- pub fn set_time_override(&mut self, time: Option<Duration>) {
- self.time_override.set(time);
+ /// Returns the current time.
+ pub fn now(&self) -> Duration {
+ self.inner.borrow_mut().now()
}
- /// Gets the current time.
- #[inline]
- pub fn now(&self) -> Duration {
- self.time_override.get().unwrap_or_else(get_monotonic_time)
+ /// Returns the underlying time not adjusted for rate change.
+ pub fn now_unadjusted(&self) -> Duration {
+ self.inner.borrow_mut().inner.now()
+ }
+
+ /// Sets the unadjusted clock time.
+ pub fn set_unadjusted(&mut self, time: Duration) {
+ self.inner.borrow_mut().inner.set(time);
+ }
+
+ /// Clears the stored time so it's re-fetched again next.
+ pub fn clear(&mut self) {
+ self.inner.borrow_mut().inner.clear();
+ }
+
+ /// Gets the clock rate.
+ pub fn rate(&self) -> f64 {
+ self.inner.borrow().rate()
+ }
+
+ /// Sets the clock rate.
+ pub fn set_rate(&mut self, rate: f64) {
+ self.inner.borrow_mut().set_rate(rate);
+ }
+
+ /// Returns whether animations should complete instantly.
+ pub fn should_complete_instantly(&self) -> bool {
+ self.inner.borrow().should_complete_instantly()
+ }
+
+ /// Sets whether animations should complete instantly.
+ pub fn set_complete_instantly(&mut self, value: bool) {
+ self.inner.borrow_mut().set_complete_instantly(value);
}
}
impl PartialEq for Clock {
fn eq(&self, other: &Self) -> bool {
- Rc::ptr_eq(&self.time_override, &other.time_override)
+ Rc::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for Clock {}
+
+impl LazyClock {
+ pub fn with_time(time: Duration) -> Self {
+ Self { time: Some(time) }
+ }
+
+ pub fn clear(&mut self) {
+ self.time = None;
+ }
+
+ pub fn set(&mut self, time: Duration) {
+ self.time = Some(time);
+ }
+
+ pub fn now(&mut self) -> Duration {
+ *self.time.get_or_insert_with(get_monotonic_time)
+ }
+}
+
+impl AdjustableClock {
+ pub fn new(mut inner: LazyClock) -> Self {
+ let time = inner.now();
+ Self {
+ inner,
+ current_time: time,
+ last_seen_time: time,
+ rate: 1.,
+ complete_instantly: false,
+ }
+ }
+
+ pub fn rate(&self) -> f64 {
+ self.rate
+ }
+
+ pub fn set_rate(&mut self, rate: f64) {
+ self.rate = rate.clamp(0., 1000.);
+ }
+
+ pub fn should_complete_instantly(&self) -> bool {
+ self.complete_instantly
+ }
+
+ pub fn set_complete_instantly(&mut self, value: bool) {
+ self.complete_instantly = value;
+ }
+
+ pub fn now(&mut self) -> Duration {
+ let time = self.inner.now();
+
+ if self.last_seen_time == time {
+ return self.current_time;
+ }
+
+ if self.last_seen_time < time {
+ let delta = time - self.last_seen_time;
+ let delta = delta.mul_f64(self.rate);
+ self.current_time = self.current_time.saturating_add(delta);
+ } else {
+ let delta = self.last_seen_time - time;
+ let delta = delta.mul_f64(self.rate);
+ self.current_time = self.current_time.saturating_sub(delta);
+ }
+
+ self.last_seen_time = time;
+ self.current_time
+ }
+}
+
+impl Default for AdjustableClock {
+ fn default() -> Self {
+ Self::new(LazyClock::default())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn frozen_clock() {
+ let mut clock = Clock::with_time(Duration::ZERO);
+ assert_eq!(clock.now(), Duration::ZERO);
+
+ clock.set_unadjusted(Duration::from_millis(100));
+ assert_eq!(clock.now(), Duration::from_millis(100));
+
+ clock.set_unadjusted(Duration::from_millis(200));
+ assert_eq!(clock.now(), Duration::from_millis(200));
+ }
+
+ #[test]
+ fn rate_change() {
+ let mut clock = Clock::with_time(Duration::ZERO);
+ clock.set_rate(0.5);
+
+ clock.set_unadjusted(Duration::from_millis(100));
+ assert_eq!(clock.now_unadjusted(), Duration::from_millis(100));
+ assert_eq!(clock.now(), Duration::from_millis(50));
+
+ clock.set_unadjusted(Duration::from_millis(200));
+ assert_eq!(clock.now_unadjusted(), Duration::from_millis(200));
+ assert_eq!(clock.now(), Duration::from_millis(100));
+
+ clock.set_unadjusted(Duration::from_millis(150));
+ assert_eq!(clock.now_unadjusted(), Duration::from_millis(150));
+ assert_eq!(clock.now(), Duration::from_millis(75));
+
+ clock.set_rate(2.0);
+
+ clock.set_unadjusted(Duration::from_millis(250));
+ assert_eq!(clock.now_unadjusted(), Duration::from_millis(250));
+ assert_eq!(clock.now(), Duration::from_millis(275));
+ }
+}
diff --git a/src/animation/mod.rs b/src/animation/mod.rs
index 5efb0f16..50dfc195 100644
--- a/src/animation/mod.rs
+++ b/src/animation/mod.rs
@@ -2,7 +2,6 @@ use std::time::Duration;
use keyframe::functions::{EaseOutCubic, EaseOutQuad};
use keyframe::EasingFunction;
-use portable_atomic::{AtomicF64, Ordering};
mod spring;
pub use spring::{Spring, SpringParams};
@@ -10,8 +9,6 @@ pub use spring::{Spring, SpringParams};
mod clock;
pub use clock::Clock;
-pub static ANIMATION_SLOWDOWN: AtomicF64 = AtomicF64::new(1.);
-
#[derive(Debug, Clone)]
pub struct Animation {
from: f64,
@@ -24,7 +21,7 @@ pub struct Animation {
/// Best effort; not always exactly precise.
clamped_duration: Duration,
start_time: Duration,
- current_time: Duration,
+ clock: Clock,
kind: Kind,
}
@@ -50,23 +47,16 @@ pub enum Curve {
impl Animation {
pub fn new(
- current_time: Duration,
+ clock: Clock,
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);
+ // Scale the velocity by rate to keep the touchpad gestures feeling right.
+ let initial_velocity = initial_velocity / clock.rate().max(0.001);
- let mut rv = Self::ease(
- current_time,
- from,
- to,
- initial_velocity,
- 0,
- Curve::EaseOutCubic,
- );
+ let mut rv = Self::ease(clock, from, to, initial_velocity, 0, Curve::EaseOutCubic);
if config.off {
rv.is_off = true;
return rv;
@@ -85,7 +75,6 @@ impl Animation {
}
let start_time = self.start_time;
- let current_time = self.current_time;
match config.kind {
niri_config::AnimationKind::Spring(p) => {
@@ -97,11 +86,11 @@ impl Animation {
initial_velocity: self.initial_velocity,
params,
};
- *self = Self::spring(current_time, spring);
+ *self = Self::spring(self.clock.clone(), spring);
}
niri_config::AnimationKind::Easing(p) => {
*self = Self::ease(
- current_time,
+ self.clock.clone(),
self.from,
self.to,
self.initial_velocity,
@@ -112,27 +101,20 @@ impl Animation {
}
self.start_time = start_time;
- self.current_time = current_time;
}
/// Restarts the animation using the previous config.
- pub fn restarted(
- &self,
- current_time: Duration,
- from: f64,
- to: f64,
- initial_velocity: f64,
- ) -> Self {
+ pub fn restarted(&self, from: f64, to: f64, initial_velocity: f64) -> Self {
if self.is_off {
return self.clone();
}
- // Scale the velocity by slowdown to keep the touchpad gestures feeling right.
- let initial_velocity = initial_velocity * ANIMATION_SLOWDOWN.load(Ordering::Relaxed);
+ // Scale the velocity by rate to keep the touchpad gestures feeling right.
+ let initial_velocity = initial_velocity / self.clock.rate().max(0.001);
match self.kind {
Kind::Easing { curve } => Self::ease(
- current_time,
+ self.clock.clone(),
from,
to,
initial_velocity,
@@ -146,7 +128,7 @@ impl Animation {
initial_velocity: self.initial_velocity,
params: spring.params,
};
- Self::spring(current_time, spring)
+ Self::spring(self.clock.clone(), spring)
}
Kind::Deceleration {
initial_velocity,
@@ -154,7 +136,7 @@ impl Animation {
} => {
let threshold = 0.001; // FIXME
Self::decelerate(
- current_time,
+ self.clock.clone(),
from,
initial_velocity,
deceleration_rate,
@@ -165,7 +147,7 @@ impl Animation {
}
pub fn ease(
- current_time: Duration,
+ clock: Clock,
from: f64,
to: f64,
initial_velocity: f64,
@@ -183,13 +165,13 @@ impl Animation {
duration,
// Our current curves never overshoot.
clamped_duration: duration,
- start_time: current_time,
- current_time,
+ start_time: clock.now(),
+ clock,
kind,
}
}
- pub fn spring(current_time: Duration, spring: Spring) -> Self {
+ pub fn spring(clock: Clock, spring: Spring) -> Self {
let _span = tracy_client::span!("Animation::spring");
let duration = spring.duration();
@@ -203,14 +185,14 @@ impl Animation {
is_off: false,
duration,
clamped_duration,
- start_time: current_time,
- current_time,
+ start_time: clock.now(),
+ clock,
kind,
}
}
pub fn decelerate(
- current_time: Duration,
+ clock: Clock,
from: f64,
initial_velocity: f64,
deceleration_rate: f64,
@@ -238,77 +220,26 @@ impl Animation {
is_off: false,
duration,
clamped_duration: duration,
- start_time: current_time,
- current_time,
+ start_time: clock.now(),
+ clock,
kind,
}
}
- pub fn set_current_time(&mut self, time: Duration) {
- if self.duration.is_zero() {
- self.current_time = time;
- return;
- }
-
- let end_time = self.start_time + self.duration;
- if end_time <= self.current_time {
- return;
- }
-
- let slowdown = ANIMATION_SLOWDOWN.load(Ordering::Relaxed);
- if slowdown <= f64::EPSILON {
- // Zero slowdown will cause the animation to end right away.
- self.current_time = end_time;
- return;
- }
-
- // We can't change current_time (since the incoming time values are always real-time), so
- // apply the slowdown by shifting the start time to compensate.
- if self.current_time <= time {
- let delta = time - self.current_time;
-
- let max_delta = end_time - self.current_time;
- let min_slowdown = delta.as_secs_f64() / max_delta.as_secs_f64();
- if slowdown <= min_slowdown {
- // Our slowdown value will cause the animation to end right away.
- self.current_time = end_time;
- return;
- }
-
- let adjusted_delta = delta.div_f64(slowdown);
- if adjusted_delta >= delta {
- self.start_time -= adjusted_delta - delta;
- } else {
- self.start_time += delta - adjusted_delta;
- }
- } else {
- let delta = self.current_time - time;
-
- let min_slowdown = delta.as_secs_f64() / self.current_time.as_secs_f64();
- if slowdown <= min_slowdown {
- // Current time was about to jump to before the animation had started; let's just
- // cancel the animation in this case.
- self.current_time = end_time;
- return;
- }
-
- let adjusted_delta = delta.div_f64(slowdown);
- if adjusted_delta >= delta {
- self.start_time += adjusted_delta - delta;
- } else {
- self.start_time -= delta - adjusted_delta;
- }
+ pub fn is_done(&self) -> bool {
+ if self.clock.should_complete_instantly() {
+ return true;
}
- self.current_time = time;
- }
-
- pub fn is_done(&self) -> bool {
- self.current_time >= self.start_time + self.duration
+ self.clock.now() >= self.start_time + self.duration
}
pub fn is_clamped_done(&self) -> bool {
- self.current_time >= self.start_time + self.clamped_duration
+ if self.clock.should_complete_instantly() {
+ return true;
+ }
+
+ self.clock.now() >= self.start_time + self.clamped_duration
}
pub fn value(&self) -> f64 {
@@ -316,7 +247,7 @@ impl Animation {
return self.to;
}
- let passed = self.current_time.saturating_sub(self.start_time);
+ let passed = self.clock.now().saturating_sub(self.start_time);
match self.kind {
Kind::Easing { curve } => {
diff --git a/src/input/mod.rs b/src/input/mod.rs
index eec8fbe4..bafa1505 100644
--- a/src/input/mod.rs
+++ b/src/input/mod.rs
@@ -83,11 +83,8 @@ impl State {
{
let _span = tracy_client::span!("process_input_event");
- // A bit of a hack, but animation end runs some logic (i.e. workspace clean-up) and it
- // doesn't always trigger due to damage, etc. So run it here right before it might prove
- // important. Besides, animations affect the input, so it's best to have up-to-date values
- // here.
- self.niri.advance_animations(get_monotonic_time());
+ // Make sure some logic like workspace clean-up has a chance to run before doing actions.
+ self.niri.advance_animations();
if self.niri.monitors_active {
// Notify the idle-notifier of activity.
diff --git a/src/ipc/server.rs b/src/ipc/server.rs
index 16cc5524..facb59d3 100644
--- a/src/ipc/server.rs
+++ b/src/ipc/server.rs
@@ -314,6 +314,9 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
let action = niri_config::Action::from(action);
ctx.event_loop.insert_idle(move |state| {
+ // Make sure some logic like workspace clean-up has a chance to run before doing
+ // actions.
+ state.niri.advance_animations();
state.do_action(action, false);
let _ = tx.send_blocking(());
});
diff --git a/src/layout/closing_window.rs b/src/layout/closing_window.rs
index 744c099e..12a77f92 100644
--- a/src/layout/closing_window.rs
+++ b/src/layout/closing_window.rs
@@ -1,5 +1,4 @@
use std::collections::HashMap;
-use std::time::Duration;
use anyhow::Context as _;
use glam::{Mat3, Vec2};
@@ -138,15 +137,15 @@ impl ClosingWindow {
})
}
- pub fn advance_animations(&mut self, current_time: Duration) {
+ pub fn advance_animations(&mut self) {
match &mut self.anim_state {
AnimationState::Waiting { blocker, anim } => {
if blocker.state() != BlockerState::Pending {
- let anim = anim.restarted(current_time, 0., 1., 0.);
+ let anim = anim.restarted(0., 1., 0.);
self.anim_state = AnimationState::Animating(anim);
}
}
- AnimationState::Animating(anim) => anim.set_current_time(current_time),
+ AnimationState::Animating(_anim) => (),
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index f4e59af3..45228038 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -219,6 +219,8 @@ pub struct Layout<W: LayoutElement> {
interactive_move: Option<InteractiveMoveState<W>>,
/// Clock for driving animations.
clock: Clock,
+ /// Time that we last updated render elements for.
+ update_render_elements_time: Duration,
/// Configurable properties of the layout.
options: Rc<Options>,
}
@@ -447,6 +449,7 @@ impl<W: LayoutElement> Layout<W> {
last_active_workspace_id: HashMap::new(),
interactive_move: None,
clock,
+ update_render_elements_time: Duration::ZERO,
options: Rc::new(options),
}
}
@@ -468,6 +471,7 @@ impl<W: LayoutElement> Layout<W> {
last_active_workspace_id: HashMap::new(),
interactive_move: None,
clock,
+ update_render_elements_time: Duration::ZERO,
options: opts,
}
}
@@ -2194,22 +2198,22 @@ impl<W: LayoutElement> Layout<W> {
}
}
- pub fn advance_animations(&mut self, current_time: Duration) {
+ pub fn advance_animations(&mut self) {
let _span = tracy_client::span!("Layout::advance_animations");
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
- move_.tile.advance_animations(current_time);
+ move_.tile.advance_animations();
}
match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => {
for mon in monitors {
- mon.advance_animations(current_time);
+ mon.advance_animations();
}
}
MonitorSet::NoOutputs { workspaces, .. } => {
for ws in workspaces {
- ws.advance_animations(current_time);
+ ws.advance_animations();
}
}
}
@@ -2242,6 +2246,8 @@ impl<W: LayoutElement> Layout<W> {
pub fn update_render_elements(&mut self, output: Option<&Output>) {
let _span = tracy_client::span!("Layout::update_render_elements");
+ self.update_render_elements_time = self.clock.now();
+
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if output.map_or(true, |output| move_.output == *output) {
let pos_within_output = move_.tile_render_location();
@@ -3475,6 +3481,10 @@ impl<W: LayoutElement> Layout<W> {
output: &Output,
target: RenderTarget,
) -> impl Iterator<Item = TileRenderElement<R>> {
+ if self.update_render_elements_time != self.clock.now() {
+ error!("clock moved between updating render elements and rendering");
+ }
+
let mut rv = None;
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
@@ -3653,7 +3663,7 @@ mod tests {
impl<W: LayoutElement> Default for Layout<W> {
fn default() -> Self {
- Self::with_options(Clock::with_override(Duration::ZERO), Default::default())
+ Self::with_options(Clock::with_time(Duration::ZERO), Default::default())
}
}
@@ -4553,14 +4563,14 @@ mod tests {
layout.refresh(is_active);
}
Op::AdvanceAnimations { msec_delta } => {
- let mut now = layout.clock.now();
+ let mut now = layout.clock.now_unadjusted();
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);
+ layout.clock.set_unadjusted(now);
+ layout.advance_animations();
}
Op::MoveWorkspaceToOutput(id) => {
let name = format!("output{id}");
@@ -4674,7 +4684,7 @@ mod tests {
#[track_caller]
fn check_ops_with_options(options: Options, ops: &[Op]) {
- let mut layout = Layout::with_options(Clock::with_override(Duration::ZERO), options);
+ let mut layout = Layout::with_options(Clock::with_time(Duration::ZERO), options);
for op in ops {
op.apply(&mut layout);
diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs
index aad921bb..52e73cce 100644
--- a/src/layout/monitor.rs
+++ b/src/layout/monitor.rs
@@ -184,7 +184,7 @@ impl<W: LayoutElement> Monitor<W> {
self.active_workspace_idx = idx;
self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
- self.clock.now(),
+ self.clock.clone(),
current_idx,
idx as f64,
0.,
@@ -734,9 +734,8 @@ impl<W: LayoutElement> Monitor<W> {
Some(column.tiles[column.active_tile_idx].window())
}
- pub fn advance_animations(&mut self, current_time: Duration) {
+ pub fn advance_animations(&mut self) {
if let Some(WorkspaceSwitch::Animation(anim)) = &mut self.workspace_switch {
- anim.set_current_time(current_time);
if anim.is_done() {
self.workspace_switch = None;
self.clean_up_workspaces();
@@ -744,7 +743,7 @@ impl<W: LayoutElement> Monitor<W> {
}
for ws in &mut self.workspaces {
- ws.advance_animations(current_time);
+ ws.advance_animations();
}
}
@@ -1112,7 +1111,7 @@ impl<W: LayoutElement> Monitor<W> {
self.active_workspace_idx = new_idx;
self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
- self.clock.now(),
+ self.clock.clone(),
gesture.current_idx,
new_idx as f64,
velocity,
diff --git a/src/layout/opening_window.rs b/src/layout/opening_window.rs
index 90e50581..0a3d4973 100644
--- a/src/layout/opening_window.rs
+++ b/src/layout/opening_window.rs
@@ -1,5 +1,4 @@
use std::collections::HashMap;
-use std::time::Duration;
use anyhow::Context as _;
use glam::{Mat3, Vec2};
@@ -41,9 +40,7 @@ impl OpenAnimation {
}
}
- pub fn advance_animations(&mut self, current_time: Duration) {
- self.anim.set_current_time(current_time);
- }
+ pub fn advance_animations(&mut self) {}
pub fn is_done(&self) -> bool {
self.anim.is_done()
diff --git a/src/layout/tile.rs b/src/layout/tile.rs
index dd3d50af..6646999f 100644
--- a/src/layout/tile.rs
+++ b/src/layout/tile.rs
@@ -1,5 +1,4 @@
use std::rc::Rc;
-use std::time::Duration;
use niri_config::{Color, CornerRadius, GradientInterpolation};
use smithay::backend::allocator::Fourcc;
@@ -185,7 +184,7 @@ impl<W: LayoutElement> Tile<W> {
let change = f64::max(change.x.abs(), change.y.abs());
if change > RESIZE_ANIMATION_THRESHOLD {
let anim = Animation::new(
- self.clock.now(),
+ self.clock.clone(),
0.,
1.,
0.,
@@ -218,29 +217,25 @@ impl<W: LayoutElement> Tile<W> {
self.rounded_corner_damage.set_size(window_size);
}
- pub fn advance_animations(&mut self, current_time: Duration) {
+ pub fn advance_animations(&mut self) {
if let Some(open) = &mut self.open_animation {
- open.advance_animations(current_time);
if open.is_done() {
self.open_animation = None;
}
}
if let Some(resize) = &mut self.resize_animation {
- resize.anim.set_current_time(current_time);
if resize.anim.is_done() {
self.resize_animation = None;
}
}
if let Some(move_) = &mut self.move_x_animation {
- move_.anim.set_current_time(current_time);
if move_.anim.is_done() {
self.move_x_animation = None;
}
}
if let Some(move_) = &mut self.move_y_animation {
- move_.anim.set_current_time(current_time);
if move_.anim.is_done() {
self.move_y_animation = None;
}
@@ -326,7 +321,7 @@ impl<W: LayoutElement> Tile<W> {
pub fn start_open_animation(&mut self) {
self.open_animation = Some(OpenAnimation::new(Animation::new(
- self.clock.now(),
+ self.clock.clone(),
0.,
1.,
0.,
@@ -353,8 +348,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(self.clock.now(), 1., 0., 0.))
- .unwrap_or_else(|| Animation::new(self.clock.now(), 1., 0., 0., config));
+ .map(|anim| anim.restarted(1., 0., 0.))
+ .unwrap_or_else(|| Animation::new(self.clock.clone(), 1., 0., 0., config));
self.move_x_animation = Some(MoveAnimation {
anim,
@@ -372,8 +367,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(self.clock.now(), 1., 0., 0.))
- .unwrap_or_else(|| Animation::new(self.clock.now(), 1., 0., 0., config));
+ .map(|anim| anim.restarted(1., 0., 0.))
+ .unwrap_or_else(|| Animation::new(self.clock.clone(), 1., 0., 0., config));
self.move_y_animation = Some(MoveAnimation {
anim,
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index 6432ca8b..2b3a462c 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -84,10 +84,7 @@ pub struct Workspace<W: LayoutElement> {
/// Any gaps, including left padding from work area left exclusive zone, is handled
/// with this view offset (rather than added as a constant elsewhere in the code). This allows
/// for natural handling of fullscreen windows, which must ignore work area padding.
- view_offset: f64,
-
- /// Adjustment of the view offset, if one is currently ongoing.
- view_offset_adj: Option<ViewOffsetAdjustment>,
+ view_offset: ViewOffset,
/// Whether to activate the previous, rather than the next, column upon column removal.
///
@@ -188,8 +185,12 @@ struct ColumnData {
}
#[derive(Debug)]
-enum ViewOffsetAdjustment {
+enum ViewOffset {
+ /// The view offset is static.
+ Static(f64),
+ /// The view offset is animating.
Animation(Animation),
+ /// The view offset is controlled by the ongoing gesture.
Gesture(ViewGesture),
}
@@ -199,7 +200,7 @@ struct ViewGesture {
tracker: SwipeTracker,
delta_from_tracker: f64,
// The view offset we'll use if needed for activate_prev_column_on_removal.
- static_view_offset: f64,
+ stationary_view_offset: f64,
/// Whether the gesture is controlled by the touchpad.
is_touchpad: bool,
}
@@ -325,17 +326,70 @@ impl OutputId {
}
}
-impl ViewOffsetAdjustment {
+impl ViewOffset {
+ /// Returns the current view offset.
+ pub fn current(&self) -> f64 {
+ match self {
+ ViewOffset::Static(offset) => *offset,
+ ViewOffset::Animation(anim) => anim.value(),
+ ViewOffset::Gesture(gesture) => gesture.current_view_offset,
+ }
+ }
+
+ /// Returns the target view offset suitable for computing the new view offset.
+ pub fn target(&self) -> f64 {
+ match self {
+ ViewOffset::Static(offset) => *offset,
+ ViewOffset::Animation(anim) => anim.to(),
+ // This can be used for example if a gesture is interrupted.
+ ViewOffset::Gesture(gesture) => gesture.current_view_offset,
+ }
+ }
+
+ /// Returns a view offset value suitable for saving and later restoration.
+ ///
+ /// This means that it shouldn't return an in-progress animation or gesture value.
+ fn stationary(&self) -> f64 {
+ match self {
+ ViewOffset::Static(offset) => *offset,
+ // For animations we can return the final value.
+ ViewOffset::Animation(anim) => anim.to(),
+ ViewOffset::Gesture(gesture) => gesture.stationary_view_offset,
+ }
+ }
+
+ pub fn is_static(&self) -> bool {
+ matches!(self, Self::Static(_))
+ }
+
pub fn is_animation(&self) -> bool {
matches!(self, Self::Animation(_))
}
- pub fn target_view_offset(&self) -> f64 {
+ pub fn is_gesture(&self) -> bool {
+ matches!(self, Self::Gesture(_))
+ }
+
+ pub fn offset(&mut self, delta: f64) {
match self {
- ViewOffsetAdjustment::Animation(anim) => anim.to(),
- ViewOffsetAdjustment::Gesture(gesture) => gesture.current_view_offset,
+ ViewOffset::Static(offset) => *offset += delta,
+ ViewOffset::Animation(anim) => anim.offset(delta),
+ ViewOffset::Gesture(_gesture) => {
+ // Is this needed?
+ error!("cancel gesture before offsetting");
+ }
}
}
+
+ pub fn cancel_gesture(&mut self) {
+ if let ViewOffset::Gesture(gesture) = self {
+ *self = ViewOffset::Static(gesture.current_view_offset);
+ }
+ }
+
+ pub fn stop_anim_and_gesture(&mut self) {
+ *self = ViewOffset::Static(self.current());
+ }
}
impl ColumnData {
@@ -442,8 +496,7 @@ impl<W: LayoutElement> Workspace<W> {
data: vec![],
active_column_idx: 0,
interactive_resize: None,
- view_offset: 0.,
- view_offset_adj: None,
+ view_offset: ViewOffset::Static(0.),
activate_prev_column_on_removal: None,
view_offset_before_fullscreen: None,
closing_windows: ve