Refine animation planner test fixes
This commit is contained in:
parent
d2138b45f6
commit
1a75f47709
3 changed files with 214 additions and 77 deletions
|
|
@ -22,8 +22,6 @@ const DEFAULT_DURATION_MS: u32 = 160;
|
||||||
const CURVE_MAX_POINTS: usize = 33;
|
const CURVE_MAX_POINTS: usize = 33;
|
||||||
const CURVE_FLATNESS_EPSILON: f32 = 0.001;
|
const CURVE_FLATNESS_EPSILON: f32 = 0.001;
|
||||||
const CURVE_MAX_DEPTH: u8 = 8;
|
const CURVE_MAX_DEPTH: u8 = 8;
|
||||||
const SPAWN_IN_INITIAL_SCALE_NUMERATOR: i32 = 4;
|
|
||||||
const SPAWN_IN_INITIAL_SCALE_DENOMINATOR: i32 = 5;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum AnimationCurve {
|
pub enum AnimationCurve {
|
||||||
|
|
@ -296,7 +294,7 @@ impl AnimationState {
|
||||||
duration_ms: u32,
|
duration_ms: u32,
|
||||||
curve: AnimationCurve,
|
curve: AnimationCurve,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if old == new || old.is_empty() || new.is_empty() || duration_ms == 0 {
|
if old == new || new.is_empty() || duration_ms == 0 {
|
||||||
self.windows.borrow_mut().remove(&node_id);
|
self.windows.borrow_mut().remove(&node_id);
|
||||||
self.phased.borrow_mut().remove(&node_id);
|
self.phased.borrow_mut().remove(&node_id);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -420,7 +418,7 @@ impl AnimationState {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let to = spawn_in_start_rect(from);
|
let to = spawn_in_start_rect(from);
|
||||||
if to == from || to.is_empty() {
|
if to == from {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let source_body_size = body_size_for_frame(from, frame_inset);
|
let source_body_size = body_size_for_frame(from, frame_inset);
|
||||||
|
|
@ -690,20 +688,8 @@ impl LatchListener for AnimationTick {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn spawn_in_start_rect(target: Rect) -> Rect {
|
pub(crate) fn spawn_in_start_rect(target: Rect) -> Rect {
|
||||||
fn scaled_dimension(value: i32) -> i32 {
|
let (cx, cy) = target.center();
|
||||||
let scaled = (value as i64 * SPAWN_IN_INITIAL_SCALE_NUMERATOR as i64
|
Rect::new_empty(cx, cy)
|
||||||
/ SPAWN_IN_INITIAL_SCALE_DENOMINATOR as i64) as i32;
|
|
||||||
scaled.clamp(1, value.max(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
let width = scaled_dimension(target.width());
|
|
||||||
let height = scaled_dimension(target.height());
|
|
||||||
Rect::new_sized_saturating(
|
|
||||||
target.x1() + (target.width() - width) / 2,
|
|
||||||
target.y1() + (target.height() - height) / 2,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn body_size_for_frame(rect: Rect, frame_inset: i32) -> (i32, i32) {
|
fn body_size_for_frame(rect: Rect, frame_inset: i32) -> (i32, i32) {
|
||||||
|
|
@ -936,12 +922,9 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn spawn_in_start_rect_is_centered_and_non_empty() {
|
fn spawn_in_start_rect_is_centered_and_empty() {
|
||||||
let target = Rect::new_sized_saturating(10, 20, 100, 50);
|
let target = Rect::new_sized_saturating(10, 20, 100, 50);
|
||||||
assert_eq!(
|
assert_eq!(spawn_in_start_rect(target), Rect::new_empty(60, 45));
|
||||||
spawn_in_start_rect(target),
|
|
||||||
Rect::new_sized_saturating(20, 25, 80, 40)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -952,7 +935,7 @@ mod tests {
|
||||||
assert!(state.set_spawn_in(id, target, None, 0, 160));
|
assert!(state.set_spawn_in(id, target, None, 0, 160));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.visual_rect(id, target, 80_000_000),
|
state.visual_rect(id, target, 80_000_000),
|
||||||
Rect::new_sized_saturating(15, 23, 90, 45)
|
Rect::new_sized_saturating(35, 33, 50, 25)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ const MIN_SHRINK_DENOMINATOR: i32 = 4;
|
||||||
pub struct MultiphaseRequest {
|
pub struct MultiphaseRequest {
|
||||||
pub bounds: Rect,
|
pub bounds: Rect,
|
||||||
pub windows: Vec<MultiphaseWindow>,
|
pub windows: Vec<MultiphaseWindow>,
|
||||||
|
pub clearance: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
|
@ -488,6 +489,22 @@ fn plan_forward(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] {
|
||||||
|
match plan_space_then_orthogonal_growth(request, axis) {
|
||||||
|
Ok(plan) => return Ok(plan),
|
||||||
|
Err(error) => {
|
||||||
|
record_rejection(
|
||||||
|
&mut attempted,
|
||||||
|
direction,
|
||||||
|
PlanStrategy::SpaceThenOrthogonalGrowth { axis },
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
if error != MultiphasePlanFailure::NoPattern {
|
||||||
|
rejection.get_or_insert(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
match plan_hierarchy_ordered_axis_scales(request) {
|
match plan_hierarchy_ordered_axis_scales(request) {
|
||||||
Ok(plan) => return Ok(plan),
|
Ok(plan) => return Ok(plan),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|
@ -534,22 +551,6 @@ fn plan_forward(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] {
|
|
||||||
match plan_space_then_orthogonal_growth(request, axis) {
|
|
||||||
Ok(plan) => return Ok(plan),
|
|
||||||
Err(error) => {
|
|
||||||
record_rejection(
|
|
||||||
&mut attempted,
|
|
||||||
direction,
|
|
||||||
PlanStrategy::SpaceThenOrthogonalGrowth { axis },
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
if error != MultiphasePlanFailure::NoPattern {
|
|
||||||
rejection.get_or_insert(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(PlanForwardFailure {
|
Err(PlanForwardFailure {
|
||||||
reason: rejection.unwrap_or(MultiphasePlanFailure::NoPattern),
|
reason: rejection.unwrap_or(MultiphasePlanFailure::NoPattern),
|
||||||
attempted,
|
attempted,
|
||||||
|
|
@ -750,7 +751,13 @@ fn plan_axis_crossing_lanes(
|
||||||
request: &MultiphaseRequest,
|
request: &MultiphaseRequest,
|
||||||
axis: PhaseAxis,
|
axis: PhaseAxis,
|
||||||
) -> Result<MultiphasePlanned, MultiphasePlanFailure> {
|
) -> Result<MultiphasePlanned, MultiphasePlanFailure> {
|
||||||
if request.windows.len() != 2 {
|
let moving_windows: Vec<_> = request
|
||||||
|
.windows
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|window| window.from != window.to)
|
||||||
|
.collect();
|
||||||
|
if moving_windows.len() != 2 {
|
||||||
return Err(MultiphasePlanFailure::NoPattern);
|
return Err(MultiphasePlanFailure::NoPattern);
|
||||||
}
|
}
|
||||||
let orth_min = request
|
let orth_min = request
|
||||||
|
|
@ -765,7 +772,7 @@ fn plan_axis_crossing_lanes(
|
||||||
.map(|window| orth_end(window.from, axis))
|
.map(|window| orth_end(window.from, axis))
|
||||||
.max()
|
.max()
|
||||||
.ok_or(MultiphasePlanFailure::NoPattern)?;
|
.ok_or(MultiphasePlanFailure::NoPattern)?;
|
||||||
if request.windows.iter().any(|window| {
|
if moving_windows.iter().any(|window| {
|
||||||
main_size(window.from, axis) != main_size(window.to, axis)
|
main_size(window.from, axis) != main_size(window.to, axis)
|
||||||
|| orth_start(window.from, axis) != orth_min
|
|| orth_start(window.from, axis) != orth_min
|
||||||
|| orth_end(window.from, axis) != orth_max
|
|| orth_end(window.from, axis) != orth_max
|
||||||
|
|
@ -775,7 +782,18 @@ fn plan_axis_crossing_lanes(
|
||||||
}) {
|
}) {
|
||||||
return Err(MultiphasePlanFailure::NoPattern);
|
return Err(MultiphasePlanFailure::NoPattern);
|
||||||
}
|
}
|
||||||
let lane_size = (orth_max - orth_min) / request.windows.len() as i32;
|
let clearance = request.clearance.max(0);
|
||||||
|
let lane_count = moving_windows.len() as i32;
|
||||||
|
let available = (orth_max - orth_min) - clearance.saturating_mul(lane_count - 1);
|
||||||
|
if available <= 0 {
|
||||||
|
return Err(MultiphasePlanFailure::ShrinkBound {
|
||||||
|
axis: axis.other(),
|
||||||
|
available: 0,
|
||||||
|
required: sane_min_size(orth_max - orth_min),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let lane_size = available / lane_count;
|
||||||
|
let mut lane_remainder = available % lane_count;
|
||||||
let required = sane_min_size(orth_max - orth_min);
|
let required = sane_min_size(orth_max - orth_min);
|
||||||
if lane_size < required {
|
if lane_size < required {
|
||||||
return Err(MultiphasePlanFailure::ShrinkBound {
|
return Err(MultiphasePlanFailure::ShrinkBound {
|
||||||
|
|
@ -785,7 +803,7 @@ fn plan_axis_crossing_lanes(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut windows = request.windows.clone();
|
let mut windows = moving_windows;
|
||||||
windows.sort_by_key(|window| lane_index_for_direction(*window, axis));
|
windows.sort_by_key(|window| lane_index_for_direction(*window, axis));
|
||||||
if windows.windows(2).any(|pair| {
|
if windows.windows(2).any(|pair| {
|
||||||
lane_index_for_direction(pair[0], axis) == lane_index_for_direction(pair[1], axis)
|
lane_index_for_direction(pair[0], axis) == lane_index_for_direction(pair[1], axis)
|
||||||
|
|
@ -795,13 +813,15 @@ fn plan_axis_crossing_lanes(
|
||||||
let mut phase1 = vec![];
|
let mut phase1 = vec![];
|
||||||
let mut phase2 = vec![];
|
let mut phase2 = vec![];
|
||||||
let mut phase3 = vec![];
|
let mut phase3 = vec![];
|
||||||
|
let mut lane_start = orth_min;
|
||||||
for (idx, window) in windows.iter().enumerate() {
|
for (idx, window) in windows.iter().enumerate() {
|
||||||
let lane_start = orth_min + lane_size * idx as i32;
|
let extra = if lane_remainder > 0 {
|
||||||
let lane_end = if idx + 1 == windows.len() {
|
lane_remainder -= 1;
|
||||||
orth_max
|
1
|
||||||
} else {
|
} else {
|
||||||
lane_start + lane_size
|
0
|
||||||
};
|
};
|
||||||
|
let lane_end = lane_start + lane_size + extra;
|
||||||
let lane_from = with_orth_interval(window.from, axis, lane_start, lane_end);
|
let lane_from = with_orth_interval(window.from, axis, lane_start, lane_end);
|
||||||
let lane_to = with_main_interval(
|
let lane_to = with_main_interval(
|
||||||
lane_from,
|
lane_from,
|
||||||
|
|
@ -812,6 +832,9 @@ fn plan_axis_crossing_lanes(
|
||||||
push_step(&mut phase1, window.node_id, window.from, lane_from);
|
push_step(&mut phase1, window.node_id, window.from, lane_from);
|
||||||
push_step(&mut phase2, window.node_id, lane_from, lane_to);
|
push_step(&mut phase2, window.node_id, lane_from, lane_to);
|
||||||
push_step(&mut phase3, window.node_id, lane_to, window.to);
|
push_step(&mut phase3, window.node_id, lane_to, window.to);
|
||||||
|
if idx + 1 < windows.len() {
|
||||||
|
lane_start = lane_end + clearance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
build_validated_plan(
|
build_validated_plan(
|
||||||
request,
|
request,
|
||||||
|
|
@ -882,6 +905,7 @@ fn plan_space_then_orthogonal_growth(
|
||||||
|| main_end(window.from, axis) != main_end(window.to, axis);
|
|| main_end(window.from, axis) != main_end(window.to, axis);
|
||||||
let orth_changes = orth_start(window.from, axis) != orth_start(window.to, axis)
|
let orth_changes = orth_start(window.from, axis) != orth_start(window.to, axis)
|
||||||
|| orth_end(window.from, axis) != orth_end(window.to, axis);
|
|| orth_end(window.from, axis) != orth_end(window.to, axis);
|
||||||
|
let mut orth_from = window.from;
|
||||||
if main_changes && main_size(window.from, axis) == main_size(window.to, axis) {
|
if main_changes && main_size(window.from, axis) == main_size(window.to, axis) {
|
||||||
let after_move = with_main_interval(
|
let after_move = with_main_interval(
|
||||||
window.from,
|
window.from,
|
||||||
|
|
@ -890,22 +914,50 @@ fn plan_space_then_orthogonal_growth(
|
||||||
main_end(window.to, axis),
|
main_end(window.to, axis),
|
||||||
);
|
);
|
||||||
push_step(&mut phase2, window.node_id, window.from, after_move);
|
push_step(&mut phase2, window.node_id, window.from, after_move);
|
||||||
if orth_changes {
|
orth_from = after_move;
|
||||||
push_step(&mut phase3, window.node_id, after_move, window.to);
|
|
||||||
}
|
|
||||||
} else if main_changes {
|
} else if main_changes {
|
||||||
let after_main_scale = with_main_interval(
|
let target_size = main_size(window.to, axis);
|
||||||
window.from,
|
let after_main_scale = if main_start(window.from, axis) == main_start(window.to, axis)
|
||||||
axis,
|
|| main_end(window.from, axis) == main_end(window.to, axis)
|
||||||
main_start(window.to, axis),
|
{
|
||||||
main_end(window.to, axis),
|
with_main_interval(
|
||||||
);
|
window.from,
|
||||||
|
axis,
|
||||||
|
main_start(window.to, axis),
|
||||||
|
main_end(window.to, axis),
|
||||||
|
)
|
||||||
|
} else if main_start(window.to, axis) < main_start(window.from, axis) {
|
||||||
|
with_main_interval(
|
||||||
|
window.from,
|
||||||
|
axis,
|
||||||
|
main_end(window.from, axis) - target_size,
|
||||||
|
main_end(window.from, axis),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
with_main_interval(
|
||||||
|
window.from,
|
||||||
|
axis,
|
||||||
|
main_start(window.from, axis),
|
||||||
|
main_start(window.from, axis) + target_size,
|
||||||
|
)
|
||||||
|
};
|
||||||
push_step(&mut phase1, window.node_id, window.from, after_main_scale);
|
push_step(&mut phase1, window.node_id, window.from, after_main_scale);
|
||||||
if orth_changes {
|
orth_from = after_main_scale;
|
||||||
push_step(&mut phase3, window.node_id, after_main_scale, window.to);
|
if main_start(after_main_scale, axis) != main_start(window.to, axis)
|
||||||
|
|| main_end(after_main_scale, axis) != main_end(window.to, axis)
|
||||||
|
{
|
||||||
|
let after_move = with_main_interval(
|
||||||
|
after_main_scale,
|
||||||
|
axis,
|
||||||
|
main_start(window.to, axis),
|
||||||
|
main_end(window.to, axis),
|
||||||
|
);
|
||||||
|
push_step(&mut phase2, window.node_id, after_main_scale, after_move);
|
||||||
|
orth_from = after_move;
|
||||||
}
|
}
|
||||||
} else if orth_changes {
|
}
|
||||||
push_step(&mut phase3, window.node_id, window.from, window.to);
|
if orth_changes {
|
||||||
|
push_step(&mut phase3, window.node_id, orth_from, window.to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if phase1.is_empty() || phase2.is_empty() || phase3.is_empty() {
|
if phase1.is_empty() || phase2.is_empty() || phase3.is_empty() {
|
||||||
|
|
@ -1372,6 +1424,7 @@ fn single_action_reason(action: PhaseAction) -> PhaseReason {
|
||||||
fn reverse_request(request: &MultiphaseRequest) -> MultiphaseRequest {
|
fn reverse_request(request: &MultiphaseRequest) -> MultiphaseRequest {
|
||||||
MultiphaseRequest {
|
MultiphaseRequest {
|
||||||
bounds: request.bounds,
|
bounds: request.bounds,
|
||||||
|
clearance: request.clearance,
|
||||||
windows: request
|
windows: request
|
||||||
.windows
|
.windows
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -1686,7 +1739,11 @@ mod tests {
|
||||||
MultiphaseWindowHierarchy::new(old_leaf.hierarchy, new_leaf.hierarchy),
|
MultiphaseWindowHierarchy::new(old_leaf.hierarchy, new_leaf.hierarchy),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
MultiphaseRequest { bounds, windows }
|
MultiphaseRequest {
|
||||||
|
bounds,
|
||||||
|
windows,
|
||||||
|
clearance: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_generated_case_plans(old: &TestTree, new: &TestTree, bounds: Rect) {
|
fn assert_generated_case_plans(old: &TestTree, new: &TestTree, bounds: Rect) {
|
||||||
|
|
@ -1739,7 +1796,11 @@ mod tests {
|
||||||
.map(|window| window.from.union(window.to))
|
.map(|window| window.from.union(window.to))
|
||||||
.reduce(|bounds, rect| bounds.union(rect))
|
.reduce(|bounds, rect| bounds.union(rect))
|
||||||
.unwrap_or_else(|| rect(0, 0, 1, 1));
|
.unwrap_or_else(|| rect(0, 0, 1, 1));
|
||||||
MultiphaseRequest { bounds, windows }
|
MultiphaseRequest {
|
||||||
|
bounds,
|
||||||
|
windows,
|
||||||
|
clearance: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn actions(plan: &MultiphasePlan) -> Vec<PhaseAction> {
|
fn actions(plan: &MultiphasePlan) -> Vec<PhaseAction> {
|
||||||
|
|
@ -1765,6 +1826,20 @@ mod tests {
|
||||||
strategy: PlanStrategy::SingleAction,
|
strategy: PlanStrategy::SingleAction,
|
||||||
reason: MultiphasePlanFailure::NoPattern,
|
reason: MultiphasePlanFailure::NoPattern,
|
||||||
},
|
},
|
||||||
|
RejectedStrategy {
|
||||||
|
direction,
|
||||||
|
strategy: PlanStrategy::SpaceThenOrthogonalGrowth {
|
||||||
|
axis: PhaseAxis::Horizontal,
|
||||||
|
},
|
||||||
|
reason: MultiphasePlanFailure::NoPattern,
|
||||||
|
},
|
||||||
|
RejectedStrategy {
|
||||||
|
direction,
|
||||||
|
strategy: PlanStrategy::SpaceThenOrthogonalGrowth {
|
||||||
|
axis: PhaseAxis::Vertical,
|
||||||
|
},
|
||||||
|
reason: MultiphasePlanFailure::NoPattern,
|
||||||
|
},
|
||||||
RejectedStrategy {
|
RejectedStrategy {
|
||||||
direction,
|
direction,
|
||||||
strategy: PlanStrategy::HierarchyOrderedScales,
|
strategy: PlanStrategy::HierarchyOrderedScales,
|
||||||
|
|
@ -1798,20 +1873,6 @@ mod tests {
|
||||||
},
|
},
|
||||||
reason: MultiphasePlanFailure::NoPattern,
|
reason: MultiphasePlanFailure::NoPattern,
|
||||||
},
|
},
|
||||||
RejectedStrategy {
|
|
||||||
direction,
|
|
||||||
strategy: PlanStrategy::SpaceThenOrthogonalGrowth {
|
|
||||||
axis: PhaseAxis::Horizontal,
|
|
||||||
},
|
|
||||||
reason: MultiphasePlanFailure::NoPattern,
|
|
||||||
},
|
|
||||||
RejectedStrategy {
|
|
||||||
direction,
|
|
||||||
strategy: PlanStrategy::SpaceThenOrthogonalGrowth {
|
|
||||||
axis: PhaseAxis::Vertical,
|
|
||||||
},
|
|
||||||
reason: MultiphasePlanFailure::NoPattern,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1916,6 +1977,38 @@ mod tests {
|
||||||
assert!(validate_plan_continuous(&req, &plan));
|
assert!(validate_plan_continuous(&req, &plan));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swap_lanes_respect_requested_clearance() {
|
||||||
|
let mut req = request(vec![
|
||||||
|
window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)),
|
||||||
|
window(2, rect(100, 0, 200, 100), rect(0, 0, 100, 100)),
|
||||||
|
]);
|
||||||
|
req.clearance = 10;
|
||||||
|
|
||||||
|
let plan = plan_no_overlap(&req).unwrap();
|
||||||
|
assert_eq!(step_to(&plan, 0, id(1)), rect(0, 0, 100, 45));
|
||||||
|
assert_eq!(step_to(&plan, 0, id(2)), rect(100, 55, 200, 100));
|
||||||
|
assert!(validate_plan_continuous(&req, &plan));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swap_lanes_tolerate_stationary_siblings_in_request() {
|
||||||
|
let req = request(vec![
|
||||||
|
window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)),
|
||||||
|
window(2, rect(100, 0, 200, 100), rect(0, 0, 100, 100)),
|
||||||
|
window(3, rect(200, 0, 300, 100), rect(200, 0, 300, 100)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let planned = plan_no_overlap_explained(&req).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
planned.explanation.strategy,
|
||||||
|
PlanStrategy::SwapLanes {
|
||||||
|
axis: PhaseAxis::Horizontal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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![
|
||||||
|
|
@ -2232,6 +2325,59 @@ mod tests {
|
||||||
assert_generated_case_plans(&vertical_new, &vertical_old, rect(0, 0, 100, 400));
|
assert_generated_case_plans(&vertical_new, &vertical_old, rect(0, 0, 100, 400));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stack_extraction_with_resized_moving_child_still_moves_before_growth() {
|
||||||
|
let old = split(
|
||||||
|
10,
|
||||||
|
PhaseAxis::Horizontal,
|
||||||
|
&[1, 1],
|
||||||
|
vec![
|
||||||
|
leaf(1),
|
||||||
|
split(11, PhaseAxis::Vertical, &[1, 1], vec![leaf(2), leaf(3)]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let new = split(
|
||||||
|
10,
|
||||||
|
PhaseAxis::Horizontal,
|
||||||
|
&[1, 1, 1],
|
||||||
|
vec![leaf(1), leaf(2), leaf(3)],
|
||||||
|
);
|
||||||
|
let req = generated_request(&old, &new, rect(0, 0, 300, 120));
|
||||||
|
let planned = plan_no_overlap_explained(&req).unwrap();
|
||||||
|
let plan = &planned.plan;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
planned.explanation.strategy,
|
||||||
|
PlanStrategy::SpaceThenOrthogonalGrowth {
|
||||||
|
axis: PhaseAxis::Horizontal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
actions(plan),
|
||||||
|
vec![
|
||||||
|
PhaseAction {
|
||||||
|
kind: PhaseKind::Scale,
|
||||||
|
axis: PhaseAxis::Horizontal,
|
||||||
|
},
|
||||||
|
PhaseAction {
|
||||||
|
kind: PhaseKind::Move,
|
||||||
|
axis: PhaseAxis::Horizontal,
|
||||||
|
},
|
||||||
|
PhaseAction {
|
||||||
|
kind: PhaseKind::Scale,
|
||||||
|
axis: PhaseAxis::Vertical,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(step_to(plan, 0, id(1)), rect(0, 0, 100, 120));
|
||||||
|
assert_eq!(step_to(plan, 0, id(2)), rect(200, 0, 300, 60));
|
||||||
|
assert_eq!(step_to(plan, 0, id(3)), rect(200, 60, 300, 120));
|
||||||
|
assert_eq!(step_to(plan, 1, id(2)), rect(100, 0, 200, 60));
|
||||||
|
assert_eq!(step_to(plan, 2, id(2)), rect(100, 0, 200, 120));
|
||||||
|
assert_eq!(step_to(plan, 2, id(3)), rect(200, 0, 300, 120));
|
||||||
|
assert!(validate_plan_continuous(&req, plan));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bounded_generated_supported_split_tree_corpus_is_deterministic() {
|
fn bounded_generated_supported_split_tree_corpus_is_deterministic() {
|
||||||
let mut cases = vec![];
|
let mut cases = vec![];
|
||||||
|
|
@ -2543,6 +2689,7 @@ mod tests {
|
||||||
fn diagnostics_report_shrink_bound_rejections() {
|
fn diagnostics_report_shrink_bound_rejections() {
|
||||||
let req = MultiphaseRequest {
|
let req = MultiphaseRequest {
|
||||||
bounds: rect(0, 0, 400, 100),
|
bounds: rect(0, 0, 400, 100),
|
||||||
|
clearance: 0,
|
||||||
windows: vec![
|
windows: vec![
|
||||||
MultiphaseWindow {
|
MultiphaseWindow {
|
||||||
node_id: id(1),
|
node_id: id(1),
|
||||||
|
|
|
||||||
|
|
@ -1650,6 +1650,12 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn layout_animation_clearance(&self) -> i32 {
|
||||||
|
let border = self.theme.sizes.border_width.get().max(0);
|
||||||
|
let gap = self.theme.sizes.gap.get().max(0);
|
||||||
|
if gap == 0 { border } else { gap + 2 * border }
|
||||||
|
}
|
||||||
|
|
||||||
fn start_multiphase_layout_animation(
|
fn start_multiphase_layout_animation(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
candidates: &[LayoutAnimationCandidate],
|
candidates: &[LayoutAnimationCandidate],
|
||||||
|
|
@ -1671,6 +1677,7 @@ impl State {
|
||||||
let request = MultiphaseRequest {
|
let request = MultiphaseRequest {
|
||||||
bounds,
|
bounds,
|
||||||
windows: request_windows,
|
windows: request_windows,
|
||||||
|
clearance: self.layout_animation_clearance(),
|
||||||
};
|
};
|
||||||
let plan = match plan_no_overlap_with_diagnostics(&request) {
|
let plan = match plan_no_overlap_with_diagnostics(&request) {
|
||||||
Ok(plan) => plan,
|
Ok(plan) => plan,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue