Skip to content

Commit

Permalink
quit pop up
Browse files Browse the repository at this point in the history
  • Loading branch information
EspenTinius committed May 26, 2026
1 parent 999eb6b commit 2b79123
Show file tree
Hide file tree
Showing 8 changed files with 803 additions and 157 deletions.
21 changes: 21 additions & 0 deletions src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import edu.ntnu.idi.idatt2003.g40.mappe.view.creategame.CreateGameView;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameController;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit.QuitDialogController;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit.QuitDialogView;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.settings.InGameSettingsController;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.settings.InGameSettingsView;
import edu.ntnu.idi.idatt2003.g40.mappe.view.mainmenu.MainMenuController;
Expand Down Expand Up @@ -222,6 +224,25 @@ public <T> void handleEvent(final EventData<T> eventData) {
new InGameSettingsController(inGameSettingsView, eventManager, inGameView);
topBarController.setSettingsAction(inGameSettingsController::show);

// In-game quit confirmation dialog: replaces the previous immediate
// "auto-save and return to main menu" behaviour with a popup that
// lets the player choose Continue / Save / Save and quit to main
// menu / Save and quit game.
QuitDialogView quitDialogView = new QuitDialogView();
QuitDialogController quitDialogController =
new QuitDialogController(quitDialogView, eventManager, inGameView,
gameStateLoader, saveGameService);
quitDialogController.setOnExitApplication(() -> {
try {
javafx.application.Platform.exit();
} finally {
// Fallback in case there are non-daemon background threads
// keeping the JVM alive after JavaFX shuts down.
System.exit(0);
}
});
topBarController.setOnQuitRequested(quitDialogController::show);

topBarController.setOnQuitToMainMenu(() -> {
System.out.println("[auto-save] Quit triggered, attempting snapshot...");
SaveGame snapshot = gameStateLoader.snapshotActiveSave();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit;

/**
* User-triggered actions available in the in-game quit dialog overlay.
*
* <p>The quit dialog is shown when the player clicks the "Quit" button
* from the in-game dashboard, and lets them choose between continuing,
* saving in place, saving and returning to the main menu, or saving
* and exiting the application.</p>
* */
public enum QuitDialogActions {
/** Closes the dialog and returns the player to the game. */
CONTINUE,

/** Saves the current game state without leaving the in-game session. */
SAVE,

/** Saves the current game state and returns to the main menu. */
SAVE_AND_QUIT_TO_MAIN_MENU,

/** Saves the current game state and exits the application. */
SAVE_AND_QUIT_GAME;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit;

import edu.ntnu.idi.idatt2003.g40.mappe.model.SaveGame;
import edu.ntnu.idi.idatt2003.g40.mappe.service.GameStateLoader;
import edu.ntnu.idi.idatt2003.g40.mappe.service.SaveGameService;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewEnum;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView;

/**
* Controller for {@link QuitDialogView}.
*
* <p>Wires the four dialog buttons to the appropriate save / scene-change
* / application-exit actions:</p>
* <ul>
* <li>{@code CONTINUE} - hides the overlay; the player stays in the
* current game session.</li>
* <li>{@code SAVE} - snapshots and writes the active save to disk, but
* keeps the overlay open so the player sees the confirmation message.
* They can then choose to continue, quit, or exit.</li>
* <li>{@code SAVE_AND_QUIT_TO_MAIN_MENU} - snapshots, writes to disk,
* hides the overlay, and changes the scene back to the main menu.</li>
* <li>{@code SAVE_AND_QUIT_GAME} - snapshots, writes to disk, then
* invokes the configured exit-application runnable to close the JVM.</li>
* </ul>
*
* <p>The save logic mirrors the auto-save hook in {@code Main}: a missing
* active save (e.g. before any game has been loaded) is treated as a
* no-op rather than an error, since there's nothing meaningful to write.</p>
* */
public final class QuitDialogController
extends ViewController<QuitDialogView> {

/** The in-game view hosting this overlay. */
private final InGameView inGameView;

/** Builds {@link SaveGame} snapshots of the current player/exchange. */
private final GameStateLoader gameStateLoader;

/** Persists save games to disk. */
private final SaveGameService saveGameService;

/** Runnable invoked to close the application. */
private Runnable onExitApplication = () -> { };

/**
* Constructor.
*
* @param view the {@link QuitDialogView} this controller is
* attached to.
* @param eventManager the active {@link EventManager}, used to
* publish scene-change events.
* @param inGameView the in-game view that hosts this overlay.
* @param gameStateLoader the loader used to snapshot the active save.
* @param saveGameService the service used to persist saves to disk.
*
* @throws IllegalArgumentException if any constructor argument is null.
* */
public QuitDialogController(final QuitDialogView view,
final EventManager eventManager,
final InGameView inGameView,
final GameStateLoader gameStateLoader,
final SaveGameService saveGameService)
throws IllegalArgumentException {
this.inGameView = inGameView;
this.gameStateLoader = gameStateLoader;
this.saveGameService = saveGameService;
super(view, eventManager);
if (inGameView == null
|| gameStateLoader == null
|| saveGameService == null) {
throw new IllegalArgumentException(
"Invalid QuitDialogController arguments!");
}
}

/**
* Installs the runnable used to close the application when the player
* chooses "Save and quit game".
*
* <p>Typically this is wired to {@code Platform.exit()} (plus a
* {@code System.exit(0)} fallback) from {@code Main}, so the controller
* itself stays decoupled from the JavaFX runtime.</p>
*
* @param exitRunnable runnable invoked after the save completes; a null
* value resets the hook to a no-op.
* */
public void setOnExitApplication(final Runnable exitRunnable) {
this.onExitApplication =
(exitRunnable != null) ? exitRunnable : () -> { };
}

/** {@inheritDoc} */
@Override
protected void initInteractions() {
getViewElement().setOnAction(QuitDialogActions.CONTINUE, this::close);

getViewElement().setOnAction(QuitDialogActions.SAVE, () -> {
boolean ok = performSave();
if (ok) {
getViewElement().setStatus("Game saved.", false);
}
// Failures already wrote an error to the status label inside
// performSave(); nothing more to do here.
});

getViewElement().setOnAction(
QuitDialogActions.SAVE_AND_QUIT_TO_MAIN_MENU, () -> {
if (performSave()) {
close();
changeScene(ViewEnum.MAIN_MENU);
}
});

getViewElement().setOnAction(QuitDialogActions.SAVE_AND_QUIT_GAME, () -> {
if (performSave()) {
close();
onExitApplication.run();
}
});
}

/**
* Shows the quit dialog overlay on the host {@link InGameView}.
*
* <p>Clears any stale status message left over from a previous opening
* so the player doesn't see "Game saved." from a save they made hours
* ago.</p>
* */
public void show() {
getViewElement().setStatus(null, false);
inGameView.showSettingsOverlay(getViewElement().getRootPane());
}

/**
* Hides the overlay if it's currently visible.
* */
private void close() {
inGameView.hideSettingsOverlay();
}

/**
* Snapshots the active save and writes it to disk.
*
* <p>If no save is currently active (e.g. the player got into the
* in-game view through a path that doesn't load a save), this is
* treated as a successful no-op so the quit / exit flows can proceed.
* Any I/O failure is reported back to the player via the dialog's
* status label.</p>
*
* @return {@code true} when the operation can be considered successful
* (a real save was written, or no save was needed); {@code false}
* when an actual error occurred.
* */
private boolean performSave() {
SaveGame snapshot;
try {
snapshot = gameStateLoader.snapshotActiveSave();
} catch (Exception e) {
System.err.println("[quit-dialog] Snapshot failed: "
+ e.getMessage());
getViewElement().setStatus(
"Could not prepare save: " + e.getMessage(), true);
return false;
}

if (snapshot == null) {
System.out.println(
"[quit-dialog] No active save - nothing to write.");
// Nothing to persist, but the user's intent (continue past this
// step) is still valid - treat as success.
return true;
}

try {
saveGameService.saveGame(snapshot);
System.out.println("[quit-dialog] Wrote save '"
+ snapshot.getName() + "' to disk.");
return true;
} catch (Exception e) {
System.err.println("[quit-dialog] Save failed: " + e.getMessage());
getViewElement().setStatus(
"Save failed: " + e.getMessage(), true);
return false;
}
}
}
Loading

0 comments on commit 2b79123

Please sign in to comment.