fix(android): visual polish — green fallback, A-markers, wider fan, compact HUD
- camera clear colour → TABLE_COLOUR green so the background reads as felt even before bg_0.png finishes loading (async on Android) - foundation empty markers now show "A" child text (same pattern as the "K" on tableau markers) — no suit letter since any Ace claims any slot - HUD_BAND_HEIGHT = 128 on Android to accommodate the two-row button wrap on narrow phones; card grid reserves this space so buttons no longer overlap the top card row - TABLEAU_FACEDOWN_FAN_FRAC 0.12 → 0.20 (layout.rs + card_plugin.rs): face-down stacks show ~67% more back strip per card on fresh deal, bringing the deepest column from ~27% to ~40% of available screen height - update_tableau_fan_frac: return early when max face-up depth ≤ 1 instead of overwriting the layout-computed adaptive value with the desktop minimum (0.25); fixes a regression where the portrait-phone adaptive fan_frac was silently snapped to 0.25 on every new deal - update_tableau_fan_frac: also propagate facedown_fan_frac updates in the mid-game path (previously computed but immediately discarded) - Android HUD buttons: compact Unicode icon labels (≡ ↩ ? ⏸ ⚙▾ +) with tighter padding (4 dp) and min-size (44 dp), max-width 90% — all 7 buttons fit in a single 44 dp row on a 411 dp phone Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,6 @@ use crate::ui_theme::{
|
|||||||
/// Fraction of card height used as vertical offset between face-up tableau cards.
|
/// Fraction of card height used as vertical offset between face-up tableau cards.
|
||||||
pub const TABLEAU_FAN_FRAC: f32 = 0.25;
|
pub const TABLEAU_FAN_FRAC: f32 = 0.25;
|
||||||
|
|
||||||
/// Tighter fan for face-down cards in the tableau — just enough to show the stack.
|
|
||||||
/// Per-card vertical step for face-down tableau cards, as a fraction of
|
/// Per-card vertical step for face-down tableau cards, as a fraction of
|
||||||
/// card height. Smaller than [`TABLEAU_FAN_FRAC`] because face-down cards
|
/// card height. Smaller than [`TABLEAU_FAN_FRAC`] because face-down cards
|
||||||
/// don't need their full body shown — only the back-pattern strip is
|
/// don't need their full body shown — only the back-pattern strip is
|
||||||
@@ -49,7 +48,12 @@ pub const TABLEAU_FAN_FRAC: f32 = 0.25;
|
|||||||
/// when hit-testing tableau columns; any drift between this and the
|
/// when hit-testing tableau columns; any drift between this and the
|
||||||
/// renderer creates a visible offset between the card face and where
|
/// renderer creates a visible offset between the card face and where
|
||||||
/// clicks land.
|
/// clicks land.
|
||||||
pub const TABLEAU_FACEDOWN_FAN_FRAC: f32 = 0.12;
|
///
|
||||||
|
/// Matches `layout::TABLEAU_FACEDOWN_FAN_FRAC` (0.20). Both constants must
|
||||||
|
/// stay in sync; the layout constant drives the adaptive LayoutResource value
|
||||||
|
/// used at runtime, while this one is the minimum floor used by
|
||||||
|
/// `update_tableau_fan_frac` when computing proportional updates.
|
||||||
|
pub const TABLEAU_FACEDOWN_FAN_FRAC: f32 = 0.20;
|
||||||
|
|
||||||
/// Fraction of card height used as a tiny offset between stacked cards in
|
/// Fraction of card height used as a tiny offset between stacked cards in
|
||||||
/// non-tableau piles, so stacking is visible. Public so other plugins
|
/// non-tableau piles, so stacking is visible. Public so other plugins
|
||||||
@@ -1834,9 +1838,13 @@ fn resize_cards_in_place(
|
|||||||
|
|
||||||
/// Adjusts `LayoutResource.tableau_fan_frac` to match the current maximum
|
/// Adjusts `LayoutResource.tableau_fan_frac` to match the current maximum
|
||||||
/// face-up column depth. Runs after every `StateChangedEvent` so the fan
|
/// face-up column depth. Runs after every `StateChangedEvent` so the fan
|
||||||
/// grows as the player reveals cards — preventing over-spread early-game
|
/// expands as the player reveals cards while staying within the window.
|
||||||
/// (fresh deal: max depth = 1, fan_frac = TABLEAU_FAN_FRAC = 0.25) while
|
///
|
||||||
/// allowing the full window-sized fan late-game (up to 13 face-up cards).
|
/// On fresh deal (max face-up depth = 1) the function returns early, leaving
|
||||||
|
/// both fracs at the window-size-adaptive values that `compute_layout` already
|
||||||
|
/// computed for the current viewport. Previously it overwrote the adaptive
|
||||||
|
/// value with the desktop minimum (0.25) — the wrong behaviour on portrait
|
||||||
|
/// phones where the adaptive value is much larger.
|
||||||
fn update_tableau_fan_frac(
|
fn update_tableau_fan_frac(
|
||||||
mut events: MessageReader<StateChangedEvent>,
|
mut events: MessageReader<StateChangedEvent>,
|
||||||
game: Option<Res<GameStateResource>>,
|
game: Option<Res<GameStateResource>>,
|
||||||
@@ -1857,22 +1865,25 @@ fn update_tableau_fan_frac(
|
|||||||
let card_h = layout.0.card_size.y;
|
let card_h = layout.0.card_size.y;
|
||||||
let avail = layout.0.available_tableau_height;
|
let avail = layout.0.available_tableau_height;
|
||||||
|
|
||||||
let new_frac = if max_depth <= 1 || card_h <= 0.0 {
|
// With ≤ 1 face-up card per column (fresh deal, or completely face-down
|
||||||
TABLEAU_FAN_FRAC
|
// piles) the face-up fan fraction has no visible effect. Leave both fracs
|
||||||
} else {
|
// at the adaptive values set by compute_layout rather than snapping them
|
||||||
|
// to the desktop minimum.
|
||||||
|
if max_depth <= 1 || card_h <= 0.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let ideal = avail / ((max_depth - 1) as f32 * card_h);
|
let ideal = avail / ((max_depth - 1) as f32 * card_h);
|
||||||
let max_frac = if card_h > 0.0 { avail / (12.0 * card_h) } else { TABLEAU_FAN_FRAC };
|
let max_frac = if card_h > 0.0 { avail / (12.0 * card_h) } else { TABLEAU_FAN_FRAC };
|
||||||
ideal.clamp(TABLEAU_FAN_FRAC, max_frac.max(TABLEAU_FAN_FRAC))
|
let new_frac = ideal.clamp(TABLEAU_FAN_FRAC, max_frac.max(TABLEAU_FAN_FRAC));
|
||||||
};
|
|
||||||
let new_facedown_frac = new_frac * (TABLEAU_FACEDOWN_FAN_FRAC / TABLEAU_FAN_FRAC);
|
let new_facedown_frac = new_frac * (TABLEAU_FACEDOWN_FAN_FRAC / TABLEAU_FAN_FRAC);
|
||||||
|
|
||||||
// Only update the face-up fan. The face-down fan is left at the
|
|
||||||
// window-size-adaptive value from compute_layout so stacked face-down
|
|
||||||
// cards remain visible regardless of how many face-up cards are out.
|
|
||||||
let _ = new_facedown_frac; // computed but unused — leave facedown alone
|
|
||||||
if (layout.0.tableau_fan_frac - new_frac).abs() > 1e-4 {
|
if (layout.0.tableau_fan_frac - new_frac).abs() > 1e-4 {
|
||||||
layout.0.tableau_fan_frac = new_frac;
|
layout.0.tableau_fan_frac = new_frac;
|
||||||
}
|
}
|
||||||
|
if (layout.0.tableau_facedown_fan_frac - new_facedown_frac).abs() > 1e-4 {
|
||||||
|
layout.0.tableau_facedown_fan_frac = new_facedown_frac;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -628,15 +628,42 @@ fn spawn_action_buttons(
|
|||||||
let top_inset = insets.as_deref().copied().unwrap_or_default().top;
|
let top_inset = insets.as_deref().copied().unwrap_or_default().top;
|
||||||
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(),
|
||||||
// TYPE_BODY (14.0) — was a hardcoded `16.0` until the
|
|
||||||
// top-bar-overlap fix. Aligns with the rest of `hud_plugin`'s
|
|
||||||
// text (which already routes through the `TYPE_*` tokens) and
|
|
||||||
// reclaims horizontal space so the action button row doesn't
|
|
||||||
// collide with the left-anchored HUD column at narrow window
|
|
||||||
// widths.
|
|
||||||
font_size: TYPE_BODY,
|
font_size: TYPE_BODY,
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// On Android, 7 text-labelled buttons at 48 dp each wrap to two rows on
|
||||||
|
// a 411 dp phone. Use compact Unicode symbols and tighter gaps so all 7
|
||||||
|
// fit in a single row (7×44 + 6×4 = 332 dp, well within a 90%-wide band
|
||||||
|
// of 370 dp). On desktop, keep the descriptive text labels.
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
let (max_width, col_gap, row_gap_val) =
|
||||||
|
(Val::Percent(90.0), Val::Px(4.0), Val::Px(4.0));
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
let (max_width, col_gap, row_gap_val) =
|
||||||
|
(Val::Percent(65.0), VAL_SPACE_2, VAL_SPACE_2);
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
let labels = (
|
||||||
|
/* menu */ "\u{2261}", // ≡ hamburger
|
||||||
|
/* undo */ "\u{21A9}", // ↩ undo arrow
|
||||||
|
/* pause */ "\u{23F8}", // ⏸ pause symbol
|
||||||
|
/* help */ "?",
|
||||||
|
/* hint */ "\u{2605}", // ★ star
|
||||||
|
/* modes */ "\u{2699}\u{25BE}", // ⚙▾ gear+chevron
|
||||||
|
/* new */ "+",
|
||||||
|
);
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
let labels = (
|
||||||
|
"Menu \u{25BE}",
|
||||||
|
"Undo",
|
||||||
|
"Pause",
|
||||||
|
"Help",
|
||||||
|
"Hint",
|
||||||
|
"Modes \u{25BE}",
|
||||||
|
"New Game",
|
||||||
|
);
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Node {
|
Node {
|
||||||
@@ -644,19 +671,11 @@ fn spawn_action_buttons(
|
|||||||
right: VAL_SPACE_3,
|
right: VAL_SPACE_3,
|
||||||
top: Val::Px(SPACE_2 + top_inset),
|
top: Val::Px(SPACE_2 + top_inset),
|
||||||
flex_direction: FlexDirection::Row,
|
flex_direction: FlexDirection::Row,
|
||||||
// 6 buttons total ~510 px wide; on a desktop window
|
max_width,
|
||||||
// (typically >= 1280 px) `max_width: 65%` is >= 832 px
|
|
||||||
// and the row stays a single line. On a 411 dp phone
|
|
||||||
// 65% is 267 px; the 6 buttons wrap to 2 lines instead
|
|
||||||
// of 3, reclaiming one row of vertical HUD space.
|
|
||||||
max_width: Val::Percent(65.0),
|
|
||||||
flex_wrap: FlexWrap::Wrap,
|
flex_wrap: FlexWrap::Wrap,
|
||||||
// When the row wraps, buttons pack to the *end* of each
|
|
||||||
// line so the row stays visually right-aligned (matches
|
|
||||||
// the `right: VAL_SPACE_3` anchor).
|
|
||||||
justify_content: JustifyContent::FlexEnd,
|
justify_content: JustifyContent::FlexEnd,
|
||||||
column_gap: VAL_SPACE_2,
|
column_gap: col_gap,
|
||||||
row_gap: VAL_SPACE_2,
|
row_gap: row_gap_val,
|
||||||
align_items: AlignItems::Center,
|
align_items: AlignItems::Center,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
@@ -664,77 +683,15 @@ fn spawn_action_buttons(
|
|||||||
SafeAreaAnchoredTop { base_top: SPACE_2 },
|
SafeAreaAnchoredTop { base_top: SPACE_2 },
|
||||||
))
|
))
|
||||||
.with_children(|row| {
|
.with_children(|row| {
|
||||||
// Menu and Modes don't have a single hotkey accelerator
|
// The trailing `order` argument feeds `Focusable { group: Hud, order }`
|
||||||
// (each row inside their popover has its own); their button
|
// so Tab cycles the action bar in visual reading order.
|
||||||
// labels carry the dropdown chevron in lieu of a key chip.
|
spawn_action_button(row, MenuButton, labels.0, None, "Open Stats, Achievements, Profile, Settings, or Leaderboard.", &font, 0);
|
||||||
//
|
spawn_action_button(row, UndoButton, labels.1, Some("U"), "Take back your last move. Costs points and blocks No Undo.", &font, 1);
|
||||||
// The trailing `order` argument is the per-button index in
|
spawn_action_button(row, PauseButton, labels.2, Some("Esc"), "Pause the game and freeze the timer.", &font, 2);
|
||||||
// visual reading order (left → right). It feeds
|
spawn_action_button(row, HelpButton, labels.3, Some("F1"), "Show controls, rules, and keyboard shortcuts.", &font, 3);
|
||||||
// `Focusable { group: Hud, order }` so Tab cycles the action
|
spawn_action_button(row, HintButton, labels.4, Some("H"), "Highlight a suggested move. Cycles through alternatives on repeat taps.", &font, 4);
|
||||||
// bar in the same order the eye scans it.
|
spawn_action_button(row, ModesButton, labels.5, None, "Switch modes: Classic, Daily, Zen, Challenge, Time Attack.", &font, 5);
|
||||||
spawn_action_button(
|
spawn_action_button(row, NewGameButton,labels.6, Some("N"), "Start a fresh deal. Confirms first if a game is in progress.", &font, 6);
|
||||||
row,
|
|
||||||
MenuButton,
|
|
||||||
"Menu \u{25BE}",
|
|
||||||
None,
|
|
||||||
"Open Stats, Achievements, Profile, Settings, or Leaderboard.",
|
|
||||||
&font,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
spawn_action_button(
|
|
||||||
row,
|
|
||||||
UndoButton,
|
|
||||||
"Undo",
|
|
||||||
Some("U"),
|
|
||||||
"Take back your last move. Costs points and blocks No Undo.",
|
|
||||||
&font,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
spawn_action_button(
|
|
||||||
row,
|
|
||||||
PauseButton,
|
|
||||||
"Pause",
|
|
||||||
Some("Esc"),
|
|
||||||
"Pause the game and freeze the timer.",
|
|
||||||
&font,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
spawn_action_button(
|
|
||||||
row,
|
|
||||||
HelpButton,
|
|
||||||
"Help",
|
|
||||||
Some("F1"),
|
|
||||||
"Show controls, rules, and keyboard shortcuts.",
|
|
||||||
&font,
|
|
||||||
3,
|
|
||||||
);
|
|
||||||
spawn_action_button(
|
|
||||||
row,
|
|
||||||
HintButton,
|
|
||||||
"Hint",
|
|
||||||
Some("H"),
|
|
||||||
"Highlight a suggested move. Cycles through alternatives on repeat taps.",
|
|
||||||
&font,
|
|
||||||
4,
|
|
||||||
);
|
|
||||||
spawn_action_button(
|
|
||||||
row,
|
|
||||||
ModesButton,
|
|
||||||
"Modes \u{25BE}",
|
|
||||||
None,
|
|
||||||
"Switch modes: Classic, Daily, Zen, Challenge, Time Attack.",
|
|
||||||
&font,
|
|
||||||
5,
|
|
||||||
);
|
|
||||||
spawn_action_button(
|
|
||||||
row,
|
|
||||||
NewGameButton,
|
|
||||||
"New Game",
|
|
||||||
Some("N"),
|
|
||||||
"Start a fresh deal. Confirms first if a game is in progress.",
|
|
||||||
&font,
|
|
||||||
6,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,35 +730,29 @@ fn spawn_action_button<M: Component>(
|
|||||||
font_size: TYPE_CAPTION,
|
font_size: TYPE_CAPTION,
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
|
// On Android, use tighter padding and a slightly smaller min-size so all
|
||||||
|
// 7 icon-label buttons fit in one row on a ~411 dp phone. 44 dp ≥
|
||||||
|
// Apple's minimum touch target; padding of 4 dp each side keeps the icon
|
||||||
|
// centred with room to breathe. On desktop, keep the comfortable 48 dp
|
||||||
|
// floor and 8 dp side padding.
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
let (pad, min_w, min_h) = (UiRect::axes(Val::Px(4.0), Val::Px(4.0)), Val::Px(44.0), Val::Px(44.0));
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
let (pad, min_w, min_h) = (UiRect::axes(VAL_SPACE_2, VAL_SPACE_2), Val::Px(48.0), Val::Px(48.0));
|
||||||
|
|
||||||
row.spawn((
|
row.spawn((
|
||||||
marker,
|
marker,
|
||||||
ActionButton,
|
ActionButton,
|
||||||
Button,
|
Button,
|
||||||
Tooltip::new(tooltip),
|
Tooltip::new(tooltip),
|
||||||
// Joins the `Hud` focus group at the supplied order so Tab
|
|
||||||
// cycles HUD buttons left-to-right under Phase 2. The HUD focus
|
|
||||||
// ring still only engages when a HUD button is hovered (or in
|
|
||||||
// future phases, when the player explicitly switches groups);
|
|
||||||
// the marker just declares membership.
|
|
||||||
Focusable {
|
Focusable {
|
||||||
group: FocusGroup::Hud,
|
group: FocusGroup::Hud,
|
||||||
order,
|
order,
|
||||||
},
|
},
|
||||||
Node {
|
Node {
|
||||||
// Horizontal padding stepped down from VAL_SPACE_3 to
|
padding: pad,
|
||||||
// VAL_SPACE_2 to reclaim ~96px across the 6-button row at
|
min_width: min_w,
|
||||||
// narrow window widths (see top-bar-overlap fix in the
|
min_height: min_h,
|
||||||
// companion commit). Vertical padding stays at VAL_SPACE_2
|
|
||||||
// so button height tracks the rest of the chrome band.
|
|
||||||
padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_2),
|
|
||||||
// 48 px floors meet Material's recommended thumb-target
|
|
||||||
// size on touch and are a no-op on desktop for buttons
|
|
||||||
// whose content already exceeds 48 px in either axis
|
|
||||||
// (Menu, Modes, New Game, etc.). Without these, "Undo"
|
|
||||||
// ends up ~46 × 33 px — comfortably tappable with a mouse
|
|
||||||
// but right at the threshold for a finger.
|
|
||||||
min_width: Val::Px(48.0),
|
|
||||||
min_height: Val::Px(48.0),
|
|
||||||
justify_content: JustifyContent::Center,
|
justify_content: JustifyContent::Center,
|
||||||
align_items: AlignItems::Center,
|
align_items: AlignItems::Center,
|
||||||
border_radius: BorderRadius::all(Val::Px(RADIUS_MD)),
|
border_radius: BorderRadius::all(Val::Px(RADIUS_MD)),
|
||||||
|
|||||||
@@ -61,7 +61,12 @@ const TABLEAU_FAN_FRAC: f32 = 0.25;
|
|||||||
|
|
||||||
/// Minimum fraction for face-down tableau cards. Scales proportionally with
|
/// Minimum fraction for face-down tableau cards. Scales proportionally with
|
||||||
/// the adaptive face-up fraction so hit-testing and rendering stay in sync.
|
/// the adaptive face-up fraction so hit-testing and rendering stay in sync.
|
||||||
const TABLEAU_FACEDOWN_FAN_FRAC: f32 = 0.12;
|
///
|
||||||
|
/// Raised from 0.12 to 0.20 so face-down stacks on portrait phones show
|
||||||
|
/// enough of each card back to read as a meaningful stack rather than a
|
||||||
|
/// thin sliver. The ratio to TABLEAU_FAN_FRAC (0.80) is preserved by
|
||||||
|
/// the adaptive scaling in `compute_layout`.
|
||||||
|
const TABLEAU_FACEDOWN_FAN_FRAC: f32 = 0.20;
|
||||||
|
|
||||||
/// Largest possible face-up tableau column in Klondike: a King down to an Ace
|
/// Largest possible face-up tableau column in Klondike: a King down to an Ace
|
||||||
/// after every face-down card has flipped on column 7. Layout sizing must keep
|
/// after every face-down card has flipped on column 7. Layout sizing must keep
|
||||||
@@ -72,10 +77,15 @@ const MAX_TABLEAU_CARDS: f32 = 13.0;
|
|||||||
/// (action buttons, Score / Moves / Timer readouts). The card grid starts
|
/// (action buttons, Score / Moves / Timer readouts). The card grid starts
|
||||||
/// below this band so the HUD doesn't bleed into the play surface.
|
/// below this band so the HUD doesn't bleed into the play surface.
|
||||||
///
|
///
|
||||||
/// 64 px comfortably fits the action button bar (~32 px tall) plus the
|
/// Desktop: 64 px fits the single-row action bar plus the Score/Moves line.
|
||||||
/// Score/Moves text line plus padding, with a few pixels of breathing room.
|
/// Android: 128 px accommodates the two-row button wrap on narrow phones
|
||||||
/// The matching translucent background is painted by `hud_plugin::spawn_hud_band`.
|
/// (7 buttons × ~52 dp each, with a 65% max-width constraint, wraps to two
|
||||||
|
/// ~48 dp rows plus row-gap). Without this larger reserve the bottom row of
|
||||||
|
/// buttons overlaps the top card row.
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
pub const HUD_BAND_HEIGHT: f32 = 64.0;
|
pub const HUD_BAND_HEIGHT: f32 = 64.0;
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub const HUD_BAND_HEIGHT: f32 = 128.0;
|
||||||
|
|
||||||
/// Table background colour (dark green felt).
|
/// Table background colour (dark green felt).
|
||||||
pub const TABLE_COLOUR: [f32; 3] = [0.059, 0.322, 0.196];
|
pub const TABLE_COLOUR: [f32; 3] = [0.059, 0.322, 0.196];
|
||||||
|
|||||||
@@ -151,9 +151,22 @@ fn setup_table(
|
|||||||
safe_area: Option<Res<SafeAreaInsets>>,
|
safe_area: Option<Res<SafeAreaInsets>>,
|
||||||
) {
|
) {
|
||||||
// Only spawn a camera if one does not already exist (e.g. a parent app
|
// Only spawn a camera if one does not already exist (e.g. a parent app
|
||||||
// may have added one in tests).
|
// may have added one in tests). Use the felt-green clear colour so the
|
||||||
|
// background reads as green even before the background PNG finishes
|
||||||
|
// loading (which is asynchronous and can lag by several frames on
|
||||||
|
// Android).
|
||||||
if existing_camera.is_empty() {
|
if existing_camera.is_empty() {
|
||||||
commands.spawn(Camera2d);
|
commands.spawn((
|
||||||
|
Camera2d,
|
||||||
|
Camera {
|
||||||
|
clear_color: ClearColorConfig::Custom(Color::srgb(
|
||||||
|
crate::layout::TABLE_COLOUR[0],
|
||||||
|
crate::layout::TABLE_COLOUR[1],
|
||||||
|
crate::layout::TABLE_COLOUR[2],
|
||||||
|
)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (window_size, scale) = windows.iter().next().map_or(
|
let (window_size, scale) = windows.iter().next().map_or(
|
||||||
@@ -267,12 +280,11 @@ fn spawn_pile_markers(commands: &mut Commands, layout: &Layout) {
|
|||||||
PileMarker(pile.clone()),
|
PileMarker(pile.clone()),
|
||||||
));
|
));
|
||||||
|
|
||||||
// Foundation slots no longer carry a suit letter — any Ace can claim
|
// Tableau markers show "K" (only a King may start an empty column).
|
||||||
// any empty slot, so a fixed C/D/H/S badge would be misleading. Empty
|
// Foundation markers show "A" (only an Ace may claim an empty slot).
|
||||||
// foundation markers render as plain translucent rectangles.
|
// Neither label carries a suit because any suit may start any slot.
|
||||||
|
match &pile {
|
||||||
// Task #43 — King indicator on empty tableau placeholders.
|
PileType::Tableau(_) => {
|
||||||
if let PileType::Tableau(_) = &pile {
|
|
||||||
entity.with_children(|b| {
|
entity.with_children(|b| {
|
||||||
b.spawn((
|
b.spawn((
|
||||||
Text2d::new("K"),
|
Text2d::new("K"),
|
||||||
@@ -282,6 +294,18 @@ fn spawn_pile_markers(commands: &mut Commands, layout: &Layout) {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
PileType::Foundation(_) => {
|
||||||
|
entity.with_children(|b| {
|
||||||
|
b.spawn((
|
||||||
|
Text2d::new("A"),
|
||||||
|
TextFont { font_size, ..default() },
|
||||||
|
TextColor(TEXT_PRIMARY.with_alpha(0.35)),
|
||||||
|
Transform::from_xyz(0.0, 0.0, 0.1),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user