aboutsummaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2025-02-10 14:12:54 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2025-02-10 07:29:33 -0800
commit20769b4c2f843566880f020f117e7bde36edc332 (patch)
tree5c8270c86caa7fe8d284c30ec377e5f2e916d76b /src/layout
parent14ac2cff4cafb2e4df74d2194eaeb1664b33c831 (diff)
downloadniri-20769b4c2f843566880f020f117e7bde36edc332.tar.gz
niri-20769b4c2f843566880f020f117e7bde36edc332.tar.bz2
niri-20769b4c2f843566880f020f117e7bde36edc332.zip
tab indicator: Animate opening
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/floating.rs9
-rw-r--r--src/layout/mod.rs7
-rw-r--r--src/layout/scrolling.rs55
-rw-r--r--src/layout/tab_indicator.rs30
-rw-r--r--src/layout/workspace.rs4
5 files changed, 98 insertions, 7 deletions
diff --git a/src/layout/floating.rs b/src/layout/floating.rs
index 342663ac..fef92309 100644
--- a/src/layout/floating.rs
+++ b/src/layout/floating.rs
@@ -641,6 +641,15 @@ impl<W: LayoutElement> FloatingSpace<W> {
self.interactive_resize_end(Some(&id));
}
+ pub fn start_open_animation(&mut self, id: &W::Id) -> bool {
+ let Some(idx) = self.idx_of(id) else {
+ return false;
+ };
+
+ self.tiles[idx].start_open_animation();
+ true
+ }
+
pub fn toggle_window_height(&mut self, id: Option<&W::Id>) {
let Some(id) = id.or(self.active_window_id.as_ref()).cloned() else {
return;
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 4dec85f6..bdddf462 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -4112,11 +4112,8 @@ impl<W: LayoutElement> Layout<W> {
}
for ws in self.workspaces_mut() {
- for tile in ws.tiles_mut() {
- if tile.window().id() == window {
- tile.start_open_animation();
- return;
- }
+ if ws.start_open_animation(window) {
+ return;
}
}
}
diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs
index 18da2b46..6a876910 100644
--- a/src/layout/scrolling.rs
+++ b/src/layout/scrolling.rs
@@ -1429,6 +1429,12 @@ impl<W: LayoutElement> ScrollingSpace<W> {
}
}
+ pub fn start_open_animation(&mut self, id: &W::Id) -> bool {
+ self.columns
+ .iter_mut()
+ .any(|col| col.start_open_animation(id))
+ }
+
pub fn focus_left(&mut self) -> bool {
if self.active_column_idx == 0 {
return false;
@@ -3432,6 +3438,17 @@ impl<W: LayoutElement> Column<W> {
rv.set_fullscreen(true);
}
+ // Animate the tab indicator for new columns.
+ if display_mode == ColumnDisplay::Tabbed
+ && !rv.options.tab_indicator.hide_when_single_tab
+ && !is_pending_fullscreen
+ {
+ // Usually new columns are created together with window movement actions. For new
+ // windows, we handle that in start_open_animation().
+ rv.tab_indicator
+ .start_open_animation(rv.clock.clone(), rv.options.animations.window_movement.0);
+ }
+
rv
}
@@ -3499,10 +3516,14 @@ impl<W: LayoutElement> Column<W> {
for tile in &mut self.tiles {
tile.advance_animations();
}
+
+ self.tab_indicator.advance_animations();
}
pub fn are_animations_ongoing(&self) -> bool {
- self.move_animation.is_some() || self.tiles.iter().any(Tile::are_animations_ongoing)
+ self.move_animation.is_some()
+ || self.tab_indicator.are_animations_ongoing()
+ || self.tiles.iter().any(Tile::are_animations_ongoing)
}
pub fn update_render_elements(&mut self, is_active: bool, view_rect: Rectangle<f64, Logical>) {
@@ -4375,6 +4396,14 @@ impl<W: LayoutElement> Column<W> {
}
}
+ // Animate the appearance of the tab indicator.
+ if display == ColumnDisplay::Tabbed {
+ self.tab_indicator.start_open_animation(
+ self.clock.clone(),
+ self.options.animations.window_movement.0,
+ );
+ }
+
// Now switch the display mode for real.
self.display_mode = display;
self.update_tile_sizes(true);
@@ -4555,6 +4584,30 @@ impl<W: LayoutElement> Column<W> {
Rectangle::new(self.tiles_origin(), area_size)
}
+ pub fn start_open_animation(&mut self, id: &W::Id) -> bool {
+ for tile in &mut self.tiles {
+ if tile.window().id() == id {
+ tile.start_open_animation();
+
+ // Animate the appearance of the tab indicator.
+ if self.display_mode == ColumnDisplay::Tabbed
+ && !self.is_fullscreen
+ && self.tiles.len() == 1
+ && !self.tab_indicator.config().hide_when_single_tab
+ {
+ self.tab_indicator.start_open_animation(
+ self.clock.clone(),
+ self.options.animations.window_open.anim,
+ );
+ }
+
+ return true;
+ }
+ }
+
+ false
+ }
+
#[cfg(test)]
fn verify_invariants(&self) {
assert!(!self.tiles.is_empty(), "columns can't be empty");
diff --git a/src/layout/tab_indicator.rs b/src/layout/tab_indicator.rs
index cbe1e932..e0e9ce75 100644
--- a/src/layout/tab_indicator.rs
+++ b/src/layout/tab_indicator.rs
@@ -6,6 +6,7 @@ use smithay::utils::{Logical, Point, Rectangle, Size};
use super::tile::Tile;
use super::LayoutElement;
+use crate::animation::{Animation, Clock};
use crate::niri_render_elements;
use crate::render_helpers::border::BorderRenderElement;
use crate::render_helpers::renderer::NiriRenderer;
@@ -15,6 +16,7 @@ use crate::utils::{floor_logical_in_physical_max1, round_logical_in_physical};
pub struct TabIndicator {
shader_locs: Vec<Point<f64, Logical>>,
shaders: Vec<BorderRenderElement>,
+ open_anim: Option<Animation>,
config: niri_config::TabIndicator,
}
@@ -37,6 +39,7 @@ impl TabIndicator {
Self {
shader_locs: Vec::new(),
shaders: Vec::new(),
+ open_anim: None,
config,
}
}
@@ -51,6 +54,22 @@ impl TabIndicator {
}
}
+ pub fn advance_animations(&mut self) {
+ if let Some(anim) = &mut self.open_anim {
+ if anim.is_done() {
+ self.open_anim = None;
+ }
+ }
+ }
+
+ pub fn are_animations_ongoing(&self) -> bool {
+ self.open_anim.is_some()
+ }
+
+ pub fn start_open_animation(&mut self, clock: Clock, config: niri_config::Animation) {
+ self.open_anim = Some(Animation::new(clock, 0., 1., 0., config));
+ }
+
fn tab_rects(
&self,
area: Rectangle<f64, Logical>,
@@ -59,6 +78,8 @@ impl TabIndicator {
) -> impl Iterator<Item = Rectangle<f64, Logical>> {
let round = |logical: f64| round_logical_in_physical(scale, logical);
+ let progress = self.open_anim.as_ref().map_or(1., |a| a.value().max(0.));
+
let width = round(self.config.width.0);
let gap = round(self.config.gap.0);
let gaps_between = round(self.config.gaps_between_tabs.0);
@@ -71,13 +92,20 @@ impl TabIndicator {
let total_prop = self.config.length.total_proportion.unwrap_or(0.5);
let min_length = round(side * total_prop.clamp(0., 2.));
+ // Compute px_per_tab before applying the animation to gaps_between in order to avoid it
+ // growing and shrinking over the duration of the animation.
let pixel = 1. / scale;
let shortest_length = count as f64 * (pixel + gaps_between) - gaps_between;
let length = f64::max(min_length, shortest_length);
let px_per_tab = (length + gaps_between) / count as f64 - gaps_between;
+
+ let px_per_tab = px_per_tab * progress;
+ let gaps_between = round(self.config.gaps_between_tabs.0 * progress);
+
+ let length = count as f64 * (px_per_tab + gaps_between) - gaps_between;
let px_per_tab = floor_logical_in_physical_max1(scale, px_per_tab);
let floored_length = count as f64 * (px_per_tab + gaps_between) - gaps_between;
- let mut ones_left = ((length - floored_length) / pixel).max(0.).round() as usize;
+ let mut ones_left = ((length - floored_length) / pixel).round() as usize;
let mut shader_loc = Point::from((-gap - width, round((side - length) / 2.)));
match position {
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index 25d2bb80..a402ad44 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -1474,6 +1474,10 @@ impl<W: LayoutElement> Workspace<W> {
.start_close_animation_for_tile(renderer, snapshot, tile_size, tile_pos, blocker);
}
+ pub fn start_open_animation(&mut self, id: &W::Id) -> bool {
+ self.scrolling.start_open_animation(id) || self.floating.start_open_animation(id)
+ }
+
pub fn window_under(&self, pos: Point<f64, Logical>) -> Option<(&W, HitType)> {
// This logic is consistent with tiles_with_render_positions().
if self.is_floating_visible() {