1
0
Fork 0
forked from wry/wry

Use live content for normal animations

This commit is contained in:
atagen 2026-05-27 22:08:09 +10:00
parent 6c133018aa
commit 502a93a00a
5 changed files with 134 additions and 52 deletions

View file

@ -290,7 +290,7 @@ impl AnimationState {
node_id: NodeId, node_id: NodeId,
old: Rect, old: Rect,
new: Rect, new: Rect,
retained: Option<Rc<RetainedToplevel>>, _retained: Option<Rc<RetainedToplevel>>,
now_nsec: u64, now_nsec: u64,
duration_ms: u32, duration_ms: u32,
curve: AnimationCurve, curve: AnimationCurve,
@ -302,7 +302,6 @@ impl AnimationState {
} }
let duration_nsec = duration_ms as u64 * 1_000_000; let duration_nsec = duration_ms as u64 * 1_000_000;
let mut from = old; let mut from = old;
let mut retained = retained;
{ {
let phased = self.phased.borrow(); let phased = self.phased.borrow();
if let Some(anim) = phased.get(&node_id) { if let Some(anim) = phased.get(&node_id) {
@ -310,7 +309,6 @@ impl AnimationState {
return false; return false;
} }
from = anim.rect_at(now_nsec); from = anim.rect_at(now_nsec);
retained = anim.retained.clone().or(retained);
} }
} }
{ {
@ -320,7 +318,6 @@ impl AnimationState {
return false; return false;
} }
from = anim.rect_at(now_nsec); from = anim.rect_at(now_nsec);
retained = anim.retained.clone().or(retained);
} }
} }
if from == new { if from == new {
@ -338,7 +335,7 @@ impl AnimationState {
duration_nsec, duration_nsec,
curve, curve,
last_damage: from, last_damage: from,
retained, retained: None,
}, },
); );
true true
@ -348,7 +345,7 @@ impl AnimationState {
&self, &self,
node_id: NodeId, node_id: NodeId,
phases: Vec<(Rect, Rect)>, phases: Vec<(Rect, Rect)>,
retained: Option<Rc<RetainedToplevel>>, _retained: Option<Rc<RetainedToplevel>>,
now_nsec: u64, now_nsec: u64,
duration_ms: u32, duration_ms: u32,
curve: AnimationCurve, curve: AnimationCurve,
@ -388,7 +385,7 @@ impl AnimationState {
last_damage: from, last_damage: from,
final_rect, final_rect,
route_edges, route_edges,
retained, retained: None,
}, },
); );
true true
@ -994,6 +991,44 @@ mod tests {
assert!(state.exit_frames(160_000_000).is_empty()); assert!(state.exit_frames(160_000_000).is_empty());
} }
#[test]
fn normal_window_animations_do_not_retain_content() {
let state = AnimationState::default();
let id = NodeId(1);
let from = Rect::new_sized_saturating(0, 0, 100, 100);
let to = Rect::new_sized_saturating(100, 0, 100, 100);
assert!(state.set_target(
id,
from,
to,
Some(retained_for_tests()),
0,
160,
AnimationCurve::Linear
));
assert!(state.retained_snapshot(id, 80_000_000).is_none());
}
#[test]
fn phased_window_animations_do_not_retain_content() {
let state = AnimationState::default();
let id = NodeId(1);
let a = Rect::new_sized_saturating(0, 0, 100, 100);
let b = Rect::new_sized_saturating(0, 0, 100, 50);
let c = Rect::new_sized_saturating(100, 0, 100, 50);
assert!(state.set_phased_target(
id,
vec![(a, b), (b, c)],
Some(retained_for_tests()),
0,
100,
AnimationCurve::Linear
));
assert!(state.retained_snapshot(id, 50_000_000).is_none());
}
#[test] #[test]
fn phased_animation_uses_full_duration_per_phase() { fn phased_animation_uses_full_duration_per_phase() {
let state = AnimationState::default(); let state = AnimationState::default();

View file

@ -809,7 +809,7 @@ fn plan_axis_crossing_lanes(
.copied() .copied()
.filter(|window| window.from != window.to) .filter(|window| window.from != window.to)
.collect(); .collect();
if moving_windows.len() != 2 { if moving_windows.len() < 2 {
return Err(MultiphasePlanFailure::NoPattern); return Err(MultiphasePlanFailure::NoPattern);
} }
let orth_min = request let orth_min = request
@ -855,12 +855,7 @@ fn plan_axis_crossing_lanes(
} }
let mut windows = moving_windows; let mut windows = moving_windows;
windows.sort_by_key(|window| lane_index_for_direction(*window, axis)); windows.sort_by_key(|window| lane_sort_key(*window, axis));
if windows.windows(2).any(|pair| {
lane_index_for_direction(pair[0], axis) == lane_index_for_direction(pair[1], axis)
}) {
return Err(MultiphasePlanFailure::NoPattern);
}
let mut phase1 = vec![]; let mut phase1 = vec![];
let mut phase2 = vec![]; let mut phase2 = vec![];
let mut phase3 = vec![]; let mut phase3 = vec![];
@ -935,13 +930,19 @@ fn crossing_lane_move_rect(from: Rect, target: Rect, axis: PhaseAxis) -> Rect {
} }
} }
fn lane_index_for_direction(window: MultiphaseWindow, axis: PhaseAxis) -> Option<usize> { fn lane_sort_key(window: MultiphaseWindow, axis: PhaseAxis) -> (usize, i32, i32, u32) {
let delta = main_start(window.to, axis) - main_start(window.from, axis); let delta = main_start(window.to, axis) - main_start(window.from, axis);
match delta.cmp(&0) { let direction = match delta.cmp(&0) {
std::cmp::Ordering::Greater => Some(0), std::cmp::Ordering::Greater => 0,
std::cmp::Ordering::Less => Some(1), std::cmp::Ordering::Less => 1,
std::cmp::Ordering::Equal => None, std::cmp::Ordering::Equal => 2,
} };
(
direction,
main_start(window.from, axis),
main_start(window.to, axis),
window.node_id.0,
)
} }
fn plan_space_then_orthogonal_growth( fn plan_space_then_orthogonal_growth(
@ -2151,6 +2152,41 @@ mod tests {
assert!(validate_plan_continuous(&req, &planned.plan)); assert!(validate_plan_continuous(&req, &planned.plan));
} }
#[test]
fn horizontal_rotation_uses_crossing_lanes() {
let req = request(vec![
window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)),
window(2, rect(100, 0, 200, 100), rect(200, 0, 300, 100)),
window(3, rect(200, 0, 300, 100), rect(0, 0, 100, 100)),
]);
let planned = plan_no_overlap_explained(&req).unwrap();
assert_eq!(
planned.explanation.strategy,
PlanStrategy::SwapLanes {
axis: PhaseAxis::Horizontal,
}
);
assert_eq!(
actions(&planned.plan),
vec![
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Vertical,
},
PhaseAction {
kind: PhaseKind::Move,
axis: PhaseAxis::Horizontal,
},
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Vertical,
},
]
);
assert!(validate_plan_continuous(&req, &planned.plan));
}
#[test] #[test]
fn vertical_swap_lanes_follow_motion_direction_not_node_id() { fn vertical_swap_lanes_follow_motion_direction_not_node_id() {
let req = request(vec![ let req = request(vec![
@ -2875,6 +2911,36 @@ mod tests {
assert!(validate_plan_continuous(&req, &plan)); assert!(validate_plan_continuous(&req, &plan));
} }
#[test]
fn vertical_stack_extraction_with_clearance_still_plans() {
let old = split(
20,
PhaseAxis::Vertical,
&[1, 1],
vec![
leaf(1),
split(21, PhaseAxis::Horizontal, &[1, 1], vec![leaf(2), leaf(3)]),
],
);
let new = split(
20,
PhaseAxis::Vertical,
&[1, 2, 1],
vec![leaf(1), leaf(2), leaf(3)],
);
let mut req = generated_request(&old, &new, rect(0, 0, 100, 400));
req.clearance = 10;
let planned = plan_no_overlap_explained(&req).unwrap();
assert_eq!(
planned.explanation.strategy,
PlanStrategy::SpaceThenOrthogonalGrowth {
axis: PhaseAxis::Vertical,
}
);
assert!(validate_plan_continuous(&req, &planned.plan));
}
#[test] #[test]
fn vertical_stack_extraction_reverse_replays_phases_in_reverse() { fn vertical_stack_extraction_reverse_replays_phases_in_reverse() {
let req = request(vec![ let req = request(vec![

View file

@ -167,7 +167,6 @@ pub(crate) struct LayoutAnimationCandidate {
node_id: NodeId, node_id: NodeId,
old: Rect, old: Rect,
new: Rect, new: Rect,
retained: Option<Rc<RetainedToplevel>>,
curve: AnimationCurve, curve: AnimationCurve,
hierarchy: MultiphaseWindowHierarchy, hierarchy: MultiphaseWindowHierarchy,
} }
@ -1503,7 +1502,6 @@ impl State {
node_id: NodeId, node_id: NodeId,
old: Rect, old: Rect,
new: Rect, new: Rect,
retained: Option<Rc<RetainedToplevel>>,
) { ) {
let curve = self let curve = self
.layout_animation_curve_override .layout_animation_curve_override
@ -1513,7 +1511,6 @@ impl State {
node_id, node_id,
old, old,
new, new,
retained,
curve, curve,
MultiphaseWindowHierarchy::default(), MultiphaseWindowHierarchy::default(),
); );
@ -1524,14 +1521,13 @@ impl State {
node_id: NodeId, node_id: NodeId,
old: Rect, old: Rect,
new: Rect, new: Rect,
retained: Option<Rc<RetainedToplevel>>,
hierarchy: MultiphaseWindowHierarchy, hierarchy: MultiphaseWindowHierarchy,
) { ) {
let curve = self let curve = self
.layout_animation_curve_override .layout_animation_curve_override
.get() .get()
.unwrap_or_else(|| self.animations.curve.get()); .unwrap_or_else(|| self.animations.curve.get());
self.queue_layout_animation(node_id, old, new, retained, curve, hierarchy); self.queue_layout_animation(node_id, old, new, curve, hierarchy);
} }
pub fn queue_linear_layout_animation( pub fn queue_linear_layout_animation(
@ -1539,13 +1535,11 @@ impl State {
node_id: NodeId, node_id: NodeId,
old: Rect, old: Rect,
new: Rect, new: Rect,
retained: Option<Rc<RetainedToplevel>>,
) { ) {
self.queue_layout_animation( self.queue_layout_animation(
node_id, node_id,
old, old,
new, new,
retained,
AnimationCurve::Linear, AnimationCurve::Linear,
MultiphaseWindowHierarchy::default(), MultiphaseWindowHierarchy::default(),
); );
@ -1556,7 +1550,6 @@ impl State {
node_id: NodeId, node_id: NodeId,
old: Rect, old: Rect,
new: Rect, new: Rect,
retained: Option<Rc<RetainedToplevel>>,
curve: AnimationCurve, curve: AnimationCurve,
hierarchy: MultiphaseWindowHierarchy, hierarchy: MultiphaseWindowHierarchy,
) { ) {
@ -1583,7 +1576,6 @@ impl State {
node_id, node_id,
old, old,
new, new,
retained,
curve, curve,
hierarchy, hierarchy,
}; };
@ -1603,7 +1595,7 @@ impl State {
candidate.node_id, candidate.node_id,
candidate.old, candidate.old,
candidate.new, candidate.new,
candidate.retained, None,
now_nsec, now_nsec,
self.animations.duration_ms.get(), self.animations.duration_ms.get(),
candidate.curve, candidate.curve,
@ -1763,18 +1755,14 @@ impl State {
if current != window.to { if current != window.to {
return false; return false;
} }
let retained = self entries.push((candidate.clone(), phases, damage));
.animations
.retained_snapshot(candidate.node_id, now_nsec)
.or_else(|| candidate.retained.clone());
entries.push((candidate.clone(), phases, damage, retained));
} }
let mut started_any = false; let mut started_any = false;
for (candidate, phases, damage, retained) in entries { for (candidate, phases, damage) in entries {
if self.animations.set_phased_target( if self.animations.set_phased_target(
candidate.node_id, candidate.node_id,
phases, phases,
retained, None,
now_nsec, now_nsec,
self.animations.duration_ms.get(), self.animations.duration_ms.get(),
candidate.curve, candidate.curve,
@ -1796,7 +1784,6 @@ impl State {
self: &Rc<Self>, self: &Rc<Self>,
node_id: NodeId, node_id: NodeId,
target: Rect, target: Rect,
retained: Option<Rc<RetainedToplevel>>,
) { ) {
if !self.animations.enabled.get() || target.is_empty() { if !self.animations.enabled.get() || target.is_empty() {
return; return;
@ -1806,7 +1793,7 @@ impl State {
let started = self.animations.set_spawn_in( let started = self.animations.set_spawn_in(
node_id, node_id,
target, target,
retained, None,
now, now,
self.animations.duration_ms.get(), self.animations.duration_ms.get(),
self.animations.curve.get(), self.animations.curve.get(),

View file

@ -161,8 +161,7 @@ impl FloatNode {
data.spawn_in_pending.get() && data.kind.is_app_window() && !data.is_fullscreen.get() data.spawn_in_pending.get() && data.kind.is_app_window() && !data.is_fullscreen.get()
}; };
if spawn_in_pending && self.visible.get() { if spawn_in_pending && self.visible.get() {
self.state self.state.queue_spawn_in_animation(self.id.into(), pos);
.queue_spawn_in_animation(self.id.into(), pos, None);
} }
let theme = &self.state.theme; let theme = &self.state.theme;
let bw = theme.sizes.border_width.get(); let bw = theme.sizes.border_width.get();
@ -401,7 +400,7 @@ impl FloatNode {
fn queue_position_animation(&self, old_pos: Rect, new_pos: Rect) { fn queue_position_animation(&self, old_pos: Rect, new_pos: Rect) {
self.state self.state
.clone() .clone()
.queue_tiled_animation(self.id.into(), old_pos, new_pos, None); .queue_tiled_animation(self.id.into(), old_pos, new_pos);
let Some(child) = self.child.get() else { let Some(child) = self.child.get() else {
return; return;
}; };
@ -409,7 +408,6 @@ impl FloatNode {
child.node_id(), child.node_id(),
self.body_for_outer(old_pos), self.body_for_outer(old_pos),
self.body_for_outer(new_pos), self.body_for_outer(new_pos),
child.tl_animation_snapshot(),
); );
} }

View file

@ -232,16 +232,13 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
data.node_id, data.node_id,
prev, prev,
*rect, *rect,
self.tl_animation_snapshot(),
hierarchy, hierarchy,
); );
} }
if spawn_in_eligible { if spawn_in_eligible {
data.state.clone().queue_spawn_in_animation( data.state
data.node_id, .clone()
*rect, .queue_spawn_in_animation(data.node_id, *rect);
self.tl_animation_snapshot(),
);
} }
if spawn_in_eligible { if spawn_in_eligible {
data.spawn_in_pending.set(false); data.spawn_in_pending.set(false);
@ -1273,14 +1270,13 @@ pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floati
.animations .animations
.visual_rect(node_id, tl.node_absolute_position(), state.now_nsec()); .visual_rect(node_id, tl.node_absolute_position(), state.now_nsec());
let old_outer = float_outer_for_body(state, old_body); let old_outer = float_outer_for_body(state, old_body);
let retained = tl.tl_animation_snapshot();
parent.cnode_remove_child2(&*tl, true); parent.cnode_remove_child2(&*tl, true);
let (width, height) = data.float_size(&ws); let (width, height) = data.float_size(&ws);
let floater = state.map_floating(tl, width, height, &ws, None); let floater = state.map_floating(tl, width, height, &ws, None);
let new_outer = floater.position.get(); let new_outer = floater.position.get();
let new_body = float_body_for_outer(state, new_outer); let new_body = float_body_for_outer(state, new_outer);
state.queue_linear_layout_animation(floater.node_id(), old_outer, new_outer, None); state.queue_linear_layout_animation(floater.node_id(), old_outer, new_outer);
state.queue_linear_layout_animation(node_id, old_body, new_body, retained); state.queue_linear_layout_animation(node_id, old_body, new_body);
} }
} }