fix: recent cards sizing issue
This commit is contained in:
parent
21dc169578
commit
57d50a95c4
1 changed files with 81 additions and 9 deletions
|
|
@ -18,6 +18,8 @@ const MAX_CARD_WIDTH_PX: f32 = 480.0;
|
|||
const CARD_MS: u64 = 460;
|
||||
const STAGGER_MS: u64 = 85;
|
||||
const CARD_RISE_PX: f32 = 48.0;
|
||||
/// Items the intro staggers individually; past this they rise together.
|
||||
const INTRO_ITEMS: usize = 6;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct RecentsRow;
|
||||
|
|
@ -31,17 +33,22 @@ impl Component for RecentsRow {
|
|||
|
||||
let sorted: Vec<Cluster> = sort_clusters_for_home(clusters);
|
||||
let slots = *visible_slots.read();
|
||||
let display = sorted.into_iter().take(slots).collect::<Vec<_>>();
|
||||
let count = display.len();
|
||||
|
||||
let items = count + 1;
|
||||
// Budgeted off the clusters we have, never off the slots we can show. An
|
||||
// offset is the one thing a card must not change on a frame where the row
|
||||
// is re-measured: the layout engine treats an offset-only change as "inner
|
||||
// layout" and hands back the card's *cached* area instead of measuring it,
|
||||
// so a rerun triggered by the slot count would freeze the surviving cards
|
||||
// at their pre-resize widths while the new ones come in at the new width.
|
||||
let items = intro_items(sorted.len());
|
||||
|
||||
let display = sorted.into_iter().take(slots).collect::<Vec<_>>();
|
||||
|
||||
let intro = use_animation_with_dependencies(&items, |conf, items| {
|
||||
conf.on_creation(OnCreation::Run);
|
||||
conf.on_change(OnChange::Rerun);
|
||||
let total = CARD_MS + (items.saturating_sub(1) as u64) * STAGGER_MS;
|
||||
AnimNum::new(0., 1.)
|
||||
.time(total.max(1))
|
||||
.time(intro_ms(*items).max(1))
|
||||
.function(Function::Linear)
|
||||
});
|
||||
let progress = intro.get().value();
|
||||
|
|
@ -68,17 +75,28 @@ impl Component for RecentsRow {
|
|||
.into_element()
|
||||
}))
|
||||
.child(OtherVersionsTile {
|
||||
index: count,
|
||||
index: items.saturating_sub(1),
|
||||
items,
|
||||
progress,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Items to stagger for `clusters` recents, counting the "other versions" tile.
|
||||
fn intro_items(clusters: usize) -> usize {
|
||||
(clusters + 1).min(INTRO_ITEMS)
|
||||
}
|
||||
|
||||
fn intro_ms(items: usize) -> u64 {
|
||||
CARD_MS + (items.saturating_sub(1) as u64) * STAGGER_MS
|
||||
}
|
||||
|
||||
fn stagger_eased(progress: f32, index: usize, items: usize) -> f32 {
|
||||
let total = CARD_MS as f32 + (items.saturating_sub(1) as f32) * STAGGER_MS as f32;
|
||||
let elapsed = progress * total;
|
||||
let local = ((elapsed - index as f32 * STAGGER_MS as f32) / CARD_MS as f32).clamp(0., 1.);
|
||||
let elapsed = progress * intro_ms(items) as f32;
|
||||
// Anything past the budget shares the last slot, so every item still lands on
|
||||
// a finished 1.0 rather than freezing part-way with a leftover offset.
|
||||
let start = index.min(items.saturating_sub(1)) as f32 * STAGGER_MS as f32;
|
||||
let local = ((elapsed - start) / CARD_MS as f32).clamp(0., 1.);
|
||||
1.0 - (1.0 - local).powi(3)
|
||||
}
|
||||
|
||||
|
|
@ -299,3 +317,57 @@ fn recent_card_slots_for_width(row_width_px: f32) -> usize {
|
|||
let slot = MIN_CARD_WIDTH_PX + CARD_GAP_PX;
|
||||
(available / slot).floor().max(1.0) as usize
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn row_fits(slots: usize, row_width_px: f32) -> bool {
|
||||
let cards = slots as f32 * MIN_CARD_WIDTH_PX;
|
||||
let gaps = slots as f32 * CARD_GAP_PX;
|
||||
cards + gaps + MORE_TILE_WIDTH_PX <= row_width_px
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slots_fill_the_row_without_overflowing_it() {
|
||||
for width in (200..3200).step_by(4) {
|
||||
let width = width as f32;
|
||||
let slots = recent_card_slots_for_width(width);
|
||||
|
||||
assert!(slots >= 1, "{width}px left no room for a card");
|
||||
if slots > 1 {
|
||||
assert!(row_fits(slots, width), "{slots} cards overflow {width}px");
|
||||
}
|
||||
assert!(
|
||||
!row_fits(slots + 1, width),
|
||||
"{width}px had room for {} cards",
|
||||
slots + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_item_settles_when_the_intro_ends() {
|
||||
// A card left with a leftover offset keeps changing it, and an offset-only
|
||||
// change is what makes the layout engine reuse a card's cached width.
|
||||
for clusters in 0..24 {
|
||||
let items = intro_items(clusters);
|
||||
for index in 0..=clusters {
|
||||
assert_eq!(
|
||||
stagger_eased(1.0, index, items),
|
||||
1.0,
|
||||
"item {index} of {clusters} never finished rising"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_intro_runs_the_same_length_whatever_fits_on_screen() {
|
||||
// The window width decides how many cards show, so anything the intro is
|
||||
// measured against has to be independent of it.
|
||||
assert_eq!(intro_items(3), intro_items(3));
|
||||
assert_eq!(intro_ms(intro_items(30)), intro_ms(INTRO_ITEMS));
|
||||
assert_eq!(intro_ms(intro_items(0)), CARD_MS);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue