diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2025-09-02 14:16:39 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-09-17 22:04:23 +0300 |
| commit | c832bdecdda44648542f60dab73904f3e80dc857 (patch) | |
| tree | 74e1b009017e02aef2afcf6a46a3e91db7ad68a6 /src/layout | |
| parent | b8995a12d1191053628c0df7b986435e156fb79c (diff) | |
| download | niri-c832bdecdda44648542f60dab73904f3e80dc857.tar.gz niri-c832bdecdda44648542f60dab73904f3e80dc857.tar.bz2 niri-c832bdecdda44648542f60dab73904f3e80dc857.zip | |
layout/scrolling: Track pending vs. current fullscreen for Columns
We already did that for Tiles, but for Columns we only tracked what was
effectively pending fullscreen. We used it in several places where the current
fullscreen should've been used instead, like the tile origin or the view offset.
This commit splits the two and makes every place use the right one.
Fixes things like tiles briefly appearing at y=0 between issuing the fullscreen
command and the tile committing in response to the fullscreen configure.
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/scrolling.rs | 125 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 6 |
2 files changed, 93 insertions, 38 deletions
diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs index 20e4c388..48773110 100644 --- a/src/layout/scrolling.rs +++ b/src/layout/scrolling.rs @@ -166,8 +166,15 @@ pub struct Column<W: LayoutElement> { /// Whether this column is full-width. is_full_width: bool, - /// Whether this column contains a single full-screened window. - is_fullscreen: bool, + /// Whether this column is going to be fullscreen. + /// + /// This is the compositor-side fullscreen state, so it changes immediately upon + /// set_fullscreen(). The actual tiles will take some time to respond to the fullscreen request + /// and become fullscreen. + /// + /// Similarly, unsetting fullscreen will change this value to false immediately, and tiles will + /// take some time to catch up and actually unfullscreen. + is_pending_fullscreen: bool, /// How this column displays and arranges windows. display_mode: ColumnDisplay, @@ -426,13 +433,13 @@ impl<W: LayoutElement> ScrollingSpace<W> { Some(&mut col.tiles[col.active_tile_idx]) } - pub fn is_active_fullscreen(&self) -> bool { + pub fn is_active_pending_fullscreen(&self) -> bool { if self.columns.is_empty() { return false; } let col = &self.columns[self.active_column_idx]; - col.is_fullscreen + col.is_pending_fullscreen } pub fn new_window_toplevel_bounds(&self, rules: &ResolvedWindowRules) -> Size<i32, Logical> { @@ -570,7 +577,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { target_x, self.column_x(idx), col.width(), - col.is_fullscreen, + col.is_fullscreen(), ) } @@ -584,7 +591,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { target_x, self.column_x(idx), col.width(), - col.is_fullscreen, + col.is_fullscreen(), ) } @@ -1052,14 +1059,14 @@ impl<W: LayoutElement> ScrollingSpace<W> { tile.animate_alpha(0., 1., movement_config); } - let was_fullscreen = column.is_any_fullscreen(); + let was_fullscreen = column.is_fullscreen(); let tile = column.tiles.remove(tile_idx); column.data.remove(tile_idx); // If an active column became non-fullscreen after removing the tile, clear the stored // unfullscreen offset. - if column_idx == self.active_column_idx && was_fullscreen && !column.is_any_fullscreen() { + if column_idx == self.active_column_idx && was_fullscreen && !column.is_fullscreen() { self.view_offset_before_fullscreen = None; } @@ -1218,7 +1225,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { .enumerate() .find(|(_, col)| col.contains(window)) .unwrap(); - let was_fullscreen = column.is_any_fullscreen(); + let was_fullscreen = column.is_fullscreen(); let (tile_idx, tile) = column .tiles @@ -1317,7 +1324,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { } // When the active column goes fullscreen, store the view offset to restore later. - let is_fullscreen = self.columns[col_idx].is_any_fullscreen(); + let is_fullscreen = self.columns[col_idx].is_fullscreen(); if !was_fullscreen && is_fullscreen { self.view_offset_before_fullscreen = Some(self.view_offset.stationary()); } @@ -2450,7 +2457,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { // Adjust for place-within-column tab indicator. let origin_x = col.tiles_origin().x; - let extra_w = if is_tabbed && !col.is_fullscreen { + let extra_w = if is_tabbed && !col.is_fullscreen() { col.tab_indicator.extra_size(col.tiles.len(), col.scale).w } else { 0. @@ -2669,7 +2676,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { } let col = &mut self.columns[self.active_column_idx]; - if col.is_fullscreen || col.is_full_width { + if col.is_pending_fullscreen || col.is_full_width { return; } @@ -2765,7 +2772,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { .find_map(|(col_idx, col)| col.position(window).map(|tile_idx| (col_idx, tile_idx))) .unwrap(); - if is_fullscreen == self.columns[col_idx].is_fullscreen { + if is_fullscreen == self.columns[col_idx].is_pending_fullscreen { return false; } @@ -2813,7 +2820,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { return false; } - self.columns[self.active_column_idx].is_fullscreen + self.columns[self.active_column_idx].is_fullscreen() } pub fn render_elements<R: NiriRenderer>( @@ -2893,7 +2900,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { let col_render_off = col.render_offset(); // Hit the tab indicator. - if col.display_mode == ColumnDisplay::Tabbed && !col.is_fullscreen { + if col.display_mode == ColumnDisplay::Tabbed && !col.is_fullscreen() { let col_pos = view_off + col_off + col_render_off; let col_pos = col_pos.to_physical_precise_round(scale).to_logical(scale); @@ -3133,7 +3140,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { for (col_idx, col) in self.columns.iter().enumerate() { let col_w = col.width(); - let view_pos = if col.is_fullscreen { + let view_pos = if col.is_fullscreen() { col_x } else if self.working_area.size.w <= col_w { col_x - left_strut @@ -3160,7 +3167,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { // Normal columns align with the working area, but fullscreen columns align with // the view size. - if col.is_fullscreen { + if col.is_fullscreen() { let left = col_x; let right = col_x + col_w; (left, right) @@ -3287,7 +3294,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { let col_x = self.column_x(col_idx); let col_w = col.width(); - if col.is_fullscreen { + if col.is_fullscreen() { if target_snap.view_pos + self.view_size.w < col_x + col_w { break; } @@ -3309,7 +3316,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { let col_x = self.column_x(col_idx); let col_w = col.width(); - if col.is_fullscreen { + if col.is_fullscreen() { if col_x < target_snap.view_pos { break; } @@ -3382,7 +3389,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { .find(|col| col.contains(&window)) .unwrap(); - if col.is_fullscreen { + if col.is_pending_fullscreen { return false; } @@ -3630,7 +3637,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { if self.view_offset_before_fullscreen.is_some() { assert!( - col.is_any_fullscreen(), + col.is_fullscreen(), "when view_offset_before_fullscreen is set, \ the active column must be fullscreen" ); @@ -3807,7 +3814,7 @@ impl<W: LayoutElement> Column<W> { width, preset_width_idx: None, is_full_width, - is_fullscreen: false, + is_pending_fullscreen: false, display_mode, tab_indicator: TabIndicator::new(options.tab_indicator), move_animation: None, @@ -3829,7 +3836,7 @@ impl<W: LayoutElement> Column<W> { // Animate the tab indicator for new columns. if display_mode == ColumnDisplay::Tabbed && !rv.options.tab_indicator.hide_when_single_tab - && !is_pending_fullscreen + && !rv.is_fullscreen() { // Usually new columns are created together with window movement actions. For new // windows, we handle that in start_open_animation(). @@ -3945,7 +3952,7 @@ impl<W: LayoutElement> Column<W> { // you don't want that to happen in fullscreen. Also, laying things out correctly when the // tab indicator is within the column and the column goes fullscreen, would require too // many changes to the code for too little benefit (it's mostly invisible anyway). - let enabled = self.display_mode == ColumnDisplay::Tabbed && !self.is_fullscreen; + let enabled = self.display_mode == ColumnDisplay::Tabbed && !self.is_fullscreen(); self.tab_indicator.update_render_elements( enabled, @@ -4003,7 +4010,52 @@ impl<W: LayoutElement> Column<W> { } } - fn is_any_fullscreen(&self) -> bool { + /// Returns whether this column is currently fullscreen. + /// + /// As in, if it contains one currently-fullscreen tile, or in tabbed mode, if it contains at + /// least one currently-fullscreen tile. + /// + /// This will lag behind is_pending_fullscreen, depending on when the tiles actually respond to + /// the un/fullscreen request. But, it's possible for is_fullscreen() to flip instantly, for + /// example when consuming a fullscreen tile into a non-pending-fullscreen column. + /// + /// This controls things like: + /// + /// - whether the column draws at the top of the screen or at the start of the working area + /// - whether the column draws above the top layer-shell layer + /// - whether the tab indicator is shown + /// - restoring view_offset_before_fullscreen + /// + /// Edge cases to watch out for: + /// + /// - Consuming a fullscreen tile into a non-tabbed column will keep that tile fullscreen until + /// it responds to the unfullscreen request. This tile may be anywhere in the column, + /// including at the active position. + /// + /// - Changing a fullscreen tabbed column into normal mode is an easy way to get randomly + /// delayed unfullscreening tiles in a normal column. + /// + /// - is_fullscreen() can suddenly change when consuming/expelling a fullscreen tile into/from a + /// non-fullscreen column. This can influence the code that saves/restores the unfullscreen + /// view offset. + fn is_fullscreen(&self) -> bool { + // Behaviors that we want: + // + // 1. The common case: single tile in a column. Assume no animations. Fullscreening the tile + // should make it jump to the top-left of the screen only when the tile finishes + // fullscreening. Similarly, unfullscreening should keep it at the top-left until the + // tile had unfullscreened. + // + // 2. Unfullscreening a tabbed column with multiple tiles should restore the view offset + // correctly. This means waiting for *all* tiles to unfullscreen, because otherwise the + // restored view offset will immediately get overwritted by the still screen-wide column + // (it uses the largest tile's width). + // + // 3. Changing a fullscreen tabbed column to normal should probably also restore the view + // offset correctly. Same problem as above, but now for normal columns (since display + // mode change applies instantly). + // + // The logic that satisfies these behaviors is to check if *any* tile is fullscreen. self.tiles.iter().any(|tile| tile.is_fullscreen()) } @@ -4047,7 +4099,7 @@ impl<W: LayoutElement> Column<W> { prev_offsets.extend(self.tile_offsets().take(self.tiles.len())); if self.display_mode != ColumnDisplay::Tabbed { - self.is_fullscreen = false; + self.is_pending_fullscreen = false; } self.data @@ -4159,7 +4211,7 @@ impl<W: LayoutElement> Column<W> { } fn update_tile_sizes_with_transaction(&mut self, animate: bool, transaction: Transaction) { - if self.is_fullscreen { + if self.is_pending_fullscreen { for (tile_idx, tile) in self.tiles.iter_mut().enumerate() { // In tabbed mode, only the visible window participates in the transaction. let is_active = tile_idx == self.active_tile_idx; @@ -4456,7 +4508,7 @@ impl<W: LayoutElement> Column<W> { .map(NotNan::into_inner) .unwrap(); - if self.display_mode == ColumnDisplay::Tabbed && !self.is_fullscreen { + if self.display_mode == ColumnDisplay::Tabbed && !self.is_fullscreen() { let extra_size = self.tab_indicator.extra_size(self.tiles.len(), self.scale); tiles_width += extra_size.w; } @@ -4807,7 +4859,7 @@ impl<W: LayoutElement> Column<W> { } fn set_fullscreen(&mut self, is_fullscreen: bool) { - if self.is_fullscreen == is_fullscreen { + if self.is_pending_fullscreen == is_fullscreen { return; } @@ -4815,7 +4867,7 @@ impl<W: LayoutElement> Column<W> { assert!(self.tiles.len() == 1 || self.display_mode == ColumnDisplay::Tabbed); } - self.is_fullscreen = is_fullscreen; + self.is_pending_fullscreen = is_fullscreen; self.update_tile_sizes(false); } @@ -4880,7 +4932,7 @@ impl<W: LayoutElement> Column<W> { fn tiles_origin(&self) -> Point<f64, Logical> { let mut origin = Point::from((0., 0.)); - if self.is_fullscreen { + if self.is_fullscreen() { return origin; } @@ -5040,7 +5092,7 @@ impl<W: LayoutElement> Column<W> { // Animate the appearance of the tab indicator. if self.display_mode == ColumnDisplay::Tabbed - && !self.is_fullscreen + && !self.is_fullscreen() && self.tiles.len() == 1 && !self.tab_indicator.config().hide_when_single_tab { @@ -5063,7 +5115,7 @@ impl<W: LayoutElement> Column<W> { assert!(self.active_tile_idx < self.tiles.len()); assert_eq!(self.tiles.len(), self.data.len()); - if self.is_fullscreen { + if self.is_pending_fullscreen { assert!(self.tiles.len() == 1 || self.display_mode == ColumnDisplay::Tabbed); } @@ -5094,7 +5146,10 @@ impl<W: LayoutElement> Column<W> { assert!(Rc::ptr_eq(&self.options, &tile.options)); assert_eq!(self.clock, tile.clock); assert_eq!(self.scale, tile.scale()); - assert_eq!(self.is_fullscreen, tile.window().is_pending_fullscreen()); + assert_eq!( + self.is_pending_fullscreen, + tile.window().is_pending_fullscreen() + ); assert_eq!(self.view_size, tile.view_size()); tile.verify_invariants(); @@ -5119,7 +5174,7 @@ impl<W: LayoutElement> Column<W> { tile.tile_height_for_window_height(f64::from(requested_size.h)); let min_tile_height = f64::max(1., tile.min_size_nonfullscreen().h); - if !self.is_fullscreen + if !self.is_pending_fullscreen && self.scale.round() == self.scale && working_size.h.round() == working_size.h && gaps.round() == gaps diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index efba6ee6..6c343858 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -444,8 +444,8 @@ impl<W: LayoutElement> Workspace<W> { } } - pub fn is_active_fullscreen(&self) -> bool { - self.scrolling.is_active_fullscreen() + pub fn is_active_pending_fullscreen(&self) -> bool { + self.scrolling.is_active_pending_fullscreen() } pub fn set_output(&mut self, output: Option<Output>) { @@ -571,7 +571,7 @@ impl<W: LayoutElement> Workspace<W> { match target { WorkspaceAddWindowTarget::Auto => { // Don't steal focus from an active fullscreen window. - let activate = activate.map_smart(|| !self.is_active_fullscreen()); + let activate = activate.map_smart(|| !self.is_active_pending_fullscreen()); // If the tile is pending fullscreen, open it in the scrolling layout where it can // go fullscreen. |
