//////////////////////////////////////// // Programming project code // UniBw M, 2022, 2023, 2024 // www.unibw.de/inf2 // (c) Mark Minas (mark.minas@unibw.de) //////////////////////////////////////// package pp.dialog; import com.jme3.app.SimpleApplication; import com.jme3.math.Vector3f; import com.jme3.scene.Spatial; import com.jme3.system.AppSettings; import com.simsilica.lemur.Panel; import com.simsilica.lemur.focus.FocusManagerState; import java.util.ArrayDeque; import java.util.Deque; import static java.lang.Math.max; import java.lang.System.Logger; /** * Manages dialog boxes within the application, handling their display, positioning, and focus. */ public class DialogManager { /** * The application instance. */ private final SimpleApplication app; private final static Logger LOGGER = System.getLogger(DialogManager.class.getName()); /** * A stack to keep track of the dialogs. */ private final Deque dialogStack = new ArrayDeque<>(); private final Deque popUpStack = new ArrayDeque<>(); /** * Constructs a DialogManager for the specified application. * * @param app the SimpleApplication instance */ public DialogManager(SimpleApplication app) { this.app = app; } /** * Checks if any dialog is currently displayed. * * @return true if any dialog is currently shown, false otherwise */ public boolean showsDialog() { return !dialogStack.isEmpty(); } /** * Retrieves the stack of dialogs. * * @return the dialog stack */ public Deque getDialogStack() { return dialogStack; } /** * Calculates the depth for the next dialog to be displayed. * * @return the next depth value */ public int nextDepth() { return dialogStack.isEmpty() ? 10 : dialogStack.peek().depth + 10; } /** * Positions the specified panel in the center of the screen, with a specified z coordinate. * * @param panel the panel to center * @param z the z coordinate */ public void centering(Panel panel, float z) { final Vector3f size = panel.getPreferredSize(); centering(panel, size.getX(), size.getY(), z); } /** * Positions the specified spatial in the center of the screen, with specified width, height, and z coordinate. * * @param spatial the spatial to center * @param width the width reserved for the spatial * @param height the height reserved for the spatial * @param z the z coordinate */ public void centering(Spatial spatial, float width, float height, float z) { final AppSettings settings = app.getContext().getSettings(); spatial.setLocalTranslation(max(0f, 0.5f * (settings.getWidth() - width)), max(0f, 0.5f * (settings.getHeight() + height)), z); } /** * Arranges for the specified spatial to receive the focus. * * @param spatial the spatial to focus */ public void setFocus(Spatial spatial) { final var focusManager = app.getStateManager().getState(FocusManagerState.class); if (focusManager != null) focusManager.setFocus(spatial); } /** * Opens the specified dialog and adds it to the dialog stack. * * @param dialog the dialog to open */ public void open(Dialog dialog) { if(dialog instanceof PopupDialog) { popUpStack.push((PopupDialog) dialog); processPopUps(); } else { showDialog(dialog); } } private void showDialog(Dialog dialog) { dialogStack.push(dialog); if(dialog instanceof PopupDialog) { ((PopupDialog)dialog).show(); } dialog.update(); app.getGuiNode().attachChild(dialog); } private void processPopUps() { if (popUpStack.isEmpty()) { return; // Nothing to process } // Check if a popup dialog is already on top if (dialogStack.peek() instanceof PopupDialog) { LOGGER.log(Logger.Level.DEBUG, "Popup dialog already on top"); return; // Already a popup dialog on top } else { // Pop the next popup from the stack and validate before showing PopupDialog popUp = popUpStack.pop(); showDialog( (Dialog) popUp); } } /** * Checks if the specified dialog is the topmost dialog in the dialog stack. * * @param dialog a dialog. * @return true if the dialog is the top dialog, false otherwise. */ boolean isTop(Dialog dialog) { return !dialogStack.isEmpty() && dialogStack.peek() == dialog; } /** * Closes the specified dialog, removing it from the dialog stack. * * @param dialog the dialog to close * @throws IllegalArgumentException if the specified dialog is not the top dialog */ public void close(Dialog dialog) { if (!isTop(dialog)) throw new IllegalArgumentException(dialog + " is not the top dialog"); dialogStack.pop(); if (!dialogStack.isEmpty()) dialogStack.peek().update(); app.getGuiNode().detachChild(dialog); processPopUps(); } /** * Calls the escape action of the top dialog, if a dialog is shown. */ public void escape() { if (dialogStack.isEmpty()) return; dialogStack.peek().escape(); } public void update(float delta) { for (Dialog dialog : dialogStack) dialog.update(delta); } }