//////////////////////////////////////// // 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.scene.Spatial; import com.simsilica.lemur.Button; import com.simsilica.lemur.Container; import com.simsilica.lemur.Label; import com.simsilica.lemur.component.BorderLayout; import com.simsilica.lemur.style.ElementId; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import static com.simsilica.lemur.component.BorderLayout.Position.East; import static com.simsilica.lemur.component.BorderLayout.Position.West; /** * A builder class for creating and customizing dialog boxes of type {@link SimpleDialog} or its subclasses. * This builder pattern facilitates the construction of dialog boxes with various configurations, * such as titles, text content, buttons, and additional custom behaviors. * * @param the type of dialog to be built, typically extending {@link SimpleDialog} */ public class DialogBuilder { /** * Creates a {@link DialogBuilder} for a simple dialog with default settings. * * @param manager the dialog manager responsible for managing the dialog's lifecycle * @return a {@link DialogBuilder} instance for creating simple dialogs */ public static DialogBuilder simple(DialogManager manager) { return new DialogBuilder<>(manager, SimpleDialog::new); } protected final DialogManager manager; private final Function dialogFactory; private final List> extensionList = new ArrayList<>(); private String title; private String text; private String okLabel; private String noLabel; private Consumer okAction = d -> {}; private Consumer noAction = d -> {}; private boolean okClose = true; private boolean noClose = true; private Function focus; /** * Constructs a dialog builder with the specified dialog manager and dialog factory. * * @param manager the dialog manager responsible for managing the dialog's lifecycle * @param dialogFactory a factory function to create instances of the dialog */ public DialogBuilder(DialogManager manager, Function dialogFactory) { this.manager = manager; this.dialogFactory = dialogFactory; } /** * Applies all registered extensions to the given dialog. * Extensions allow for additional customizations beyond the standard configurations. * * @param dialog the dialog object to which the extensions will be applied * @see #setExtension(java.util.function.Consumer) */ protected void extendDialog(D dialog) { for (Consumer extension : extensionList) extension.accept(dialog); } /** * Builds and returns the dialog with the specified configurations. * This method creates a new dialog object and applies all the configured settings. * * @return the fully configured dialog object */ public D build() { return build(dialogFactory.apply(manager)); } /** * Builds the dialog by configuring an existing dialog object with the specified settings. * This method allows for further customization of a pre-existing dialog object. * * @param dialog the dialog object to configure * @return the configured dialog object for chaining */ public D build(D dialog) { configureTitle(dialog); configureText(dialog); extendDialog(dialog); configureButtons(dialog); configureFocus(dialog); return dialog; } /** * Configures the title of the dialog if a title has been set. * * @param dialog the dialog to which the title will be added */ private void configureTitle(D dialog) { if (title != null) dialog.addChild(new Label(title, new ElementId("header"))); // NON-NLS } /** * Configures the main text content of the dialog if text has been set. * * @param dialog the dialog to which the text content will be added */ private void configureText(D dialog) { if (text != null) dialog.addChild(new Label(text)); } /** * Configures the OK and NO buttons for the dialog if labels for them have been set. * * @param dialog the dialog to which the buttons will be added */ private void configureButtons(D dialog) { if (okLabel != null || noLabel != null) { final Container buttons = dialog.addChild(new Container(new BorderLayout())); if (okLabel != null) { final Button okButton = buttons.addChild(new Button(okLabel), West); dialog.setOkButton(okButton); configureButton(okButton, okAction, okClose, dialog); } if (noLabel != null) { final Button noButton = buttons.addChild(new Button(noLabel), East); configureButton(noButton, noAction, noClose, dialog); } } } /** * Configures a button with its action and whether the dialog should close after the action is performed. * * @param button the button to configure * @param action the action to perform when the button is clicked * @param close whether the dialog should close after the action is performed * @param dialog the dialog that contains the button */ private void configureButton(Button button, Consumer action, boolean close, D dialog) { button.addClickCommands(s -> { if (dialog.isTopDialog()) { action.accept(dialog); if (close) { dialog.close(); } } }); } /** * Configures the initial focus for the dialog when it is displayed. * The focus will be set to either a specified component or the OK button if available. * * @param dialog the dialog to configure focus for */ private void configureFocus(D dialog) { final Spatial focusComponent = focus == null ? null : focus.apply(dialog); if (focusComponent != null || dialog.getOkButton() != null) manager.setFocus(focusComponent != null ? focusComponent : dialog.getOkButton()); } /** * Sets the title of the dialog. * * @param title the title text to be displayed at the top of the dialog * @return this builder instance for chaining */ public DialogBuilder setTitle(String title) { this.title = title; return this; } /** * Sets the main text content of the dialog. * * @param text the main content text to be displayed in the dialog * @return this builder instance for chaining */ public DialogBuilder setText(String text) { this.text = text; return this; } /** * Sets the label for the OK button. * * @param okLabel the text label to display on the OK button * @return this builder instance for chaining */ public DialogBuilder setOkButton(String okLabel) { this.okLabel = okLabel; return this; } /** * Sets the label and action for the OK button. * When the OK button is clicked, the specified action will be executed. * * @param okLabel the text label to display on the OK button * @param okAction the action to perform when the OK button is clicked * @return this builder instance for chaining */ public DialogBuilder setOkButton(String okLabel, Consumer okAction) { this.okAction = okAction; return setOkButton(okLabel); } /** * Sets the label and action for the OK button. * When the OK button is clicked, the specified runnable action will be executed. * * @param okLabel the text label to display on the OK button * @param okAction the runnable action to perform when the OK button is clicked * @return this builder instance for chaining */ public DialogBuilder setOkButton(String okLabel, Runnable okAction) { this.okAction = d -> okAction.run(); return setOkButton(okLabel); } /** * Sets the label for the NO button. * * @param noLabel the text label to display on the NO button * @return this builder instance for chaining */ public DialogBuilder setNoButton(String noLabel) { this.noLabel = noLabel; return this; } /** * Sets the label and action for the NO button. * When the NO button is clicked, the specified action will be executed. * * @param noLabel the text label to display on the NO button * @param noAction the action to perform when the NO button is clicked * @return this builder instance for chaining */ public DialogBuilder setNoButton(String noLabel, Consumer noAction) { this.noAction = noAction; return setNoButton(noLabel); } /** * Sets the label and action for the NO button. * When the NO button is clicked, the specified runnable action will be executed. * * @param noLabel the text label to display on the NO button * @param noAction the runnable action to perform when the NO button is clicked * @return this builder instance for chaining */ public DialogBuilder setNoButton(String noLabel, Runnable noAction) { this.noAction = d -> noAction.run(); return setNoButton(noLabel); } /** * Sets whether the dialog should automatically close when the OK button is clicked. * * @param okClose true to close the dialog when the OK button is clicked, false otherwise * @return this builder instance for chaining */ public DialogBuilder setOkClose(boolean okClose) { this.okClose = okClose; return this; } /** * Sets whether the dialog should automatically close when the NO button is clicked. * * @param noClose true to close the dialog when the NO button is clicked, false otherwise * @return this builder instance for chaining */ public DialogBuilder setNoClose(boolean noClose) { this.noClose = noClose; return this; } /** * Sets the component that should initially receive focus when the dialog is displayed. * If a focus function is not provided, the focus defaults to the OK button. * * @param focus a function specifying which component of the dialog should receive focus * @return this builder instance for chaining */ public DialogBuilder setFocus(Function focus) { this.focus = focus; return this; } /** * Adds an extension to the dialog. * Extensions allow for additional customizations and behaviors beyond the basic configuration. * * @param extender a consumer that applies the extension to the dialog * @return this builder instance for chaining */ public DialogBuilder setExtension(Consumer extender) { extensionList.add(extender); return this; } }