use ratatui::crossterm::event::{Event, KeyCode, MouseEventKind}; use kiln_core::Direction; use kiln_core::game::{GameState, ScrollLine}; use crate::scroll_overlay::ScrollAnimState; use crate::ui::Ui; /// Moves the player and, if the move crosses a portal, captures both board Rcs /// and stores them in [`Ui::pending_transition`] for the next draw call. fn try_move_with_transition(game: &mut GameState, ui: &mut Ui, dir: Direction) { let old_rc = game.board_rc(); let old_name = game.current_board_name().to_string(); game.try_move(dir); if game.current_board_name() != old_name { // Portal crossed: queue transition; consume the engine-side signal. ui.pending_transition = Some((old_rc, game.board_rc())); game.board_transition = None; } } /// Which input mode the front-end is currently in. pub enum InputMode { /// Normal gameplay: arrow keys move the player, `l` toggles the log. /// The `bool` is whether the log panel is open (affects PageUp/Down and mouse). Board, /// A scroll overlay is active; all input is captured for scroll navigation. Scroll, /// A board-wipe animation is running; all events are silently discarded. Transition, /// The wipe just finished; game is frozen until the player presses any key. Frozen, } /// Determines the current input mode from game and UI state. pub(crate) fn current_input_mode(game: &GameState, ui: &Ui) -> InputMode { // Transition takes highest priority (pending or animating). if ui.transition.is_some() || ui.pending_transition.is_some() { return InputMode::Transition; } // Post-wipe freeze: show the new board but don't tick or move until a key is pressed. if ui.post_transition_wait { return InputMode::Frozen; } let scroll_active = game.active_scroll.is_some() && !matches!(ui.overlay.anim, Some(ScrollAnimState::Closing(_) | ScrollAnimState::Done)); if scroll_active { InputMode::Scroll } else { InputMode::Board } } /// Handles an input event in [`InputMode::Board`]. Returns `true` if the player quit. pub(crate) fn handle_board_input(event: Event, log_open: bool, terminal_h: u16, game: &mut GameState, ui: &mut Ui) -> bool { match event { Event::Key(key) => match key.code { KeyCode::Char('q') | KeyCode::Esc => return true, KeyCode::Up => try_move_with_transition(game, ui, Direction::North), KeyCode::Down => try_move_with_transition(game, ui, Direction::South), KeyCode::Left => try_move_with_transition(game, ui, Direction::West), KeyCode::Right => try_move_with_transition(game, ui, Direction::East), KeyCode::Char('l') => ui.log.toggle(), KeyCode::PageUp if log_open => ui.log.scroll_by(-5, game.log.len()), KeyCode::PageDown if log_open => ui.log.scroll_by(5, game.log.len()), _ => {} }, Event::Mouse(m) if log_open => match m.kind { MouseEventKind::ScrollUp => ui.log.scroll_by(-1, game.log.len()), MouseEventKind::ScrollDown => ui.log.scroll_by(1, game.log.len()), MouseEventKind::Drag(_) => { // The divider sits at row `total - height`; dragging up grows the panel. let target = terminal_h.saturating_sub(m.row); ui.log.resize(target, terminal_h); } _ => {} }, _ => {} } false } /// Returns the choice string for the given key character, scanning the scroll lines. fn resolve_choice(lines: &[ScrollLine], c: char) -> Option { let mut letter = b'a'; for line in lines { if let ScrollLine::Choice { choice, .. } = line { if c == letter as char { return Some(choice.clone()); } letter += 1; } } None } /// Handles an input event in [`InputMode::Scroll`]. pub(crate) fn handle_scroll_input(event: Event, game: &mut GameState, ui: &mut Ui) { match event { Event::Key(key) => { let scroll = game.active_scroll.as_ref().unwrap(); let has_choices = scroll.lines.iter().any(|l| matches!(l, ScrollLine::Choice { .. })); match key.code { // Esc dismisses only when there are no choices; with choices the player must select. KeyCode::Esc if !has_choices => ui.begin_close(None), KeyCode::Up => ui.scroll_lines(-1), KeyCode::Down => ui.scroll_lines(1), KeyCode::Char(c) => { if let Some(ch) = resolve_choice(&scroll.lines, c) { ui.begin_close(Some(ch)); } } _ => {} } } Event::Mouse(m) => match m.kind { MouseEventKind::ScrollUp => ui.scroll_lines(-1), MouseEventKind::ScrollDown => ui.scroll_lines(1), _ => {} }, _ => {} } }