micro optimization fixes
This commit is contained in:
1 parent
0d7203f84b
commit
da60ecb719
36 files changed
+1781
-521
No files matched your search
@@ -20,7 +20,56 @@ impl Ferese {
|
||||
self.last_animation_tick = Instant::now();
|
||||
}
|
||||
|
||||
fn animations_need_tick(&self) -> bool {
|
||||
if self.focus_swipe.is_some()
|
||||
|| !self.workspace_slides.is_empty()
|
||||
|| !self.dismissing_popups.is_empty()
|
||||
|| self.render.snapshots().next().is_some()
|
||||
|| self.overview.needs_tick()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
let moving = |value: &AnimatedValue| value.current != value.target || value.velocity != 0.0;
|
||||
|
||||
if self.viewport_animations.iter().any(|(workspace, viewport)| {
|
||||
self.output_workspaces
|
||||
.output_for_workspace(*workspace)
|
||||
.is_some_and(|output| self.output_workspaces.active_workspace(output) == Some(*workspace))
|
||||
&& moving(viewport)
|
||||
}) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let selected = if self.overview.is_active() {
|
||||
self.overview.selected()
|
||||
} else {
|
||||
self.focused_window
|
||||
};
|
||||
|
||||
self.windows.records().any(|(&id, record)| {
|
||||
let focus = if selected == Some(id) { 1.0 } else { 0.0 };
|
||||
let dim = crate::dimming::target(
|
||||
self.inactive_dim,
|
||||
self.focused_window,
|
||||
id,
|
||||
self.overview.is_presenting(),
|
||||
);
|
||||
|
||||
record_needs_tick(record, focus, dim, || {
|
||||
self.windows
|
||||
.window(id)
|
||||
.is_some_and(|window| self.space.element_location(window).is_some())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn advance_animations_by(&mut self, delta: std::time::Duration) -> bool {
|
||||
if !self.animations_need_tick() {
|
||||
self.sync_window_stacking();
|
||||
return false;
|
||||
}
|
||||
|
||||
let delta = delta.mul_f64(self.animation_speed);
|
||||
if self
|
||||
.focus_swipe
|
||||
@@ -30,21 +79,6 @@ impl Ferese {
|
||||
self.focus_swipe = None;
|
||||
}
|
||||
|
||||
let visible_workspaces = if self.viewport_animations.is_empty() {
|
||||
HashSet::new()
|
||||
} else {
|
||||
self.space
|
||||
.outputs()
|
||||
.filter_map(|output| self.output_ids.get(output))
|
||||
.filter_map(|output| self.output_workspaces.active_workspace(*output))
|
||||
.collect::<HashSet<_>>()
|
||||
};
|
||||
|
||||
let windows = self
|
||||
.space
|
||||
.elements()
|
||||
.filter_map(|window| self.windows.ids().get(window).copied().map(|id| (window.clone(), id)))
|
||||
.collect::<Vec<_>>();
|
||||
let mut active_animation = false;
|
||||
let mut completed_slides = Vec::new();
|
||||
for (output, slide) in &mut self.workspace_slides {
|
||||
@@ -102,9 +136,12 @@ impl Ferese {
|
||||
dim_changed |= previous != focus.current;
|
||||
}
|
||||
|
||||
for (_, id) in &windows {
|
||||
let target = crate::dimming::target(dim_settings, self.focused_window, *id, self.overview.is_presenting());
|
||||
let Some(record) = self.windows.record_mut(*id) else {
|
||||
for window in self.space.elements() {
|
||||
let Some(id) = self.windows.ids().get(window).copied() else {
|
||||
continue;
|
||||
};
|
||||
let target = crate::dimming::target(dim_settings, self.focused_window, id, self.overview.is_presenting());
|
||||
let Some(record) = self.windows.record_mut(id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -184,7 +221,11 @@ impl Ferese {
|
||||
// Keep scheduling frames while waiting, so the deadline cannot stall.
|
||||
active_animation |= !blocked_workspaces.is_empty();
|
||||
for (workspace, viewport) in &mut self.viewport_animations {
|
||||
if !visible_workspaces.contains(workspace) {
|
||||
if !self
|
||||
.output_workspaces
|
||||
.output_for_workspace(*workspace)
|
||||
.is_some_and(|output| self.output_workspaces.active_workspace(output) == Some(*workspace))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -202,8 +243,13 @@ impl Ferese {
|
||||
}
|
||||
}
|
||||
|
||||
let mut settled_coupled_widths = Vec::new();
|
||||
for (window, id) in windows {
|
||||
let mut remaps = Vec::new();
|
||||
|
||||
for window in self.space.elements() {
|
||||
let Some(id) = self.windows.ids().get(window).copied() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if self
|
||||
.workspaces
|
||||
.workspace_for_window(id)
|
||||
@@ -229,6 +275,11 @@ impl Ferese {
|
||||
geometry.visual.velocity.width = 0.0;
|
||||
}
|
||||
active_animation |= geometry.advance(delta, self.spring_config, self.animations_enabled);
|
||||
|
||||
if zooming != geometry.is_zooming() {
|
||||
self.stacking_cache.invalidate();
|
||||
}
|
||||
|
||||
if let Some(target) = coupled_target {
|
||||
geometry.visual.target.width = target;
|
||||
}
|
||||
@@ -262,7 +313,7 @@ impl Ferese {
|
||||
geometry.visual.current.width = width.current;
|
||||
geometry.visual.velocity.width = width.velocity;
|
||||
if !width_active {
|
||||
settled_coupled_widths.push(id);
|
||||
record.coupled_width = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,12 +338,18 @@ impl Ferese {
|
||||
}
|
||||
|
||||
let visual = geometry.visual.current;
|
||||
self.space
|
||||
.map_element(window, (visual.x.round() as i32, visual.y.round() as i32), false);
|
||||
let location = (visual.x.round() as i32, visual.y.round() as i32).into();
|
||||
|
||||
if self.space.element_location(window) != Some(location) {
|
||||
remaps.push((id, location));
|
||||
}
|
||||
}
|
||||
|
||||
for id in settled_coupled_widths {
|
||||
self.windows.update(id, |record| record.coupled_width = None);
|
||||
for (id, location) in remaps {
|
||||
if let Some(window) = self.windows.window(id) {
|
||||
self.space.map_element(window.clone(), location, false);
|
||||
self.stacking_cache.remapped();
|
||||
}
|
||||
}
|
||||
|
||||
if !completed_slides.is_empty() {
|
||||
@@ -320,6 +377,7 @@ impl Ferese {
|
||||
active_animation |= self
|
||||
.overview
|
||||
.advance(delta, self.spring_config, self.animations_enabled);
|
||||
self.refresh_workspace_slide_offsets();
|
||||
self.sync_window_stacking();
|
||||
|
||||
if active_animation || dim_changed {
|
||||
@@ -337,48 +395,173 @@ impl Ferese {
|
||||
}
|
||||
|
||||
pub(crate) fn workspace_slide_offset(&self, window: WindowId) -> (f64, f64) {
|
||||
self.workspace_slide_offset_at(window, Duration::ZERO)
|
||||
self.workspaces
|
||||
.workspace_for_window(window)
|
||||
.and_then(|workspace| self.workspace_slide_offsets.get(&workspace).copied())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(super) fn workspace_slide_offset_at(&self, window: WindowId, delta: Duration) -> (f64, f64) {
|
||||
let Some(workspace) = self.workspaces.workspace_for_window(window) else {
|
||||
return (0.0, 0.0);
|
||||
};
|
||||
pub(super) fn sample_workspace_slide_offsets(
|
||||
&self,
|
||||
output: Option<OutputId>,
|
||||
delta: Duration,
|
||||
) -> HashMap<WorkspaceId, (f64, f64)> {
|
||||
let mut offsets = HashMap::new();
|
||||
self.fill_workspace_slide_offsets(output, delta, &mut offsets);
|
||||
offsets
|
||||
}
|
||||
|
||||
let Some(output_id) = self
|
||||
.workspace_slides
|
||||
.iter()
|
||||
.find_map(|(output, slide)| slide.contains(workspace).then_some(*output))
|
||||
.or_else(|| self.output_workspaces.output_for_workspace(workspace))
|
||||
else {
|
||||
return (0.0, 0.0);
|
||||
};
|
||||
pub(super) fn refresh_workspace_slide_offsets(&mut self) {
|
||||
// Reuse the input snapshot's allocation. Predicted render snapshots
|
||||
// remain separate and never change hit-testing or gesture state.
|
||||
let mut offsets = std::mem::take(&mut self.workspace_slide_offsets);
|
||||
offsets.clear();
|
||||
self.fill_workspace_slide_offsets(None, Duration::ZERO, &mut offsets);
|
||||
self.workspace_slide_offsets = offsets;
|
||||
}
|
||||
|
||||
let Some(slide) = self.workspace_slides.get(&output_id) else {
|
||||
return (0.0, 0.0);
|
||||
};
|
||||
fn fill_workspace_slide_offsets(
|
||||
&self,
|
||||
output: Option<OutputId>,
|
||||
delta: Duration,
|
||||
offsets: &mut HashMap<WorkspaceId, (f64, f64)>,
|
||||
) {
|
||||
for (output_id, slide) in &self.workspace_slides {
|
||||
if output.is_some_and(|output| output != *output_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(size) = self
|
||||
.output_ids
|
||||
.iter()
|
||||
.find(|(_, id)| **id == output_id)
|
||||
.and_then(|(output, _)| self.space.output_geometry(output))
|
||||
.map(|geometry| geometry.size)
|
||||
else {
|
||||
return (0.0, 0.0);
|
||||
};
|
||||
let Some(size) = self
|
||||
.outputs_by_id
|
||||
.get(output_id)
|
||||
.and_then(|output| self.space.output_geometry(output))
|
||||
.map(|geometry| geometry.size)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let progress = slide.progress_at(delta);
|
||||
|
||||
let mut slide = slide.clone();
|
||||
if slide.held_progress.is_none() && !delta.is_zero() {
|
||||
slide.advance(delta);
|
||||
for item in &slide.items {
|
||||
let position = item.start.between(item.target, progress);
|
||||
offsets.insert(
|
||||
item.workspace,
|
||||
(position.x * f64::from(size.w), position.y * f64::from(size.h)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
slide.offset(workspace, f64::from(size.w), f64::from(size.h))
|
||||
}
|
||||
|
||||
pub(crate) fn cancel_workspace_slides(&mut self) -> bool {
|
||||
let active = !self.workspace_slides.is_empty();
|
||||
self.workspace_slides.clear();
|
||||
self.workspace_slide_offsets.clear();
|
||||
active
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect current targets, not the previous frame's activity. Target changes
|
||||
// and client waits must wake a desktop that was settled on its last tick.
|
||||
fn record_needs_tick(
|
||||
record: &super::window_registry::WindowRecord,
|
||||
focus: f64,
|
||||
dim: f64,
|
||||
mapped: impl FnOnce() -> bool,
|
||||
) -> bool {
|
||||
if record.resize.is_some()
|
||||
|| record.closing.as_ref().is_some_and(|close| !close.close_sent)
|
||||
|| record.focus.as_ref().is_none_or(|motion| motion.needs_update(focus))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
let moving = |value: &AnimatedValue| value.current != value.target || value.velocity != 0.0;
|
||||
let pending = record.dimming.as_ref().is_none_or(|motion| motion.needs_update(dim))
|
||||
|| record.world_x.as_ref().is_some_and(|(_, world)| moving(world))
|
||||
|| record.coupled_width.is_some()
|
||||
|| record.geometry.is_some_and(|geometry| {
|
||||
geometry.is_zooming()
|
||||
|| geometry.presentation_changed
|
||||
|| geometry.client.waiting_for_commit()
|
||||
|| geometry.visual.current != geometry.visual.target
|
||||
|| geometry.visual.velocity != ferese_animation::RectVelocity::default()
|
||||
|| (!record.natural_floating_pending
|
||||
&& geometry.client.last_configured_size != Some(ClientSize::from_rect(geometry.logical)))
|
||||
});
|
||||
|
||||
// Hidden geometry and dimming do not advance in the full path.
|
||||
// A stale hidden target must not keep waking the whole desktop.
|
||||
pending && mapped()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::window_registry::WindowRecord;
|
||||
use super::*;
|
||||
|
||||
fn settled() -> WindowRecord {
|
||||
let rect = Rect::new(0.0, 0.0, 400.0, 300.0);
|
||||
let mut geometry = WindowGeometry::new(rect, Some(ClientSize::from_rect(rect)));
|
||||
geometry.presentation_changed = false;
|
||||
|
||||
WindowRecord {
|
||||
geometry: Some(geometry),
|
||||
focus: Some(DimAnimation::new(1.0)),
|
||||
dimming: Some(DimAnimation::new(0.0)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idle_check_skips_mapping_lookup_but_new_focus_and_dim_targets_wake_it() {
|
||||
let record = settled();
|
||||
|
||||
for _ in 0..100 {
|
||||
assert!(!record_needs_tick(&record, 1.0, 0.0, || panic!(
|
||||
"settled record needs no mapping lookup"
|
||||
)));
|
||||
}
|
||||
|
||||
assert!(record_needs_tick(&record, 0.0, 0.0, || true));
|
||||
assert!(record_needs_tick(&record, 1.0, 0.15, || true));
|
||||
assert!(!record_needs_tick(&record, 1.0, 0.15, || false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn geometry_motion_client_waits_and_resize_barriers_cannot_be_skipped() {
|
||||
let mut record = settled();
|
||||
record
|
||||
.geometry
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.visual
|
||||
.set_target(Rect::new(10.0, 0.0, 400.0, 300.0));
|
||||
assert!(record_needs_tick(&record, 1.0, 0.0, || true));
|
||||
assert!(!record_needs_tick(&record, 1.0, 0.0, || false));
|
||||
|
||||
let mut record = settled();
|
||||
record.geometry.as_mut().unwrap().client.request_size(
|
||||
ClientSize {
|
||||
width: 500,
|
||||
height: 300,
|
||||
},
|
||||
Duration::ZERO,
|
||||
);
|
||||
assert!(record_needs_tick(&record, 1.0, 0.0, || true));
|
||||
record.resize = Some(crate::resize_transaction::ResizeTransaction::new(
|
||||
1.into(),
|
||||
Duration::ZERO,
|
||||
));
|
||||
assert!(record_needs_tick(&record, 1.0, 0.0, || false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_requests_and_coupled_width_cleanup_still_get_their_final_tick() {
|
||||
let mut record = settled();
|
||||
record.closing = Some(ClosingAnimation::default());
|
||||
assert!(record_needs_tick(&record, 1.0, 0.0, || false));
|
||||
record.closing.as_mut().unwrap().close_sent = true;
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,7 @@ impl Ferese {
|
||||
Some(WindowPlacement::Floating { rect }) => rect,
|
||||
None => return,
|
||||
};
|
||||
if let Some(handle) = self
|
||||
.windows
|
||||
.ids()
|
||||
.iter()
|
||||
.find_map(|(handle, id)| (*id == window).then_some(handle.clone()))
|
||||
{
|
||||
if let Some(handle) = self.windows.window(window).cloned() {
|
||||
if matches!(
|
||||
self.workspaces.placement(window),
|
||||
Some(WindowPlacement::Floating { .. })
|
||||
@@ -178,6 +173,10 @@ impl Ferese {
|
||||
.geometry
|
||||
.get_or_insert_with(|| WindowGeometry::new(rect, client_size(window)));
|
||||
|
||||
if geometry.is_zooming() {
|
||||
self.stacking_cache.invalidate();
|
||||
}
|
||||
|
||||
if let Some(size) = geometry.follow_pointer(rect, self.start_time.elapsed())
|
||||
&& let Some(toplevel) = window.toplevel()
|
||||
{
|
||||
@@ -188,7 +187,7 @@ impl Ferese {
|
||||
}
|
||||
self.windows.update(id, |record| record.world_x = None);
|
||||
self.windows.update(id, |record| record.coupled_width = None);
|
||||
self.space.map_element(window.clone(), location, false);
|
||||
self.map_window_geometry(window.clone(), location);
|
||||
self.sync_window_stacking();
|
||||
// Move/resize/cancel callbacks run with Smithay's pointer mutex held.
|
||||
// Cursor rendering reads current_location(), which would lock it again.
|
||||
@@ -269,14 +268,7 @@ impl Ferese {
|
||||
let (app, parent) = Self::floating_metadata(window);
|
||||
let parent_rect = parent
|
||||
.as_ref()
|
||||
.and_then(|surface| {
|
||||
self.windows.ids().iter().find_map(|(window, id)| {
|
||||
window
|
||||
.toplevel()
|
||||
.is_some_and(|top| top.wl_surface() == surface)
|
||||
.then_some(*id)
|
||||
})
|
||||
})
|
||||
.and_then(|surface| self.windows.id_for_surface(surface))
|
||||
.and_then(|parent| self.presented_window_rect(parent))
|
||||
.and_then(|rect| intersection(rect, work));
|
||||
let dialog = crate::window_rules::is_native_dialog(app.as_deref());
|
||||
@@ -420,10 +412,9 @@ impl Ferese {
|
||||
.workspaces
|
||||
.workspace_for_window(window)
|
||||
.and_then(|workspace| self.output_workspaces.output_for_workspace(workspace));
|
||||
self.output_ids
|
||||
.iter()
|
||||
.find(|(_, id)| Some(**id) == output_id)
|
||||
.and_then(|(output, _)| self.output_bounds_for(output))
|
||||
output_id
|
||||
.and_then(|id| self.outputs_by_id.get(&id))
|
||||
.and_then(|output| self.output_bounds_for(output))
|
||||
.or_else(|| self.output_bounds())
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ impl Ferese {
|
||||
let available = self.focus_candidates();
|
||||
if !preview {
|
||||
self.cancel_focus_cycle();
|
||||
let order = self.focus_history.candidates(self.focused_window, available.clone());
|
||||
let order = self.focus_history.candidates(self.focused_window, &available);
|
||||
let mut cycle = ferese_core::FocusCycle::new(order, self.focused_window);
|
||||
if let Some(id) = cycle.advance(reverse, &available) {
|
||||
self.activate_managed_window(id);
|
||||
@@ -57,7 +57,7 @@ impl Ferese {
|
||||
return;
|
||||
}
|
||||
if self.focus_cycle.is_none() {
|
||||
let order = self.focus_history.candidates(self.focused_window, available.clone());
|
||||
let order = self.focus_history.candidates(self.focused_window, &available);
|
||||
if order.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,10 +166,7 @@ impl Ferese {
|
||||
};
|
||||
|
||||
if overview_active {
|
||||
let mut candidates = self.windows.ids().keys().cloned().collect::<Vec<_>>();
|
||||
// Match Overview's front-to-back render order during overlapping motion.
|
||||
candidates.sort_by_key(|window| std::cmp::Reverse(self.windows.ids().get(window).map_or(0, |id| id.0)));
|
||||
candidates.iter().find_map(hit)
|
||||
self.windows.overview_windows().find_map(hit)
|
||||
} else {
|
||||
self.space.elements().rev().find_map(hit)
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ impl Ferese {
|
||||
// Consume idle time before setting new targets; it must not become a
|
||||
// large first animation step after a keypress on an idle desktop.
|
||||
self.advance_animations(Instant::now());
|
||||
self.stacking_cache.invalidate();
|
||||
self.arrange_layers();
|
||||
let mut previous_scrolling_world_x = self.windows.take_world_positions();
|
||||
let pending_column_width_cycles = self.windows.take_column_width_requests();
|
||||
@@ -303,7 +304,7 @@ impl Ferese {
|
||||
|
||||
let location = (visual.x.round() as i32, visual.y.round() as i32);
|
||||
|
||||
self.space.map_element(window.clone(), location, false);
|
||||
self.map_window_geometry(window.clone(), location.into());
|
||||
if let Some(toplevel) = window.toplevel() {
|
||||
let state_changed = toplevel.with_pending_state(|state| {
|
||||
if natural_pending {
|
||||
@@ -372,39 +373,66 @@ impl Ferese {
|
||||
}
|
||||
|
||||
self.space.raise_element(window, activate);
|
||||
self.stacking_cache.remapped();
|
||||
self.sync_window_stacking();
|
||||
}
|
||||
|
||||
pub(crate) fn invalidate_window_stacking(&mut self) {
|
||||
self.stacking_cache.invalidate();
|
||||
}
|
||||
|
||||
pub(crate) fn sync_window_stacking(&mut self) {
|
||||
let mut windows = self.space.elements().cloned().collect::<Vec<_>>();
|
||||
windows.sort_by_key(|window| {
|
||||
let id = self.windows.ids().get(window).copied();
|
||||
let geometry = id.and_then(|id| self.windows.geometry(&id));
|
||||
let floating =
|
||||
id.is_some_and(|id| matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. })));
|
||||
let priority = crate::stacking::layer_priority(
|
||||
floating,
|
||||
geometry.is_some_and(|geometry| geometry.is_zooming()),
|
||||
geometry.is_some_and(|geometry| geometry.is_fullscreen()),
|
||||
floating
|
||||
&& id.is_some_and(|id| {
|
||||
self.floating_above_fullscreen.get(&id).is_some_and(|parent| {
|
||||
self.workspaces
|
||||
.workspace_for_window(id)
|
||||
.and_then(|workspace| self.workspaces.workspace(workspace))
|
||||
.is_some_and(|workspace| workspace.fullscreen == Some(*parent))
|
||||
})
|
||||
}),
|
||||
);
|
||||
(priority, id.map_or(usize::MAX, |id| self.window_stack.rank(id)))
|
||||
});
|
||||
if stacking_order_settled(self.space.elements(), &windows, |window| window.z_index()) {
|
||||
let revision = self.window_stack.revision();
|
||||
let rebuild = self.stacking_cache.needs_rebuild(revision, self.space.elements().len());
|
||||
|
||||
if !rebuild && !self.stacking_cache.needs_restore() {
|
||||
return;
|
||||
}
|
||||
|
||||
for window in windows {
|
||||
self.space.raise_element(&window, false);
|
||||
if rebuild {
|
||||
let mut windows = self.space.elements().cloned().collect::<Vec<_>>();
|
||||
windows.sort_by_key(|window| {
|
||||
let id = self.windows.ids().get(window).copied();
|
||||
let geometry = id.and_then(|id| self.windows.geometry(&id));
|
||||
let floating = id
|
||||
.is_some_and(|id| matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. })));
|
||||
let priority = crate::stacking::layer_priority(
|
||||
floating,
|
||||
geometry.is_some_and(|geometry| geometry.is_zooming()),
|
||||
geometry.is_some_and(|geometry| geometry.is_fullscreen()),
|
||||
floating
|
||||
&& id.is_some_and(|id| {
|
||||
self.floating_above_fullscreen.get(&id).is_some_and(|parent| {
|
||||
self.workspaces
|
||||
.workspace_for_window(id)
|
||||
.and_then(|workspace| self.workspaces.workspace(workspace))
|
||||
.is_some_and(|workspace| workspace.fullscreen == Some(*parent))
|
||||
})
|
||||
}),
|
||||
);
|
||||
(priority, id.map_or(usize::MAX, |id| self.window_stack.rank(id)))
|
||||
});
|
||||
self.stacking_cache.replace(windows, revision);
|
||||
}
|
||||
|
||||
if !stacking_order_settled(self.space.elements(), self.stacking_cache.order(), |window| {
|
||||
window.z_index()
|
||||
}) {
|
||||
for window in self.stacking_cache.order() {
|
||||
self.space.raise_element(window, false);
|
||||
}
|
||||
}
|
||||
|
||||
self.stacking_cache.restored();
|
||||
}
|
||||
|
||||
pub(crate) fn map_window_geometry(&mut self, window: Window, location: Point<i32, Logical>) {
|
||||
if self.space.element_location(&window) == Some(location) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.space.map_element(window, location, false);
|
||||
self.stacking_cache.remapped();
|
||||
}
|
||||
|
||||
pub(super) fn tiled_layout(&mut self, bounds: Rect) -> Result<LayoutResult, ferese_layout::LayoutError> {
|
||||
|
||||
@@ -139,12 +139,7 @@ impl Ferese {
|
||||
{
|
||||
tracing::warn!(%error, ?current, ?next, "failed to slide swipe focus");
|
||||
}
|
||||
let Some(window) = self
|
||||
.windows
|
||||
.ids()
|
||||
.iter()
|
||||
.find_map(|(window, id)| (*id == next).then(|| window.clone()))
|
||||
else {
|
||||
let Some(window) = self.windows.window(next).cloned() else {
|
||||
return;
|
||||
};
|
||||
let Some(surface) = window.toplevel().map(|toplevel| toplevel.wl_surface().clone()) else {
|
||||
@@ -306,12 +301,7 @@ impl Ferese {
|
||||
}
|
||||
|
||||
pub(crate) fn activate_managed_window(&mut self, id: WindowId) -> bool {
|
||||
let Some(window) = self
|
||||
.windows
|
||||
.ids()
|
||||
.iter()
|
||||
.find_map(|(window, window_id)| (*window_id == id).then(|| window.clone()))
|
||||
else {
|
||||
let Some(window) = self.windows.window(id).cloned() else {
|
||||
return false;
|
||||
};
|
||||
if !activate_window_workspace(&mut self.workspaces, &mut self.output_workspaces, id) {
|
||||
@@ -441,6 +431,7 @@ impl Ferese {
|
||||
.get_mut(&output)
|
||||
.expect("gesture output exists")
|
||||
.held_progress = Some(progress);
|
||||
self.refresh_workspace_slide_offsets();
|
||||
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||
self.cursor_redraw_pending = true;
|
||||
return;
|
||||
@@ -555,14 +546,11 @@ impl Ferese {
|
||||
self.focus_lock_surface();
|
||||
return;
|
||||
}
|
||||
let surface = self.focused_window.and_then(|focused| {
|
||||
self.windows.ids().iter().find_map(|(window, id)| {
|
||||
(*id == focused)
|
||||
.then(|| window.toplevel())
|
||||
.flatten()
|
||||
.map(|toplevel| toplevel.wl_surface().clone())
|
||||
})
|
||||
});
|
||||
let surface = self
|
||||
.focused_window
|
||||
.and_then(|focused| self.windows.window(focused))
|
||||
.and_then(|window| window.toplevel())
|
||||
.map(|toplevel| toplevel.wl_surface().clone());
|
||||
|
||||
self.seat.get_keyboard().expect("seat has a keyboard").set_focus(
|
||||
self,
|
||||
|
||||
@@ -29,6 +29,8 @@ impl Ferese {
|
||||
match registration {
|
||||
Ok(_) => {
|
||||
self.output_ids.insert(output.clone(), output_id);
|
||||
self.outputs_by_id.insert(output_id, output.clone());
|
||||
self.output_names.insert(output_id, output.name());
|
||||
self.restore_output_focus();
|
||||
let visible = self.visible_workspace_ids();
|
||||
self.reconcile_workspaces(&visible);
|
||||
@@ -45,6 +47,8 @@ impl Ferese {
|
||||
return;
|
||||
};
|
||||
|
||||
self.outputs_by_id.remove(&output_id);
|
||||
self.output_names.remove(&output_id);
|
||||
self.workspace_slides.remove(&output_id);
|
||||
self.render.remove_output(output_id);
|
||||
self.pending_screencopies.retain(|capture| {
|
||||
@@ -113,9 +117,7 @@ impl Ferese {
|
||||
|
||||
pub(crate) fn focused_output(&self) -> Option<&Output> {
|
||||
let focused = self.output_workspaces.focused_output()?;
|
||||
self.output_ids
|
||||
.iter()
|
||||
.find_map(|(output, id)| (*id == focused).then_some(output))
|
||||
self.outputs_by_id.get(&focused)
|
||||
}
|
||||
|
||||
pub(crate) fn restore_output_focus(&mut self) {
|
||||
|
||||
@@ -5,6 +5,8 @@ use super::*;
|
||||
pub(crate) struct FrameScene {
|
||||
pub windows: HashMap<WindowId, WindowFrame>,
|
||||
pub overview: crate::overview::OverviewMotion,
|
||||
// Plane selection and scheduling reuse the activity sampled with this scene.
|
||||
pub animating: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct WindowFrame {
|
||||
@@ -17,6 +19,31 @@ pub(crate) struct WindowFrame {
|
||||
}
|
||||
|
||||
impl Ferese {
|
||||
fn output_window_records<'a>(
|
||||
&'a self,
|
||||
output: &'a Output,
|
||||
block_resizes: bool,
|
||||
) -> impl Iterator<Item = (WindowId, &'a super::window_registry::WindowRecord, bool)> + 'a {
|
||||
self.output_id(output)
|
||||
.into_iter()
|
||||
.flat_map(move |output| self.output_workspaces.assigned_workspaces(output))
|
||||
.filter_map(move |id| self.workspaces.workspace(id))
|
||||
.flat_map(move |workspace| {
|
||||
let blocked = block_resizes
|
||||
&& workspace
|
||||
.layout
|
||||
.window_ids()
|
||||
.chain(workspace.floating.iter().copied())
|
||||
.any(|id| self.windows.record(id).is_some_and(|record| record.resize.is_some()));
|
||||
|
||||
workspace
|
||||
.layout
|
||||
.window_ids()
|
||||
.chain(workspace.floating.iter().copied())
|
||||
.filter_map(move |id| Some((id, self.windows.record(id)?, blocked)))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn output_has_animations(&self, output: &Output) -> bool {
|
||||
if self.overview.is_animating(self.spring_config) || !self.dismissing_popups.is_empty() {
|
||||
return true;
|
||||
@@ -30,17 +57,17 @@ impl Ferese {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.windows.records().any(|(id, record)| {
|
||||
self.output_window_records(output, false).any(|(id, record, _)| {
|
||||
let Some(mut geometry) = record.geometry else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if !self.window_belongs_to_output(*id, output) {
|
||||
if !self.window_belongs_to_output(id, output) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if record.resize.is_some()
|
||||
|| self.render.snapshot(id).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())
|
||||
@@ -52,7 +79,7 @@ impl Ferese {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(workspace) = self.workspaces.workspace_for_window(*id) else {
|
||||
let Some(workspace) = self.workspaces.workspace_for_window(id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -71,28 +98,28 @@ impl Ferese {
|
||||
}
|
||||
|
||||
pub(crate) fn sample_frame(&self, output: &Output, horizon: Duration) -> FrameScene {
|
||||
let delta = if self.animations_enabled && self.output_has_animations(output) {
|
||||
let animating = self.output_has_animations(output);
|
||||
let delta = if self.animations_enabled && animating {
|
||||
horizon.min(Duration::from_millis(100)).mul_f64(self.animation_speed)
|
||||
} else {
|
||||
Duration::ZERO
|
||||
};
|
||||
|
||||
let overview = self.overview.sample(delta, self.spring_config);
|
||||
let blocked = self
|
||||
.windows
|
||||
.resizing()
|
||||
.filter_map(|id| self.workspaces.workspace_for_window(*id))
|
||||
.collect::<HashSet<_>>();
|
||||
let predicted_slide_offsets =
|
||||
(!delta.is_zero()).then(|| self.sample_workspace_slide_offsets(self.output_id(output), delta));
|
||||
let slide_offsets = predicted_slide_offsets
|
||||
.as_ref()
|
||||
.unwrap_or(&self.workspace_slide_offsets);
|
||||
let mut windows = HashMap::new();
|
||||
|
||||
for (&id, record) in self.windows.records() {
|
||||
for (id, record, blocked) in self.output_window_records(output, !delta.is_zero()) {
|
||||
let Some(original) = record.geometry else { continue };
|
||||
|
||||
if !self.window_belongs_to_output(id, output) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let workspace = self.workspaces.workspace_for_window(id);
|
||||
let world = record.world_x.as_ref().and_then(|(workspace, world)| {
|
||||
let viewport = self.viewport_animations.get(workspace)?;
|
||||
let held = self
|
||||
@@ -103,7 +130,7 @@ impl Ferese {
|
||||
Some((*world, *viewport, held))
|
||||
});
|
||||
let width = record.coupled_width.as_ref().map(|(_, width)| *width);
|
||||
let geometry = if delta.is_zero() || workspace.is_some_and(|workspace| blocked.contains(&workspace)) {
|
||||
let geometry = if delta.is_zero() || blocked {
|
||||
original
|
||||
} else {
|
||||
predict_geometry(
|
||||
@@ -118,7 +145,11 @@ impl Ferese {
|
||||
|
||||
let mut rect = overview.presented_rect(id, geometry.visual.current);
|
||||
if !overview.is_presenting() {
|
||||
let (x, y) = self.workspace_slide_offset_at(id, delta);
|
||||
let (x, y) = self
|
||||
.workspaces
|
||||
.workspace_for_window(id)
|
||||
.and_then(|workspace| slide_offsets.get(&workspace).copied())
|
||||
.unwrap_or_default();
|
||||
rect.x += x;
|
||||
rect.y += y;
|
||||
}
|
||||
@@ -160,7 +191,11 @@ impl Ferese {
|
||||
);
|
||||
}
|
||||
|
||||
FrameScene { windows, overview }
|
||||
FrameScene {
|
||||
windows,
|
||||
overview,
|
||||
animating,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,12 +294,109 @@ mod tests {
|
||||
{
|
||||
let forecast = state.sample_frame(&output, horizon);
|
||||
assert!(forecast.windows[&id].geometry.visual.current.x > geometry.visual.current.x);
|
||||
assert!(forecast.animating);
|
||||
}
|
||||
|
||||
assert_eq!(*state.windows.geometry(&id).unwrap(), geometry);
|
||||
assert_eq!(state.last_animation_tick, tick);
|
||||
}
|
||||
|
||||
// Sampling one output must not visit or include another output's records.
|
||||
let other = Output::new(
|
||||
"other-forecast".into(),
|
||||
smithay::output::PhysicalProperties {
|
||||
size: (0, 0).into(),
|
||||
subpixel: smithay::output::Subpixel::Unknown,
|
||||
make: "test".into(),
|
||||
model: "test".into(),
|
||||
},
|
||||
);
|
||||
other.change_current_state(
|
||||
Some(smithay::output::Mode {
|
||||
size: (1280, 720).into(),
|
||||
refresh: 240_000,
|
||||
}),
|
||||
Some(smithay::utils::Transform::Normal),
|
||||
None,
|
||||
Some((1920, 0).into()),
|
||||
);
|
||||
state.space.map_output(&other, (1920, 0));
|
||||
state.register_output(&other, "other-forecast".into());
|
||||
let other_workspace = state
|
||||
.output_workspaces
|
||||
.active_workspace(state.output_id(&other).unwrap())
|
||||
.unwrap();
|
||||
let other_id = WindowId(2);
|
||||
state
|
||||
.workspaces
|
||||
.insert_floating_window(other_id, other_workspace, geometry.visual.current, false)
|
||||
.unwrap();
|
||||
state.windows.records.insert(
|
||||
other_id,
|
||||
super::super::window_registry::WindowRecord {
|
||||
geometry: Some(geometry),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state
|
||||
.output_window_records(&output, false)
|
||||
.map(|(id, _, _)| id)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![id]
|
||||
);
|
||||
assert_eq!(
|
||||
state
|
||||
.output_window_records(&other, false)
|
||||
.map(|(id, _, _)| id)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![other_id]
|
||||
);
|
||||
assert!(
|
||||
!state
|
||||
.sample_frame(&output, Duration::from_millis(8))
|
||||
.windows
|
||||
.contains_key(&other_id)
|
||||
);
|
||||
assert!(
|
||||
!state
|
||||
.sample_frame(&other, Duration::from_millis(4))
|
||||
.windows
|
||||
.contains_key(&id)
|
||||
);
|
||||
|
||||
let output_id = state.output_id(&output).unwrap();
|
||||
let from = state.workspaces.workspace_for_window(id).unwrap();
|
||||
let to = state.workspaces.create_workspace();
|
||||
state.output_workspaces.assign_workspace(output_id, to).unwrap();
|
||||
let mut slide = WorkspaceSlide::new(None, from, to, SwipeDirection::Left);
|
||||
slide.held_progress = Some(0.25);
|
||||
state.workspace_slides.insert(output_id, slide);
|
||||
state.refresh_workspace_slide_offsets();
|
||||
let offsets = state.workspace_slide_offsets.clone();
|
||||
assert_eq!(offsets[&from].0, -480.0);
|
||||
assert_eq!(offsets[&to].0, 1440.0);
|
||||
assert_eq!(
|
||||
state.sample_frame(&output, Duration::from_millis(8)).windows[&id]
|
||||
.rect
|
||||
.x,
|
||||
state.sample_frame(&output, Duration::from_millis(8)).windows[&id]
|
||||
.geometry
|
||||
.visual
|
||||
.current
|
||||
.x
|
||||
- 480.0
|
||||
);
|
||||
assert_eq!(state.workspace_slide_offsets, offsets);
|
||||
assert!(
|
||||
state
|
||||
.sample_workspace_slide_offsets(state.output_id(&other), Duration::from_millis(8))
|
||||
.is_empty()
|
||||
);
|
||||
state.cancel_workspace_slides();
|
||||
assert!(state.workspace_slide_offsets.is_empty());
|
||||
|
||||
// A prediction must neither advance nor expire a client resize barrier.
|
||||
state.windows.set_transaction(
|
||||
id,
|
||||
@@ -278,6 +410,12 @@ mod tests {
|
||||
state.animations_enabled = false;
|
||||
let forecast = state.sample_frame(&output, Duration::from_millis(16));
|
||||
assert_eq!(forecast.windows[&id].geometry, geometry);
|
||||
|
||||
let settled = WindowGeometry::new(Rect::new(0., 0., 400., 300.), None);
|
||||
state.windows.set_geometry(id, settled);
|
||||
state.animations_enabled = true;
|
||||
assert!(!state.sample_frame(&output, Duration::ZERO).animating);
|
||||
assert!(state.sample_frame(&other, Duration::ZERO).animating);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -4,6 +4,10 @@ use std::hash::Hash;
|
||||
use ferese_animation::{AnimatedValue, WindowGeometry};
|
||||
use ferese_core::WorkspaceId;
|
||||
use ferese_layout::{ColumnWidth, Rect, WindowId};
|
||||
use smithay::desktop::Window;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::wayland::compositor::with_states;
|
||||
use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
|
||||
|
||||
use crate::resize_transaction::ResizeTransaction;
|
||||
|
||||
@@ -12,6 +16,10 @@ use crate::resize_transaction::ResizeTransaction;
|
||||
#[derive(Default)]
|
||||
pub(crate) struct WindowRecord {
|
||||
pub geometry: Option<WindowGeometry>,
|
||||
pub surface: Option<WlSurface>,
|
||||
pub app_id: String,
|
||||
pub title: String,
|
||||
pub metadata_revision: u64,
|
||||
pub resize: Option<ResizeTransaction>,
|
||||
pub focus: Option<crate::dimming::DimAnimation>,
|
||||
pub dimming: Option<crate::dimming::DimAnimation>,
|
||||
@@ -32,7 +40,11 @@ pub(crate) struct WindowRecord {
|
||||
/// handle always updates its record as well. IDs are never reused on remapping.
|
||||
pub(crate) struct WindowRegistry<W> {
|
||||
ids: HashMap<W, WindowId>,
|
||||
by_id: HashMap<WindowId, W>,
|
||||
surface_ids: HashMap<WlSurface, WindowId>,
|
||||
pub(super) records: HashMap<WindowId, WindowRecord>,
|
||||
// Ascending IDs; overview reverses this for front-to-back traversal.
|
||||
overview_order: Vec<WindowId>,
|
||||
next_id: u64,
|
||||
}
|
||||
|
||||
@@ -40,7 +52,10 @@ impl<W> Default for WindowRegistry<W> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ids: HashMap::new(),
|
||||
by_id: HashMap::new(),
|
||||
surface_ids: HashMap::new(),
|
||||
records: HashMap::new(),
|
||||
overview_order: Vec::new(),
|
||||
next_id: 1,
|
||||
}
|
||||
}
|
||||
@@ -54,10 +69,16 @@ impl<W: Eq + Hash> WindowRegistry<W> {
|
||||
id
|
||||
}
|
||||
|
||||
pub fn register(&mut self, window: W, id: WindowId) {
|
||||
pub fn register(&mut self, window: W, id: WindowId)
|
||||
where
|
||||
W: Clone,
|
||||
{
|
||||
assert!(!self.ids.contains_key(&window), "window already registered");
|
||||
assert!(!self.records.contains_key(&id), "window ID already registered");
|
||||
|
||||
let index = self.overview_order.partition_point(|existing| existing.0 < id.0);
|
||||
self.overview_order.insert(index, id);
|
||||
self.by_id.insert(id, window.clone());
|
||||
self.ids.insert(window, id);
|
||||
self.records.insert(id, WindowRecord::default());
|
||||
}
|
||||
@@ -66,6 +87,24 @@ impl<W: Eq + Hash> WindowRegistry<W> {
|
||||
&self.ids
|
||||
}
|
||||
|
||||
/// Overview render and hit order, with the newest ID in front. Normal
|
||||
/// stacking changes do not affect this independent presentation order.
|
||||
pub fn overview_windows(&self) -> impl DoubleEndedIterator<Item = &W> {
|
||||
self.overview_order.iter().rev().filter_map(|id| self.by_id.get(id))
|
||||
}
|
||||
|
||||
pub fn window(&self, id: WindowId) -> Option<&W> {
|
||||
self.by_id.get(&id)
|
||||
}
|
||||
|
||||
pub fn id_for_surface(&self, surface: &WlSurface) -> Option<WindowId> {
|
||||
self.surface_ids.get(surface).copied()
|
||||
}
|
||||
|
||||
pub fn ordered_ids(&self) -> impl DoubleEndedIterator<Item = WindowId> + '_ {
|
||||
self.overview_order.iter().copied()
|
||||
}
|
||||
|
||||
pub fn record(&self, id: WindowId) -> Option<&WindowRecord> {
|
||||
self.records.get(&id)
|
||||
}
|
||||
@@ -74,14 +113,14 @@ impl<W: Eq + Hash> WindowRegistry<W> {
|
||||
self.records.get_mut(&id)
|
||||
}
|
||||
|
||||
pub fn records_mut(&mut self) -> impl Iterator<Item = (&WindowId, &mut WindowRecord)> {
|
||||
self.records.iter_mut()
|
||||
}
|
||||
|
||||
pub fn records(&self) -> impl Iterator<Item = (&WindowId, &WindowRecord)> {
|
||||
self.records.iter()
|
||||
}
|
||||
|
||||
pub fn records_mut(&mut self) -> impl Iterator<Item = (&WindowId, &mut WindowRecord)> {
|
||||
self.records.iter_mut()
|
||||
}
|
||||
|
||||
pub fn take_world_positions(&mut self) -> HashMap<WindowId, (WorkspaceId, AnimatedValue)> {
|
||||
self.records
|
||||
.iter_mut()
|
||||
@@ -140,7 +179,17 @@ impl<W: Eq + Hash> WindowRegistry<W> {
|
||||
|
||||
pub fn remove(&mut self, window: &W) -> Option<WindowId> {
|
||||
let id = self.ids.remove(window)?;
|
||||
self.records.remove(&id);
|
||||
self.by_id.remove(&id);
|
||||
if let Some(record) = self.records.remove(&id)
|
||||
&& let Some(surface) = record.surface
|
||||
{
|
||||
self.surface_ids.remove(&surface);
|
||||
}
|
||||
let index = self
|
||||
.overview_order
|
||||
.binary_search_by_key(&id.0, |id| id.0)
|
||||
.expect("registered windows have an overview entry");
|
||||
self.overview_order.remove(index);
|
||||
|
||||
Some(id)
|
||||
}
|
||||
@@ -153,10 +202,96 @@ impl<W: Eq + Hash> WindowRegistry<W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowRegistry<Window> {
|
||||
pub fn register_window(&mut self, window: Window, id: WindowId) {
|
||||
let surface = window.toplevel().map(|toplevel| toplevel.wl_surface().clone());
|
||||
self.register(window, id);
|
||||
|
||||
if let Some(surface) = surface {
|
||||
self.surface_ids.insert(surface.clone(), id);
|
||||
self.records.get_mut(&id).unwrap().surface = Some(surface.clone());
|
||||
self.refresh_metadata(&surface);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_metadata(&mut self, surface: &WlSurface) -> bool {
|
||||
let Some(id) = self.id_for_surface(surface) else {
|
||||
return false;
|
||||
};
|
||||
let record = self.records.get_mut(&id).unwrap();
|
||||
with_states(surface, |states| {
|
||||
let attributes = states.data_map.get::<XdgToplevelSurfaceData>().unwrap().lock().unwrap();
|
||||
let app_id = attributes.app_id.as_deref().unwrap_or_default();
|
||||
let title = attributes.title.as_deref().unwrap_or_default();
|
||||
let changed = record.app_id != app_id || record.title != title;
|
||||
|
||||
if changed {
|
||||
record.app_id.clear();
|
||||
record.app_id.push_str(app_id);
|
||||
record.title.clear();
|
||||
record.title.push_str(title);
|
||||
record.metadata_revision = record.metadata_revision.wrapping_add(1);
|
||||
}
|
||||
|
||||
changed
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn overview_order_tracks_registration_removal_and_remapping() {
|
||||
let mut windows = WindowRegistry::default();
|
||||
let first = windows.allocate_id();
|
||||
let second = windows.allocate_id();
|
||||
let third = windows.allocate_id();
|
||||
// Registration need not happen in allocation order.
|
||||
windows.register("second", second);
|
||||
windows.register("first", first);
|
||||
windows.register("third", third);
|
||||
assert_eq!(
|
||||
windows.overview_windows().copied().collect::<Vec<_>>(),
|
||||
["third", "second", "first"]
|
||||
);
|
||||
|
||||
windows.remove(&"second");
|
||||
assert_eq!(
|
||||
windows.overview_windows().copied().collect::<Vec<_>>(),
|
||||
["third", "first"]
|
||||
);
|
||||
windows.remove(&"first");
|
||||
let remapped = windows.allocate_id();
|
||||
windows.register("first", remapped);
|
||||
assert_eq!(
|
||||
windows.overview_windows().copied().collect::<Vec<_>>(),
|
||||
["first", "third"]
|
||||
);
|
||||
|
||||
windows.remove(&"first");
|
||||
windows.remove(&"third");
|
||||
assert_eq!(windows.overview_windows().count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overview_iteration_borrows_handles_and_removal_releases_cached_handles() {
|
||||
let handle = std::rc::Rc::new("surface");
|
||||
let mut windows = WindowRegistry::default();
|
||||
let id = windows.allocate_id();
|
||||
windows.register(handle.clone(), id);
|
||||
assert_eq!(std::rc::Rc::strong_count(&handle), 3);
|
||||
|
||||
for _ in 0..100 {
|
||||
assert!(std::rc::Rc::ptr_eq(windows.overview_windows().next().unwrap(), &handle));
|
||||
assert_eq!(std::rc::Rc::strong_count(&handle), 3);
|
||||
}
|
||||
|
||||
windows.remove(&handle);
|
||||
assert_eq!(std::rc::Rc::strong_count(&handle), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unmap_drops_metadata_and_late_updates_cannot_resurrect_it() {
|
||||
let mut windows = WindowRegistry::default();
|
||||
|
||||
@@ -115,7 +115,7 @@ impl Ferese {
|
||||
return;
|
||||
}
|
||||
|
||||
self.windows.register(window.clone(), id);
|
||||
self.windows.register_window(window.clone(), id);
|
||||
self.windows.update(id, |w| w.placement_anchor = Some(anchor));
|
||||
|
||||
if rule.width.is_none() && rule.height.is_none() && client_size(&window).is_none() && remembered_work.is_none()
|
||||
@@ -156,7 +156,7 @@ impl Ferese {
|
||||
return;
|
||||
}
|
||||
|
||||
self.windows.register(window.clone(), id);
|
||||
self.windows.register_window(window.clone(), id);
|
||||
self.window_stack.insert(id);
|
||||
if focus_new_window {
|
||||
self.focused_window = Some(id);
|
||||
@@ -196,7 +196,7 @@ impl Ferese {
|
||||
return;
|
||||
}
|
||||
|
||||
self.windows.register(window.clone(), id);
|
||||
self.windows.register_window(window.clone(), id);
|
||||
self.window_stack.insert(id);
|
||||
if client_size(&window).is_none() {
|
||||
self.windows.update(id, |w| w.natural_floating_pending = true);
|
||||
@@ -649,7 +649,7 @@ impl Ferese {
|
||||
}
|
||||
|
||||
pub(crate) fn close_managed_window(&mut self, id: WindowId) -> bool {
|
||||
if !self.windows.ids().values().any(|window_id| *window_id == id) {
|
||||
if !self.windows.window(id).is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -666,12 +666,7 @@ impl Ferese {
|
||||
}
|
||||
|
||||
pub(super) fn send_window_close(&self, id: WindowId) {
|
||||
let Some(toplevel) = self
|
||||
.windows
|
||||
.ids()
|
||||
.iter()
|
||||
.find_map(|(window, window_id)| (*window_id == id).then(|| window.toplevel()).flatten())
|
||||
else {
|
||||
let Some(toplevel) = self.windows.window(id).and_then(|window| window.toplevel()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user