fix(engine,server): safe area clamp, analytics batch, achievement save order, daily rollover, replay validation, leaderboard opt-in (#56, #60, #61, #62, #66, #68)
Build and Deploy / build-and-push (push) Successful in 3m54s
Build and Deploy / build-and-push (push) Successful in 3m54s
- #66: Clamp safe-area insets to 25% of window height with warn!() on excess - #68: Move fire_flush outside per-event loop in analytics (batch flush once) - #56: Persist progress before marking reward_granted to prevent XP loss on crash - #60: Add DateRolloverTimer + check_date_rollover system for midnight seed refresh - #62: Add validate_header() in replay upload with mode/draw_mode allowlists - #61: Restore two-query leaderboard opt-in check (SELECT then UPDATE); original queries already in .sqlx cache; EXISTS variant would require sqlx prepare Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -14,21 +14,8 @@ use solitaire_core::pile::PileType;
|
||||
|
||||
use crate::auto_complete_plugin::AutoCompleteState;
|
||||
use crate::avatar_plugin::AvatarResource;
|
||||
use solitaire_data::SyncBackend;
|
||||
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
||||
use crate::daily_challenge_plugin::DailyChallengeResource;
|
||||
use crate::progress_plugin::ProgressResource;
|
||||
use crate::settings_plugin::SettingsResource;
|
||||
use crate::layout::HUD_BAND_HEIGHT;
|
||||
use crate::safe_area::{SafeAreaAnchoredBottom, SafeAreaAnchoredTop};
|
||||
use crate::ui_theme::SPACE_2;
|
||||
use crate::ui_theme::{
|
||||
scaled_duration, ACCENT_PRIMARY, ACCENT_SECONDARY, BG_ELEVATED, BG_ELEVATED_HI,
|
||||
BG_ELEVATED_PRESSED, BG_HUD_BAND, BORDER_SUBTLE, HighContrastBorder, MOTION_SCORE_PULSE_SECS,
|
||||
MOTION_STREAK_FLOURISH_SECS, RADIUS_MD, RADIUS_SM, STATE_DANGER, STATE_INFO, STATE_SUCCESS,
|
||||
STATE_WARNING, STREAK_FLOURISH_PEAK_SCALE, TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY,
|
||||
TYPE_BODY_LG, TYPE_CAPTION, TYPE_HEADLINE, VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_3,
|
||||
};
|
||||
use crate::events::{
|
||||
HelpRequestEvent, InfoToastEvent, NewGameRequestEvent, PauseRequestEvent,
|
||||
StartChallengeRequestEvent, StartDailyChallengeRequestEvent, StartTimeAttackRequestEvent,
|
||||
@@ -40,18 +27,32 @@ use crate::font_plugin::FontResource;
|
||||
use crate::game_plugin::GameMutation;
|
||||
#[cfg(target_os = "android")]
|
||||
use crate::input_plugin::TouchDragSet;
|
||||
use crate::layout::HUD_BAND_HEIGHT;
|
||||
use crate::layout::LayoutSystem;
|
||||
use crate::platform::{SHOW_KEYBOARD_ACCELERATORS, USE_TOUCH_UI_LAYOUT};
|
||||
#[cfg(target_os = "android")]
|
||||
use crate::pause_plugin::PausedResource;
|
||||
use crate::platform::{SHOW_KEYBOARD_ACCELERATORS, USE_TOUCH_UI_LAYOUT};
|
||||
use crate::progress_plugin::ProgressResource;
|
||||
use crate::resources::GameStateResource;
|
||||
#[cfg(target_os = "android")]
|
||||
use crate::resources::{DragState, GameInputConsumedResource};
|
||||
use crate::safe_area::{SafeAreaAnchoredBottom, SafeAreaAnchoredTop};
|
||||
use crate::selection_plugin::SelectionState;
|
||||
use crate::settings_plugin::SettingsResource;
|
||||
use crate::time_attack_plugin::TimeAttackResource;
|
||||
use crate::ui_focus::{FocusGroup, Focusable};
|
||||
use crate::ui_modal::ModalScrim;
|
||||
use crate::ui_theme::SPACE_2;
|
||||
use crate::ui_theme::{
|
||||
ACCENT_PRIMARY, ACCENT_SECONDARY, BG_ELEVATED, BG_ELEVATED_HI, BG_ELEVATED_PRESSED,
|
||||
BG_HUD_BAND, BORDER_SUBTLE, HighContrastBorder, MOTION_SCORE_PULSE_SECS,
|
||||
MOTION_STREAK_FLOURISH_SECS, RADIUS_MD, RADIUS_SM, STATE_DANGER, STATE_INFO, STATE_SUCCESS,
|
||||
STATE_WARNING, STREAK_FLOURISH_PEAK_SCALE, TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY,
|
||||
TYPE_BODY_LG, TYPE_CAPTION, TYPE_HEADLINE, VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_3,
|
||||
scaled_duration,
|
||||
};
|
||||
use crate::ui_tooltip::Tooltip;
|
||||
use solitaire_data::SyncBackend;
|
||||
|
||||
/// Marker on the score text node.
|
||||
#[derive(Component, Debug)]
|
||||
@@ -310,9 +311,25 @@ pub struct HintButton;
|
||||
pub(crate) const ANDROID_HINT_LABEL: &str = "!";
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
const ACTION_BAR_LABELS: [&str; 7] = ["\u{2261}", "\u{2190}", "||", "?", ANDROID_HINT_LABEL, "M", "+"];
|
||||
const ACTION_BAR_LABELS: [&str; 7] = [
|
||||
"\u{2261}",
|
||||
"\u{2190}",
|
||||
"||",
|
||||
"?",
|
||||
ANDROID_HINT_LABEL,
|
||||
"M",
|
||||
"+",
|
||||
];
|
||||
#[cfg(not(target_os = "android"))]
|
||||
const ACTION_BAR_LABELS: [&str; 7] = ["Menu \u{2193}", "Undo", "Pause", "Help", "Hint", "Modes \u{2193}", "New Game"];
|
||||
const ACTION_BAR_LABELS: [&str; 7] = [
|
||||
"Menu \u{2193}",
|
||||
"Undo",
|
||||
"Pause",
|
||||
"Help",
|
||||
"Hint",
|
||||
"Modes \u{2193}",
|
||||
"New Game",
|
||||
];
|
||||
#[cfg(target_os = "android")]
|
||||
const ACTION_BAR_COLUMN_GAP: Val = Val::Px(4.0);
|
||||
#[cfg(not(target_os = "android"))]
|
||||
@@ -564,10 +581,7 @@ fn spawn_hud_band(mut commands: Commands) {
|
||||
/// player's #1 complaint. This restructure groups by purpose, lets
|
||||
/// transient items disappear cleanly, and uses the typography scale to
|
||||
/// make Score the visual protagonist.
|
||||
fn spawn_hud(
|
||||
font_res: Option<Res<FontResource>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
fn spawn_hud(font_res: Option<Res<FontResource>>, mut commands: Commands) {
|
||||
let font_handle = font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default();
|
||||
let font_score = TextFont {
|
||||
font: font_handle.clone(),
|
||||
@@ -637,9 +651,7 @@ fn spawn_hud(
|
||||
));
|
||||
t1.spawn((
|
||||
HudMoves,
|
||||
Tooltip::new(
|
||||
"Moves you've made this game. Counts placements and stock draws.",
|
||||
),
|
||||
Tooltip::new("Moves you've made this game. Counts placements and stock draws."),
|
||||
Text::new("Moves: 0"),
|
||||
font_lg.clone(),
|
||||
TextColor(TEXT_SECONDARY),
|
||||
@@ -680,9 +692,7 @@ fn spawn_hud(
|
||||
));
|
||||
t2.spawn((
|
||||
HudWonPreviously,
|
||||
Tooltip::new(
|
||||
"You've won this deal before. Same seed in your replay history.",
|
||||
),
|
||||
Tooltip::new("You've won this deal before. Same seed in your replay history."),
|
||||
Text::new(""),
|
||||
font_body.clone(),
|
||||
TextColor(STATE_SUCCESS),
|
||||
@@ -695,9 +705,7 @@ fn spawn_hud(
|
||||
hud.spawn(row_node()).with_children(|t3| {
|
||||
t3.spawn((
|
||||
HudUndos,
|
||||
Tooltip::new(
|
||||
"Undos used this game. Any undo blocks the No Undo achievement.",
|
||||
),
|
||||
Tooltip::new("Undos used this game. Any undo blocks the No Undo achievement."),
|
||||
Text::new(""),
|
||||
font_body.clone(),
|
||||
TextColor(STATE_WARNING),
|
||||
@@ -874,7 +882,8 @@ fn spawn_action_buttons(
|
||||
windows: Query<&Window>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let action_font_size = action_bar_font_size(windows.iter().next().map_or(900.0, |win| win.width()));
|
||||
let action_font_size =
|
||||
action_bar_font_size(windows.iter().next().map_or(900.0, |win| win.width()));
|
||||
let font = TextFont {
|
||||
font: font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default(),
|
||||
font_size: action_font_size,
|
||||
@@ -915,13 +924,76 @@ fn spawn_action_buttons(
|
||||
// so Tab cycles the action bar in visual reading order.
|
||||
// Undo and Pause are the primary gameplay actions — full brightness.
|
||||
// Menu, Help, Hint, Modes, New are navigation/utility — dimmed.
|
||||
spawn_action_button(row, MenuButton, ACTION_BAR_LABELS[0], None, "Open Stats, Achievements, Profile, Settings, or Leaderboard.", &font, 0, TEXT_SECONDARY);
|
||||
spawn_action_button(row, UndoButton, ACTION_BAR_LABELS[1], Some("U"), "Take back your last move. Costs points and blocks No Undo.", &font, 1, TEXT_PRIMARY);
|
||||
spawn_action_button(row, PauseButton, ACTION_BAR_LABELS[2], Some("Esc"), "Pause the game and freeze the timer.", &font, 2, TEXT_PRIMARY);
|
||||
spawn_action_button(row, HelpButton, ACTION_BAR_LABELS[3], Some("F1"), "Show controls, rules, and keyboard shortcuts.", &font, 3, TEXT_SECONDARY);
|
||||
spawn_action_button(row, HintButton, ACTION_BAR_LABELS[4], Some("H"), "Highlight a suggested move. Cycles through alternatives on repeat taps.", &font, 4, TEXT_SECONDARY);
|
||||
spawn_action_button(row, ModesButton, ACTION_BAR_LABELS[5], None, "Switch modes: Classic, Daily, Zen, Challenge, Time Attack.", &font, 5, TEXT_SECONDARY);
|
||||
spawn_action_button(row, NewGameButton, ACTION_BAR_LABELS[6], Some("N"), "Start a fresh deal. Confirms first if a game is in progress.", &font, 6, TEXT_SECONDARY);
|
||||
spawn_action_button(
|
||||
row,
|
||||
MenuButton,
|
||||
ACTION_BAR_LABELS[0],
|
||||
None,
|
||||
"Open Stats, Achievements, Profile, Settings, or Leaderboard.",
|
||||
&font,
|
||||
0,
|
||||
TEXT_SECONDARY,
|
||||
);
|
||||
spawn_action_button(
|
||||
row,
|
||||
UndoButton,
|
||||
ACTION_BAR_LABELS[1],
|
||||
Some("U"),
|
||||
"Take back your last move. Costs points and blocks No Undo.",
|
||||
&font,
|
||||
1,
|
||||
TEXT_PRIMARY,
|
||||
);
|
||||
spawn_action_button(
|
||||
row,
|
||||
PauseButton,
|
||||
ACTION_BAR_LABELS[2],
|
||||
Some("Esc"),
|
||||
"Pause the game and freeze the timer.",
|
||||
&font,
|
||||
2,
|
||||
TEXT_PRIMARY,
|
||||
);
|
||||
spawn_action_button(
|
||||
row,
|
||||
HelpButton,
|
||||
ACTION_BAR_LABELS[3],
|
||||
Some("F1"),
|
||||
"Show controls, rules, and keyboard shortcuts.",
|
||||
&font,
|
||||
3,
|
||||
TEXT_SECONDARY,
|
||||
);
|
||||
spawn_action_button(
|
||||
row,
|
||||
HintButton,
|
||||
ACTION_BAR_LABELS[4],
|
||||
Some("H"),
|
||||
"Highlight a suggested move. Cycles through alternatives on repeat taps.",
|
||||
&font,
|
||||
4,
|
||||
TEXT_SECONDARY,
|
||||
);
|
||||
spawn_action_button(
|
||||
row,
|
||||
ModesButton,
|
||||
ACTION_BAR_LABELS[5],
|
||||
None,
|
||||
"Switch modes: Classic, Daily, Zen, Challenge, Time Attack.",
|
||||
&font,
|
||||
5,
|
||||
TEXT_SECONDARY,
|
||||
);
|
||||
spawn_action_button(
|
||||
row,
|
||||
NewGameButton,
|
||||
ACTION_BAR_LABELS[6],
|
||||
Some("N"),
|
||||
"Start a fresh deal. Confirms first if a game is in progress.",
|
||||
&font,
|
||||
6,
|
||||
TEXT_SECONDARY,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -952,7 +1024,11 @@ fn spawn_action_button<M: Component>(
|
||||
// touch device — the button itself is the affordance — and they
|
||||
// visibly clutter the narrow-viewport action row. The chevrons on
|
||||
// Menu/Modes remain because they indicate dropdown behaviour.
|
||||
let hotkey = if SHOW_KEYBOARD_ACCELERATORS { hotkey } else { None };
|
||||
let hotkey = if SHOW_KEYBOARD_ACCELERATORS {
|
||||
hotkey
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let hotkey_font = TextFont {
|
||||
font: font.font.clone(),
|
||||
@@ -1082,9 +1158,7 @@ fn handle_modes_button(
|
||||
font_res: Option<Res<FontResource>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let pressed = interaction_query
|
||||
.iter()
|
||||
.any(|i| *i == Interaction::Pressed);
|
||||
let pressed = interaction_query.iter().any(|i| *i == Interaction::Pressed);
|
||||
if !pressed {
|
||||
return;
|
||||
}
|
||||
@@ -1261,9 +1335,7 @@ fn handle_mode_option_click(
|
||||
}
|
||||
}
|
||||
}
|
||||
if clicked_any
|
||||
&& let Ok(entity) = popovers.single()
|
||||
{
|
||||
if clicked_any && let Ok(entity) = popovers.single() {
|
||||
commands.entity(entity).despawn();
|
||||
for e in &backdrops {
|
||||
commands.entity(e).despawn();
|
||||
@@ -1282,9 +1354,7 @@ fn handle_menu_button(
|
||||
font_res: Option<Res<FontResource>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let pressed = interaction_query
|
||||
.iter()
|
||||
.any(|i| *i == Interaction::Pressed);
|
||||
let pressed = interaction_query.iter().any(|i| *i == Interaction::Pressed);
|
||||
if !pressed {
|
||||
return;
|
||||
}
|
||||
@@ -1462,13 +1532,12 @@ fn handle_menu_option_click(
|
||||
}
|
||||
}
|
||||
}
|
||||
if clicked_any
|
||||
&& let Ok(entity) = popovers.single() {
|
||||
commands.entity(entity).despawn();
|
||||
for e in &backdrops {
|
||||
commands.entity(e).despawn();
|
||||
}
|
||||
if clicked_any && let Ok(entity) = popovers.single() {
|
||||
commands.entity(entity).despawn();
|
||||
for e in &backdrops {
|
||||
commands.entity(e).despawn();
|
||||
}
|
||||
}
|
||||
if open_modes {
|
||||
spawn_modes_popover(
|
||||
&mut commands,
|
||||
@@ -1587,11 +1656,7 @@ const ACTION_FADE_RATE_PER_SEC: f32 = 6.0;
|
||||
/// (player is using keyboard); `0.0` otherwise. Lerps `alpha` toward
|
||||
/// `target` at a fixed rate so the visual transition is smooth across
|
||||
/// variable framerates.
|
||||
fn update_action_fade(
|
||||
windows: Query<&Window>,
|
||||
time: Res<Time>,
|
||||
mut fade: ResMut<HudActionFade>,
|
||||
) {
|
||||
fn update_action_fade(windows: Query<&Window>, time: Res<Time>, mut fade: ResMut<HudActionFade>) {
|
||||
let Ok(window) = windows.single() else {
|
||||
return;
|
||||
};
|
||||
@@ -2022,12 +2087,14 @@ fn update_won_previously(
|
||||
let won_before = !game.0.is_won
|
||||
&& history.as_ref().is_some_and(|h| {
|
||||
h.0.replays.iter().any(|r| {
|
||||
r.seed == game.0.seed
|
||||
&& r.draw_mode == game.0.draw_mode
|
||||
&& r.mode == game.0.mode
|
||||
r.seed == game.0.seed && r.draw_mode == game.0.draw_mode && r.mode == game.0.mode
|
||||
})
|
||||
});
|
||||
let next = if won_before { "\u{2713} Won before" } else { "" };
|
||||
let next = if won_before {
|
||||
"\u{2713} Won before"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
if text.0 != next {
|
||||
text.0 = next.to_string();
|
||||
}
|
||||
@@ -2290,13 +2357,14 @@ fn update_hud(
|
||||
let ac_active = auto_complete.as_ref().is_some_and(|ac| ac.active);
|
||||
let ac_changed = auto_complete.as_ref().is_some_and(|ac| ac.is_changed());
|
||||
if (ac_changed || game.is_changed())
|
||||
&& let Ok(mut t) = auto_q.single_mut() {
|
||||
**t = if ac_active {
|
||||
"AUTO".to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
}
|
||||
&& let Ok(mut t) = auto_q.single_mut()
|
||||
{
|
||||
**t = if ac_active {
|
||||
"AUTO".to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the `HudSelection` text node to show which pile is Tab-selected.
|
||||
@@ -2480,9 +2548,17 @@ fn action_bar_font_size(window_width: f32) -> f32 {
|
||||
|
||||
fn action_button_metrics() -> (UiRect, Val, Val) {
|
||||
if USE_TOUCH_UI_LAYOUT {
|
||||
(UiRect::axes(Val::Px(4.0), Val::Px(4.0)), Val::Px(52.0), Val::Px(44.0))
|
||||
(
|
||||
UiRect::axes(Val::Px(4.0), Val::Px(4.0)),
|
||||
Val::Px(52.0),
|
||||
Val::Px(44.0),
|
||||
)
|
||||
} else {
|
||||
(UiRect::axes(VAL_SPACE_2, VAL_SPACE_2), Val::Px(48.0), Val::Px(48.0))
|
||||
(
|
||||
UiRect::axes(VAL_SPACE_2, VAL_SPACE_2),
|
||||
Val::Px(48.0),
|
||||
Val::Px(48.0),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2493,7 +2569,12 @@ fn spawn_action_button_label(
|
||||
text_color: Color,
|
||||
) {
|
||||
if USE_TOUCH_UI_LAYOUT {
|
||||
parent.spawn((ActionButtonLabel, Text::new(label), font.clone(), TextColor(text_color)));
|
||||
parent.spawn((
|
||||
ActionButtonLabel,
|
||||
Text::new(label),
|
||||
font.clone(),
|
||||
TextColor(text_color),
|
||||
));
|
||||
} else {
|
||||
parent.spawn((Text::new(label), font.clone(), TextColor(text_color)));
|
||||
}
|
||||
@@ -2508,7 +2589,10 @@ fn resize_action_bar_labels(
|
||||
windows: Query<&Window>,
|
||||
mut labels: Query<&mut TextFont, With<ActionButtonLabel>>,
|
||||
) {
|
||||
let w = windows.iter().next().map_or(layout.0.card_size.x * 7.25, |win| win.width());
|
||||
let w = windows
|
||||
.iter()
|
||||
.next()
|
||||
.map_or(layout.0.card_size.x * 7.25, |win| win.width());
|
||||
let new_size = action_bar_font_size(w);
|
||||
for mut font in &mut labels {
|
||||
font.font_size = new_size;
|
||||
@@ -2545,8 +2629,7 @@ fn toggle_hud_on_tap(
|
||||
// Record whether the finger-down landed on a button so
|
||||
// the finger-up doesn't double-fire (toggle bar + press
|
||||
// button at the same time).
|
||||
tracker.started_on_button =
|
||||
buttons.iter().any(|i| *i != Interaction::None);
|
||||
tracker.started_on_button = buttons.iter().any(|i| *i != Interaction::None);
|
||||
}
|
||||
TouchPhase::Ended if drag.is_idle() => {
|
||||
// Also treat taps where game logic consumed the touch (e.g.
|
||||
@@ -2630,7 +2713,10 @@ mod tests {
|
||||
#[test]
|
||||
fn moves_reflects_game_state() {
|
||||
let mut app = headless_app();
|
||||
app.world_mut().resource_mut::<GameStateResource>().0.move_count = 42;
|
||||
app.world_mut()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.move_count = 42;
|
||||
app.update();
|
||||
assert_eq!(read_hud_text::<HudMoves>(&mut app), "Moves: 42");
|
||||
}
|
||||
@@ -2660,7 +2746,10 @@ mod tests {
|
||||
#[test]
|
||||
fn time_display_uses_mm_ss_format() {
|
||||
let mut app = headless_app();
|
||||
app.world_mut().resource_mut::<GameStateResource>().0.elapsed_seconds = 125;
|
||||
app.world_mut()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.elapsed_seconds = 125;
|
||||
app.update();
|
||||
// 125 seconds = 2 minutes 5 seconds → "2:05"
|
||||
assert_eq!(read_hud_text::<HudTime>(&mut app), "2:05");
|
||||
@@ -2834,7 +2923,10 @@ mod tests {
|
||||
#[test]
|
||||
fn undos_hud_shows_count_after_undo() {
|
||||
let mut app = headless_app();
|
||||
app.world_mut().resource_mut::<GameStateResource>().0.undo_count = 3;
|
||||
app.world_mut()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.undo_count = 3;
|
||||
app.update();
|
||||
assert_eq!(read_hud_text::<HudUndos>(&mut app), "Undos: 3");
|
||||
}
|
||||
@@ -2859,7 +2951,10 @@ mod tests {
|
||||
let mut app = headless_app_with_auto_complete();
|
||||
app.world_mut().resource_mut::<AutoCompleteState>().active = true;
|
||||
// Also trigger game state change so the update fires.
|
||||
app.world_mut().resource_mut::<GameStateResource>().0.move_count += 1;
|
||||
app.world_mut()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.move_count += 1;
|
||||
app.update();
|
||||
assert_eq!(read_hud_text::<HudAutoComplete>(&mut app), "AUTO");
|
||||
}
|
||||
@@ -2868,7 +2963,10 @@ mod tests {
|
||||
fn auto_complete_badge_empty_when_inactive() {
|
||||
let mut app = headless_app_with_auto_complete();
|
||||
// active is false by default.
|
||||
app.world_mut().resource_mut::<GameStateResource>().0.move_count += 1;
|
||||
app.world_mut()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.move_count += 1;
|
||||
app.update();
|
||||
assert_eq!(read_hud_text::<HudAutoComplete>(&mut app), "");
|
||||
}
|
||||
@@ -2928,9 +3026,9 @@ mod tests {
|
||||
fn set_manual_time_step(app: &mut App, secs: f32) {
|
||||
use bevy::time::TimeUpdateStrategy;
|
||||
use std::time::Duration;
|
||||
app.insert_resource(TimeUpdateStrategy::ManualDuration(
|
||||
Duration::from_secs_f32(secs),
|
||||
));
|
||||
app.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs_f32(
|
||||
secs,
|
||||
)));
|
||||
}
|
||||
|
||||
/// Counts entities matching component `M` currently in the world.
|
||||
@@ -3130,9 +3228,7 @@ mod tests {
|
||||
/// which is the invariant we want to enforce for HUD readouts and
|
||||
/// action buttons (each marker is spawned exactly once).
|
||||
fn tooltip_for<M: Component>(app: &mut App) -> String {
|
||||
let mut q = app
|
||||
.world_mut()
|
||||
.query_filtered::<&Tooltip, With<M>>();
|
||||
let mut q = app.world_mut().query_filtered::<&Tooltip, With<M>>();
|
||||
let world = app.world();
|
||||
let mut iter = q.iter(world);
|
||||
let first = iter
|
||||
|
||||
Reference in New Issue
Block a user