diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/layout/scrolling.rs | 23 | ||||
| -rw-r--r-- | src/layout/tile.rs | 278 |
2 files changed, 207 insertions, 94 deletions
diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs index 04788bca..78a072ff 100644 --- a/src/layout/scrolling.rs +++ b/src/layout/scrolling.rs @@ -2778,11 +2778,10 @@ impl<W: LayoutElement> ScrollingSpace<W> { } pub fn set_fullscreen(&mut self, window: &W::Id, is_fullscreen: bool) -> bool { - let (mut col_idx, tile_idx) = self + let mut col_idx = self .columns .iter() - .enumerate() - .find_map(|(col_idx, col)| col.position(window).map(|tile_idx| (col_idx, tile_idx))) + .position(|col| col.contains(window)) .unwrap(); if is_fullscreen == self.columns[col_idx].is_pending_fullscreen { @@ -2796,21 +2795,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { if is_fullscreen && (col.tiles.len() > 1 && !is_tabbed) { // This wasn't the only window in its column; extract it into a separate column. - let activate = self.active_column_idx == col_idx && col.active_tile_idx == tile_idx; - - let removed = self.remove_tile_by_idx(col_idx, tile_idx, Transaction::new(), None); - // Create a column manually to disable the resize animation. - let column = Column::new_with_tile( - removed.tile, - self.view_size, - self.working_area, - self.scale, - removed.width, - removed.is_full_width, - false, - ); - self.add_column(Some(col_idx + 1), column, activate, None); - + self.consume_or_expel_window_right(Some(window)); col_idx += 1; col = &mut self.columns[col_idx]; } @@ -4881,7 +4866,7 @@ impl<W: LayoutElement> Column<W> { } self.is_pending_fullscreen = is_fullscreen; - self.update_tile_sizes(false); + self.update_tile_sizes(true); } fn set_column_display(&mut self, display: ColumnDisplay) { diff --git a/src/layout/tile.rs b/src/layout/tile.rs index cc399cc8..89c9f93d 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -138,6 +138,13 @@ struct ResizeAnimation { size_from: Size<f64, Logical>, snapshot: LayoutElementRenderSnapshot, offscreen: OffscreenBuffer, + tile_size_from: Size<f64, Logical>, + // If the resize involved the fullscreen state at some point, this is the progress toward the + // fullscreen state. Used for things like fullscreen backdrop alpha. + // + // 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>, } #[derive(Debug)] @@ -178,7 +185,7 @@ impl<W: LayoutElement> Tile<W> { focus_ring: FocusRing::new(focus_ring_config.into()), shadow: Shadow::new(shadow_config), is_fullscreen, - fullscreen_backdrop: SolidColorBuffer::new(view_size, [0., 0., 0., 1.]), + fullscreen_backdrop: SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.]), unfullscreen_to_floating: false, floating_window_size: None, floating_pos: None, @@ -229,8 +236,6 @@ impl<W: LayoutElement> Tile<W> { let shadow_config = rules.shadow.resolve_against(self.options.shadow); self.shadow.update_config(shadow_config); - - self.fullscreen_backdrop.resize(view_size); } pub fn update_shaders(&mut self) { @@ -240,28 +245,67 @@ impl<W: LayoutElement> Tile<W> { } pub fn update_window(&mut self) { + let was_fullscreen = self.is_fullscreen; self.is_fullscreen = self.window.is_fullscreen(); if let Some(animate_from) = self.window.take_animation_snapshot() { - let (size_from, offscreen) = if let Some(resize) = self.resize_animation.take() { + let params = if let Some(resize) = self.resize_animation.take() { // Compute like in animated_window_size(), but using the snapshot geometry (since // the current one is already overwritten). let mut size = animate_from.size; let val = resize.anim.value(); let size_from = resize.size_from; + let tile_size_from = resize.tile_size_from; size.w = size_from.w + (size.w - size_from.w) * val; size.h = size_from.h + (size.h - size_from.h) * val; + let mut tile_size = animate_from.size; + if was_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() { + let width = self.border.width(); + tile_size.w += width * 2.; + tile_size.h += width * 2.; + } + + tile_size.w = tile_size_from.w + (tile_size.w - tile_size_from.w) * val; + tile_size.h = tile_size_from.h + (tile_size.h - tile_size_from.h) * val; + + let fullscreen_from = resize + .fullscreen_progress + .map(|anim| anim.clamped_value().clamp(0., 1.)) + .unwrap_or(if was_fullscreen { 1. } else { 0. }); + // Also try to reuse the existing offscreen buffer if we have one. - (size, resize.offscreen) + (size, tile_size, fullscreen_from, resize.offscreen) } else { - (animate_from.size, OffscreenBuffer::default()) + let size = animate_from.size; + + // Compute like in tile_size(). + let mut tile_size = size; + if was_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() { + let width = self.border.width(); + tile_size.w += width * 2.; + tile_size.h += width * 2.; + } + + let fullscreen_from = if was_fullscreen { 1. } else { 0. }; + + (size, tile_size, fullscreen_from, OffscreenBuffer::default()) }; + let (size_from, tile_size_from, fullscreen_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()); + let tile_change = self.tile_size().to_f64().to_point() - tile_size_from.to_point(); + let tile_change = f64::max(tile_change.x.abs(), tile_change.y.abs()); + let change = f64::max(change, tile_change); if change > RESIZE_ANIMATION_THRESHOLD { let anim = Animation::new( self.clock.clone(), @@ -270,11 +314,18 @@ impl<W: LayoutElement> Tile<W> { 0., self.options.animations.window_resize.anim, ); + + let fullscreen_to = if self.is_fullscreen { 1. } else { 0. }; + let fullscreen_progress = (fullscreen_from != fullscreen_to) + .then(|| anim.restarted(fullscreen_from, fullscreen_to, 0.)); + self.resize_animation = Some(ResizeAnimation { anim, size_from, snapshot: animate_from, offscreen, + tile_size_from, + fullscreen_progress, }); } else { self.resize_animation = None; @@ -349,24 +400,30 @@ 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_window_size = self.animated_window_size(); let animated_tile_size = self.animated_tile_size(); + let fullscreen_progress = self.fullscreen_progress(); let draw_border_with_background = rules .draw_border_with_background - .unwrap_or_else(|| !self.window.has_ssd()); - let border_width = self.effective_border_width().unwrap_or(0.); - let radius = if self.is_fullscreen { - CornerRadius::default() - } else { - rules - .geometry_corner_radius - .map_or(CornerRadius::default(), |radius| { - radius.expanded_by(border_width as f32) - }) - }; + .unwrap_or_else(|| !self.window.has_ssd()) + && fullscreen_progress < 1.; + let border_width = self.visual_border_width().unwrap_or(0.); + + // Do the inverse of tile_size() in order to handle the unfullscreen animation for windows + // that were smaller than the fullscreen size, and therefore their animated_window_size() is + // currently much smaller than the tile size. + let mut border_window_size = animated_tile_size; + border_window_size.w -= border_width * 2.; + border_window_size.h -= border_width * 2.; + + let radius = rules + .geometry_corner_radius + .map_or(CornerRadius::default(), |radius| { + radius.expanded_by(border_width as f32) + }) + .scaled_by(1. - fullscreen_progress as f32); self.border.update_render_elements( - animated_window_size, + border_window_size, is_active, !draw_border_with_background, self.window.is_urgent(), @@ -379,24 +436,25 @@ impl<W: LayoutElement> Tile<W> { 1., ); - let radius = if self.is_fullscreen { - CornerRadius::default() - } else if self.effective_border_width().is_some() { + let radius = if self.visual_border_width().is_some() { radius } else { - rules.geometry_corner_radius.unwrap_or_default() + rules + .geometry_corner_radius + .unwrap_or_default() + .scaled_by(1. - fullscreen_progress as f32) }; self.shadow .update_render_elements(animated_tile_size, is_active, radius, self.scale, 1.); - let draw_focus_ring_with_background = if self.effective_border_width().is_some() { - false - } else { + let draw_focus_ring_with_background = if self.border.is_off() && fullscreen_progress < 1. { draw_border_with_background + } else { + false }; let radius = radius.expanded_by(self.focus_ring.width() as f32); self.focus_ring.update_render_elements( - self.animated_tile_size(), + animated_tile_size, is_active, !draw_focus_ring_with_background, self.window.is_urgent(), @@ -405,6 +463,8 @@ impl<W: LayoutElement> Tile<W> { self.scale, 1., ); + + self.fullscreen_backdrop.resize(animated_tile_size); } pub fn scale(&self) -> f64 { @@ -544,6 +604,20 @@ impl<W: LayoutElement> Tile<W> { self.is_fullscreen } + fn fullscreen_progress(&self) -> f64 { + if let Some(resize) = &self.resize_animation { + if let Some(anim) = &resize.fullscreen_progress { + return anim.clamped_value().clamp(0., 1.); + } + } + + if self.is_fullscreen { + 1. + } else { + 0. + } + } + /// 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 { @@ -557,33 +631,47 @@ impl<W: LayoutElement> Tile<W> { Some(self.border.width()) } + fn visual_border_width(&self) -> Option<f64> { + if self.border.is_off() { + return None; + } + + let fullscreen_progress = self.fullscreen_progress(); + + // Only hide the border when fully fullscreen to avoid jarring border appearance. + if fullscreen_progress == 1. { + return None; + } + + // FIXME: would be cool to, like, gradually resize the border from full width to 0 during + // fullscreening, but the rest of the code isn't quite ready for that yet. It needs to + // handle things like computing intermediate tile size when an animated resize starts during + // an animated unfullscreen resize. + Some(self.border.width()) + } + /// Returns the location of the window's visual geometry within this Tile. pub fn window_loc(&self) -> Point<f64, Logical> { let mut loc = Point::from((0., 0.)); - // In fullscreen, center the window in the given size. - if self.is_fullscreen { - let window_size = self.window_size(); - let target_size = self.view_size; - - // Windows aren't supposed to be larger than the fullscreen size, but in case we get - // one, leave it at the top-left as usual. - if window_size.w < target_size.w { - loc.x += (target_size.w - window_size.w) / 2.; - } - if window_size.h < target_size.h { - loc.y += (target_size.h - window_size.h) / 2.; - } - - // Round to physical pixels. - loc = loc - .to_physical_precise_round(self.scale) - .to_logical(self.scale); - } + let window_size = self.animated_window_size(); + let target_size = self.animated_tile_size(); - if let Some(width) = self.effective_border_width() { - loc += (width, width).into(); - } + // Center the window within its tile. + // + // - Without borders, the sizes match, so this difference is zero. + // - Borders always match from all sides, so this difference is pre-rounded to physical. + // - In fullscreen, if the window is smaller than the tile, then it gets centered, otherwise + // the tile size matches the window. + // - During animations, the window remains centered within the tile; this is important for + // the to/from fullscreen animation. + loc.x += (target_size.w - window_size.w) / 2.; + loc.y += (target_size.h - window_size.h) / 2.; + + // Round to physical pixels. + loc = loc + .to_physical_precise_round(self.scale) + .to_logical(self.scale); loc } @@ -661,19 +749,17 @@ impl<W: LayoutElement> Tile<W> { } pub fn animated_tile_size(&self) -> Size<f64, Logical> { - let mut size = self.animated_window_size(); + let mut size = self.tile_size(); - if self.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); - size.h = f64::max(size.h, self.view_size.h); - return size; - } + if let Some(resize) = &self.resize_animation { + let val = resize.anim.value(); + let size_from = resize.tile_size_from.to_f64(); - if let Some(width) = self.effective_border_width() { - size.w += width * 2.; - size.h += width * 2.; + size.w = f64::max(1., size_from.w + (size.w - size_from.w) * val); + size.h = f64::max(1., size_from.h + (size.h - size_from.h) * val); + size = size + .to_physical_precise_round(self.scale) + .to_logical(self.scale); } size @@ -837,11 +923,16 @@ impl<W: LayoutElement> Tile<W> { let _span = tracy_client::span!("Tile::render_inner"); let scale = Scale::from(self.scale); + let fullscreen_progress = self.fullscreen_progress(); - let win_alpha = if self.is_fullscreen || self.window.is_ignoring_opacity_window_rule() { + let win_alpha = if self.window.is_ignoring_opacity_window_rule() { 1. } else { - self.window.rules().opacity.unwrap_or(1.).clamp(0., 1.) + let alpha = self.window.rules().opacity.unwrap_or(1.).clamp(0., 1.); + + // Interpolate towards alpha = 1. at fullscreen. + let p = fullscreen_progress as f32; + alpha * (1. - p) + 1. * p }; // This is here rather than in render_offset() because render_offset() is currently assumed @@ -861,8 +952,14 @@ impl<W: LayoutElement> Tile<W> { let area = Rectangle::new(window_render_loc, animated_window_size); let rules = self.window.rules(); - let clip_to_geometry = !self.is_fullscreen && rules.clip_to_geometry == Some(true); - let radius = rules.geometry_corner_radius.unwrap_or_default(); + + // Clip to geometry including during the fullscreen animation to help with buggy clients + // that submit a full-sized buffer before acking the fullscreen state (Firefox). + let clip_to_geometry = fullscreen_progress < 1. && rules.clip_to_geometry == Some(true); + let radius = rules + .geometry_corner_radius + .unwrap_or_default() + .scaled_by(1. - fullscreen_progress as f32); // If we're resizing, try to render a shader, or a fallback. let mut resize_shader = None; @@ -956,6 +1053,7 @@ impl<W: LayoutElement> Tile<W> { let mut window_surface = None; let mut window_popups = None; let mut rounded_corner_damage = None; + let has_border_shader = BorderRenderElement::has_shader(renderer); if resize_shader.is_none() && resize_fallback.is_none() { let window = self .window @@ -965,7 +1063,6 @@ impl<W: LayoutElement> Tile<W> { let radius = radius.fit_to(window_size.w as f32, window_size.h as f32); let clip_shader = ClippedSurfaceRenderElement::shader(renderer).cloned(); - let has_border_shader = BorderRenderElement::has_shader(renderer); if clip_to_geometry && clip_shader.is_some() { let damage = self.rounded_corner_damage.element(); @@ -1035,18 +1132,50 @@ impl<W: LayoutElement> Tile<W> { .chain(rounded_corner_damage) .chain(window_surface.into_iter().flatten()); - let elem = self.is_fullscreen.then(|| { - SolidColorRenderElement::from_buffer( - &self.fullscreen_backdrop, - location, - 1., - Kind::Unspecified, - ) - .into() + let elem = (fullscreen_progress > 0.).then(|| { + let alpha = fullscreen_progress as f32; + + // During the un/fullscreen animation, render a border element in order to use the + // animated corner radius. + if fullscreen_progress < 1. && has_border_shader { + let border_width = self.visual_border_width().unwrap_or(0.); + let radius = rules + .geometry_corner_radius + .map_or(CornerRadius::default(), |radius| { + radius.expanded_by(border_width as f32) + }) + .scaled_by(1. - fullscreen_progress as f32); + + let size = self.fullscreen_backdrop.size(); + let color = self.fullscreen_backdrop.color(); + BorderRenderElement::new( + size, + Rectangle::from_size(size), + GradientInterpolation::default(), + Color::from_color32f(color), + Color::from_color32f(color), + 0., + Rectangle::from_size(size), + 0., + radius, + scale.x as f32, + alpha, + ) + .with_location(location) + .into() + } else { + SolidColorRenderElement::from_buffer( + &self.fullscreen_backdrop, + location, + alpha, + Kind::Unspecified, + ) + .into() + } }); let rv = rv.chain(elem); - let elem = self.effective_border_width().map(|width| { + let elem = self.visual_border_width().map(|width| { self.border .render(renderer, location + Point::from((width, width))) .map(Into::into) @@ -1186,7 +1315,6 @@ impl<W: LayoutElement> Tile<W> { use approx::assert_abs_diff_eq; assert_eq!(self.is_fullscreen, self.window.is_fullscreen()); - assert_eq!(self.fullscreen_backdrop.size(), self.view_size); let scale = self.scale; let size = self.tile_size(); |
