aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/layout/scrolling.rs77
-rw-r--r--src/layout/tile.rs48
-rw-r--r--src/layout/workspace.rs8
3 files changed, 127 insertions, 6 deletions
diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs
index 55ddfc1b..59681cdf 100644
--- a/src/layout/scrolling.rs
+++ b/src/layout/scrolling.rs
@@ -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);
}
diff --git a/src/layout/tile.rs b/src/layout/tile.rs
index 7a49f5ed..7877457b 100644
--- a/src/layout/tile.rs
+++ b/src/layout/tile.rs
@@ -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()
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index 9fd57baa..b5392644 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -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");
+ }
+ }
}
}
}