tab indicator: Animate opening

This commit is contained in:
Ivan Molodetskikh
2025-02-10 14:12:54 +03:00
parent 14ac2cff4c
commit 20769b4c2f
5 changed files with 98 additions and 7 deletions
+9
View File
@@ -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;
+1 -4
View File
@@ -4112,14 +4112,11 @@ 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();
if ws.start_open_animation(window) {
return;
}
}
}
}
pub fn store_unmap_snapshot(&mut self, renderer: &mut GlesRenderer, window: &W::Id) {
let _span = tracy_client::span!("Layout::store_unmap_snapshot");
+54 -1
View File
@@ -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");
+29 -1
View File
@@ -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 {
+4
View File
@@ -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() {