Implement Continuum compositor and shell motion
This commit is contained in:
1 parent
259f7e93b6
commit
f8bc17e913
42 files changed
+1873
-647
No files matched your search
@@ -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)),
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)));
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user