Implement window close transaction

Mainly visible with disabled animations.
This commit is contained in:
Ivan Molodetskikh
2024-08-23 15:41:06 +03:00
parent dfe3580607
commit c8839f7658
7 changed files with 172 additions and 39 deletions
+5 -2
View File
@@ -20,6 +20,7 @@ use smithay::{delegate_compositor, delegate_shm};
use super::xdg_shell::add_mapped_toplevel_pre_commit_hook;
use crate::niri::{ClientState, State};
use crate::utils::send_scale_transform;
use crate::utils::transaction::Transaction;
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped};
impl CompositorHandler for State {
@@ -193,11 +194,13 @@ impl CompositorHandler for State {
});
// Must start the close animation before window.on_commit().
let transaction = Transaction::new();
if !is_mapped {
let blocker = transaction.blocker();
self.backend.with_primary_renderer(|renderer| {
self.niri
.layout
.start_close_animation_for_window(renderer, &window);
.start_close_animation_for_window(renderer, &window, blocker);
});
}
@@ -216,7 +219,7 @@ impl CompositorHandler for State {
id: u64::from(id.get()),
});
self.niri.layout.remove_window(&window);
self.niri.layout.remove_window(&window, transaction);
self.add_default_dmabuf_pre_commit_hook(surface);
if was_active {
+6 -2
View File
@@ -40,6 +40,7 @@ use crate::input::resize_grab::ResizeGrab;
use crate::input::DOUBLE_CLICK_TIME;
use crate::layout::workspace::ColumnWidth;
use crate::niri::{PopupGrabState, State};
use crate::utils::transaction::Transaction;
use crate::utils::{get_monotonic_time, send_scale_transform, ResizeEdge};
use crate::window::{InitialConfigureState, ResolvedWindowRules, Unmapped, WindowRef};
@@ -485,16 +486,19 @@ impl XdgShellHandler for State {
self.backend.with_primary_renderer(|renderer| {
self.niri.layout.store_unmap_snapshot(renderer, &window);
});
let transaction = Transaction::new();
let blocker = transaction.blocker();
self.backend.with_primary_renderer(|renderer| {
self.niri
.layout
.start_close_animation_for_window(renderer, &window);
.start_close_animation_for_window(renderer, &window, blocker);
});
let active_window = self.niri.layout.active_window().map(|(m, _)| &m.window);
let was_active = active_window == Some(&window);
self.niri.layout.remove_window(&window);
self.niri.layout.remove_window(&window, transaction);
self.add_default_dmabuf_pre_commit_hook(surface.wl_surface());
if was_active {
+72 -7
View File
@@ -12,6 +12,7 @@ use smithay::backend::renderer::element::{Kind, RenderElement};
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture, Uniform};
use smithay::backend::renderer::Texture;
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
use smithay::wayland::compositor::{Blocker, BlockerState};
use crate::animation::Animation;
use crate::niri_render_elements;
@@ -21,6 +22,7 @@ use crate::render_helpers::shaders::{mat3_uniform, ProgramType, Shaders};
use crate::render_helpers::snapshot::RenderSnapshot;
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
use crate::render_helpers::{render_to_encompassing_texture, RenderTarget};
use crate::utils::transaction::TransactionBlocker;
#[derive(Debug)]
pub struct ClosingWindow {
@@ -46,7 +48,7 @@ pub struct ClosingWindow {
blocked_out_buffer_offset: Point<f64, Logical>,
/// The closing animation.
anim: Animation,
anim_state: AnimationState,
/// Random seed for the shader.
random_seed: f32,
@@ -59,6 +61,29 @@ niri_render_elements! {
}
}
#[derive(Debug)]
enum AnimationState {
Waiting {
/// Blocker for a transaction before starting the animation.
blocker: TransactionBlocker,
anim: Animation,
},
Animating(Animation),
}
impl AnimationState {
pub fn new(blocker: TransactionBlocker, anim: Animation) -> Self {
if blocker.state() == BlockerState::Pending {
Self::Waiting { blocker, anim }
} else {
// This actually doesn't normally happen because the window is removed only after the
// closing animation is created. Though, it does happen with disable-transactions debug
// flag.
Self::Animating(anim)
}
}
}
impl ClosingWindow {
pub fn new<E: RenderElement<GlesRenderer>>(
renderer: &mut GlesRenderer,
@@ -66,6 +91,7 @@ impl ClosingWindow {
scale: Scale<f64>,
geo_size: Size<f64, Logical>,
pos: Point<f64, Logical>,
blocker: TransactionBlocker,
anim: Animation,
) -> anyhow::Result<Self> {
let _span = tracy_client::span!("ClosingWindow::new");
@@ -107,17 +133,29 @@ impl ClosingWindow {
pos,
buffer_offset,
blocked_out_buffer_offset,
anim,
anim_state: AnimationState::new(blocker, anim),
random_seed: fastrand::f32(),
})
}
pub fn advance_animations(&mut self, current_time: Duration) {
self.anim.set_current_time(current_time);
match &mut self.anim_state {
AnimationState::Waiting { blocker, anim } => {
if blocker.state() != BlockerState::Pending {
let mut anim = anim.restarted(0., 1., 0.);
anim.set_current_time(current_time);
self.anim_state = AnimationState::Animating(anim);
}
}
AnimationState::Animating(anim) => anim.set_current_time(current_time),
}
}
pub fn are_animations_ongoing(&self) -> bool {
!self.anim.is_done()
match &self.anim_state {
AnimationState::Waiting { .. } => true,
AnimationState::Animating(anim) => !anim.is_done(),
}
}
pub fn render(
@@ -127,15 +165,42 @@ impl ClosingWindow {
scale: Scale<f64>,
target: RenderTarget,
) -> ClosingWindowRenderElement {
let progress = self.anim.value();
let clamped_progress = self.anim.clamped_value().clamp(0., 1.);
let (buffer, offset) = if target.should_block_out(self.block_out_from) {
(&self.blocked_out_buffer, self.blocked_out_buffer_offset)
} else {
(&self.buffer, self.buffer_offset)
};
let anim = match &self.anim_state {
AnimationState::Waiting { .. } => {
let elem = TextureRenderElement::from_texture_buffer(
buffer.clone(),
Point::from((0., 0.)),
1.,
None,
None,
Kind::Unspecified,
);
let elem = PrimaryGpuTextureRenderElement(elem);
let elem = RescaleRenderElement::from_element(elem, Point::from((0, 0)), 1.);
let mut location = self.pos + offset;
location.x -= view_rect.loc.x;
let elem = RelocateRenderElement::from_element(
elem,
location.to_physical_precise_round(scale),
Relocate::Relative,
);
return elem.into();
}
AnimationState::Animating(anim) => anim,
};
let progress = anim.value();
let clamped_progress = anim.clamped_value().clamp(0., 1.);
if Shaders::get(renderer).program(ProgramType::Close).is_some() {
let area_loc = Vec2::new(view_rect.loc.x as f32, view_rect.loc.y as f32);
let area_size = Vec2::new(view_rect.size.w as f32, view_rect.size.h as f32);
+18 -14
View File
@@ -52,7 +52,7 @@ use crate::render_helpers::snapshot::RenderSnapshot;
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
use crate::render_helpers::texture::TextureBuffer;
use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements};
use crate::utils::transaction::Transaction;
use crate::utils::transaction::{Transaction, TransactionBlocker};
use crate::utils::{output_size, round_logical_in_physical_max1, ResizeEdge};
use crate::window::ResolvedWindowRules;
@@ -752,15 +752,13 @@ impl<W: LayoutElement> Layout<W> {
);
}
pub fn remove_window(&mut self, window: &W::Id) -> Option<W> {
let mut rv = None;
pub fn remove_window(&mut self, window: &W::Id, transaction: Transaction) -> Option<W> {
match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => {
for mon in monitors {
for (idx, ws) in mon.workspaces.iter_mut().enumerate() {
if ws.has_window(window) {
rv = Some(ws.remove_window(window));
let win = ws.remove_window(window, transaction);
// Clean up empty workspaces that are not active and not last.
if !ws.has_windows()
@@ -776,7 +774,7 @@ impl<W: LayoutElement> Layout<W> {
}
}
break;
return Some(win);
}
}
}
@@ -784,20 +782,20 @@ impl<W: LayoutElement> Layout<W> {
MonitorSet::NoOutputs { workspaces, .. } => {
for (idx, ws) in workspaces.iter_mut().enumerate() {
if ws.has_window(window) {
rv = Some(ws.remove_window(window));
let win = ws.remove_window(window, transaction);
// Clean up empty workspaces.
if !ws.has_windows() && workspaces[idx].name.is_none() {
workspaces.remove(idx);
}
break;
return Some(win);
}
}
}
}
rv
None
}
pub fn update_window(&mut self, window: &W::Id, serial: Option<Serial>) {
@@ -1970,7 +1968,12 @@ impl<W: LayoutElement> Layout<W> {
let width = column.width;
let is_full_width = column.is_full_width;
let window = ws
.remove_tile_by_idx(ws.active_column_idx, column.active_tile_idx, None)
.remove_tile_by_idx(
ws.active_column_idx,
column.active_tile_idx,
Transaction::new(),
None,
)
.into_window();
let workspace_idx = monitors[new_idx].active_workspace_idx;
@@ -2022,7 +2025,7 @@ impl<W: LayoutElement> Layout<W> {
let Some(width) = width else { return };
let window = self.remove_window(window).unwrap();
let window = self.remove_window(window, Transaction::new()).unwrap();
if let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set {
let new_idx = monitors
@@ -2425,6 +2428,7 @@ impl<W: LayoutElement> Layout<W> {
&mut self,
renderer: &mut GlesRenderer,
window: &W::Id,
blocker: TransactionBlocker,
) {
let _span = tracy_client::span!("Layout::start_close_animation_for_window");
@@ -2433,7 +2437,7 @@ impl<W: LayoutElement> Layout<W> {
for mon in monitors {
for ws in &mut mon.workspaces {
if ws.has_window(window) {
ws.start_close_animation_for_window(renderer, window);
ws.start_close_animation_for_window(renderer, window, blocker);
return;
}
}
@@ -2442,7 +2446,7 @@ impl<W: LayoutElement> Layout<W> {
MonitorSet::NoOutputs { workspaces, .. } => {
for ws in workspaces {
if ws.has_window(window) {
ws.start_close_animation_for_window(renderer, window);
ws.start_close_animation_for_window(renderer, window, blocker);
return;
}
}
@@ -3143,7 +3147,7 @@ mod tests {
layout.add_window_to_named_workspace(&ws_name, win, None, false);
}
Op::CloseWindow(id) => {
layout.remove_window(&id);
layout.remove_window(&id, Transaction::new());
}
Op::FullscreenWindow(id) => {
layout.toggle_fullscreen(&id);
+19 -3
View File
@@ -19,6 +19,7 @@ use crate::input::swipe_tracker::SwipeTracker;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::RenderTarget;
use crate::rubber_band::RubberBand;
use crate::utils::transaction::Transaction;
use crate::utils::{output_size, to_physical_precise_round, ResizeEdge};
/// Amount of touchpad movement to scroll the height of one workspace.
@@ -442,7 +443,12 @@ impl<W: LayoutElement> Monitor<W> {
let width = column.width;
let is_full_width = column.is_full_width;
let window = workspace
.remove_tile_by_idx(workspace.active_column_idx, column.active_tile_idx, None)
.remove_tile_by_idx(
workspace.active_column_idx,
column.active_tile_idx,
Transaction::new(),
None,
)
.into_window();
self.add_window(new_idx, window, true, width, is_full_width);
@@ -465,7 +471,12 @@ impl<W: LayoutElement> Monitor<W> {
let width = column.width;
let is_full_width = column.is_full_width;
let window = workspace
.remove_tile_by_idx(workspace.active_column_idx, column.active_tile_idx, None)
.remove_tile_by_idx(
workspace.active_column_idx,
column.active_tile_idx,
Transaction::new(),
None,
)
.into_window();
self.add_window(new_idx, window, true, width, is_full_width);
@@ -488,7 +499,12 @@ impl<W: LayoutElement> Monitor<W> {
let width = column.width;
let is_full_width = column.is_full_width;
let window = workspace
.remove_tile_by_idx(workspace.active_column_idx, column.active_tile_idx, None)
.remove_tile_by_idx(
workspace.active_column_idx,
column.active_tile_idx,
Transaction::new(),
None,
)
.into_window();
self.add_window(new_idx, window, true, width, is_full_width);
+46 -11
View File
@@ -22,7 +22,7 @@ use crate::niri_render_elements;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::RenderTarget;
use crate::utils::id::IdCounter;
use crate::utils::transaction::Transaction;
use crate::utils::transaction::{Transaction, TransactionBlocker};
use crate::utils::{output_size, send_scale_transform, ResizeEdge};
use crate::window::ResolvedWindowRules;
@@ -1098,6 +1098,7 @@ impl<W: LayoutElement> Workspace<W> {
&mut self,
column_idx: usize,
window_idx: usize,
transaction: Transaction,
anim_config: Option<niri_config::Animation>,
) -> Tile<W> {
let offset = self.column_x(column_idx + 1) - self.column_x(column_idx);
@@ -1139,7 +1140,7 @@ impl<W: LayoutElement> Workspace<W> {
offset
} else {
column.active_tile_idx = min(column.active_tile_idx, column.tiles.len() - 1);
column.update_tile_sizes(true);
column.update_tile_sizes_with_transaction(true, transaction);
self.data[column_idx].update(column);
prev_width - column.width()
@@ -1294,7 +1295,7 @@ impl<W: LayoutElement> Workspace<W> {
column
}
pub fn remove_window(&mut self, window: &W::Id) -> W {
pub fn remove_window(&mut self, window: &W::Id, transaction: Transaction) -> W {
let column_idx = self
.columns
.iter()
@@ -1303,7 +1304,7 @@ impl<W: LayoutElement> Workspace<W> {
let column = &self.columns[column_idx];
let window_idx = column.position(window).unwrap();
self.remove_tile_by_idx(column_idx, window_idx, None)
self.remove_tile_by_idx(column_idx, window_idx, transaction, None)
.into_window()
}
@@ -1490,6 +1491,7 @@ impl<W: LayoutElement> Workspace<W> {
&mut self,
renderer: &mut GlesRenderer,
window: &W::Id,
blocker: TransactionBlocker,
) {
let output_scale = Scale::from(self.scale.fractional_scale());
@@ -1542,7 +1544,21 @@ impl<W: LayoutElement> Workspace<W> {
let anim = Animation::new(0., 1., 0., self.options.animations.window_close.anim);
let res = ClosingWindow::new(renderer, snapshot, output_scale, tile_size, tile_pos, anim);
let blocker = if self.options.disable_transactions {
TransactionBlocker::completed()
} else {
blocker
};
let res = ClosingWindow::new(
renderer,
snapshot,
output_scale,
tile_size,
tile_pos,
blocker,
anim,
);
match res {
Ok(closing) => {
self.closing_windows.push(closing);
@@ -1781,6 +1797,7 @@ impl<W: LayoutElement> Workspace<W> {
let tile = self.remove_tile_by_idx(
source_col_idx,
0,
Transaction::new(),
Some(self.options.animations.window_movement.0),
);
self.enter_output_for_window(tile.window());
@@ -1813,7 +1830,12 @@ impl<W: LayoutElement> Workspace<W> {
let mut offset = Point::from((source_column.render_offset().x, 0.));
let tile = self.remove_tile_by_idx(source_col_idx, source_column.active_tile_idx, None);
let tile = self.remove_tile_by_idx(
source_col_idx,
source_column.active_tile_idx,
Transaction::new(),
None,
);
self.add_tile_at(
self.active_column_idx,
@@ -1861,6 +1883,7 @@ impl<W: LayoutElement> Workspace<W> {
let tile = self.remove_tile_by_idx(
source_col_idx,
0,
Transaction::new(),
Some(self.options.animations.window_movement.0),
);
self.enter_output_for_window(tile.window());
@@ -1888,7 +1911,12 @@ impl<W: LayoutElement> Workspace<W> {
let width = source_column.width;
let is_full_width = source_column.is_full_width;
let tile = self.remove_tile_by_idx(source_col_idx, source_column.active_tile_idx, None);
let tile = self.remove_tile_by_idx(
source_col_idx,
source_column.active_tile_idx,
Transaction::new(),
None,
);
self.add_tile(
tile,
@@ -1921,7 +1949,7 @@ impl<W: LayoutElement> Workspace<W> {
let mut offset = Point::from((offset, 0.));
let prev_off = self.columns[source_column_idx].tile_offset(0);
let tile = self.remove_tile_by_idx(source_column_idx, 0, None);
let tile = self.remove_tile_by_idx(source_column_idx, 0, Transaction::new(), None);
self.enter_output_for_window(tile.window());
let prev_next_x = self.column_x(self.active_column_idx + 1);
@@ -1970,8 +1998,12 @@ impl<W: LayoutElement> Workspace<W> {
let width = source_column.width;
let is_full_width = source_column.is_full_width;
let tile =
self.remove_tile_by_idx(self.active_column_idx, source_column.active_tile_idx, None);
let tile = self.remove_tile_by_idx(
self.active_column_idx,
source_column.active_tile_idx,
Transaction::new(),
None,
);
self.add_tile(
tile,
@@ -3031,6 +3063,10 @@ impl<W: LayoutElement> Column<W> {
}
fn update_tile_sizes(&mut self, animate: bool) {
self.update_tile_sizes_with_transaction(animate, Transaction::new());
}
fn update_tile_sizes_with_transaction(&mut self, animate: bool, transaction: Transaction) {
if self.is_fullscreen {
self.tiles[0].request_fullscreen(self.view_size);
return;
@@ -3195,7 +3231,6 @@ impl<W: LayoutElement> Column<W> {
assert_eq!(auto_tiles_left, 0);
}
let transaction = Transaction::new();
for (tile, h) in zip(&mut self.tiles, heights) {
let WindowHeight::Fixed(height) = h else {
unreachable!()
+6
View File
@@ -148,6 +148,12 @@ impl Drop for Transaction {
}
}
impl TransactionBlocker {
pub fn completed() -> Self {
Self(Weak::new())
}
}
impl Blocker for TransactionBlocker {
fn state(&self) -> BlockerState {
if self.0.upgrade().map_or(true, |x| x.is_completed()) {