fix(engine): extend touch scroll to achievements and stats panels via generic helper
Extracts touch_scroll_panel<M: Component> into ui_modal.rs and wires it to SettingsPanelScrollable, AchievementsScrollable, and StatsScrollable so all three panels respond to finger swipe on Android. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -116,6 +116,7 @@ impl Plugin for AchievementPlugin {
|
|||||||
// achievements-scroll system also runs cleanly under
|
// achievements-scroll system also runs cleanly under
|
||||||
// `MinimalPlugins` in tests.
|
// `MinimalPlugins` in tests.
|
||||||
.add_message::<MouseWheel>()
|
.add_message::<MouseWheel>()
|
||||||
|
.add_message::<bevy::input::touch::TouchInput>()
|
||||||
// Run after GameMutation (so GameWonEvent is available), after
|
// Run after GameMutation (so GameWonEvent is available), after
|
||||||
// StatsUpdate (so stats reflect this win), and after ProgressUpdate
|
// StatsUpdate (so stats reflect this win), and after ProgressUpdate
|
||||||
// (so daily_challenge_streak is up to date for daily_devotee).
|
// (so daily_challenge_streak is up to date for daily_devotee).
|
||||||
@@ -139,6 +140,7 @@ impl Plugin for AchievementPlugin {
|
|||||||
.add_systems(Update, toggle_achievements_screen)
|
.add_systems(Update, toggle_achievements_screen)
|
||||||
.add_systems(Update, handle_achievements_close_button)
|
.add_systems(Update, handle_achievements_close_button)
|
||||||
.add_systems(Update, scroll_achievements_panel)
|
.add_systems(Update, scroll_achievements_panel)
|
||||||
|
.add_systems(Update, crate::ui_modal::touch_scroll_panel::<AchievementsScrollable>)
|
||||||
// Event-driven unlock: observe `ReplayPlaybackState` and unlock
|
// Event-driven unlock: observe `ReplayPlaybackState` and unlock
|
||||||
// `cinephile` the first time playback runs to natural completion.
|
// `cinephile` the first time playback runs to natural completion.
|
||||||
// Reads the resource via `Option<Res<_>>` so headless tests that
|
// Reads the resource via `Option<Res<_>>` so headless tests that
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
|
use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
|
||||||
use bevy::input::touch::{TouchInput, TouchPhase};
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::ui::{ComputedNode, UiGlobalTransform};
|
use bevy::ui::{ComputedNode, UiGlobalTransform};
|
||||||
use bevy::window::{WindowMoved, WindowResized};
|
use bevy::window::{WindowMoved, WindowResized};
|
||||||
@@ -371,7 +370,7 @@ impl Plugin for SettingsPlugin {
|
|||||||
handle_volume_keys,
|
handle_volume_keys,
|
||||||
toggle_settings_screen,
|
toggle_settings_screen,
|
||||||
scroll_settings_panel,
|
scroll_settings_panel,
|
||||||
touch_scroll_settings_panel,
|
crate::ui_modal::touch_scroll_panel::<SettingsPanelScrollable>,
|
||||||
record_window_geometry_changes,
|
record_window_geometry_changes,
|
||||||
persist_window_geometry_after_debounce,
|
persist_window_geometry_after_debounce,
|
||||||
),
|
),
|
||||||
@@ -1370,43 +1369,6 @@ fn scroll_settings_panel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scrolls the settings panel in response to touch pan gestures (Android).
|
|
||||||
/// Tracks the most recent touch Y so each `Moved` event's delta can be
|
|
||||||
/// applied to `ScrollPosition`. `MouseWheel` handles desktop; this system
|
|
||||||
/// fills the gap for single-finger swipe on touchscreen devices.
|
|
||||||
fn touch_scroll_settings_panel(
|
|
||||||
mut touch_evr: MessageReader<TouchInput>,
|
|
||||||
screen: Res<SettingsScreen>,
|
|
||||||
mut scrollables: Query<&mut ScrollPosition, With<SettingsPanelScrollable>>,
|
|
||||||
mut last_y: Local<Option<f32>>,
|
|
||||||
) {
|
|
||||||
if !screen.0 {
|
|
||||||
touch_evr.clear();
|
|
||||||
*last_y = None;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for event in touch_evr.read() {
|
|
||||||
match event.phase {
|
|
||||||
TouchPhase::Started => {
|
|
||||||
*last_y = Some(event.position.y);
|
|
||||||
}
|
|
||||||
TouchPhase::Moved => {
|
|
||||||
if let Some(prev) = *last_y {
|
|
||||||
let delta = event.position.y - prev;
|
|
||||||
for mut sp in scrollables.iter_mut() {
|
|
||||||
// Swiping up (delta < 0) scrolls content down (sp.y increases).
|
|
||||||
sp.0.y = (sp.0.y - delta).max(0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*last_y = Some(event.position.y);
|
|
||||||
}
|
|
||||||
TouchPhase::Ended | TouchPhase::Canceled => {
|
|
||||||
*last_y = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Window geometry persistence
|
// Window geometry persistence
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ impl Plugin for StatsPlugin {
|
|||||||
// `DefaultPlugins`; register it explicitly so the stats-scroll
|
// `DefaultPlugins`; register it explicitly so the stats-scroll
|
||||||
// system also runs cleanly under `MinimalPlugins` in tests.
|
// system also runs cleanly under `MinimalPlugins` in tests.
|
||||||
.add_message::<MouseWheel>()
|
.add_message::<MouseWheel>()
|
||||||
|
.add_message::<bevy::input::touch::TouchInput>()
|
||||||
// record_abandoned must read `move_count` BEFORE handle_new_game
|
// record_abandoned must read `move_count` BEFORE handle_new_game
|
||||||
// clobbers it with a fresh game. These are NOT in StatsUpdate because
|
// clobbers it with a fresh game. These are NOT in StatsUpdate because
|
||||||
// StatsUpdate (as a set) is ordered after GameMutation by external
|
// StatsUpdate (as a set) is ordered after GameMutation by external
|
||||||
@@ -238,7 +239,8 @@ impl Plugin for StatsPlugin {
|
|||||||
)
|
)
|
||||||
.chain(),
|
.chain(),
|
||||||
)
|
)
|
||||||
.add_systems(Update, scroll_stats_panel);
|
.add_systems(Update, scroll_stats_panel)
|
||||||
|
.add_systems(Update, crate::ui_modal::touch_scroll_panel::<StatsScrollable>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
//! );
|
//! );
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
use bevy::input::touch::{TouchInput, TouchPhase};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::ui::{ComputedNode, UiGlobalTransform};
|
use bevy::ui::{ComputedNode, UiGlobalTransform};
|
||||||
use bevy::window::PrimaryWindow;
|
use bevy::window::PrimaryWindow;
|
||||||
@@ -390,6 +391,47 @@ pub fn spawn_modal_button<M: Component>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Generic touch-scroll helper
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Scrolls any `Overflow::scroll_y()` panel marked with `M` via single-finger
|
||||||
|
/// touch pan. Add this as a system for each scrollable modal panel:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// app.add_message::<TouchInput>()
|
||||||
|
/// .add_systems(Update, touch_scroll_panel::<MyScrollable>);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// On desktop `TouchInput` events never fire, so the system is a no-op.
|
||||||
|
pub fn touch_scroll_panel<M: Component>(
|
||||||
|
mut touch_evr: MessageReader<TouchInput>,
|
||||||
|
mut scrollables: Query<&mut ScrollPosition, With<M>>,
|
||||||
|
mut last_y: Local<Option<f32>>,
|
||||||
|
) {
|
||||||
|
for event in touch_evr.read() {
|
||||||
|
match event.phase {
|
||||||
|
TouchPhase::Started => {
|
||||||
|
*last_y = Some(event.position.y);
|
||||||
|
}
|
||||||
|
TouchPhase::Moved => {
|
||||||
|
if let Some(prev) = *last_y {
|
||||||
|
let delta = event.position.y - prev;
|
||||||
|
for mut sp in scrollables.iter_mut() {
|
||||||
|
// Swiping up (delta < 0 in screen coords) scrolls
|
||||||
|
// content down, so sp.y increases.
|
||||||
|
sp.0.y = (sp.0.y - delta).max(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*last_y = Some(event.position.y);
|
||||||
|
}
|
||||||
|
TouchPhase::Ended | TouchPhase::Canceled => {
|
||||||
|
*last_y = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Helpers + paint system
|
// Helpers + paint system
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user