Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 005efa2ee4 | |||
| 1190ed3ce6 | |||
| 2869e1c34e | |||
| 783f01628e | |||
| 94a6feb5db | |||
| 3daaf47689 |
+3
-1
@@ -41,7 +41,9 @@ project follows [Semantic Versioning](https://semver.org/).
|
|||||||
each column's total depth (face-down cards included) and refills on the
|
each column's total depth (face-down cards included) and refills on the
|
||||||
cold-start deal, every move, and on resize (incl. the Android safe-area-inset
|
cold-start deal, every move, and on resize (incl. the Android safe-area-inset
|
||||||
resize and fold/unfold), so a near-square viewport such as an unfolded Galaxy
|
resize and fold/unfold), so a near-square viewport such as an unfolded Galaxy
|
||||||
Fold no longer leaves the bottom of the screen empty.
|
Fold no longer leaves the bottom of the screen empty. The fan spread cap was
|
||||||
|
raised so very tall / narrow viewports (e.g. a foldable cover screen) fill
|
||||||
|
further.
|
||||||
- **Card-move animation jank.** A move now rebuilds a card's child visuals only
|
- **Card-move animation jank.** A move now rebuilds a card's child visuals only
|
||||||
when its appearance changes (flip / resize / accessibility) instead of
|
when its appearance changes (flip / resize / accessibility) instead of
|
||||||
despawning and respawning every card's children each `StateChangedEvent`,
|
despawning and respawning every card's children each `StateChangedEvent`,
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ const MAX_TABLEAU_CARDS: f32 = 13.0;
|
|||||||
/// clear overlap (a readable stack) while still filling most of a near-square /
|
/// clear overlap (a readable stack) while still filling most of a near-square /
|
||||||
/// unfolded-foldable screen. Tunable purely for feel — no effect on correctness
|
/// unfolded-foldable screen. Tunable purely for feel — no effect on correctness
|
||||||
/// or hit-testing.
|
/// or hit-testing.
|
||||||
pub(crate) const MAX_DYNAMIC_FAN_FRAC: f32 = 0.6;
|
pub(crate) const MAX_DYNAMIC_FAN_FRAC: f32 = 0.9;
|
||||||
|
|
||||||
/// Vertical pixel band reserved at the top of the play area for the HUD
|
/// Vertical pixel band reserved at the top of the play area for the HUD
|
||||||
/// (action buttons, Score / Moves / Timer readouts). The card grid starts
|
/// (action buttons, Score / Moves / Timer readouts). The card grid starts
|
||||||
@@ -285,13 +285,12 @@ pub fn compute_layout(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adaptive tableau fan fraction. On height-limited (desktop) windows the
|
// Adaptive tableau fan fraction. On height-limited windows the height-based
|
||||||
// height-based sizing already ensures a worst-case 13-card column fits at
|
// sizing already ensures a worst-case 13-card column fits at TABLEAU_FAN_FRAC,
|
||||||
// TABLEAU_FAN_FRAC (0.25), so the formula returns ≈0.25 and the clamp
|
// so the formula returns the minimum and the clamp keeps it there. On
|
||||||
// keeps it there — no change from prior behaviour. On width-limited
|
// width-limited (portrait phone) windows card_size is small and lots of
|
||||||
// (portrait phone) windows card_size is small and lots of vertical space
|
// vertical space is unused; solve for the fraction that fills the available
|
||||||
// is unused; we solve for the fraction that exactly fills the available
|
// space. `apply_dynamic_tableau_fan` later refines this for the actual deal.
|
||||||
// space to the bottom margin.
|
|
||||||
//
|
//
|
||||||
// avail = distance from the top of the first tableau card to the bottom
|
// avail = distance from the top of the first tableau card to the bottom
|
||||||
// margin — i.e. the space available for 12 fan steps.
|
// margin — i.e. the space available for 12 fan steps.
|
||||||
@@ -302,20 +301,17 @@ pub fn compute_layout(
|
|||||||
} else {
|
} else {
|
||||||
TABLEAU_FAN_FRAC
|
TABLEAU_FAN_FRAC
|
||||||
};
|
};
|
||||||
// Never go below the desktop minimum — avoids shrinking the fan on
|
|
||||||
// degenerate near-square windows where the formula might undershoot.
|
|
||||||
let tableau_fan_frac = ideal_fan_frac.max(TABLEAU_FAN_FRAC);
|
let tableau_fan_frac = ideal_fan_frac.max(TABLEAU_FAN_FRAC);
|
||||||
// Scale the face-down fraction proportionally so rendering and hit-testing
|
|
||||||
// stay in sync (TABLEAU_FACEDOWN_FAN_FRAC / TABLEAU_FAN_FRAC = 0.48 ratio).
|
|
||||||
let facedown_scale = TABLEAU_FACEDOWN_FAN_FRAC / TABLEAU_FAN_FRAC;
|
let facedown_scale = TABLEAU_FACEDOWN_FAN_FRAC / TABLEAU_FAN_FRAC;
|
||||||
let tableau_facedown_fan_frac = tableau_fan_frac * facedown_scale;
|
let tableau_facedown_fan_frac = tableau_fan_frac * facedown_scale;
|
||||||
|
let available_tableau_height = avail;
|
||||||
|
|
||||||
Layout {
|
Layout {
|
||||||
card_size,
|
card_size,
|
||||||
pile_positions,
|
pile_positions,
|
||||||
tableau_fan_frac,
|
tableau_fan_frac,
|
||||||
tableau_facedown_fan_frac,
|
tableau_facedown_fan_frac,
|
||||||
available_tableau_height: avail,
|
available_tableau_height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,10 +191,12 @@ fn apply_safe_area_to_modal_scrims(
|
|||||||
/// whatever `SafeAreaInsets` are current at that moment.
|
/// whatever `SafeAreaInsets` are current at that moment.
|
||||||
///
|
///
|
||||||
/// On Android the `android::rearm_on_resumed` system runs in the same frame
|
/// On Android the `android::rearm_on_resumed` system runs in the same frame
|
||||||
/// and resets both `SafeAreaPollTries` and `SafeAreaInsets` to zero, causing
|
/// and resets `SafeAreaPollTries` (the cached `SafeAreaInsets` keep their
|
||||||
/// `refresh_insets` to re-poll JNI over the next few frames. When it resolves
|
/// last-known values), causing `refresh_insets` to re-poll JNI over the next
|
||||||
/// the correct values, `on_safe_area_changed` in `table_plugin` emits a second
|
/// few frames. If the insets changed while backgrounded, `on_safe_area_changed`
|
||||||
/// synthetic `WindowResized` and the layout converges to the right position.
|
/// in `table_plugin` emits a second synthetic `WindowResized` and the layout
|
||||||
|
/// converges to the right position; if they didn't, nothing is rewritten and
|
||||||
|
/// the layout stays put.
|
||||||
///
|
///
|
||||||
/// On non-Android targets this handler still fires — it ensures that a resume
|
/// On non-Android targets this handler still fires — it ensures that a resume
|
||||||
/// event always refreshes the layout (e.g., after a minimise/restore on
|
/// event always refreshes the layout (e.g., after a minimise/restore on
|
||||||
@@ -232,9 +234,14 @@ mod android {
|
|||||||
pub(super) struct SafeAreaPollTries(pub u32);
|
pub(super) struct SafeAreaPollTries(pub u32);
|
||||||
|
|
||||||
/// Polls Android for safe-area insets until we get a non-zero
|
/// Polls Android for safe-area insets until we get a non-zero
|
||||||
/// reading, then stops. `getRootWindowInsets()` returns `null` (or
|
/// reading, then settles until [`rearm_on_resumed`] re-arms it on the
|
||||||
/// all-zero `Insets`) until the decor view has been laid out, which
|
/// next foreground resume — insets can change while backgrounded
|
||||||
/// is typically frame 1–3 of a fresh launch.
|
/// (rotation, fold/unfold, gesture ↔ 3-button nav). The poll counter
|
||||||
|
/// (not `insets.is_populated()`) gates the loop, so a re-armed cycle
|
||||||
|
/// re-queries JNI even though cached values are already populated.
|
||||||
|
/// `getRootWindowInsets()` returns `null` (or all-zero `Insets`)
|
||||||
|
/// until the decor view has been laid out, which is typically frame
|
||||||
|
/// 1–3 of a fresh launch.
|
||||||
pub(super) fn refresh_insets(
|
pub(super) fn refresh_insets(
|
||||||
mut insets: ResMut<SafeAreaInsets>,
|
mut insets: ResMut<SafeAreaInsets>,
|
||||||
mut poll: ResMut<SafeAreaPollTries>,
|
mut poll: ResMut<SafeAreaPollTries>,
|
||||||
@@ -243,18 +250,25 @@ mod android {
|
|||||||
// devices that genuinely report zero insets.
|
// devices that genuinely report zero insets.
|
||||||
const MAX_TRIES: u32 = 120; // ~2 seconds @ 60 fps
|
const MAX_TRIES: u32 = 120; // ~2 seconds @ 60 fps
|
||||||
|
|
||||||
if poll.0 >= MAX_TRIES || insets.is_populated() {
|
if poll.0 >= MAX_TRIES {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
poll.0 += 1;
|
poll.0 += 1;
|
||||||
|
|
||||||
match query_insets() {
|
match query_insets() {
|
||||||
Ok(v) if v.is_populated() => {
|
Ok(v) if v.is_populated() => {
|
||||||
info!(
|
if *insets != v {
|
||||||
"safe_area: insets resolved top={} bottom={} left={} right={} (after {} frames)",
|
info!(
|
||||||
v.top, v.bottom, v.left, v.right, poll.0
|
"safe_area: insets resolved top={} bottom={} left={} right={} (after {} frames)",
|
||||||
);
|
v.top, v.bottom, v.left, v.right, poll.0
|
||||||
*insets = v;
|
);
|
||||||
|
*insets = v;
|
||||||
|
}
|
||||||
|
// Settled for this poll cycle; `rearm_on_resumed` re-arms on
|
||||||
|
// the next resume. Writing `insets` only on an actual change
|
||||||
|
// keeps resource change detection (and the relayout it
|
||||||
|
// triggers) quiet on resumes where nothing moved.
|
||||||
|
poll.0 = MAX_TRIES;
|
||||||
}
|
}
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Layout not ready yet; try again next frame.
|
// Layout not ready yet; try again next frame.
|
||||||
|
|||||||
@@ -1649,62 +1649,62 @@ function __wbg_get_imports() {
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 114855, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 114846, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hea4b5125da4e5ded);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hea4b5125da4e5ded);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 9841, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 9832, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Array<any>"), NamedExternref("ResizeObserver")], shim_idx: 9847, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Array<any>"), NamedExternref("ResizeObserver")], shim_idx: 9838, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0fec466277fb30e8);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0fec466277fb30e8);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_0000000000000004: function(arg0, arg1) {
|
__wbindgen_cast_0000000000000004: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Array<any>")], shim_idx: 9841, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Array<any>")], shim_idx: 9832, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_3);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_3);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_0000000000000005: function(arg0, arg1) {
|
__wbindgen_cast_0000000000000005: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 9841, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 9832, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_4);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_4);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_0000000000000006: function(arg0, arg1) {
|
__wbindgen_cast_0000000000000006: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("FocusEvent")], shim_idx: 9841, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("FocusEvent")], shim_idx: 9832, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_5);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_5);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_0000000000000007: function(arg0, arg1) {
|
__wbindgen_cast_0000000000000007: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 9841, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 9832, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_6);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_6);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_0000000000000008: function(arg0, arg1) {
|
__wbindgen_cast_0000000000000008: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("PageTransitionEvent")], shim_idx: 9841, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("PageTransitionEvent")], shim_idx: 9832, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_7);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_7);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_0000000000000009: function(arg0, arg1) {
|
__wbindgen_cast_0000000000000009: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("PointerEvent")], shim_idx: 9841, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("PointerEvent")], shim_idx: 9832, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_8);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_8);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_000000000000000a: function(arg0, arg1) {
|
__wbindgen_cast_000000000000000a: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("WheelEvent")], shim_idx: 9841, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("WheelEvent")], shim_idx: 9832, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_9);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a0b90ad2a013a3e_9);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_000000000000000b: function(arg0, arg1) {
|
__wbindgen_cast_000000000000000b: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Option(NamedExternref("Blob"))], shim_idx: 9843, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Option(NamedExternref("Blob"))], shim_idx: 9834, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hf05069bc44820cc5);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hf05069bc44820cc5);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
__wbindgen_cast_000000000000000c: function(arg0, arg1) {
|
__wbindgen_cast_000000000000000c: function(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 9839, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 9830, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h5e26b448f43bfba9);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h5e26b448f43bfba9);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user