feat(engine,data): add tap-to-select touch input mode (#70)
- Add TouchInputMode enum (OneTap | TapToSelect) to solitaire_data settings - Create TouchSelectionPlugin with TouchSelectionState resource and highlight - Branch handle_double_tap: OneTap → existing auto-move, TapToSelect → two-tap flow - Add Settings UI toggle row (Touch Input Mode) with TouchInputModeText marker - Register TouchSelectionPlugin in CoreGamePlugin Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -48,7 +48,9 @@ use crate::radial_menu::RightClickRadialState;
|
||||
use crate::replay_playback::ReplayPlaybackState;
|
||||
use crate::resources::{DragState, GameInputConsumedResource, GameStateResource, HintCycleIndex};
|
||||
use crate::selection_plugin::SelectionState;
|
||||
use crate::settings_plugin::SettingsResource;
|
||||
use crate::time_attack_plugin::TimeAttackResource;
|
||||
use crate::touch_selection_plugin::TouchSelectionState;
|
||||
use crate::ui_theme::{MOTION_DRAG_REJECT_SECS, STATE_SUCCESS, STATE_WARNING};
|
||||
use solitaire_core::game_state::DrawMode;
|
||||
|
||||
@@ -1503,11 +1505,15 @@ fn handle_double_tap(
|
||||
radial: Option<Res<RightClickRadialState>>,
|
||||
drag: Res<DragState>,
|
||||
game: Res<GameStateResource>,
|
||||
settings: Option<Res<SettingsResource>>,
|
||||
mut touch_selection: Option<ResMut<TouchSelectionState>>,
|
||||
mut moves: MessageWriter<MoveRequestEvent>,
|
||||
mut rejected: MessageWriter<MoveRejectedEvent>,
|
||||
mut commands: Commands,
|
||||
mut card_sprites: Query<(Entity, &CardEntity, &mut Sprite)>,
|
||||
) {
|
||||
use solitaire_data::settings::TouchInputMode;
|
||||
|
||||
if paused.is_some_and(|p| p.0) {
|
||||
return;
|
||||
}
|
||||
@@ -1524,6 +1530,10 @@ fn handle_double_tap(
|
||||
return;
|
||||
}
|
||||
|
||||
let tap_to_select = settings
|
||||
.as_ref()
|
||||
.is_some_and(|s| s.0.touch_input_mode == TouchInputMode::TapToSelect);
|
||||
|
||||
for event in touch_events.read() {
|
||||
if event.id != active_id || event.phase != TouchPhase::Ended {
|
||||
continue;
|
||||
@@ -1533,10 +1543,10 @@ fn handle_double_tap(
|
||||
let Some(&top_card_id) = drag.cards.last() else {
|
||||
return;
|
||||
};
|
||||
let Some(ref pile) = drag.origin_pile else {
|
||||
let Some(ref tapped_pile) = drag.origin_pile else {
|
||||
return;
|
||||
};
|
||||
let Some(pile_cards) = game.0.piles.get(pile) else {
|
||||
let Some(pile_cards) = game.0.piles.get(tapped_pile) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1547,6 +1557,34 @@ fn handle_double_tap(
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Tap-to-select mode ---
|
||||
if tap_to_select {
|
||||
if let Some(ref mut sel) = touch_selection {
|
||||
if let Some((ref source_pile, ref source_cards)) = sel.selected.clone() {
|
||||
// Second tap: this is the destination.
|
||||
if tapped_pile == source_pile {
|
||||
// Re-tap on selected source → cancel.
|
||||
sel.clear();
|
||||
return;
|
||||
}
|
||||
// Attempt the move. MoveRequestEvent carries validation;
|
||||
// a rejection will fire MoveRejectedEvent automatically.
|
||||
moves.write(MoveRequestEvent {
|
||||
from: source_pile.clone(),
|
||||
to: tapped_pile.clone(),
|
||||
count: source_cards.len(),
|
||||
});
|
||||
sel.clear();
|
||||
return;
|
||||
}
|
||||
// First tap: select the source.
|
||||
sel.set(tapped_pile.clone(), drag.cards.clone());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// --- One-tap auto-move (original behaviour) ---
|
||||
|
||||
// Priority 1: move single top card.
|
||||
if let Some(dest) = best_destination(top_card, &game.0) {
|
||||
for (entity, ce, mut sprite) in card_sprites.iter_mut() {
|
||||
@@ -1559,7 +1597,7 @@ fn handle_double_tap(
|
||||
}
|
||||
}
|
||||
moves.write(MoveRequestEvent {
|
||||
from: pile.clone(),
|
||||
from: tapped_pile.clone(),
|
||||
to: dest,
|
||||
count: 1,
|
||||
});
|
||||
@@ -1571,7 +1609,7 @@ fn handle_double_tap(
|
||||
let stack_index = pile_cards.cards.len() - drag.cards.len();
|
||||
if let Some(bottom_card) = pile_cards.cards.get(stack_index)
|
||||
&& let Some((dest, count)) =
|
||||
best_tableau_destination_for_stack(bottom_card, pile, &game.0, drag.cards.len())
|
||||
best_tableau_destination_for_stack(bottom_card, tapped_pile, &game.0, drag.cards.len())
|
||||
{
|
||||
for (entity, ce, mut sprite) in card_sprites.iter_mut() {
|
||||
if drag.cards.contains(&ce.card_id) {
|
||||
@@ -1582,7 +1620,7 @@ fn handle_double_tap(
|
||||
}
|
||||
}
|
||||
moves.write(MoveRequestEvent {
|
||||
from: pile.clone(),
|
||||
from: tapped_pile.clone(),
|
||||
to: dest,
|
||||
count,
|
||||
});
|
||||
@@ -1591,8 +1629,8 @@ fn handle_double_tap(
|
||||
}
|
||||
|
||||
rejected.write(MoveRejectedEvent {
|
||||
from: pile.clone(),
|
||||
to: pile.clone(),
|
||||
from: tapped_pile.clone(),
|
||||
to: tapped_pile.clone(),
|
||||
count: drag.cards.len(),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user