more refactor
This commit is contained in:
+6
-64
@@ -13,6 +13,7 @@ mod scroll_overlay;
|
||||
mod term;
|
||||
mod utils;
|
||||
mod speech;
|
||||
mod ui;
|
||||
|
||||
use kiln_core::game::{GameState, ScrollLine};
|
||||
use kiln_core::log::LogLine;
|
||||
@@ -33,53 +34,11 @@ use std::io;
|
||||
use std::process::ExitCode;
|
||||
use std::time::{Duration, Instant};
|
||||
use term::TerminalCaps;
|
||||
use crate::scroll_overlay::{ScrollAnimState, ScrollOverlayState, ScrollOverlayWidget};
|
||||
use crate::scroll_overlay::{ScrollAnimState, ScrollOverlayWidget};
|
||||
use crate::speech::SpeechBubblesWidget;
|
||||
use crate::ui::Ui;
|
||||
use crate::utils::logline_to_line;
|
||||
|
||||
/// View-only UI state for the log panel and scroll overlay. This is presentation
|
||||
/// state, so it lives in the front-end rather than on the engine's [`GameState`].
|
||||
struct Ui {
|
||||
/// Whether the bottom log panel is open.
|
||||
log_open: bool,
|
||||
/// Height (in terminal rows, borders included) of the log panel when open.
|
||||
log_height: u16,
|
||||
/// Vertical scroll offset of the log panel, in lines from the top (newest).
|
||||
scroll: u16,
|
||||
/// Scroll position, bounds, and animation state for an active scroll overlay.
|
||||
overlay: ScrollOverlayState,
|
||||
}
|
||||
|
||||
impl Default for Ui {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_open: false,
|
||||
log_height: 10,
|
||||
scroll: 0,
|
||||
overlay: ScrollOverlayState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
/// Scrolls the overlay by `delta` lines, clamped to the valid range.
|
||||
fn scroll_lines(&mut self, delta: i32) {
|
||||
self.overlay.offset = (self.overlay.offset as i32 + delta)
|
||||
.clamp(0, self.overlay.max_scroll as i32) as u16;
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the scroll-close animation with the optional player choice.
|
||||
/// Uses the current opening progress if the overlay was still animating in.
|
||||
fn begin_close(ui: &mut Ui, choice: Option<String>) {
|
||||
let progress = match &ui.overlay.anim {
|
||||
Some(ScrollAnimState::Opening(p)) => p.min(1.0),
|
||||
_ => 1.0,
|
||||
};
|
||||
ui.overlay.pending_choice = choice;
|
||||
ui.overlay.anim = Some(ScrollAnimState::Closing(progress));
|
||||
}
|
||||
|
||||
/// Returns the choice string for the given key character, or `None` if it doesn't
|
||||
/// match any choice in the scroll. Choices are assigned letters a, b, c… in order.
|
||||
fn resolve_choice(lines: &[ScrollLine], c: char) -> Option<String> {
|
||||
@@ -197,12 +156,12 @@ fn run(
|
||||
match key.code {
|
||||
// Esc dismisses the scroll only when there are no choices;
|
||||
// with choices present the player must select one.
|
||||
KeyCode::Esc if !has_choices => begin_close(ui, None),
|
||||
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) {
|
||||
begin_close(ui, Some(ch));
|
||||
ui.begin_close(Some(ch));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -263,24 +222,7 @@ fn run(
|
||||
if last_tick.elapsed() >= FRAME {
|
||||
let dt = last_tick.elapsed();
|
||||
last_tick = Instant::now();
|
||||
|
||||
// Advance scroll animation regardless of game pause state.
|
||||
let dt_s = dt.as_secs_f32();
|
||||
ui.overlay.advance_anim(dt_s);
|
||||
if ui.overlay.close_finished() {
|
||||
// Animation done — dispatch choice and clear the scroll.
|
||||
game.close_scroll(ui.overlay.take_choice().as_deref());
|
||||
ui.overlay = ScrollOverlayState::default();
|
||||
}
|
||||
|
||||
// Tick the game only when no scroll overlay is blocking.
|
||||
if game.active_scroll.is_none() {
|
||||
game.tick(dt);
|
||||
// Detect a scroll just opened by the tick; start its open animation.
|
||||
if game.active_scroll.is_some() && ui.overlay.anim.is_none() {
|
||||
ui.overlay.anim = Some(ScrollAnimState::Opening(0.0));
|
||||
}
|
||||
}
|
||||
ui.tick(dt, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
use std::time::Duration;
|
||||
use kiln_core::game::GameState;
|
||||
use crate::scroll_overlay::{ScrollAnimState, ScrollOverlayState};
|
||||
|
||||
/// View-only UI state for the log panel and scroll overlay. This is presentation
|
||||
/// state, so it lives in the front-end rather than on the engine's [`GameState`].
|
||||
pub(crate) struct Ui {
|
||||
/// Whether the bottom log panel is open.
|
||||
pub(crate) log_open: bool,
|
||||
/// Height (in terminal rows, borders included) of the log panel when open.
|
||||
pub(crate) log_height: u16,
|
||||
/// Vertical scroll offset of the log panel, in lines from the top (newest).
|
||||
pub(crate) scroll: u16,
|
||||
/// Scroll position, bounds, and animation state for an active scroll overlay.
|
||||
pub(crate) overlay: ScrollOverlayState,
|
||||
}
|
||||
|
||||
impl Default for Ui {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_open: false,
|
||||
log_height: 10,
|
||||
scroll: 0,
|
||||
overlay: ScrollOverlayState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
/// Scrolls the overlay by `delta` lines, clamped to the valid range.
|
||||
pub(crate) fn scroll_lines(&mut self, delta: i32) {
|
||||
self.overlay.offset = (self.overlay.offset as i32 + delta)
|
||||
.clamp(0, self.overlay.max_scroll as i32) as u16;
|
||||
}
|
||||
|
||||
/// Advances all time-driven UI state by `dt` and drives the game tick.
|
||||
///
|
||||
/// Advances the scroll overlay animation, dispatches any finished close
|
||||
/// animation, ticks the game when no overlay is blocking, and starts the
|
||||
/// open animation when a new scroll appears.
|
||||
pub(crate) fn tick(&mut self, dt: Duration, game: &mut GameState) {
|
||||
self.overlay.advance_anim(dt.as_secs_f32());
|
||||
if self.overlay.close_finished() {
|
||||
game.close_scroll(self.overlay.take_choice().as_deref());
|
||||
self.overlay = ScrollOverlayState::default();
|
||||
}
|
||||
|
||||
if game.active_scroll.is_none() {
|
||||
game.tick(dt);
|
||||
if game.active_scroll.is_some() && self.overlay.anim.is_none() {
|
||||
self.overlay.anim = Some(ScrollAnimState::Opening(0.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the scroll-close animation with the optional player choice.
|
||||
/// Uses the current opening progress if the overlay was still animating in.
|
||||
pub(crate) fn begin_close(&mut self, choice: Option<String>) {
|
||||
let progress = match self.overlay.anim {
|
||||
Some(ScrollAnimState::Opening(p)) => p.min(1.0),
|
||||
_ => 1.0,
|
||||
};
|
||||
self.overlay.pending_choice = choice;
|
||||
self.overlay.anim = Some(ScrollAnimState::Closing(progress));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user