aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/layout/scrolling.rs23
-rw-r--r--src/layout/tile.rs278
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();