From 0e3b80033729f96691f903522cd12add3202c568 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Mon, 14 Aug 2023 15:54:11 +0400 Subject: [PATCH] Add focus change animations --- Cargo.toml | 1 + src/animation.rs | 46 ++++++++++++++++++++++++++++++ src/layout.rs | 74 +++++++++++++++++++++++++++++++++++++++++------- src/main.rs | 1 + src/niri.rs | 5 +++- 5 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 src/animation.rs diff --git a/Cargo.toml b/Cargo.toml index 6720107..8febe26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" anyhow = { version = "1.0.72", features = ["backtrace"] } bitflags = "2.3.3" clap = { version = "4.3.21", features = ["derive"] } +keyframe = { version = "1.1.1", default-features = false } profiling = "1.0.9" smithay-drm-extras = { version = "0.1.0", path = "../smithay/smithay-drm-extras" } tracing = "0.1.37" diff --git a/src/animation.rs b/src/animation.rs new file mode 100644 index 0000000..2744924 --- /dev/null +++ b/src/animation.rs @@ -0,0 +1,46 @@ +use std::time::Duration; + +use keyframe::functions::EaseOutCubic; +use keyframe::EasingFunction; + +use crate::utils::get_monotonic_time; + +#[derive(Debug)] +pub struct Animation { + from: f64, + to: f64, + duration: Duration, + start_time: Duration, + current_time: Duration, +} + +impl Animation { + pub fn new(from: f64, to: f64, over: Duration) -> 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(); + + Self { + from, + to, + duration: over, + start_time: now, + current_time: now, + } + } + + pub fn set_current_time(&mut self, time: Duration) { + self.current_time = time; + } + + pub fn is_done(&self) -> bool { + self.current_time >= self.start_time + self.duration + } + + pub fn value(&self) -> f64 { + let passed = (self.current_time - self.start_time).as_secs_f64(); + let total = self.duration.as_secs_f64(); + let x = (passed / total).clamp(0., 1.); + EaseOutCubic.y(x) * (self.to - self.from) + self.from + } +} diff --git a/src/layout.rs b/src/layout.rs index 2bda5cc..bc2d5e5 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -44,6 +44,8 @@ use smithay::utils::{Logical, Point, Rectangle, Scale, Size}; use smithay::wayland::compositor::{with_states, SurfaceData}; use smithay::wayland::shell::xdg::XdgToplevelSurfaceData; +use crate::animation::Animation; + const PADDING: i32 = 16; #[derive(Debug, Clone, PartialEq, Eq)] @@ -114,6 +116,9 @@ pub struct Workspace { /// Offset of the view computed from the active column. view_offset: i32, + + /// Animation of the view offset, if one is currently ongoing. + view_offset_anim: Option, } /// Width of a column. @@ -770,6 +775,20 @@ impl MonitorSet { }) } + pub fn workspace_for_output_mut(&mut self, output: &Output) -> Option<&mut Workspace> { + let MonitorSet::Normal { monitors, .. } = self else { + return None; + }; + + monitors.iter_mut().find_map(|monitor| { + if &monitor.output == output { + Some(&mut monitor.workspaces[monitor.active_workspace_idx]) + } else { + None + } + }) + } + pub fn window_under( &self, output: &Output, @@ -868,6 +887,20 @@ impl Workspace { columns: vec![], active_column_idx: 0, view_offset: 0, + view_offset_anim: None, + } + } + + pub fn advance_animations(&mut self, current_time: Duration) { + match &mut self.view_offset_anim { + Some(anim) => { + anim.set_current_time(current_time); + self.view_offset = anim.value().round() as i32; + if anim.is_done() { + self.view_offset_anim = None; + } + } + None => (), } } @@ -918,6 +951,25 @@ impl Workspace { } } + fn activate_column(&mut self, idx: usize) { + if self.active_column_idx == idx { + return; + } + + let current_x = self.view_pos(); + + self.active_column_idx = idx; + + self.view_offset = 0; + let new_x = self.view_pos(); + + self.view_offset_anim = Some(Animation::new( + (current_x - new_x) as f64, + 0., + Duration::from_millis(250), + )); + } + fn has_windows(&self) -> bool { self.windows().next().is_some() } @@ -954,7 +1006,7 @@ impl Workspace { self.columns.insert(idx, column); if activate { - self.active_column_idx = idx; + self.activate_column(idx); } } @@ -978,7 +1030,7 @@ impl Workspace { return; } - self.active_column_idx = min(self.active_column_idx, self.columns.len() - 1); + self.activate_column(min(self.active_column_idx, self.columns.len() - 1)); return; } @@ -1004,22 +1056,24 @@ impl Workspace { let column = &mut self.columns[column_idx]; column.activate_window(window); - self.active_column_idx = column_idx; + self.activate_column(column_idx); } fn verify_invariants(&self) { assert!(self.view_size.w > 0); assert!(self.view_size.h > 0); - assert!(self.columns.is_empty() || self.active_column_idx < self.columns.len()); + if !self.columns.is_empty() { + assert!(self.active_column_idx < self.columns.len()); - for column in &self.columns { - column.verify_invariants(); + for column in &self.columns { + column.verify_invariants(); + } } } fn focus_left(&mut self) { - self.active_column_idx = self.active_column_idx.saturating_sub(1); + self.activate_column(self.active_column_idx.saturating_sub(1)); } fn focus_right(&mut self) { @@ -1027,7 +1081,7 @@ impl Workspace { return; } - self.active_column_idx = min(self.active_column_idx + 1, self.columns.len() - 1); + self.activate_column(min(self.active_column_idx + 1, self.columns.len() - 1)); } fn focus_down(&mut self) { @@ -1053,7 +1107,7 @@ impl Workspace { } self.columns.swap(self.active_column_idx, new_idx); - self.active_column_idx = new_idx; + self.activate_column(new_idx); } fn move_right(&mut self) { @@ -1067,7 +1121,7 @@ impl Workspace { } self.columns.swap(self.active_column_idx, new_idx); - self.active_column_idx = new_idx; + self.activate_column(new_idx); } fn move_down(&mut self) { diff --git a/src/main.rs b/src/main.rs index 49a8598..d87a3a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate tracing; mod handlers; +mod animation; mod backend; mod frame_clock; mod grabs; diff --git a/src/niri.rs b/src/niri.rs index 905f073..714f849 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -302,11 +302,14 @@ impl Niri { fn redraw(&mut self, backend: &mut dyn Backend, output: &Output) { let _span = tracy_client::span!("redraw"); let state = self.output_state.get_mut(output).unwrap(); + let presentation_time = state.frame_clock.next_presentation_time(); assert!(state.queued_redraw.take().is_some()); assert!(!state.waiting_for_vblank); - let elements = self.monitor_set.render_elements(backend.renderer(), output); + let ws = self.monitor_set.workspace_for_output_mut(output).unwrap(); + ws.advance_animations(presentation_time); + let elements = ws.render_elements(backend.renderer()); let output_pos = self.global_space.output_geometry(output).unwrap().loc; let pointer_pos = self.seat.get_pointer().unwrap().current_location() - output_pos.to_f64();