mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
layout: Fix/add animations around tabbed columns
This commit is contained in:
+76
-1
@@ -865,6 +865,19 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
}
|
||||
}
|
||||
|
||||
let target_column = &mut self.columns[col_idx];
|
||||
if target_column.display_mode == ColumnDisplay::Tabbed {
|
||||
if target_column.active_tile_idx == tile_idx {
|
||||
// Fade out the previously active tile.
|
||||
let tile = &mut target_column.tiles[prev_active_tile_idx];
|
||||
tile.animate_alpha(1., 0., self.options.animations.window_movement.0);
|
||||
} else {
|
||||
// Fade out when adding into a tabbed column into the background.
|
||||
let tile = &mut target_column.tiles[tile_idx];
|
||||
tile.animate_alpha(1., 0., self.options.animations.window_movement.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Adding a wider window into a column increases its width now (even if the window will
|
||||
// shrink later). Move the columns to account for this.
|
||||
let offset = self.column_x(col_idx + 1) - prev_next_x;
|
||||
@@ -1004,6 +1017,8 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
let column = &mut self.columns[column_idx];
|
||||
let prev_width = self.data[column_idx].width;
|
||||
|
||||
let movement_config = anim_config.unwrap_or(self.options.animations.window_movement.0);
|
||||
|
||||
// Animate movement of other tiles.
|
||||
// FIXME: tiles can move by X too, in a centered or resizing layout with one window smaller
|
||||
// than the others.
|
||||
@@ -1012,6 +1027,12 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
tile.animate_move_y_from(offset_y);
|
||||
}
|
||||
|
||||
if column.display_mode == ColumnDisplay::Tabbed && tile_idx != column.active_tile_idx {
|
||||
// Fade in when removing background tab from a tabbed column.
|
||||
let tile = &mut column.tiles[tile_idx];
|
||||
tile.animate_alpha(0., 1., movement_config);
|
||||
}
|
||||
|
||||
let tile = column.tiles.remove(tile_idx);
|
||||
column.data.remove(tile_idx);
|
||||
|
||||
@@ -1036,6 +1057,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
is_floating: false,
|
||||
};
|
||||
|
||||
#[allow(clippy::comparison_chain)] // What do you even want here?
|
||||
if tile_idx < column.active_tile_idx {
|
||||
// A tile above was removed; preserve the current position.
|
||||
column.active_tile_idx -= 1;
|
||||
@@ -1044,6 +1066,9 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
if tile_idx == column.tiles.len() {
|
||||
// The bottom tile was removed and it was active, update active idx to remain valid.
|
||||
column.activate_idx(tile_idx - 1);
|
||||
} else {
|
||||
// Ensure the newly active tile animates to opaque.
|
||||
column.tiles[tile_idx].ensure_alpha_animates_to_1();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1052,7 +1077,6 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
let offset = prev_width - column.width();
|
||||
|
||||
// Animate movement of the other columns.
|
||||
let movement_config = anim_config.unwrap_or(self.options.animations.window_movement.0);
|
||||
if self.active_column_idx <= column_idx {
|
||||
for col in &mut self.columns[column_idx + 1..] {
|
||||
col.animate_move_from_with_config(offset, movement_config);
|
||||
@@ -1980,6 +2004,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
// Animations
|
||||
self.columns[target_column_idx].tiles[target_tile_idx]
|
||||
.animate_move_from(source_pt - target_pt);
|
||||
self.columns[target_column_idx].tiles[target_tile_idx].ensure_alpha_animates_to_1();
|
||||
|
||||
// FIXME: this stop_move_animations() causes the target tile animation to "reset" when
|
||||
// swapping. It's here as a workaround to stop the unwanted animation of moving the source
|
||||
@@ -1989,6 +2014,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
self.columns[source_column_idx].tiles[source_tile_idx].stop_move_animations();
|
||||
self.columns[source_column_idx].tiles[source_tile_idx]
|
||||
.animate_move_from(target_pt - source_pt);
|
||||
self.columns[source_column_idx].tiles[source_tile_idx].ensure_alpha_animates_to_1();
|
||||
|
||||
self.activate_column(target_column_idx);
|
||||
}
|
||||
@@ -2602,6 +2628,12 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
let focus_ring = focus_ring && first;
|
||||
first = false;
|
||||
|
||||
// In the scrolling layout, we currently use visible only for hidden tabs in the
|
||||
// tabbed mode. We want to animate their opacity when going in and out of tabbed
|
||||
// mode, so we don't want to apply "visible" immediately. However, "visible" is
|
||||
// also used for input handling, and there we *do* want to apply it immediately.
|
||||
// So, let's just selectively ignore "visible" here when animating alpha.
|
||||
let visible = visible || tile.alpha_animation.is_some();
|
||||
if !visible {
|
||||
continue;
|
||||
}
|
||||
@@ -3521,6 +3553,8 @@ impl<W: LayoutElement> Column<W> {
|
||||
|
||||
self.active_tile_idx = idx;
|
||||
|
||||
self.tiles[idx].ensure_alpha_animates_to_1();
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
@@ -4258,6 +4292,47 @@ impl<W: LayoutElement> Column<W> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Animate the movement.
|
||||
//
|
||||
// We're doing some shortcuts here because we know that currently normal vs. tabbed can
|
||||
// only cause a vertical shift + a shift to the origin.
|
||||
//
|
||||
// Doing it this way to avoid storing all tile positions in a vector. If more display modes
|
||||
// are added it might be simpler to just collect everything into a smallvec.
|
||||
let prev_origin = self.tiles_origin();
|
||||
self.display_mode = display;
|
||||
let new_origin = self.tiles_origin();
|
||||
let origin_delta = prev_origin - new_origin;
|
||||
|
||||
// When need to walk the tiles in the normal display mode to get the right offsets.
|
||||
self.display_mode = ColumnDisplay::Normal;
|
||||
for (tile, pos) in self.tiles_mut() {
|
||||
let mut y_delta = pos.y - prev_origin.y;
|
||||
|
||||
// Invert the Y motion when transitioning *to* normal display mode.
|
||||
if display == ColumnDisplay::Normal {
|
||||
y_delta *= -1.;
|
||||
}
|
||||
|
||||
let mut delta = origin_delta;
|
||||
delta.y += y_delta;
|
||||
tile.animate_move_from(delta);
|
||||
}
|
||||
|
||||
// Animate the opacity.
|
||||
for (idx, tile) in self.tiles.iter_mut().enumerate() {
|
||||
let is_active = idx == self.active_tile_idx;
|
||||
if !is_active {
|
||||
let (from, to) = if display == ColumnDisplay::Tabbed {
|
||||
(1., 0.)
|
||||
} else {
|
||||
(0., 1.)
|
||||
};
|
||||
tile.animate_alpha(from, to, self.options.animations.window_movement.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Now switch the display mode for real.
|
||||
self.display_mode = display;
|
||||
self.update_tile_sizes(true);
|
||||
}
|
||||
|
||||
+44
-4
@@ -84,6 +84,9 @@ pub struct Tile<W: LayoutElement> {
|
||||
/// The animation of a tile visually moving vertically.
|
||||
move_y_animation: Option<MoveAnimation>,
|
||||
|
||||
/// The animation of the tile's opacity.
|
||||
pub(super) alpha_animation: Option<Animation>,
|
||||
|
||||
/// Offset during the initial interactive move rubberband.
|
||||
pub(super) interactive_move_offset: Point<f64, Logical>,
|
||||
|
||||
@@ -168,6 +171,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
resize_animation: None,
|
||||
move_x_animation: None,
|
||||
move_y_animation: None,
|
||||
alpha_animation: None,
|
||||
interactive_move_offset: Point::from((0., 0.)),
|
||||
unmap_snapshot: None,
|
||||
rounded_corner_damage: Default::default(),
|
||||
@@ -301,6 +305,12 @@ impl<W: LayoutElement> Tile<W> {
|
||||
self.move_y_animation = None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(alpha) = &mut self.alpha_animation {
|
||||
if alpha.is_done() {
|
||||
self.alpha_animation = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn are_animations_ongoing(&self) -> bool {
|
||||
@@ -308,10 +318,12 @@ impl<W: LayoutElement> Tile<W> {
|
||||
|| self.resize_animation.is_some()
|
||||
|| self.move_x_animation.is_some()
|
||||
|| self.move_y_animation.is_some()
|
||||
|| self.alpha_animation.is_some()
|
||||
}
|
||||
|
||||
pub fn update_render_elements(&mut self, is_active: bool, view_rect: Rectangle<f64, Logical>) {
|
||||
let rules = self.window.rules();
|
||||
let alpha = self.tile_alpha();
|
||||
|
||||
let draw_border_with_background = rules
|
||||
.draw_border_with_background
|
||||
@@ -336,7 +348,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
),
|
||||
radius,
|
||||
self.scale,
|
||||
1.,
|
||||
alpha,
|
||||
);
|
||||
|
||||
let radius = if self.is_fullscreen {
|
||||
@@ -351,7 +363,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
is_active,
|
||||
radius,
|
||||
self.scale,
|
||||
1.,
|
||||
alpha,
|
||||
);
|
||||
|
||||
let draw_focus_ring_with_background = if self.effective_border_width().is_some() {
|
||||
@@ -367,7 +379,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
view_rect,
|
||||
radius,
|
||||
self.scale,
|
||||
1.,
|
||||
alpha,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -452,6 +464,31 @@ impl<W: LayoutElement> Tile<W> {
|
||||
self.move_y_animation = None;
|
||||
}
|
||||
|
||||
pub fn animate_alpha(&mut self, from: f64, to: f64, config: niri_config::Animation) {
|
||||
let from = from.clamp(0., 1.);
|
||||
let to = to.clamp(0., 1.);
|
||||
let current = self.alpha_animation.take().map(|anim| anim.clamped_value());
|
||||
let current = current.unwrap_or(from);
|
||||
self.alpha_animation = Some(Animation::new(self.clock.clone(), current, to, 0., config));
|
||||
}
|
||||
|
||||
pub fn ensure_alpha_animates_to_1(&mut self) {
|
||||
if let Some(anim) = &self.alpha_animation {
|
||||
if anim.to() != 1. {
|
||||
// Cancel animation instead of starting a new one because the user likely wants to
|
||||
// see the tile right away.
|
||||
self.alpha_animation = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Opacity that applies to both window and decorations.
|
||||
fn tile_alpha(&self) -> f32 {
|
||||
self.alpha_animation
|
||||
.as_ref()
|
||||
.map_or(1., |anim| anim.clamped_value()) as f32
|
||||
}
|
||||
|
||||
pub fn window(&self) -> &W {
|
||||
&self.window
|
||||
}
|
||||
@@ -740,6 +777,9 @@ impl<W: LayoutElement> Tile<W> {
|
||||
self.window.rules().opacity.unwrap_or(1.).clamp(0., 1.)
|
||||
};
|
||||
|
||||
let tile_alpha = self.tile_alpha();
|
||||
let win_alpha = win_alpha * tile_alpha;
|
||||
|
||||
let window_loc = self.window_loc();
|
||||
let window_size = self.window_size().to_f64();
|
||||
let animated_window_size = self.animated_window_size();
|
||||
@@ -921,7 +961,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&self.fullscreen_backdrop,
|
||||
location,
|
||||
1.,
|
||||
tile_alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into()
|
||||
|
||||
@@ -1688,7 +1688,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
);
|
||||
}
|
||||
|
||||
for (tile, tile_pos, _visible) in self.tiles_with_render_positions() {
|
||||
for (tile, tile_pos, visible) in self.tiles_with_render_positions() {
|
||||
if Some(tile.window().id()) != move_win_id {
|
||||
assert_eq!(tile.interactive_move_offset, Point::from((0., 0.)));
|
||||
}
|
||||
@@ -1698,6 +1698,12 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
// Tile positions must be rounded to physical pixels.
|
||||
assert_abs_diff_eq!(tile_pos.x, rounded_pos.x, epsilon = 1e-5);
|
||||
assert_abs_diff_eq!(tile_pos.y, rounded_pos.y, epsilon = 1e-5);
|
||||
|
||||
if let Some(anim) = &tile.alpha_animation {
|
||||
if visible {
|
||||
assert_eq!(anim.to(), 1., "visible tiles can animate alpha only to 1");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user