Implement Continuum compositor and shell motion

This commit is contained in:
Samuel Onoja committed 2026-10-03 03:19:11 +01:00
1 parent 259f7e93b6
commit f8bc17e913
42 files changed
+1873 -647

No files matched your search

+61 -28
View File
@@ -82,6 +82,7 @@ impl Ferese {
if self.focus_swipe.is_some()
|| !self.paused_workspaces.is_empty()
|| !self.workspace_slides.is_empty()
|| !self.render.closing.is_empty()
|| !self.dismissing_popups.is_empty()
|| self.render.snapshots().next().is_some()
|| self.overview.needs_tick()
@@ -192,11 +193,22 @@ impl Ferese {
for (&id, record) in self.windows.records_mut() {
let selected = selected_window == Some(id);
let target = if selected { 1.0 } else { 0.0 };
let focus = record
.focus
.get_or_insert_with(|| crate::dimming::DimAnimation::new(target));
let focus = record.focus.get_or_insert_with(|| AnimatedValue::new(target));
let previous = focus.current;
active_animation |= focus.advance_visual(target, delta, duration);
let changed = focus.target != target;
focus.set_target(target);
if self.animations_enabled {
active_animation |= focus.advance(
if changed { Duration::ZERO } else { delta },
SpringConfig {
position_tolerance: 0.001,
velocity_tolerance: 0.001,
..self.spring_config
},
);
} else {
focus.snap();
}
dim_changed |= previous != focus.current;
}
@@ -217,19 +229,40 @@ impl Ferese {
dim_changed |= previous != dim.current;
}
let mut ready_to_close = Vec::new();
for (id, record) in self.windows.records_mut() {
let Some(animation) = &mut record.closing else { continue };
if animation.advance(delta, self.animations_enabled) {
ready_to_close.push(*id);
} else if !animation.close_sent {
active_animation = true;
for (_, record) in self.windows.records_mut() {
if let Some(opening) = &mut record.opening {
let active = if self.animations_enabled {
opening.advance(
delta,
SpringConfig {
position_tolerance: 0.00001,
velocity_tolerance: 0.00001,
..self.spring_config
},
)
} else {
opening.snap();
false
};
active_animation |= active;
if !active {
record.opening = None;
}
}
}
for id in ready_to_close {
self.send_window_close(id);
let mut finished = Vec::new();
self.render.closing.retain_mut(|window| {
let active =
self.animations_enabled && !self.session_lock.active() && window.advance(delta, self.spring_config);
if !active {
finished.push(window.id);
}
active_animation |= active;
active
});
for id in finished {
tracing::debug!(?id, "released close presentation");
self.render.remove_window(id);
}
// Include transactions expiring on this dispatch: their waiting time
@@ -325,8 +358,7 @@ impl Ferese {
viewport.current = swipe.position();
viewport.velocity = 0.0;
} else if self.animations_enabled {
active_animation |=
viewport.advance_with_policy(delta, self.viewport_spring_config, CrossingPolicy::NoCrossing);
active_animation |= viewport.advance(delta, self.viewport_spring_config);
} else {
viewport.snap();
}
@@ -379,8 +411,7 @@ impl Ferese {
if zooming {
sync_scrolling_coordinates(geometry, world_x, viewport, true);
} else if self.animations_enabled {
active_animation |=
world_x.advance_with_policy(delta, self.spring_config, CrossingPolicy::NoCrossing);
active_animation |= world_x.advance(delta, self.spring_config);
} else {
world_x.snap();
}
@@ -392,7 +423,7 @@ impl Ferese {
if let Some((_, width)) = record.coupled_width.as_mut() {
let width_active = if self.animations_enabled {
width.advance_with_policy(delta, self.viewport_spring_config, CrossingPolicy::NoCrossing)
width.advance(delta, self.viewport_spring_config)
} else {
width.snap();
false
@@ -462,6 +493,9 @@ impl Ferese {
self.send_shell_snapshots();
active_animation = true;
}
if !self.overview.is_active() {
self.retarget_overview();
}
active_animation |= self
.overview
.advance(delta, self.spring_config, self.animations_enabled);
@@ -525,10 +559,9 @@ impl Ferese {
else {
continue;
};
let progress = slide.progress_at(delta);
for item in &slide.items {
let position = item.start.between(item.target, progress);
let position = slide.position(item, delta);
offsets.insert(
item.workspace,
(position.x * f64::from(size.w), position.y * f64::from(size.h)),
@@ -573,7 +606,7 @@ fn record_needs_tick(
mapped: impl FnOnce() -> bool,
) -> bool {
if record.resize.is_some()
|| record.closing.as_ref().is_some_and(|close| !close.close_sent)
|| record.opening.is_some()
|| record.focus.as_ref().is_none_or(|motion| motion.needs_update(focus))
{
return true;
@@ -610,7 +643,7 @@ mod tests {
WindowRecord {
geometry: Some(geometry),
focus: Some(DimAnimation::new(1.0)),
focus: Some(AnimatedValue::new(1.0)),
dimming: Some(DimAnimation::new(0.0)),
..Default::default()
}
@@ -660,11 +693,11 @@ mod tests {
}
#[test]
fn close_requests_and_coupled_width_cleanup_still_get_their_final_tick() {
fn opening_and_coupled_width_cleanup_still_get_their_final_tick() {
let mut record = settled();
record.closing = Some(ClosingAnimation::default());
record.opening = Some(AnimatedValue::new(0.0));
assert!(record_needs_tick(&record, 1.0, 0.0, || false));
record.closing.as_mut().unwrap().close_sent = true;
record.opening = None;
assert!(!record_needs_tick(&record, 1.0, 0.0, || true));
record.coupled_width = Some((WorkspaceId(1), AnimatedValue::new(400.0)));
assert!(record_needs_tick(&record, 1.0, 0.0, || true));
@@ -871,7 +904,7 @@ mod tests {
// A settled transaction still has to flush its resume guard; otherwise
// the next unrelated animation would inherit an old paused workspace.
state.viewport_animations.get_mut(&workspace).unwrap().snap();
state.windows.record_mut(id).unwrap().focus = Some(DimAnimation::new(0.0));
state.windows.record_mut(id).unwrap().focus = Some(AnimatedValue::new(0.0));
state.windows.set_transaction(
id,
crate::resize_transaction::ResizeTransaction::new(3.into(), Duration::from_millis(800)),
+1 -5
View File
@@ -10,17 +10,13 @@ impl Ferese {
(window.toplevel().is_some()
&& self.window_content_ready(window)
&& self.windows.geometry(id).is_some()
&& self.output_workspaces.focused_output().is_some()
&& self.windows.record(*id).is_none_or(|record| record.closing.is_none()))
&& self.output_workspaces.focused_output().is_some())
.then_some(*id)
})
.collect()
}
pub(crate) fn focus_preview_output(&self, id: WindowId) -> Option<OutputId> {
if self.windows.record(id).is_some_and(|record| record.closing.is_some()) {
return None;
}
self.workspaces
.workspace_for_window(id)
.and_then(|workspace| self.output_workspaces.output_for_workspace(workspace))
+7 -3
View File
@@ -202,12 +202,16 @@ impl Ferese {
}
pub fn visual_scale_for_window(&self, window: &Window) -> Option<(f64, f64)> {
if !self.overview.is_presenting() {
let id = self.windows.ids().get(window)?;
if !self.overview.is_presenting() && self.windows.record(*id)?.opening.is_none() {
return Some((1.0, 1.0));
}
let id = self.windows.ids().get(window)?;
let geometry = self.windows.geometry(id)?;
let source = geometry.client.committed_size?;
let source = if self.overview.is_presenting() {
geometry.client.committed_size?
} else {
ClientSize::from_rect(geometry.visual.current)
};
let presented = self.presented_window_rect(*id)?;
if source.width <= 0 || source.height <= 0 {
+174
View File
@@ -0,0 +1,174 @@
use super::*;
use ferese_animation::AnimatedRect;
use smithay::backend::renderer::Texture;
use smithay::wayland::compositor::{BufferAssignment, SurfaceAttributes};
impl Ferese {
pub(crate) fn capture_close_before_commit(&mut self, surface: &WlSurface) {
// At this point cached attributes contain the committed removal, but
// on_commit_buffer_handler has not reset the previous renderer state.
let removed = with_states(surface, |states| {
matches!(
states.cached_state.get::<SurfaceAttributes>().current().buffer,
Some(BufferAssignment::Removed)
)
});
if removed
&& let Some(window) = self
.windows
.id_for_surface(surface)
.and_then(|id| self.windows.window(id))
.cloned()
{
self.retain_closed_window(&window);
}
}
pub(super) fn retain_closed_window(&mut self, window: &Window) {
if !self.animations_enabled || self.session_lock.active() || self.render.closing.len() >= 64 {
return;
}
let Some(&id) = self.windows.ids().get(window) else {
return;
};
if self.render.closing.iter().any(|window| window.id == id) || !self.window_content_ready(window) {
return;
}
let Some(output) = self
.space
.outputs()
.find(|output| self.window_belongs_to_output(id, output))
.cloned()
else {
return;
};
let Some(output_id) = self.output_id(&output) else {
return;
};
let Some(rect) = self.presented_window_rect(id) else {
return;
};
let record = self.windows.record(id).unwrap();
let geometry = record.geometry.unwrap();
let decorations = geometry.decorations.clamp(0.0, 1.0);
let focus = record.focus.as_ref().map_or(0.0, |focus| focus.current.clamp(0.0, 1.0));
let dim = record.dimming.as_ref().map_or(0.0, |dim| dim.current);
let mut opacity = record.opening.unwrap_or_else(|| AnimatedValue::new(1.0));
opacity.set_target(0.0);
let mut bounds = AnimatedRect::new(rect);
bounds.velocity = self.overview.presented_velocity(id).unwrap_or(geometry.visual.velocity);
if !self.overview.is_presenting()
&& let Some(slide) = self.workspace_slides.get(&output_id)
&& let Some(workspace) = self.workspaces.workspace_for_window(id)
&& let Some(item) = slide.items.iter().find(|item| item.workspace == workspace)
&& let Some(area) = self.space.output_geometry(&output)
{
bounds.velocity.x += item.velocity.x * f64::from(area.size.w);
bounds.velocity.y += item.velocity.y * f64::from(area.size.h);
}
if let Some(opening) = record.opening {
bounds.velocity = crate::presentation::scaled_visual_velocity(
self.overview.presented_rect(id, geometry.visual.current),
bounds.velocity,
0.97 + 0.03 * opening.current,
0.03 * opening.velocity,
);
}
bounds.set_target(crate::presentation::scaled_visual_rect(rect, 0.98));
let mut source_geometry = window.geometry();
if !self.overview.is_presenting() {
let size = ClientSize::from_rect(geometry.visual.current);
source_geometry.size = (size.width, size.height).into();
}
let order: Vec<_> = if self.overview.is_active() {
self.windows
.overview_windows()
.filter_map(|window| self.windows.ids().get(window).copied())
.collect()
} else {
self.space
.elements()
.rev()
.filter_map(|window| self.windows.ids().get(window).copied())
.collect()
};
let below = order
.iter()
.position(|candidate| *candidate == id)
.and_then(|index| order.get(index + 1))
.copied();
let used = self.render.snapshots().map(|snapshot| snapshot.bytes()).sum::<usize>()
+ self
.render
.closing
.iter()
.map(|window| window.snapshot.bytes())
.sum::<usize>();
let remaining = crate::presentation::SNAPSHOT_BUDGET.saturating_sub(used);
let scale = output.current_scale().fractional_scale();
let result = if let Some(backend) = &self.nested_backend {
match backend.try_borrow_mut() {
Ok(mut backend) => crate::render::capture_resize_snapshot(
backend.renderer(),
window,
source_geometry,
scale,
remaining,
),
Err(_) => return,
}
} else if let Some(backend) = &mut self.direct_backend {
backend.capture_resize_snapshot(window, source_geometry, &output, remaining)
} else {
return;
};
match result {
Ok(Some(snapshot)) => {
tracing::debug!(?id, bytes = snapshot.bytes(), "retained close presentation");
let size = snapshot.texture.size();
let material = window.toplevel().and_then(|toplevel| {
let surface = toplevel.wl_surface();
let (role, generation) = crate::effects::surface_role(surface)?;
(role == crate::effects::SemanticRole::Modal).then(|| {
(
surface.clone(),
role,
generation,
crate::effects::surface_opacity(surface),
)
})
});
let radius = if material.is_some() {
self.theme_settings.material_radius
} else {
self.theme_settings.window_radius
} * decorations;
let client = window.geometry().size;
let fill = (!self.overview.is_presenting()
&& material.is_none()
&& (client.w < source_geometry.size.w || client.h < source_geometry.size.h))
.then_some(self.theme_settings.surface_base_color.0);
self.render.closing.push(crate::render::ClosedWindow {
id,
output: output_id,
below,
bounds,
opacity,
snapshot,
source: Rectangle::from_size((f64::from(size.w), f64::from(size.h)).into()),
radius,
shape: crate::render::window_corner_shape(window),
focus,
decorations,
dim,
fill,
material,
});
self.last_animation_tick = Instant::now();
self.defer_output_redraw(output);
}
Ok(None) => {}
Err(error) => tracing::debug!(%error, ?id, "close snapshot unavailable"),
}
}
}
+50 -8
View File
@@ -34,9 +34,10 @@ impl Ferese {
if workspace.fullscreen.is_some() || !matches!(workspace.layout, WorkspaceLayout::Scrolling(_)) {
return;
}
let Ok(Some(to)) = workspace.layout.directional_neighbor(from, direction, bounds) else {
let Ok(neighbor) = workspace.layout.directional_neighbor(from, direction, bounds) else {
return;
};
let to = neighbor.unwrap_or(from);
let workspace_id = workspace.id;
let start = self
.viewport_animations
@@ -45,7 +46,8 @@ impl Ferese {
.or_else(|| workspace.layout.viewport_x())
.unwrap_or(0.0);
let mut candidate = workspace.layout.clone();
if let WorkspaceLayout::Scrolling(layout) = &mut candidate
if to != from
&& let WorkspaceLayout::Scrolling(layout) = &mut candidate
&& layout.slide_focus_from(from, to).is_err()
{
return;
@@ -63,7 +65,11 @@ impl Ferese {
direction,
gesture_direction,
start,
destination: candidate.viewport_x().unwrap_or(start),
destination: if to == from {
start
} else {
candidate.viewport_x().unwrap_or(start)
},
progress,
});
self.swipe.mark_preview_started();
@@ -91,7 +97,9 @@ impl Ferese {
// Release from the last input position even if no frame rendered that update.
if current && let Some(viewport) = self.viewport_animations.get_mut(&swipe.workspace) {
viewport.current = swipe.position();
viewport.velocity = 0.0;
viewport.velocity = swipe
.release_velocity(self.swipe.release_velocity, self.swipe.unbounded_release_velocity)
/ self.animation_speed;
}
let neighbor = self.output_bounds().and_then(|bounds| {
self.workspaces
@@ -460,6 +468,11 @@ impl Ferese {
};
let previous = self.workspace_slides.remove(&output);
let mut slide = WorkspaceSlide::new(previous, from, to, direction);
slide.spring = SpringConfig {
position_tolerance: 0.00001,
velocity_tolerance: 0.00001,
..self.viewport_spring_config
};
slide.held_progress = Some(progress);
slide.gesture = Some((from, to, direction));
self.workspace_slides.insert(output, slide);
@@ -481,7 +494,7 @@ impl Ferese {
&& self.activate_managed_workspace_from_swipe(to, expected);
if !committed {
if let Some(slide) = self.workspace_slides.get_mut(&output) {
slide.release(false);
slide.release_with_velocity(false, self.swipe.release_velocity / self.animation_speed);
}
self.last_animation_tick = Instant::now();
self.relayout();
@@ -497,26 +510,55 @@ impl Ferese {
workspace: WorkspaceId,
slide_direction: Option<SwipeDirection>,
) {
if owner != requested_output {
return;
}
let mut previous_slide = self.workspace_slides.remove(&owner);
if previous == Some(workspace) {
if let Some(slide) = previous_slide {
self.workspace_slides.insert(owner, slide);
}
return;
}
if owner == requested_output
&& let Some(slide) = previous_slide.as_mut()
&& slide
.gesture
.is_some_and(|(_, to, direction)| to == workspace && Some(direction) == slide_direction)
{
slide.release(true);
slide.release_with_velocity(true, self.swipe.release_velocity / self.animation_speed);
self.last_animation_tick = Instant::now();
self.workspace_slides.insert(owner, previous_slide.unwrap());
return;
}
let slide_direction = slide_direction.or_else(|| {
let from = previous?;
let ids = self
.output_workspaces
.ordered_workspace_ids(&self.workspaces, owner)
.collect::<Vec<_>>();
let from = ids.iter().position(|id| *id == from)?;
let to = ids.iter().position(|id| *id == workspace)?;
Some(if to > from {
SwipeDirection::Up
} else {
SwipeDirection::Down
})
});
if owner == requested_output
&& let (Some(from), Some(direction)) = (previous, slide_direction)
&& from != workspace
&& self.animations_enabled
&& !self.overview.is_presenting()
{
self.last_animation_tick = Instant::now();
self.workspace_slides
.insert(owner, WorkspaceSlide::new(previous_slide, from, workspace, direction));
let mut slide = WorkspaceSlide::new(previous_slide, from, workspace, direction);
slide.spring = SpringConfig {
position_tolerance: 0.00001,
velocity_tolerance: 0.00001,
..self.viewport_spring_config
};
self.workspace_slides.insert(owner, slide);
}
}
+51 -24
View File
@@ -7,13 +7,15 @@ pub(crate) struct FrameScene {
pub overview: crate::overview::OverviewMotion,
// Plane selection and scheduling reuse the activity sampled with this scene.
pub animating: bool,
pub delta: Duration,
}
pub(crate) struct WindowFrame {
pub geometry: WindowGeometry,
pub rect: Rect,
pub close_scale: f64,
pub close_alpha: f32,
pub scale_content: bool,
pub native_size: Option<ClientSize>,
pub alpha: f32,
pub focus: f64,
pub dim: f64,
}
@@ -68,7 +70,12 @@ impl Ferese {
}
pub(crate) fn output_has_animations(&self, output: &Output) -> bool {
if self.overview.is_animating(self.spring_config)
if self
.render
.closing
.iter()
.any(|window| Some(window.output) == self.output_id(output))
|| self.overview.is_animating(self.spring_config)
|| self
.dismissing_popups
.iter()
@@ -80,7 +87,7 @@ impl Ferese {
if self
.output_id(output)
.and_then(|id| self.workspace_slides.get(&id))
.is_some_and(|slide| slide.held_progress.is_none() && slide.elapsed < WORKSPACE_SLIDE_DURATION)
.is_some_and(|slide| slide.held_progress.is_none() && slide.moving())
{
return true;
}
@@ -95,8 +102,8 @@ impl Ferese {
}
if record.resize.is_some()
|| record.opening.is_some()
|| self.render.snapshot(&id).is_some()
|| record.closing.as_ref().is_some_and(|close| !close.close_sent)
|| record.focus.as_ref().is_some_and(|focus| focus.is_animating())
|| record.dimming.as_ref().is_some_and(|dim| dim.is_animating())
{
@@ -117,15 +124,10 @@ impl Ferese {
.focus_swipe
.as_ref()
.is_some_and(|swipe| swipe.workspace == workspace);
!held
&& viewport.advance_with_policy(
Duration::ZERO,
self.viewport_spring_config,
CrossingPolicy::NoCrossing,
)
!held && viewport.advance(Duration::ZERO, self.viewport_spring_config)
}) || record.coupled_width.as_ref().is_some_and(|(_, width)| {
let mut width = *width;
width.advance_with_policy(Duration::ZERO, self.viewport_spring_config, CrossingPolicy::NoCrossing)
width.advance(Duration::ZERO, self.viewport_spring_config)
})
})
}
@@ -187,12 +189,19 @@ impl Ferese {
rect.y += y;
}
let mut close = record.closing.as_ref().copied().unwrap_or_default();
if record.closing.is_some() && !delta.is_zero() {
close.advance(delta, true);
let mut opening = record.opening.unwrap_or_else(|| AnimatedValue::new(1.0));
if !self.animations_enabled {
opening.snap();
} else if !delta.is_zero() {
opening.advance(
delta,
SpringConfig {
position_tolerance: 0.00001,
velocity_tolerance: 0.00001,
..self.spring_config
},
);
}
let eased = smoothstep(close.progress);
let focused = if self.overview.is_active() {
self.overview_selected(id)
} else {
@@ -211,14 +220,31 @@ impl Ferese {
motion.current
};
let mut focus = record
.focus
.unwrap_or_else(|| AnimatedValue::new(if focused { 1.0 } else { 0.0 }));
if !delta.is_zero() {
focus.advance(
delta,
SpringConfig {
position_tolerance: 0.001,
velocity_tolerance: 0.001,
..self.spring_config
},
);
}
let presence_scale = 0.97 + 0.03 * opening.current;
let native_size = (!overview.is_presenting() && presence_scale != 1.0).then(|| ClientSize::from_rect(rect));
let rect = crate::presentation::scaled_visual_rect(rect, presence_scale);
windows.insert(
id,
WindowFrame {
geometry,
rect,
close_scale: 1.0 - eased * 0.02,
close_alpha: (1.0 - eased) as f32,
focus: sample_dim(record.focus.as_ref(), if focused { 1.0 } else { 0.0 }),
scale_content: overview.is_presenting(),
native_size,
alpha: opening.current.clamp(0.0, 1.0) as f32,
focus: focus.current.clamp(0.0, 1.0),
dim: sample_dim(record.dimming.as_ref(), 0.0),
},
);
@@ -228,6 +254,7 @@ impl Ferese {
windows,
overview,
animating,
delta,
}
}
}
@@ -252,9 +279,9 @@ fn predict_geometry(
if let Some((mut world, mut viewport, held)) = world
&& !zooming
{
world.advance_with_policy(delta, spring, CrossingPolicy::NoCrossing);
world.advance(delta, spring);
if !held {
viewport.advance_with_policy(delta, viewport_spring, CrossingPolicy::NoCrossing);
viewport.advance(delta, viewport_spring);
}
predicted.visual.current.x = world.current - viewport.current;
@@ -262,7 +289,7 @@ fn predict_geometry(
}
if let Some(mut width) = width {
width.advance_with_policy(delta, viewport_spring, CrossingPolicy::NoCrossing);
width.advance(delta, viewport_spring);
predicted.visual.current.width = width.current;
predicted.visual.velocity.width = width.velocity;
}
@@ -454,7 +481,7 @@ mod tests {
state.focused_window = Some(id);
state
.windows
.update(id, |record| record.focus = Some(crate::dimming::DimAnimation::new(1.0)));
.update(id, |record| record.focus = Some(AnimatedValue::new(1.0)));
state.focused_window = Some(other_id);
assert!(state.output_has_pending_visual_changes(&output));
state.animations_enabled = false;
+5 -4
View File
@@ -21,9 +21,10 @@ pub(crate) struct WindowRecord {
pub title: String,
pub metadata_revision: u64,
pub resize: Option<ResizeTransaction>,
pub focus: Option<crate::dimming::DimAnimation>,
pub focus: Option<AnimatedValue>,
pub dimming: Option<crate::dimming::DimAnimation>,
pub(super) closing: Option<super::ClosingAnimation>,
pub opening: Option<AnimatedValue>,
pub mapped_once: bool,
pub world_x: Option<(WorkspaceId, AnimatedValue)>,
pub coupled_width: Option<(WorkspaceId, AnimatedValue)>,
pub maximized: bool,
@@ -302,8 +303,8 @@ mod tests {
record.resize_anchor = Some((true, false, Rect::new(0., 0., 800., 600.)));
record.column_width_pending = true;
record.rules_applied = true;
record.focus = Some(crate::dimming::DimAnimation::new(1.0));
record.closing = Some(super::super::ClosingAnimation::default());
record.focus = Some(AnimatedValue::new(1.0));
record.opening = Some(AnimatedValue::new(0.0));
record.world_x = Some((WorkspaceId(1), AnimatedValue::new(50.0)));
record.coupled_width = Some((WorkspaceId(1), AnimatedValue::new(800.0)));
});
+24 -22
View File
@@ -417,6 +417,7 @@ impl Ferese {
}
pub fn remove_tiled_window(&mut self, window: &Window) {
self.retain_closed_window(window);
// Uncommitted toplevels have not entered a workspace yet.
self.space.unmap_elem(window);
let Some(id) = self.windows.remove(window) else {
@@ -493,7 +494,13 @@ impl Ferese {
return;
}
let used: usize = self.render.snapshots().map(|snapshot| snapshot.bytes()).sum();
let used: usize = self.render.snapshots().map(|snapshot| snapshot.bytes()).sum::<usize>()
+ self
.render
.closing
.iter()
.map(|window| window.snapshot.bytes())
.sum::<usize>();
let remaining = crate::presentation::SNAPSHOT_BUDGET.saturating_sub(used);
let result = if let Some(backend) = &self.nested_backend {
// Commit dispatch does not run inside the Winit event callback;
@@ -557,6 +564,20 @@ impl Ferese {
return;
};
if let Some(record) = self.windows.record_mut(id)
&& !record.mapped_once
{
record.mapped_once = true;
if self.animations_enabled {
record.opening = Some(AnimatedValue {
current: 0.0,
target: 1.0,
velocity: 0.0,
});
self.last_animation_tick = Instant::now();
}
}
let fullscreen = self
.workspaces
.workspace_for_window(id)
@@ -648,35 +669,16 @@ impl Ferese {
}
pub fn close_focused_window(&mut self) {
let Some(focused) = self.focused_window else {
return;
};
if !self.animations_enabled {
if let Some(focused) = self.focused_window {
self.send_window_close(focused);
return;
}
self.windows.update(focused, |record| {
record.closing.get_or_insert_default();
});
crate::backends::direct::render_window(self, focused);
}
pub(crate) fn close_managed_window(&mut self, id: WindowId) -> bool {
if self.windows.window(id).is_none() {
return false;
}
if self.animations_enabled {
self.windows.update(id, |record| {
record.closing.get_or_insert_default();
});
crate::backends::direct::render_window(self, id);
} else {
self.send_window_close(id);
}
self.send_window_close(id);
true
}