refactored log
This commit is contained in:
+18
-57
@@ -8,6 +8,7 @@
|
||||
//! as characters (see [`cp437`]) and drawn with each cell's RGB colors.
|
||||
|
||||
mod cp437;
|
||||
mod log;
|
||||
mod render;
|
||||
mod scroll_overlay;
|
||||
mod term;
|
||||
@@ -25,19 +26,17 @@ use ratatui::crossterm::event::{
|
||||
};
|
||||
use ratatui::crossterm::execute;
|
||||
use ratatui::layout::{Constraint, Layout, Spacing};
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::symbols::merge::MergeStrategy;
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Paragraph, Wrap};
|
||||
use render::{BoardWidget};
|
||||
use ratatui::widgets::Block;
|
||||
use render::BoardWidget;
|
||||
use std::io;
|
||||
use std::process::ExitCode;
|
||||
use std::time::{Duration, Instant};
|
||||
use term::TerminalCaps;
|
||||
use crate::log::{log_preview_line, LogWidget};
|
||||
use crate::scroll_overlay::{ScrollAnimState, ScrollOverlayWidget};
|
||||
use crate::speech::SpeechBubblesWidget;
|
||||
use crate::ui::Ui;
|
||||
use crate::utils::logline_to_line;
|
||||
|
||||
/// 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.
|
||||
@@ -128,7 +127,7 @@ fn run(
|
||||
terminal: &mut ratatui::DefaultTerminal,
|
||||
game: &mut GameState,
|
||||
ui: &mut Ui,
|
||||
) -> std::io::Result<()> {
|
||||
) -> io::Result<()> {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
// Redraw every iteration; ratatui diffs against the previous frame so
|
||||
@@ -175,13 +174,10 @@ fn run(
|
||||
// Right arrow moves right; 'l' is the log panel.
|
||||
KeyCode::Right => game.try_move(Direction::East),
|
||||
// 'l' toggles the log panel; reset scroll to newest.
|
||||
KeyCode::Char('l') => {
|
||||
ui.log_open = !ui.log_open;
|
||||
ui.scroll = 0;
|
||||
}
|
||||
KeyCode::Char('l') => ui.log.toggle(),
|
||||
// PageUp/PageDown scroll the log when it's open.
|
||||
KeyCode::PageUp if ui.log_open => scroll_log(ui, game, -5),
|
||||
KeyCode::PageDown if ui.log_open => scroll_log(ui, game, 5),
|
||||
KeyCode::PageUp if ui.log.open => ui.log.scroll_by(-5, game.log.len()),
|
||||
KeyCode::PageDown if ui.log.open => ui.log.scroll_by(5, game.log.len()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -197,16 +193,16 @@ fn run(
|
||||
MouseEventKind::ScrollDown => ui.scroll_lines(1),
|
||||
_ => {}
|
||||
}
|
||||
} else if ui.log_open {
|
||||
} else if ui.log.open {
|
||||
match m.kind {
|
||||
MouseEventKind::ScrollUp => scroll_log(ui, game, -1),
|
||||
MouseEventKind::ScrollDown => scroll_log(ui, game, 1),
|
||||
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 - log_height`; dragging
|
||||
// The divider sits at row `total - height`; dragging
|
||||
// it up (smaller row) grows the panel. Keep both usable.
|
||||
let total = terminal.size()?.height;
|
||||
let target = total.saturating_sub(m.row);
|
||||
ui.log_height = target.clamp(3, total.saturating_sub(3).max(3));
|
||||
ui.log.resize(target, total);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -227,27 +223,19 @@ fn run(
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjusts the log scroll offset by `delta` lines, clamped so it can neither go
|
||||
/// above the newest message nor scroll past the oldest.
|
||||
fn scroll_log(ui: &mut Ui, game: &GameState, delta: i32) {
|
||||
let max = game.log.len().saturating_sub(1) as i32;
|
||||
ui.scroll = (ui.scroll as i32 + delta).clamp(0, max) as u16;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
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();
|
||||
let board = &*board;
|
||||
|
||||
if ui.log_open {
|
||||
// Split the screen: board on top, log panel of `log_height` at the bottom.
|
||||
if ui.log.open {
|
||||
// Split the screen: board on top, log panel of `height` at the bottom.
|
||||
let [board_area, log_area] =
|
||||
Layout::vertical([Constraint::Min(3), Constraint::Length(ui.log_height)])
|
||||
Layout::vertical([Constraint::Min(3), Constraint::Length(ui.log.height)])
|
||||
.spacing(Spacing::Overlap(1))
|
||||
.areas(frame.area());
|
||||
|
||||
@@ -258,24 +246,11 @@ fn draw(frame: &mut Frame, game: &GameState, ui: &mut Ui) {
|
||||
frame.render_widget(block, board_area);
|
||||
frame.render_widget(BoardWidget::new(board), inner);
|
||||
frame.render_widget(SpeechBubblesWidget::new(board, &game.speech_bubbles), inner);
|
||||
|
||||
// Newest message on top: reverse the log into ratatui lines.
|
||||
let log_block = Block::bordered()
|
||||
.title(Span::styled(
|
||||
" [l]og: ",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
))
|
||||
.merge_borders(MergeStrategy::Exact);
|
||||
let lines: Vec<Line> = game.log.iter().rev().map(logline_to_line).collect();
|
||||
let paragraph = Paragraph::new(lines)
|
||||
.block(log_block)
|
||||
.scroll((ui.scroll, 0))
|
||||
.wrap(Wrap { trim: false });
|
||||
frame.render_widget(paragraph, log_area);
|
||||
frame.render_stateful_widget(LogWidget::new(&game.log), log_area, &mut ui.log);
|
||||
} else {
|
||||
// Panel closed: board fills the screen, latest message in the border.
|
||||
let mut block = Block::bordered().title(format!(" {} ", board.name));
|
||||
block = block.title_bottom(log_preview(game).left_aligned());
|
||||
block = block.title_bottom(log_preview_line(&game.log).left_aligned());
|
||||
let inner = block.inner(frame.area());
|
||||
frame.render_widget(block, frame.area());
|
||||
frame.render_widget(BoardWidget::new(board), inner);
|
||||
@@ -289,17 +264,3 @@ fn draw(frame: &mut Frame, game: &GameState, ui: &mut Ui) {
|
||||
frame.render_stateful_widget(ScrollOverlayWidget::new(scroll), area, &mut ui.overlay);
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the `[l]og: <latest message>` preview shown in the board's bottom-left
|
||||
/// border when the log panel is closed.
|
||||
fn log_preview(game: &GameState) -> Line<'static> {
|
||||
let mut spans = vec![Span::styled(
|
||||
" [l]og: ",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
)];
|
||||
if let Some(latest) = game.log.last() {
|
||||
spans.extend(logline_to_line(latest).spans);
|
||||
}
|
||||
spans.push(Span::raw(" "));
|
||||
Line::from(spans)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user