66 lines
2.5 KiB
Rust
66 lines
2.5 KiB
Rust
|
|
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));
|
||
|
|
}
|
||
|
|
}
|