aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2023-08-14 15:54:11 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2023-08-14 18:37:33 +0400
commit0e3b80033729f96691f903522cd12add3202c568 (patch)
tree1c356bd7aac90709d08923e55bd24019d8d2708e
parentc65a4f162405d1b8c780925b723feeb481c87919 (diff)
downloadniri-0e3b80033729f96691f903522cd12add3202c568.tar.gz
niri-0e3b80033729f96691f903522cd12add3202c568.tar.bz2
niri-0e3b80033729f96691f903522cd12add3202c568.zip
Add focus change animations
-rw-r--r--Cargo.toml1
-rw-r--r--src/animation.rs46
-rw-r--r--src/layout.rs74
-rw-r--r--src/main.rs1
-rw-r--r--src/niri.rs5
5 files changed, 116 insertions, 11 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 6720107a..8febe268 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 00000000..27449243
--- /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 2bda5cc8..bc2d5e54 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<W: LayoutElement> {
/// 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<Animation>,
}
/// Width of a column.
@@ -770,6 +775,20 @@ impl<W: LayoutElement> MonitorSet<W> {
})
}
+ pub fn workspace_for_output_mut(&mut self, output: &Output) -> Option<&mut Workspace<W>> {
+ 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<W: LayoutElement> Workspace<W> {
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<W: LayoutElement> Workspace<W> {
}
}
+ 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<W: LayoutElement> Workspace<W> {
self.columns.insert(idx, column);
if activate {
- self.active_column_idx = idx;
+ self.activate_column(idx);
}
}
@@ -978,7 +1030,7 @@ impl<W: LayoutElement> Workspace<W> {
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<W: LayoutElement> Workspace<W> {
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<W: LayoutElement> Workspace<W> {
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<W: LayoutElement> Workspace<W> {
}
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<W: LayoutElement> Workspace<W> {
}
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 49a8598d..d87a3a27 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 905f0738..714f8495 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();