diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2025-09-02 08:07:22 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-10-15 09:04:16 +0300 |
| commit | e1fad994da9565b43c7fb139cb2fb7bf404cc320 (patch) | |
| tree | 305fa0714d66ad2b4346b3aee6eb785099b29fa1 /src/layout/tile.rs | |
| parent | e5d4e7c1b1a0b61770b6711a53fe41920d56452d (diff) | |
| download | niri-e1fad994da9565b43c7fb139cb2fb7bf404cc320.tar.gz niri-e1fad994da9565b43c7fb139cb2fb7bf404cc320.tar.bz2 niri-e1fad994da9565b43c7fb139cb2fb7bf404cc320.zip | |
Implement maximize-to-edges (true Wayland maximize)
Diffstat (limited to 'src/layout/tile.rs')
| -rw-r--r-- | src/layout/tile.rs | 171 |
1 files changed, 124 insertions, 47 deletions
diff --git a/src/layout/tile.rs b/src/layout/tile.rs index 1e6f1a21..bc39db6d 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -16,6 +16,7 @@ use super::{ SizeFrac, RESIZE_ANIMATION_THRESHOLD, }; use crate::animation::{Animation, Clock}; +use crate::layout::SizingMode; use crate::niri_render_elements; use crate::render_helpers::border::BorderRenderElement; use crate::render_helpers::clipped_surface::{ClippedSurfaceRenderElement, RoundedCornerDamage}; @@ -45,17 +46,17 @@ pub struct Tile<W: LayoutElement> { /// The shadow around the window. shadow: Shadow, - /// Whether this tile is fullscreen. + /// This tile's current sizing mode. /// - /// This will update only when the `window` actually goes fullscreen, rather than right away, - /// to avoid black backdrop flicker before the window has had a chance to resize. - is_fullscreen: bool, + /// This will update only when the `window` actually goes maximized or fullscreen, rather than + /// right away, to avoid black backdrop flicker before the window has had a chance to resize. + sizing_mode: SizingMode, /// The black backdrop for fullscreen windows. fullscreen_backdrop: SolidColorBuffer, /// Whether the tile should float upon unfullscreening. - pub(super) unfullscreen_to_floating: bool, + pub(super) restore_to_floating: bool, /// The size that the window should assume when going floating. /// @@ -146,6 +147,8 @@ struct ResizeAnimation { // Note that this can be set even if this specific resize is between two non-fullscreen states, // for example when issuing a new resize during an unfullscreen resize. fullscreen_progress: Option<Animation>, + // Similar to above but for fullscreen-or-maximized. + expanded_progress: Option<Animation>, } #[derive(Debug)] @@ -178,16 +181,16 @@ impl<W: LayoutElement> Tile<W> { let border_config = options.layout.border.merged_with(&rules.border); let focus_ring_config = options.layout.focus_ring.merged_with(&rules.focus_ring); let shadow_config = options.layout.shadow.merged_with(&rules.shadow); - let is_fullscreen = window.is_fullscreen(); + let sizing_mode = window.sizing_mode(); Self { window, border: FocusRing::new(border_config.into()), focus_ring: FocusRing::new(focus_ring_config), shadow: Shadow::new(shadow_config), - is_fullscreen, + sizing_mode, fullscreen_backdrop: SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.]), - unfullscreen_to_floating: false, + restore_to_floating: false, floating_window_size: None, floating_pos: None, floating_preset_width_idx: None, @@ -248,8 +251,8 @@ impl<W: LayoutElement> Tile<W> { } pub fn update_window(&mut self) { - let was_fullscreen = self.is_fullscreen; - self.is_fullscreen = self.window.is_fullscreen(); + let prev_sizing_mode = self.sizing_mode; + self.sizing_mode = self.window.sizing_mode(); if let Some(animate_from) = self.window.take_animation_snapshot() { let params = if let Some(resize) = self.resize_animation.take() { @@ -265,10 +268,10 @@ impl<W: LayoutElement> Tile<W> { size.h = size_from.h + (size.h - size_from.h) * val; let mut tile_size = animate_from.size; - if was_fullscreen { + if prev_sizing_mode.is_fullscreen() { tile_size.w = f64::max(tile_size.w, self.view_size.w); tile_size.h = f64::max(tile_size.h, self.view_size.h); - } else if !self.border.is_off() { + } else if prev_sizing_mode.is_normal() && !self.border.is_off() { let width = self.border.width(); tile_size.w += width * 2.; tile_size.h += width * 2.; @@ -280,29 +283,56 @@ impl<W: LayoutElement> Tile<W> { let fullscreen_from = resize .fullscreen_progress .map(|anim| anim.clamped_value().clamp(0., 1.)) - .unwrap_or(if was_fullscreen { 1. } else { 0. }); + .unwrap_or(if prev_sizing_mode.is_fullscreen() { + 1. + } else { + 0. + }); + + let expanded_from = resize + .expanded_progress + .map(|anim| anim.clamped_value().clamp(0., 1.)) + .unwrap_or(if prev_sizing_mode.is_normal() { 0. } else { 1. }); // Also try to reuse the existing offscreen buffer if we have one. - (size, tile_size, fullscreen_from, resize.offscreen) + ( + size, + tile_size, + fullscreen_from, + expanded_from, + resize.offscreen, + ) } else { let size = animate_from.size; // Compute like in tile_size(). let mut tile_size = size; - if was_fullscreen { + if prev_sizing_mode.is_fullscreen() { tile_size.w = f64::max(tile_size.w, self.view_size.w); tile_size.h = f64::max(tile_size.h, self.view_size.h); - } else if !self.border.is_off() { + } else if prev_sizing_mode.is_normal() && !self.border.is_off() { let width = self.border.width(); tile_size.w += width * 2.; tile_size.h += width * 2.; } - let fullscreen_from = if was_fullscreen { 1. } else { 0. }; + let fullscreen_from = if prev_sizing_mode.is_fullscreen() { + 1. + } else { + 0. + }; + + let expanded_from = if prev_sizing_mode.is_normal() { 0. } else { 1. }; - (size, tile_size, fullscreen_from, OffscreenBuffer::default()) + ( + size, + tile_size, + fullscreen_from, + expanded_from, + OffscreenBuffer::default(), + ) }; - let (size_from, tile_size_from, fullscreen_from, offscreen) = params; + let (size_from, tile_size_from, fullscreen_from, expanded_from, offscreen) = params; let change = self.window.size().to_f64().to_point() - size_from.to_point(); let change = f64::max(change.x.abs(), change.y.abs()); @@ -318,9 +348,16 @@ impl<W: LayoutElement> Tile<W> { self.options.animations.window_resize.anim, ); - let fullscreen_to = if self.is_fullscreen { 1. } else { 0. }; + let fullscreen_to = if self.sizing_mode.is_fullscreen() { + 1. + } else { + 0. + }; + let expanded_to = if self.sizing_mode.is_normal() { 0. } else { 1. }; let fullscreen_progress = (fullscreen_from != fullscreen_to) .then(|| anim.restarted(fullscreen_from, fullscreen_to, 0.)); + let expanded_progress = (expanded_from != expanded_to) + .then(|| anim.restarted(expanded_from, expanded_to, 0.)); self.resize_animation = Some(ResizeAnimation { anim, @@ -329,6 +366,7 @@ impl<W: LayoutElement> Tile<W> { offscreen, tile_size_from, fullscreen_progress, + expanded_progress, }); } else { self.resize_animation = None; @@ -406,7 +444,7 @@ impl<W: LayoutElement> Tile<W> { pub fn update_render_elements(&mut self, is_active: bool, view_rect: Rectangle<f64, Logical>) { let rules = self.window.rules(); let animated_tile_size = self.animated_tile_size(); - let fullscreen_progress = self.fullscreen_progress(); + let expanded_progress = self.expanded_progress(); let draw_border_with_background = rules .draw_border_with_background @@ -425,7 +463,7 @@ impl<W: LayoutElement> Tile<W> { .map_or(CornerRadius::default(), |radius| { radius.expanded_by(border_width as f32) }) - .scaled_by(1. - fullscreen_progress as f32); + .scaled_by(1. - expanded_progress as f32); self.border.update_render_elements( border_window_size, is_active, @@ -437,7 +475,7 @@ impl<W: LayoutElement> Tile<W> { ), radius, self.scale, - 1. - fullscreen_progress as f32, + 1. - expanded_progress as f32, ); let radius = if self.visual_border_width().is_some() { @@ -446,17 +484,17 @@ impl<W: LayoutElement> Tile<W> { rules .geometry_corner_radius .unwrap_or_default() - .scaled_by(1. - fullscreen_progress as f32) + .scaled_by(1. - expanded_progress as f32) }; self.shadow.update_render_elements( animated_tile_size, is_active, radius, self.scale, - 1. - fullscreen_progress as f32, + 1. - expanded_progress as f32, ); - let draw_focus_ring_with_background = if self.border.is_off() && fullscreen_progress < 1. { + let draw_focus_ring_with_background = if self.border.is_off() { draw_border_with_background } else { false @@ -470,7 +508,7 @@ impl<W: LayoutElement> Tile<W> { view_rect, radius, self.scale, - 1. - fullscreen_progress as f32, + 1. - expanded_progress as f32, ); self.fullscreen_backdrop.resize(animated_tile_size); @@ -609,8 +647,8 @@ impl<W: LayoutElement> Tile<W> { &mut self.window } - pub fn is_fullscreen(&self) -> bool { - self.is_fullscreen + pub fn sizing_mode(&self) -> SizingMode { + self.sizing_mode } fn fullscreen_progress(&self) -> f64 { @@ -620,16 +658,30 @@ impl<W: LayoutElement> Tile<W> { } } - if self.is_fullscreen { + if self.sizing_mode.is_fullscreen() { 1. } else { 0. } } + fn expanded_progress(&self) -> f64 { + if let Some(resize) = &self.resize_animation { + if let Some(anim) = &resize.expanded_progress { + return anim.clamped_value().clamp(0., 1.); + } + } + + if self.sizing_mode.is_normal() { + 0. + } else { + 1. + } + } + /// Returns `None` if the border is hidden and `Some(width)` if it should be shown. pub fn effective_border_width(&self) -> Option<f64> { - if self.is_fullscreen { + if !self.sizing_mode.is_normal() { return None; } @@ -645,10 +697,10 @@ impl<W: LayoutElement> Tile<W> { return None; } - let fullscreen_progress = self.fullscreen_progress(); + let expanded_progress = self.expanded_progress(); - // Only hide the border when fully fullscreen to avoid jarring border appearance. - if fullscreen_progress == 1. { + // Only hide the border when fully expanded to avoid jarring border appearance. + if expanded_progress == 1. { return None; } @@ -688,7 +740,7 @@ impl<W: LayoutElement> Tile<W> { pub fn tile_size(&self) -> Size<f64, Logical> { let mut size = self.window_size(); - if self.is_fullscreen { + if self.sizing_mode.is_fullscreen() { // Normally we'd just return the fullscreen size here, but this makes things a bit // nicer if a fullscreen window is bigger than the fullscreen size for some reason. size.w = f64::max(size.w, self.view_size.w); @@ -707,7 +759,7 @@ impl<W: LayoutElement> Tile<W> { pub fn tile_expected_or_current_size(&self) -> Size<f64, Logical> { let mut size = self.window_expected_or_current_size(); - if self.is_fullscreen { + if self.sizing_mode.is_fullscreen() { // Normally we'd just return the fullscreen size here, but this makes things a bit // nicer if a fullscreen window is bigger than the fullscreen size for some reason. size.w = f64::max(size.w, self.view_size.w); @@ -836,8 +888,12 @@ impl<W: LayoutElement> Tile<W> { // The size request has to be i32 unfortunately, due to Wayland. We floor here instead of // round to avoid situations where proportionally-sized columns don't fit on the screen // exactly. - self.window - .request_size(size.to_i32_floor(), false, animate, transaction); + self.window.request_size( + size.to_i32_floor(), + SizingMode::Normal, + animate, + transaction, + ); } pub fn tile_width_for_window_width(&self, size: f64) -> f64 { @@ -872,9 +928,27 @@ impl<W: LayoutElement> Tile<W> { } } + pub fn request_maximized( + &mut self, + size: Size<f64, Logical>, + animate: bool, + transaction: Option<Transaction>, + ) { + self.window.request_size( + size.to_i32_round(), + SizingMode::Maximized, + animate, + transaction, + ); + } + pub fn request_fullscreen(&mut self, animate: bool, transaction: Option<Transaction>) { - self.window - .request_size(self.view_size.to_i32_round(), true, animate, transaction); + self.window.request_size( + self.view_size.to_i32_round(), + SizingMode::Fullscreen, + animate, + transaction, + ); } pub fn min_size_nonfullscreen(&self) -> Size<f64, Logical> { @@ -933,6 +1007,7 @@ impl<W: LayoutElement> Tile<W> { let scale = Scale::from(self.scale); let fullscreen_progress = self.fullscreen_progress(); + let expanded_progress = self.expanded_progress(); let win_alpha = if self.window.is_ignoring_opacity_window_rule() { 1. @@ -968,7 +1043,7 @@ impl<W: LayoutElement> Tile<W> { let radius = rules .geometry_corner_radius .unwrap_or_default() - .scaled_by(1. - fullscreen_progress as f32); + .scaled_by(1. - expanded_progress as f32); // If we're resizing, try to render a shader, or a fallback. let mut resize_shader = None; @@ -1153,7 +1228,7 @@ impl<W: LayoutElement> Tile<W> { .map_or(CornerRadius::default(), |radius| { radius.expanded_by(border_width as f32) }) - .scaled_by(1. - fullscreen_progress as f32); + .scaled_by(1. - expanded_progress as f32); let size = self.fullscreen_backdrop.size(); let color = self.fullscreen_backdrop.color(); @@ -1191,13 +1266,15 @@ impl<W: LayoutElement> Tile<W> { }); let rv = rv.chain(elem.into_iter().flatten()); - // Hide the focus ring when fullscreened. It's not normally visible anyway due to being - // outside the monitor, but it is visible in the overview (which is a bit weird). - let elem = (focus_ring && fullscreen_progress < 1.) + // Hide the focus ring when maximized/fullscreened. It's not normally visible anyway due to + // being outside the monitor or obscured by a solid colored bar, but it is visible under + // semitransparent bars in maximized state (which is a bit weird) and in the overview (also + // a bit weird). + let elem = (focus_ring && expanded_progress < 1.) .then(|| self.focus_ring.render(renderer, location).map(Into::into)); let rv = rv.chain(elem.into_iter().flatten()); - let elem = (fullscreen_progress < 1.) + let elem = (expanded_progress < 1.) .then(|| self.shadow.render(renderer, location).map(Into::into)); rv.chain(elem.into_iter().flatten()) } @@ -1328,7 +1405,7 @@ impl<W: LayoutElement> Tile<W> { pub fn verify_invariants(&self) { use approx::assert_abs_diff_eq; - assert_eq!(self.is_fullscreen, self.window.is_fullscreen()); + assert_eq!(self.sizing_mode, self.window.sizing_mode()); let scale = self.scale; let size = self.tile_size(); |
