animations

This commit is contained in:
2026-06-15 09:15:30 -05:00
parent 77eba1a09c
commit 32aef1ea08
9 changed files with 394 additions and 313 deletions
+28 -20
View File
@@ -7,6 +7,7 @@
//! Rendering is text-based: kiln's bitmap-font tile indices are reinterpreted
//! as characters (see [`cp437`]) and drawn with each cell's RGB colors.
mod animation;
mod cp437;
mod log;
mod render;
@@ -34,11 +35,11 @@ use std::io;
use std::process::ExitCode;
use std::time::{Duration, Instant};
use term::TerminalCaps;
use crate::animation::{AnimationLayer, AnimWidget};
use crate::input::{current_input_mode, handle_board_input, handle_scroll_input, InputMode};
use crate::log::{log_preview_line, LogWidget};
use crate::scroll_overlay::ScrollOverlayWidget;
use crate::speech::SpeechBubblesWidget;
use crate::transition::TransitionAnimation;
use crate::ui::Ui;
/// Entry point: parse the map path, load the board, then run the play loop with
@@ -136,11 +137,10 @@ fn run(
if handle_board_input(event, ui.log.open, terminal_h, game, ui) {
return Ok(());
}
ui.post_transition_wait = false;
}
InputMode::Scroll => handle_scroll_input(event, game, ui),
// Wipe is running — discard all events.
InputMode::Transition => {}
// Animation running — discard all events.
InputMode::Animating => {}
}
}
@@ -158,7 +158,7 @@ fn run(
/// Render a single frame: the board in a bordered block, and — when open — a log
/// panel across the bottom. When the panel is closed the latest log message is
/// previewed in the board's bottom-left border after a `[l]og:` label.
/// A scroll overlay is drawn on top of everything when active.
/// A scroll overlay or overlay animation is drawn on top of everything when active.
fn draw(frame: &mut Frame, game: &GameState, ui: &mut Ui) {
// Borrow the board for the duration of the frame (no script runs during draw).
let board = &game.board();
@@ -186,32 +186,40 @@ fn draw(frame: &mut Frame, game: &GameState, ui: &mut Ui) {
draw_board_area(frame, inner, game, ui);
}
// Scroll overlay: drawn last so it sits above all other content.
// Suppressed during a wipe enter_board() already cleared active_scroll.
if ui.transition.is_none() && let Some(scroll) = &game.active_scroll {
// Capture area before the mutable buffer borrow to satisfy the borrow checker.
// Draw overlay animations (scroll open/close) or the fully-open scroll overlay.
// Suppressed during a board-layer animation (transition wipe); enter_board()
// already cleared active_scroll so there's nothing to show anyway.
let is_overlay_anim = ui.active_animation.as_ref()
.map(|a| matches!(a.layer(), AnimationLayer::Overlay))
.unwrap_or(false);
if is_overlay_anim {
let area = frame.area();
frame.render_stateful_widget(ScrollOverlayWidget::new(scroll), area, &mut ui.overlay);
frame.render_widget(AnimWidget(ui.active_animation.as_ref().unwrap().as_ref()), area);
} else if let Some(scroll) = &game.active_scroll {
let area = frame.area();
frame.render_stateful_widget(ScrollOverlayWidget::new(&scroll.lines), area, &mut ui.overlay);
}
}
/// Renders the inner board area: either the active wipe animation or the live board.
/// Renders the inner board area: either the active board-layer animation or the live board.
///
/// If `ui.pending_transition` is set, pre-renders both boards into a new
/// [`TransitionAnimation`] (now that we know the exact `inner` area) and activates it.
/// [`TransitionAnimation`](transition::TransitionAnimation) (now that we know the
/// exact `inner` area) and activates it via [`Ui::start_transition`].
fn draw_board_area(frame: &mut Frame, inner: Rect, game: &GameState, ui: &mut Ui) {
// Initialize any pending transition now that we know the exact area.
if let Some((old_rc, new_rc)) = ui.pending_transition.take() {
ui.transition = Some(TransitionAnimation::new(
&old_rc.borrow(),
&new_rc.borrow(),
inner,
));
ui.start_transition(&old_rc.borrow(), &new_rc.borrow(), inner);
}
if let Some(ref tr) = ui.transition {
// Wipe in progress: blend old and new board buffers; suppress speech bubbles.
frame.render_widget(tr, inner);
let is_board_anim = ui.active_animation.as_ref()
.map(|a| matches!(a.layer(), AnimationLayer::Board))
.unwrap_or(false);
if is_board_anim {
// Board-layer animation (wipe): replaces the board entirely.
frame.render_widget(AnimWidget(ui.active_animation.as_ref().unwrap().as_ref()), inner);
} else {
let board = &game.board();
frame.render_widget(BoardWidget::new(board), inner);