mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
Implement window close transaction
Mainly visible with disabled animations.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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!()
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user