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).
|
the 0.5 s threshold fires).
|
||||||
Hardware verification needed: confirm the 0.5 s hold feel, verify
|
Hardware verification needed: confirm the 0.5 s hold feel, verify
|
||||||
sliding to a destination and lifting confirms the move.
|
sliding to a destination and lifting confirms the move.
|
||||||
- [ ] **HUD typography.** Reduce text sizes for `Score:`, `Moves:`,
|
- [x] **HUD typography.** *Closed 2026-05-11.* New system
|
||||||
timer so they fit cleanly in one row.
|
`update_hud_typography` fires on `WindowResized` and adjusts Tier-1
|
||||||
- [ ] **Orientation lock.** Set `android:screenOrientation="portrait"`
|
font sizes based on viewport width. Below 480 logical px: Score
|
||||||
in cargo-apk manifest (or design a landscape layout).
|
`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
|
## P3 — Asset density
|
||||||
|
|
||||||
|
|||||||
@@ -82,3 +82,9 @@ label = "Solitaire Quest"
|
|||||||
# `debuggable` defaults to false on release builds; cargo-apk flips it
|
# `debuggable` defaults to false on release builds; cargo-apk flips it
|
||||||
# automatically for debug profiles. Leaving the field unset keeps the
|
# automatically for debug profiles. Leaving the field unset keeps the
|
||||||
# default behaviour.
|
# 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.
|
//! without a separate tick system.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use bevy::window::WindowResized;
|
||||||
use solitaire_core::card::Suit;
|
use solitaire_core::card::Suit;
|
||||||
use solitaire_core::game_state::{DrawMode, GameMode};
|
use solitaire_core::game_state::{DrawMode, GameMode};
|
||||||
use solitaire_core::pile::PileType;
|
use solitaire_core::pile::PileType;
|
||||||
@@ -324,11 +325,15 @@ impl Plugin for HudPlugin {
|
|||||||
.add_message::<WinStreakMilestoneEvent>()
|
.add_message::<WinStreakMilestoneEvent>()
|
||||||
.init_resource::<PreviousScore>()
|
.init_resource::<PreviousScore>()
|
||||||
.init_resource::<HudActionFade>()
|
.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(Startup, (spawn_hud_band, spawn_hud, spawn_action_buttons))
|
||||||
.add_systems(Update, update_hud.after(GameMutation))
|
.add_systems(Update, update_hud.after(GameMutation))
|
||||||
.add_systems(Update, update_won_previously.after(GameMutation))
|
.add_systems(Update, update_won_previously.after(GameMutation))
|
||||||
.add_systems(Update, announce_auto_complete.after(GameMutation))
|
.add_systems(Update, announce_auto_complete.after(GameMutation))
|
||||||
.add_systems(Update, update_selection_hud)
|
.add_systems(Update, update_selection_hud)
|
||||||
|
.add_systems(Update, update_hud_typography)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user