Fallback layout animations by motion group
This commit is contained in:
parent
a516b2e721
commit
4ee2c324e1
3 changed files with 115 additions and 27 deletions
|
|
@ -221,6 +221,9 @@ Current pure planner status:
|
||||||
orientations: peer/container space scales first, the extracted child moves
|
orientations: peer/container space scales first, the extracted child moves
|
||||||
only after space exists, and orthogonal growth happens in the final phase.
|
only after space exists, and orthogonal growth happens in the final phase.
|
||||||
- Every produced plan is sampled for overlap at each phase before it is accepted.
|
- Every produced plan is sampled for overlap at each phase before it is accepted.
|
||||||
|
- Live layout batches are partitioned by overlapping motion bounds, so unrelated
|
||||||
|
groups can still use multiphase animation when another group falls back to
|
||||||
|
linear motion.
|
||||||
|
|
||||||
Tests:
|
Tests:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,33 @@ pub fn plan_no_overlap(request: &MultiphaseRequest) -> Result<MultiphasePlan, Mu
|
||||||
Err(MultiphaseError::NoPlan)
|
Err(MultiphaseError::NoPlan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn partition_motion_groups(windows: &[MultiphaseWindow]) -> Vec<Vec<usize>> {
|
||||||
|
let mut groups = vec![];
|
||||||
|
let mut seen = vec![false; windows.len()];
|
||||||
|
for start in 0..windows.len() {
|
||||||
|
if seen[start] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen[start] = true;
|
||||||
|
let mut group = vec![];
|
||||||
|
let mut pending = vec![start];
|
||||||
|
while let Some(idx) = pending.pop() {
|
||||||
|
group.push(idx);
|
||||||
|
let bounds = motion_bounds(windows[idx]);
|
||||||
|
for other in 0..windows.len() {
|
||||||
|
if seen[other] || !bounds.intersects(&motion_bounds(windows[other])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen[other] = true;
|
||||||
|
pending.push(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.sort_unstable();
|
||||||
|
groups.push(group);
|
||||||
|
}
|
||||||
|
groups
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_request(request: &MultiphaseRequest) -> Result<(), MultiphaseError> {
|
fn validate_request(request: &MultiphaseRequest) -> Result<(), MultiphaseError> {
|
||||||
if request.bounds.is_empty() {
|
if request.bounds.is_empty() {
|
||||||
return Err(MultiphaseError::EmptyBounds);
|
return Err(MultiphaseError::EmptyBounds);
|
||||||
|
|
@ -380,6 +407,10 @@ fn overlaps(rects: impl IntoIterator<Item = Rect>) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn motion_bounds(window: MultiphaseWindow) -> Rect {
|
||||||
|
window.from.union(window.to)
|
||||||
|
}
|
||||||
|
|
||||||
fn push_step(steps: &mut Vec<MultiphaseStep>, node_id: NodeId, from: Rect, to: Rect) {
|
fn push_step(steps: &mut Vec<MultiphaseStep>, node_id: NodeId, from: Rect, to: Rect) {
|
||||||
if from != to {
|
if from != to {
|
||||||
steps.push(MultiphaseStep { node_id, from, to });
|
steps.push(MultiphaseStep { node_id, from, to });
|
||||||
|
|
@ -697,4 +728,48 @@ mod tests {
|
||||||
}]);
|
}]);
|
||||||
assert_eq!(plan_no_overlap(&req), Err(MultiphaseError::NoPlan));
|
assert_eq!(plan_no_overlap(&req), Err(MultiphaseError::NoPlan));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motion_groups_split_disjoint_layout_changes() {
|
||||||
|
let windows = vec![
|
||||||
|
MultiphaseWindow {
|
||||||
|
node_id: id(1),
|
||||||
|
from: rect(0, 0, 100, 100),
|
||||||
|
to: rect(100, 0, 200, 100),
|
||||||
|
},
|
||||||
|
MultiphaseWindow {
|
||||||
|
node_id: id(2),
|
||||||
|
from: rect(100, 0, 200, 100),
|
||||||
|
to: rect(0, 0, 100, 100),
|
||||||
|
},
|
||||||
|
MultiphaseWindow {
|
||||||
|
node_id: id(3),
|
||||||
|
from: rect(300, 0, 400, 100),
|
||||||
|
to: rect(400, 0, 500, 100),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(partition_motion_groups(&windows), vec![vec![0, 1], vec![2]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motion_groups_are_transitive() {
|
||||||
|
let windows = vec![
|
||||||
|
MultiphaseWindow {
|
||||||
|
node_id: id(1),
|
||||||
|
from: rect(0, 0, 100, 100),
|
||||||
|
to: rect(80, 0, 180, 100),
|
||||||
|
},
|
||||||
|
MultiphaseWindow {
|
||||||
|
node_id: id(2),
|
||||||
|
from: rect(170, 0, 270, 100),
|
||||||
|
to: rect(250, 0, 350, 100),
|
||||||
|
},
|
||||||
|
MultiphaseWindow {
|
||||||
|
node_id: id(3),
|
||||||
|
from: rect(90, 0, 180, 100),
|
||||||
|
to: rect(180, 0, 260, 100),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(partition_motion_groups(&windows), vec![vec![0, 1, 2]]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
64
src/state.rs
64
src/state.rs
|
|
@ -5,7 +5,9 @@ use {
|
||||||
animation::{
|
animation::{
|
||||||
AnimationCurve, AnimationState, AnimationTick, RetainedExitLayer, RetainedToplevel,
|
AnimationCurve, AnimationState, AnimationTick, RetainedExitLayer, RetainedToplevel,
|
||||||
expand_damage_rect,
|
expand_damage_rect,
|
||||||
multiphase::{MultiphaseRequest, MultiphaseWindow, plan_no_overlap},
|
multiphase::{
|
||||||
|
MultiphaseRequest, MultiphaseWindow, partition_motion_groups, plan_no_overlap,
|
||||||
|
},
|
||||||
spawn_in_start_rect,
|
spawn_in_start_rect,
|
||||||
},
|
},
|
||||||
async_engine::{AsyncEngine, SpawnedFuture},
|
async_engine::{AsyncEngine, SpawnedFuture},
|
||||||
|
|
@ -1586,51 +1588,59 @@ impl State {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let now = self.now_nsec();
|
let now = self.now_nsec();
|
||||||
if self.start_multiphase_layout_animation(&candidates, now) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for candidate in candidates {
|
|
||||||
self.start_layout_animation_candidate(candidate, now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_multiphase_layout_animation(
|
|
||||||
self: &Rc<Self>,
|
|
||||||
candidates: &[LayoutAnimationCandidate],
|
|
||||||
now_nsec: u64,
|
|
||||||
) -> bool {
|
|
||||||
if candidates.len() < 2 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let windows: Vec<_> = candidates
|
let windows: Vec<_> = candidates
|
||||||
.iter()
|
.iter()
|
||||||
.map(|candidate| MultiphaseWindow {
|
.map(|candidate| MultiphaseWindow {
|
||||||
node_id: candidate.node_id,
|
node_id: candidate.node_id,
|
||||||
from: self
|
from: self
|
||||||
.animations
|
.animations
|
||||||
.visual_rect(candidate.node_id, candidate.old, now_nsec),
|
.visual_rect(candidate.node_id, candidate.old, now),
|
||||||
to: candidate.new,
|
to: candidate.new,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let Some(first) = windows.first() else {
|
for group in partition_motion_groups(&windows) {
|
||||||
|
if self.start_multiphase_layout_animation(&candidates, &windows, &group, now) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for idx in group {
|
||||||
|
self.start_layout_animation_candidate(candidates[idx].clone(), now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_multiphase_layout_animation(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
candidates: &[LayoutAnimationCandidate],
|
||||||
|
windows: &[MultiphaseWindow],
|
||||||
|
group: &[usize],
|
||||||
|
now_nsec: u64,
|
||||||
|
) -> bool {
|
||||||
|
if group.len() < 2 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let request_windows: Vec<_> = group.iter().map(|&idx| windows[idx]).collect();
|
||||||
|
let Some(first) = request_windows.first() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let mut bounds = first.from.union(first.to);
|
let mut bounds = first.from.union(first.to);
|
||||||
for window in &windows[1..] {
|
for window in &request_windows[1..] {
|
||||||
bounds = bounds.union(window.from).union(window.to);
|
bounds = bounds.union(window.from).union(window.to);
|
||||||
}
|
}
|
||||||
let Ok(plan) = plan_no_overlap(&MultiphaseRequest { bounds, windows }) else {
|
let Ok(plan) = plan_no_overlap(&MultiphaseRequest {
|
||||||
|
bounds,
|
||||||
|
windows: request_windows,
|
||||||
|
}) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if plan.phases.is_empty() {
|
if plan.phases.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let mut entries = vec![];
|
let mut entries = vec![];
|
||||||
for candidate in candidates {
|
for &idx in group {
|
||||||
let mut current =
|
let candidate = &candidates[idx];
|
||||||
self.animations
|
let window = windows[idx];
|
||||||
.visual_rect(candidate.node_id, candidate.old, now_nsec);
|
let mut current = window.from;
|
||||||
let mut damage = current.union(candidate.new);
|
let mut damage = current.union(window.to);
|
||||||
let mut phases = vec![];
|
let mut phases = vec![];
|
||||||
for phase in &plan.phases {
|
for phase in &plan.phases {
|
||||||
match phase
|
match phase
|
||||||
|
|
@ -1646,7 +1656,7 @@ impl State {
|
||||||
None => phases.push((current, current)),
|
None => phases.push((current, current)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if current != candidate.new {
|
if current != window.to {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let retained = self
|
let retained = self
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue