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,
old: Rect,
new: Rect,
retained: Option<Rc<RetainedToplevel>>,
_retained: Option<Rc<RetainedToplevel>>,
now_nsec: u64,
duration_ms: u32,
curve: AnimationCurve,
@ -302,7 +302,6 @@ impl AnimationState {
}
let duration_nsec = duration_ms as u64 * 1_000_000;
let mut from = old;
let mut retained = retained;
{
let phased = self.phased.borrow();
if let Some(anim) = phased.get(&node_id) {
@ -310,7 +309,6 @@ impl AnimationState {
return false;
}
from = anim.rect_at(now_nsec);
retained = anim.retained.clone().or(retained);
}
}
{
@ -320,7 +318,6 @@ impl AnimationState {
return false;
}
from = anim.rect_at(now_nsec);
retained = anim.retained.clone().or(retained);
}
}
if from == new {
@ -338,7 +335,7 @@ impl AnimationState {
duration_nsec,
curve,
last_damage: from,
retained,
retained: None,
},
);
true
@ -348,7 +345,7 @@ impl AnimationState {
&self,
node_id: NodeId,
phases: Vec<(Rect, Rect)>,
retained: Option<Rc<RetainedToplevel>>,
_retained: Option<Rc<RetainedToplevel>>,
now_nsec: u64,
duration_ms: u32,
curve: AnimationCurve,
@ -388,7 +385,7 @@ impl AnimationState {
last_damage: from,
final_rect,
route_edges,
retained,
retained: None,
},
);
true
@ -994,6 +991,44 @@ mod tests {
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]
fn phased_animation_uses_full_duration_per_phase() {
let state = AnimationState::default();

View file

@ -809,7 +809,7 @@ fn plan_axis_crossing_lanes(
.copied()
.filter(|window| window.from != window.to)
.collect();
if moving_windows.len() != 2 {
if moving_windows.len() < 2 {
return Err(MultiphasePlanFailure::NoPattern);
}
let orth_min = request
@ -855,12 +855,7 @@ fn plan_axis_crossing_lanes(
}
let mut windows = moving_windows;
windows.sort_by_key(|window| lane_index_for_direction(*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);
}
windows.sort_by_key(|window| lane_sort_key(*window, axis));
let mut phase1 = vec![];
let mut phase2 = 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);
match delta.cmp(&0) {
std::cmp::Ordering::Greater => Some(0),
std::cmp::Ordering::Less => Some(1),
std::cmp::Ordering::Equal => None,
}
let direction = match delta.cmp(&0) {
std::cmp::Ordering::Greater => 0,
std::cmp::Ordering::Less => 1,
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(
@ -2151,6 +2152,41 @@ mod tests {
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]
fn vertical_swap_lanes_follow_motion_direction_not_node_id() {
let req = request(vec![
@ -2875,6 +2911,36 @@ mod tests {
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]
fn vertical_stack_extraction_reverse_replays_phases_in_reverse() {
let req = request(vec![

View file

@ -167,7 +167,6 @@ pub(crate) struct LayoutAnimationCandidate {
node_id: NodeId,
old: Rect,
new: Rect,
retained: Option<Rc<RetainedToplevel>>,
curve: AnimationCurve,
hierarchy: MultiphaseWindowHierarchy,
}
@ -1503,7 +1502,6 @@ impl State {
node_id: NodeId,
old: Rect,
new: Rect,
retained: Option<Rc<RetainedToplevel>>,
) {
let curve = self
.layout_animation_curve_override
@ -1513,7 +1511,6 @@ impl State {
node_id,
old,
new,
retained,
curve,
MultiphaseWindowHierarchy::default(),
);
@ -1524,14 +1521,13 @@ impl State {
node_id: NodeId,
old: Rect,
new: Rect,
retained: Option<Rc<RetainedToplevel>>,
hierarchy: MultiphaseWindowHierarchy,
) {
let curve = self
.layout_animation_curve_override
.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(
@ -1539,13 +1535,11 @@ impl State {
node_id: NodeId,
old: Rect,
new: Rect,
retained: Option<Rc<RetainedToplevel>>,
) {
self.queue_layout_animation(
node_id,
old,
new,
retained,
AnimationCurve::Linear,
MultiphaseWindowHierarchy::default(),
);
@ -1556,7 +1550,6 @@ impl State {
node_id: NodeId,
old: Rect,
new: Rect,
retained: Option<Rc<RetainedToplevel>>,
curve: AnimationCurve,
hierarchy: MultiphaseWindowHierarchy,
) {
@ -1583,7 +1576,6 @@ impl State {
node_id,
old,
new,
retained,
curve,
hierarchy,
};
@ -1603,7 +1595,7 @@ impl State {
candidate.node_id,
candidate.old,
candidate.new,
candidate.retained,
None,
now_nsec,
self.animations.duration_ms.get(),
candidate.curve,
@ -1763,18 +1755,14 @@ impl State {
if current != window.to {
return false;
}
let retained = self
.animations
.retained_snapshot(candidate.node_id, now_nsec)
.or_else(|| candidate.retained.clone());
entries.push((candidate.clone(), phases, damage, retained));
entries.push((candidate.clone(), phases, damage));
}
let mut started_any = false;
for (candidate, phases, damage, retained) in entries {
for (candidate, phases, damage) in entries {
if self.animations.set_phased_target(
candidate.node_id,
phases,
retained,
None,
now_nsec,
self.animations.duration_ms.get(),
candidate.curve,
@ -1796,7 +1784,6 @@ impl State {
self: &Rc<Self>,
node_id: NodeId,
target: Rect,
retained: Option<Rc<RetainedToplevel>>,
) {
if !self.animations.enabled.get() || target.is_empty() {
return;
@ -1806,7 +1793,7 @@ impl State {
let started = self.animations.set_spawn_in(
node_id,
target,
retained,
None,
now,
self.animations.duration_ms.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()
};
if spawn_in_pending && self.visible.get() {
self.state
.queue_spawn_in_animation(self.id.into(), pos, None);
self.state.queue_spawn_in_animation(self.id.into(), pos);
}
let theme = &self.state.theme;
let bw = theme.sizes.border_width.get();
@ -401,7 +400,7 @@ impl FloatNode {
fn queue_position_animation(&self, old_pos: Rect, new_pos: Rect) {
self.state
.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 {
return;
};
@ -409,7 +408,6 @@ impl FloatNode {
child.node_id(),
self.body_for_outer(old_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,
prev,
*rect,
self.tl_animation_snapshot(),
hierarchy,
);
}
if spawn_in_eligible {
data.state.clone().queue_spawn_in_animation(
data.node_id,
*rect,
self.tl_animation_snapshot(),
);
data.state
.clone()
.queue_spawn_in_animation(data.node_id, *rect);
}
if spawn_in_eligible {
data.spawn_in_pending.set(false);
@ -1273,14 +1270,13 @@ pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floati
.animations
.visual_rect(node_id, tl.node_absolute_position(), state.now_nsec());
let old_outer = float_outer_for_body(state, old_body);
let retained = tl.tl_animation_snapshot();
parent.cnode_remove_child2(&*tl, true);
let (width, height) = data.float_size(&ws);
let floater = state.map_floating(tl, width, height, &ws, None);
let new_outer = floater.position.get();
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(node_id, old_body, new_body, retained);
state.queue_linear_layout_animation(floater.node_id(), old_outer, new_outer);
state.queue_linear_layout_animation(node_id, old_body, new_body);
}
}