fix(android): responsive HUD typography + portrait orientation lock
Closes the final two P2 Android playability items: 1. HUD typography — new `update_hud_typography` system fires on `WindowResized` and adjusts Tier-1 font sizes: below 480 logical px Score drops HEADLINE(26)→BODY_LG(18) and Moves/Timer drop BODY_LG(18)→CAPTION(11), so all three fit in the 180dp HUD column on a 360dp phone without wrapping. 2. Orientation lock — `[package.metadata.android.application.activity]` with `orientation = "portrait"` in solitaire_app/Cargo.toml; cargo-apk maps this to `android:screenOrientation="portrait"` in the generated AndroidManifest.xml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -135,10 +135,21 @@ rewrites required.
|
||||
the 0.5 s threshold fires).
|
||||
Hardware verification needed: confirm the 0.5 s hold feel, verify
|
||||
sliding to a destination and lifting confirms the move.
|
||||
- [ ] **HUD typography.** Reduce text sizes for `Score:`, `Moves:`,
|
||||
timer so they fit cleanly in one row.
|
||||
- [ ] **Orientation lock.** Set `android:screenOrientation="portrait"`
|
||||
in cargo-apk manifest (or design a landscape layout).
|
||||
- [x] **HUD typography.** *Closed 2026-05-11.* New system
|
||||
`update_hud_typography` fires on `WindowResized` and adjusts Tier-1
|
||||
font sizes based on viewport width. Below 480 logical px: Score
|
||||
`TYPE_HEADLINE` (26) → `TYPE_BODY_LG` (18), Moves/Timer
|
||||
`TYPE_BODY_LG` (18) → `TYPE_CAPTION` (11), so all three items fit
|
||||
in the 180 dp HUD column on a 360 dp phone. At ≥ 480 px the
|
||||
original sizes are restored — desktop/tablet layout unchanged.
|
||||
`add_message::<WindowResized>()` added defensively to `HudPlugin`
|
||||
so the system works under `MinimalPlugins` in tests.
|
||||
- [x] **Orientation lock.** *Closed 2026-05-11.* Added
|
||||
`[package.metadata.android.application.activity]` section to
|
||||
`solitaire_app/Cargo.toml` with `orientation = "portrait"`.
|
||||
cargo-apk/ndk-build maps this to `android:screenOrientation="portrait"`
|
||||
in the generated `AndroidManifest.xml`. Remove (or add a landscape
|
||||
layout) before enabling auto-rotate.
|
||||
|
||||
## P3 — Asset density
|
||||
|
||||
|
||||
@@ -82,3 +82,9 @@ label = "Solitaire Quest"
|
||||
# `debuggable` defaults to false on release builds; cargo-apk flips it
|
||||
# automatically for debug profiles. Leaving the field unset keeps the
|
||||
# default behaviour.
|
||||
|
||||
[package.metadata.android.application.activity]
|
||||
# Lock to portrait — the current layout has only been designed and tested
|
||||
# in portrait orientation. Remove (or add a landscape layout) before
|
||||
# enabling auto-rotate.
|
||||
orientation = "portrait"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//! without a separate tick system.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::WindowResized;
|
||||
use solitaire_core::card::Suit;
|
||||
use solitaire_core::game_state::{DrawMode, GameMode};
|
||||
use solitaire_core::pile::PileType;
|
||||
@@ -324,11 +325,15 @@ impl Plugin for HudPlugin {
|
||||
.add_message::<WinStreakMilestoneEvent>()
|
||||
.init_resource::<PreviousScore>()
|
||||
.init_resource::<HudActionFade>()
|
||||
// WindowResized is registered by table_plugin; re-register
|
||||
// defensively so the HUD plugin works standalone in tests.
|
||||
.add_message::<WindowResized>()
|
||||
.add_systems(Startup, (spawn_hud_band, spawn_hud, spawn_action_buttons))
|
||||
.add_systems(Update, update_hud.after(GameMutation))
|
||||
.add_systems(Update, update_won_previously.after(GameMutation))
|
||||
.add_systems(Update, announce_auto_complete.after(GameMutation))
|
||||
.add_systems(Update, update_selection_hud)
|
||||
.add_systems(Update, update_hud_typography)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
@@ -2003,6 +2008,48 @@ pub fn challenge_time_color(remaining: u64) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
/// Scales HUD Tier-1 font sizes to fit a narrow viewport.
|
||||
///
|
||||
/// Fires on every `WindowResized` event. Below 480 logical pixels wide the
|
||||
/// score drops from `TYPE_HEADLINE` (26 px) to `TYPE_BODY_LG` (18 px) and the
|
||||
/// Moves/Timer labels drop from `TYPE_BODY_LG` to `TYPE_CAPTION` (11 px), so
|
||||
/// all three items remain on one row inside the 50 %-wide HUD column
|
||||
/// (≈ 180 dp on a 360 dp phone). At ≥ 480 px the original sizes are
|
||||
/// restored so desktop/tablet layouts are unaffected.
|
||||
fn update_hud_typography(
|
||||
mut events: MessageReader<WindowResized>,
|
||||
mut score_q: Query<
|
||||
&mut TextFont,
|
||||
(With<HudScore>, Without<HudMoves>, Without<HudTime>),
|
||||
>,
|
||||
mut moves_q: Query<
|
||||
&mut TextFont,
|
||||
(With<HudMoves>, Without<HudScore>, Without<HudTime>),
|
||||
>,
|
||||
mut time_q: Query<
|
||||
&mut TextFont,
|
||||
(With<HudTime>, Without<HudScore>, Without<HudMoves>),
|
||||
>,
|
||||
) {
|
||||
let Some(ev) = events.read().last() else {
|
||||
return;
|
||||
};
|
||||
let (score_size, secondary_size) = if ev.width < 480.0 {
|
||||
(TYPE_BODY_LG, TYPE_CAPTION)
|
||||
} else {
|
||||
(TYPE_HEADLINE, TYPE_BODY_LG)
|
||||
};
|
||||
for mut font in &mut score_q {
|
||||
font.font_size = score_size;
|
||||
}
|
||||
for mut font in &mut moves_q {
|
||||
font.font_size = secondary_size;
|
||||
}
|
||||
for mut font in &mut time_q {
|
||||
font.font_size = secondary_size;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user