mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
tab indicator: Animate opening
This commit is contained in:
@@ -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
@@ -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
@@ -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");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user