playtest mode
This commit is contained in:
+46
-2
@@ -195,6 +195,9 @@ fn run(terminal: &mut ratatui::DefaultTerminal, mode: &mut Mode, ui: &mut Ui) ->
|
||||
// Apply any Play↔Edit switch a menu action requested this frame.
|
||||
apply_pending_mode(mode, ui);
|
||||
|
||||
// Enter/leave a playtest if the editor or a playtest Esc requested it.
|
||||
apply_playtest_transition(mode, ui);
|
||||
|
||||
// A menu action may have set should_quit; exit once any close animation finishes.
|
||||
if ui.should_quit && ui.active_animation.is_none() {
|
||||
return Ok(());
|
||||
@@ -229,6 +232,40 @@ fn apply_pending_mode(mode: &mut Mode, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a pending **playtest** enter or exit, swapping the top-level [`Mode`].
|
||||
///
|
||||
/// Entering: the editor parked a built [`GameState`] in
|
||||
/// [`EditorState::pending_playtest`]; swap to [`Mode::Play`] and stash the editor in
|
||||
/// [`Ui::suspended_editor`] so `Esc` can restore it. Taking the option first drops the
|
||||
/// `&mut EditorState` borrow before [`std::mem::replace`], whose return value hands us
|
||||
/// the old `Mode::Edit` (so the editor is preserved with no placeholder variant).
|
||||
///
|
||||
/// Exiting (`Esc` during a playtest set [`Ui::exit_playtest`]): drop the playtest game
|
||||
/// and restore the suspended editor verbatim. The deep-copied playtest world is simply
|
||||
/// dropped, so the editor's own boards were never touched.
|
||||
fn apply_playtest_transition(mode: &mut Mode, ui: &mut Ui) {
|
||||
// Enter: take the parked game (this ends the editor borrow before the swap).
|
||||
let new_game = if let Mode::Edit(ed) = mode {
|
||||
ed.pending_playtest.take()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(game) = new_game {
|
||||
let old = std::mem::replace(mode, Mode::Play(game));
|
||||
if let Mode::Edit(editor) = old {
|
||||
ui.suspended_editor = Some(editor);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit: restore the parked editor, dropping the playtest game.
|
||||
if ui.exit_playtest {
|
||||
ui.exit_playtest = false;
|
||||
if let Some(editor) = ui.suspended_editor.take() {
|
||||
*mode = Mode::Edit(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a single frame for the active [`Mode`], then the overlay layer on top.
|
||||
///
|
||||
/// The body of the frame is either the play view ([`draw_play`]) or the editor
|
||||
@@ -286,6 +323,13 @@ fn draw_play(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();
|
||||
|
||||
// A playtest (launched from the editor) marks its title so it's distinct from play.
|
||||
let title = if ui.suspended_editor.is_some() {
|
||||
format!(" {} (playtest) ", board.name)
|
||||
} else {
|
||||
format!(" {} ", board.name)
|
||||
};
|
||||
|
||||
if ui.log.open {
|
||||
// Split the screen: board on top, log panel of `height` at the bottom.
|
||||
let [board_area, log_area] =
|
||||
@@ -294,7 +338,7 @@ fn draw_play(frame: &mut Frame, game: &GameState, ui: &mut Ui) {
|
||||
.areas(frame.area());
|
||||
|
||||
let block = Block::bordered()
|
||||
.title(format!(" {} ", board.name))
|
||||
.title(title)
|
||||
.merge_borders(MergeStrategy::Exact);
|
||||
let inner = block.inner(board_area);
|
||||
frame.render_widget(block, board_area);
|
||||
@@ -302,7 +346,7 @@ fn draw_play(frame: &mut Frame, game: &GameState, ui: &mut Ui) {
|
||||
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));
|
||||
let mut block = Block::bordered().title(title);
|
||||
block = block.title_bottom(log_preview_line(&game.log).left_aligned());
|
||||
let inner = block.inner(frame.area());
|
||||
frame.render_widget(block, frame.area());
|
||||
|
||||
Reference in New Issue
Block a user