Use live content for normal animations
This commit is contained in:
parent
6c133018aa
commit
502a93a00a
5 changed files with 134 additions and 52 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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![
|
||||||
|
|
|
||||||
25
src/state.rs
25
src/state.rs
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue