diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/animation/clock.rs | 191 | ||||
| -rw-r--r-- | src/animation/mod.rs | 133 | ||||
| -rw-r--r-- | src/input/mod.rs | 7 | ||||
| -rw-r--r-- | src/ipc/server.rs | 3 | ||||
| -rw-r--r-- | src/layout/closing_window.rs | 7 | ||||
| -rw-r--r-- | src/layout/mod.rs | 28 | ||||
| -rw-r--r-- | src/layout/monitor.rs | 9 | ||||
| -rw-r--r-- | src/layout/opening_window.rs | 5 | ||||
| -rw-r--r-- | src/layout/tile.rs | 19 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 339 | ||||
| -rw-r--r-- | src/main.rs | 8 | ||||
| -rw-r--r-- | src/niri.rs | 69 | ||||
| -rw-r--r-- | src/ui/config_error_notification.rs | 10 | ||||
| -rw-r--r-- | src/ui/screen_transition.rs | 40 | ||||
| -rw-r--r-- | src/ui/screenshot_ui.rs | 11 |
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 |
