fix(android): wire FiraMono to stock-empty label, strip raw safe-area px from HUD spawns, replace tofu chevrons

CR-1: apply_stock_empty_indicator now receives a Handle<Font> from FontResource
      so the ↺ label uses FiraMono (Arrows block) instead of the default font.
      All three callers (startup, state-change, window-resize) updated.

CR-4: spawn_hud_band, spawn_hud, spawn_hud_avatar, spawn_action_buttons no
      longer add SafeAreaInsets physical-pixel values to initial Val::Px offsets.
      SafeAreaAnchoredTop/Bottom systems already divide by scale_factor and apply
      the correct logical-pixel offset when insets arrive; the initial spawn value
      is always 0.0 at Startup on Android anyway. Removed now-unused SafeAreaInsets
      import and parameter from all four Startup systems.

H-9:  Difficulty section chevrons ▶/▼ (U+25BA/U+25BC, Geometric Shapes — not in
      FiraMono) replaced with ASCII ">"/"v" which render correctly on Android.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-17 20:00:30 -07:00
parent 04e99a8d24
commit d3d8094ebb
3 changed files with 20 additions and 17 deletions
+11 -1
View File
@@ -1598,6 +1598,7 @@ fn apply_stock_empty_indicator<F: bevy::ecs::query::QueryFilter>(
pile_markers: &mut Query<(Entity, &PileMarker, &mut Sprite), F>, pile_markers: &mut Query<(Entity, &PileMarker, &mut Sprite), F>,
label_children: &Query<(Entity, &ChildOf), With<StockEmptyLabel>>, label_children: &Query<(Entity, &ChildOf), With<StockEmptyLabel>>,
layout: &Layout, layout: &Layout,
font: Handle<Font>,
) { ) {
let stock_empty = game let stock_empty = game
.piles .piles
@@ -1623,7 +1624,7 @@ fn apply_stock_empty_indicator<F: bevy::ecs::query::QueryFilter>(
b.spawn(( b.spawn((
StockEmptyLabel, StockEmptyLabel,
Text2d::new(""), Text2d::new(""),
TextFont { font_size, ..default() }, TextFont { font: font.clone(), font_size, ..default() },
TextColor(TEXT_PRIMARY.with_alpha(0.7)), TextColor(TEXT_PRIMARY.with_alpha(0.7)),
Transform::from_xyz(0.0, 0.0, 0.1), Transform::from_xyz(0.0, 0.0, 0.1),
)); ));
@@ -1649,16 +1650,19 @@ fn update_stock_empty_indicator_startup(
mut commands: Commands, mut commands: Commands,
game: Res<GameStateResource>, game: Res<GameStateResource>,
layout: Option<Res<LayoutResource>>, layout: Option<Res<LayoutResource>>,
font_res: Option<Res<FontResource>>,
mut pile_markers: Query<(Entity, &PileMarker, &mut Sprite)>, mut pile_markers: Query<(Entity, &PileMarker, &mut Sprite)>,
label_children: Query<(Entity, &ChildOf), With<StockEmptyLabel>>, label_children: Query<(Entity, &ChildOf), With<StockEmptyLabel>>,
) { ) {
let Some(layout) = layout else { return }; let Some(layout) = layout else { return };
let font = font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default();
apply_stock_empty_indicator( apply_stock_empty_indicator(
&mut commands, &mut commands,
&game.0, &game.0,
&mut pile_markers, &mut pile_markers,
&label_children, &label_children,
&layout.0, &layout.0,
font,
); );
} }
@@ -1669,6 +1673,7 @@ fn update_stock_empty_indicator(
mut commands: Commands, mut commands: Commands,
game: Res<GameStateResource>, game: Res<GameStateResource>,
layout: Option<Res<LayoutResource>>, layout: Option<Res<LayoutResource>>,
font_res: Option<Res<FontResource>>,
mut pile_markers: Query<(Entity, &PileMarker, &mut Sprite)>, mut pile_markers: Query<(Entity, &PileMarker, &mut Sprite)>,
label_children: Query<(Entity, &ChildOf), With<StockEmptyLabel>>, label_children: Query<(Entity, &ChildOf), With<StockEmptyLabel>>,
) { ) {
@@ -1676,12 +1681,14 @@ fn update_stock_empty_indicator(
return; return;
} }
let Some(layout) = layout else { return }; let Some(layout) = layout else { return };
let font = font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default();
apply_stock_empty_indicator( apply_stock_empty_indicator(
&mut commands, &mut commands,
&game.0, &game.0,
&mut pile_markers, &mut pile_markers,
&label_children, &label_children,
&layout.0, &layout.0,
font,
); );
} }
@@ -1892,6 +1899,7 @@ fn snap_cards_on_window_resize(
game: Option<Res<GameStateResource>>, game: Option<Res<GameStateResource>>,
layout: Option<Res<LayoutResource>>, layout: Option<Res<LayoutResource>>,
card_images: Option<Res<CardImageSet>>, card_images: Option<Res<CardImageSet>>,
font_res: Option<Res<FontResource>>,
entities: Query< entities: Query<
(Entity, &CardEntity, &mut Sprite, &mut Transform), (Entity, &CardEntity, &mut Sprite, &mut Transform),
(Without<CardLabel>, Without<CardShadow>, Without<CardBackFrame>), (Without<CardLabel>, Without<CardShadow>, Without<CardBackFrame>),
@@ -1940,12 +1948,14 @@ fn snap_cards_on_window_resize(
frame_query, frame_query,
); );
let font = font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default();
apply_stock_empty_indicator( apply_stock_empty_indicator(
&mut commands, &mut commands,
&game.0, &game.0,
&mut pile_markers, &mut pile_markers,
&label_children, &label_children,
&layout.0, &layout.0,
font,
); );
throttle.last_applied_secs = now; throttle.last_applied_secs = now;
+1 -1
View File
@@ -1103,7 +1103,7 @@ fn spawn_difficulty_section(parent: &mut ChildSpawnerCommands, ctx: &HomeContext
let font_label = TextFont { font: font_handle.clone(), font_size: TYPE_BODY, ..default() }; let font_label = TextFont { font: font_handle.clone(), font_size: TYPE_BODY, ..default() };
let font_chip = TextFont { font: font_handle, font_size: TYPE_CAPTION, ..default() }; let font_chip = TextFont { font: font_handle, font_size: TYPE_CAPTION, ..default() };
let chevron = if ctx.difficulty_expanded { "" } else { "" }; let chevron = if ctx.difficulty_expanded { "v" } else { ">" };
// Header row — click to toggle expand/collapse. // Header row — click to toggle expand/collapse.
parent parent
+8 -15
View File
@@ -20,7 +20,7 @@ use crate::daily_challenge_plugin::DailyChallengeResource;
use crate::progress_plugin::ProgressResource; use crate::progress_plugin::ProgressResource;
use crate::settings_plugin::SettingsResource; use crate::settings_plugin::SettingsResource;
use crate::layout::HUD_BAND_HEIGHT; use crate::layout::HUD_BAND_HEIGHT;
use crate::safe_area::{SafeAreaAnchoredBottom, SafeAreaAnchoredTop, SafeAreaInsets}; use crate::safe_area::{SafeAreaAnchoredBottom, SafeAreaAnchoredTop};
use crate::ui_theme::SPACE_2; use crate::ui_theme::SPACE_2;
use crate::ui_theme::{ use crate::ui_theme::{
scaled_duration, ACCENT_PRIMARY, ACCENT_SECONDARY, BG_ELEVATED, BG_ELEVATED_HI, scaled_duration, ACCENT_PRIMARY, ACCENT_SECONDARY, BG_ELEVATED, BG_ELEVATED_HI,
@@ -486,13 +486,12 @@ impl Plugin for HudPlugin {
/// The entity carries no `BackgroundColor` — the green felt shows through. /// The entity carries no `BackgroundColor` — the green felt shows through.
/// A slim grey background is handled by each content section individually /// A slim grey background is handled by each content section individually
/// (the bottom action bar has its own `BG_HUD_BAND` background). /// (the bottom action bar has its own `BG_HUD_BAND` background).
fn spawn_hud_band(insets: Option<Res<SafeAreaInsets>>, mut commands: Commands) { fn spawn_hud_band(mut commands: Commands) {
const BASE_TOP: f32 = 0.0; const BASE_TOP: f32 = 0.0;
let top_inset = insets.as_deref().copied().unwrap_or_default().top;
commands.spawn(( commands.spawn((
Node { Node {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: Val::Px(BASE_TOP + top_inset), top: Val::Px(BASE_TOP),
left: Val::Px(0.0), left: Val::Px(0.0),
width: Val::Percent(100.0), width: Val::Percent(100.0),
height: Val::Px(HUD_BAND_HEIGHT), height: Val::Px(HUD_BAND_HEIGHT),
@@ -525,10 +524,8 @@ fn spawn_hud_band(insets: Option<Res<SafeAreaInsets>>, mut commands: Commands) {
/// make Score the visual protagonist. /// make Score the visual protagonist.
fn spawn_hud( fn spawn_hud(
font_res: Option<Res<FontResource>>, font_res: Option<Res<FontResource>>,
insets: Option<Res<SafeAreaInsets>>,
mut commands: Commands, mut commands: Commands,
) { ) {
let top_inset = insets.as_deref().copied().unwrap_or_default().top;
let font_handle = font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default(); let font_handle = font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default();
let font_score = TextFont { let font_score = TextFont {
font: font_handle.clone(), font: font_handle.clone(),
@@ -568,7 +565,7 @@ fn spawn_hud(
Node { Node {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
left: VAL_SPACE_3, left: VAL_SPACE_3,
top: Val::Px(SPACE_2 + top_inset), top: Val::Px(SPACE_2),
flex_direction: FlexDirection::Column, flex_direction: FlexDirection::Column,
// Cap the column at 50% of viewport so on narrow // Cap the column at 50% of viewport so on narrow
// (mobile) widths the inner tier rows have a bounded // (mobile) widths the inner tier rows have a bounded
@@ -701,13 +698,11 @@ fn spawn_hud(
/// `AvatarResource` or `SettingsResource` later changes. /// `AvatarResource` or `SettingsResource` later changes.
fn spawn_hud_avatar( fn spawn_hud_avatar(
font_res: Option<Res<FontResource>>, font_res: Option<Res<FontResource>>,
insets: Option<Res<SafeAreaInsets>>,
avatar: Option<Res<AvatarResource>>, avatar: Option<Res<AvatarResource>>,
settings: Option<Res<SettingsResource>>, settings: Option<Res<SettingsResource>>,
mut commands: Commands, mut commands: Commands,
) { ) {
const SIZE: f32 = 32.0; const SIZE: f32 = 32.0;
let top_inset = insets.as_deref().copied().unwrap_or_default().top;
let id = commands let id = commands
.spawn(( .spawn((
HudAvatar, HudAvatar,
@@ -715,7 +710,7 @@ fn spawn_hud_avatar(
Tooltip::new("Your profile — tap to open."), Tooltip::new("Your profile — tap to open."),
Node { Node {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: Val::Px(SPACE_2 + top_inset), top: Val::Px(SPACE_2),
right: VAL_SPACE_3, right: VAL_SPACE_3,
width: Val::Px(SIZE), width: Val::Px(SIZE),
height: Val::Px(SIZE), height: Val::Px(SIZE),
@@ -834,10 +829,8 @@ fn handle_avatar_button(
/// on its own visual edge. /// on its own visual edge.
fn spawn_action_buttons( fn spawn_action_buttons(
font_res: Option<Res<FontResource>>, font_res: Option<Res<FontResource>>,
insets: Option<Res<SafeAreaInsets>>,
mut commands: Commands, mut commands: Commands,
) { ) {
let bottom_inset = insets.as_deref().copied().unwrap_or_default().bottom;
let font = TextFont { let font = TextFont {
font: font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default(), font: font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default(),
font_size: TYPE_BODY, font_size: TYPE_BODY,
@@ -873,13 +866,13 @@ fn spawn_action_buttons(
); );
// Bottom bar: full-width, centered, sits above the gesture-navigation zone. // Bottom bar: full-width, centered, sits above the gesture-navigation zone.
// `bottom` is set to `bottom_inset` initially; `SafeAreaAnchoredBottom` keeps // `SafeAreaAnchoredBottom` applies the correct logical-pixel inset once
// it correct as Android insets arrive in later frames. // Android reports it (frames 1-3); initial value is 0.0.
commands commands
.spawn(( .spawn((
Node { Node {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
bottom: Val::Px(bottom_inset), bottom: Val::Px(0.0),
left: Val::Px(0.0), left: Val::Px(0.0),
width: Val::Percent(100.0), width: Val::Percent(100.0),
flex_direction: FlexDirection::Row, flex_direction: FlexDirection::Row,