mirror of
https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-02.git
synced 2025-08-07 22:23:30 +02:00
added contents
This commit is contained in:
102
Projekte/jme-common/src/main/java/pp/dialog/Dialog.java
Normal file
102
Projekte/jme-common/src/main/java/pp/dialog/Dialog.java
Normal file
@@ -0,0 +1,102 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.dialog;
|
||||
|
||||
import com.simsilica.lemur.Container;
|
||||
|
||||
/**
|
||||
* Represents a dialog within a dialog manager system.
|
||||
* Extends the Container class from the Lemur GUI library.
|
||||
*/
|
||||
public class Dialog extends Container {
|
||||
/**
|
||||
* The depth of the dialog within the dialog stack.
|
||||
* Dialogs with lower depth values are considered to be "on top" of dialogs with higher values.
|
||||
*/
|
||||
protected final int depth;
|
||||
|
||||
/**
|
||||
* The manager responsible for handling this dialog.
|
||||
*/
|
||||
protected final DialogManager manager;
|
||||
|
||||
/**
|
||||
* Constructs a new Dialog with a depth automatically assigned by the DialogManager.
|
||||
*
|
||||
* @param manager The DialogManager that manages this dialog.
|
||||
*/
|
||||
public Dialog(DialogManager manager) {
|
||||
this(manager, manager.nextDepth());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Dialog with the specified depth.
|
||||
*
|
||||
* @param manager The DialogManager that manages this dialog.
|
||||
* @param depth The depth of this dialog within the dialog stack.
|
||||
* @throws IllegalArgumentException if the specified depth is invalid (i.e., it is not greater than the depth of the top dialog in the manager's stack).
|
||||
*/
|
||||
public Dialog(DialogManager manager, int depth) {
|
||||
this.manager = manager;
|
||||
this.depth = depth;
|
||||
// Ensure the dialog depth is greater than the depth of the current top dialog in the stack
|
||||
if (!manager.getDialogStack().isEmpty() && manager.getDialogStack().getLast().depth >= depth)
|
||||
throw new IllegalArgumentException("Invalid dialog depth " + depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this dialog is the topmost dialog in the dialog stack.
|
||||
*
|
||||
* @return true if this dialog is the topmost dialog, false otherwise.
|
||||
*/
|
||||
public boolean isTopDialog() {
|
||||
return manager.isTop(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the specified runnable if this dialog is the topmost dialog in the dialog stack.
|
||||
*
|
||||
* @param runnable the runnable.
|
||||
* @see Dialog#isTopDialog()
|
||||
*/
|
||||
public void ifTopDialog(Runnable runnable) {
|
||||
if (isTopDialog()) runnable.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens this dialog, centers it, and notifies the DialogManager to manage it.
|
||||
*/
|
||||
public void open() {
|
||||
manager.centering(this, depth);
|
||||
manager.open(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this dialog and notifies the DialogManager to stop managing it.
|
||||
*/
|
||||
public void close() {
|
||||
manager.close(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called whenever the {@linkplain pp.dialog.DialogManager} would
|
||||
* like to update this dialog.
|
||||
*/
|
||||
public void update() { /* empty */ }
|
||||
|
||||
/**
|
||||
* This method is called by {@linkplain DialogManager#update(float)} for periodically
|
||||
* updating this dialog. The default implementation does nothing.
|
||||
*/
|
||||
public void update(float delta) { /* empty */ }
|
||||
|
||||
/**
|
||||
* This method calls the escape action if this dialog is the top dialog.
|
||||
*/
|
||||
public void escape() { /* empty */ }
|
||||
}
|
319
Projekte/jme-common/src/main/java/pp/dialog/DialogBuilder.java
Normal file
319
Projekte/jme-common/src/main/java/pp/dialog/DialogBuilder.java
Normal file
@@ -0,0 +1,319 @@
|
||||
////////////////////////////////////////
|
||||
// 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 <D> the type of dialog to be built, typically extending {@link SimpleDialog}
|
||||
*/
|
||||
public class DialogBuilder<D extends SimpleDialog> {
|
||||
|
||||
/**
|
||||
* 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<SimpleDialog> simple(DialogManager manager) {
|
||||
return new DialogBuilder<>(manager, SimpleDialog::new);
|
||||
}
|
||||
|
||||
protected final DialogManager manager;
|
||||
private final Function<DialogManager, D> dialogFactory;
|
||||
private final List<Consumer<D>> extensionList = new ArrayList<>();
|
||||
private String title;
|
||||
private String text;
|
||||
private String okLabel;
|
||||
private String noLabel;
|
||||
private Consumer<D> okAction = d -> {};
|
||||
private Consumer<D> noAction = d -> {};
|
||||
private boolean okClose = true;
|
||||
private boolean noClose = true;
|
||||
private Function<D, Spatial> 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<DialogManager, D> 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<D> 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<D> 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<D> 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<D> 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<D> 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<D> setOkButton(String okLabel, Consumer<D> 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<D> 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<D> 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<D> setNoButton(String noLabel, Consumer<D> 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<D> 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<D> 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<D> 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<D> setFocus(Function<D, Spatial> 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<D> setExtension(Consumer<D> extender) {
|
||||
extensionList.add(extender);
|
||||
return this;
|
||||
}
|
||||
}
|
157
Projekte/jme-common/src/main/java/pp/dialog/DialogManager.java
Normal file
157
Projekte/jme-common/src/main/java/pp/dialog/DialogManager.java
Normal file
@@ -0,0 +1,157 @@
|
||||
////////////////////////////////////////
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Manages dialog boxes within the application, handling their display, positioning, and focus.
|
||||
*/
|
||||
public class DialogManager {
|
||||
/**
|
||||
* The application instance.
|
||||
*/
|
||||
private final SimpleApplication app;
|
||||
|
||||
/**
|
||||
* A stack to keep track of the dialogs.
|
||||
*/
|
||||
private final Deque<Dialog> dialogStack = 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<Dialog> 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
|
||||
*/
|
||||
void open(Dialog dialog) {
|
||||
dialogStack.push(dialog);
|
||||
dialog.update();
|
||||
app.getGuiNode().attachChild(dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.dialog;
|
||||
|
||||
import com.simsilica.lemur.Button;
|
||||
|
||||
/**
|
||||
* Represents a simple dialog with OK and Cancel buttons.
|
||||
* It extends the Dialog class and provides methods to get and set these buttons.
|
||||
*/
|
||||
public class SimpleDialog extends Dialog {
|
||||
private Button okButton;
|
||||
private Button cancelButton;
|
||||
|
||||
/**
|
||||
* Constructs a SimpleDialog with the specified DialogManager.
|
||||
*
|
||||
* @param manager the DialogManager to manage this dialog
|
||||
*/
|
||||
public SimpleDialog(DialogManager manager) {
|
||||
super(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OK button of this dialog.
|
||||
*
|
||||
* @return the OK button
|
||||
*/
|
||||
public Button getOkButton() {
|
||||
return okButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the OK button of this dialog.
|
||||
*
|
||||
* @param okButton the OK button to set
|
||||
*/
|
||||
void setOkButton(Button okButton) {
|
||||
this.okButton = okButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Cancel button of this dialog.
|
||||
*
|
||||
* @return the Cancel button
|
||||
*/
|
||||
public Button getCancelButton() {
|
||||
return cancelButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Cancel button of this dialog.
|
||||
*
|
||||
* @param cancelButton the Cancel button to set
|
||||
*/
|
||||
void setCancelButton(Button cancelButton) {
|
||||
this.cancelButton = cancelButton;
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
////////////////////////////////////////
|
||||
// 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.Application;
|
||||
import com.jme3.app.state.AppState;
|
||||
import com.simsilica.lemur.DefaultCheckboxModel;
|
||||
|
||||
/**
|
||||
* A checkbox model for enabling and disabling app states.
|
||||
* This model links a checkbox with an AppState, so that checking
|
||||
* or unchecking the box enables or disables the state, respectively.
|
||||
*/
|
||||
public class StateCheckboxModel extends DefaultCheckboxModel {
|
||||
private final AppState state;
|
||||
|
||||
/**
|
||||
* Constructs a StateCheckboxModel for the specified app state class.
|
||||
*
|
||||
* @param app the application containing the state manager
|
||||
* @param stateClass the class of the app state to be controlled
|
||||
*/
|
||||
public StateCheckboxModel(Application app, Class<? extends AppState> stateClass) {
|
||||
this(app.getStateManager().getState(stateClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a StateCheckboxModel for the specified app state.
|
||||
*
|
||||
* @param state the app state to be controlled
|
||||
*/
|
||||
public StateCheckboxModel(AppState state) {
|
||||
this.state = state;
|
||||
setChecked(state.isEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the checked state of the checkbox and enables or disables
|
||||
* the associated app state accordingly.
|
||||
*
|
||||
* @param checked true to check the box and enable the state, false to uncheck the box and disable the state
|
||||
*/
|
||||
@Override
|
||||
public void setChecked(boolean checked) {
|
||||
super.setChecked(checked);
|
||||
state.setEnabled(checked);
|
||||
}
|
||||
}
|
132
Projekte/jme-common/src/main/java/pp/dialog/TextInputDialog.java
Normal file
132
Projekte/jme-common/src/main/java/pp/dialog/TextInputDialog.java
Normal file
@@ -0,0 +1,132 @@
|
||||
////////////////////////////////////////
|
||||
// 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.input.KeyInput;
|
||||
import com.simsilica.lemur.Container;
|
||||
import com.simsilica.lemur.Label;
|
||||
import com.simsilica.lemur.TextField;
|
||||
import com.simsilica.lemur.component.SpringGridLayout;
|
||||
import com.simsilica.lemur.event.KeyAction;
|
||||
|
||||
/**
|
||||
* A dialog class that asks for text input. Usually, this is a single-line
|
||||
* text input.
|
||||
*
|
||||
* @see #getInput()
|
||||
*/
|
||||
public class TextInputDialog extends SimpleDialog {
|
||||
// TextField for user input
|
||||
private final TextField input = new TextField("");
|
||||
|
||||
/**
|
||||
* Constructs a TextInputDialog associated with the specified dialog manager.
|
||||
*
|
||||
* @param manager the dialog manager to associate the dialog with
|
||||
*/
|
||||
private TextInputDialog(DialogManager manager) {
|
||||
super(manager);
|
||||
input.setSingleLine(true); // Set the input field to be single-line
|
||||
input.setPreferredWidth(500f); // Set preferred width of the input field
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input field.
|
||||
*
|
||||
* @return the input field
|
||||
*/
|
||||
public TextField getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the specified key action to trigger a click on the OK button.
|
||||
*
|
||||
* @param action the key action to map
|
||||
*/
|
||||
private void clickOkOn(KeyAction action) {
|
||||
input.getActionMap().put(action, (c, k) -> {
|
||||
if (getOkButton() != null)
|
||||
getOkButton().click();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for TextInputDialog.
|
||||
*
|
||||
* @param manager the dialog manager to associate the dialog with
|
||||
* @return a TextInputDialogBuilder instance
|
||||
*/
|
||||
public static TextInputDialogBuilder builder(DialogManager manager) {
|
||||
return new TextInputDialogBuilder(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder class for creating TextInputDialog instances.
|
||||
*/
|
||||
public static class TextInputDialogBuilder extends DialogBuilder<TextInputDialog> {
|
||||
private String label;
|
||||
private boolean returnHitsOK = true;
|
||||
|
||||
/**
|
||||
* Constructs a TextInputDialogBuilder with the specified dialog manager.
|
||||
*
|
||||
* @param manager the dialog manager to associate the dialog with
|
||||
*/
|
||||
private TextInputDialogBuilder(DialogManager manager) {
|
||||
super(manager, TextInputDialog::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the dialog with additional components like a label and input field.
|
||||
*
|
||||
* @param dialog the dialog to be extended
|
||||
*/
|
||||
@Override
|
||||
protected void extendDialog(TextInputDialog dialog) {
|
||||
final TextField textField = dialog.getInput();
|
||||
if (label == null) {
|
||||
dialog.addChild(textField);
|
||||
}
|
||||
else {
|
||||
final Container c = dialog.addChild(new Container(new SpringGridLayout()));
|
||||
c.addChild(new Label(label));
|
||||
c.addChild(textField, 1);
|
||||
}
|
||||
if (returnHitsOK) {
|
||||
// move the caret right so that it becomes visible at the end of a long text
|
||||
textField.getDocumentModel().right();
|
||||
// Hitting a return key is like pushing the ok button
|
||||
dialog.clickOkOn(new KeyAction(KeyInput.KEY_RETURN));
|
||||
dialog.clickOkOn(new KeyAction(KeyInput.KEY_NUMPADENTER));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label for the input field.
|
||||
*
|
||||
* @param label the label text
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public TextInputDialogBuilder setLabel(String label) {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether hitting the return key triggers the OK button.
|
||||
*
|
||||
* @param returnHitsOK true to trigger OK button on return key, false otherwise
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public TextInputDialogBuilder setReturnHitsOK(boolean returnHitsOK) {
|
||||
this.returnHitsOK = returnHitsOK;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
229
Projekte/jme-common/src/main/java/pp/graphics/Draw.java
Normal file
229
Projekte/jme-common/src/main/java/pp/graphics/Draw.java
Normal file
@@ -0,0 +1,229 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.graphics;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.material.RenderState.BlendMode;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import pp.util.Position;
|
||||
import pp.util.SegmentLike;
|
||||
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static pp.util.FloatMath.FLT_EPSILON;
|
||||
import static pp.util.FloatMath.TWO_PI;
|
||||
import static pp.util.FloatMath.cos;
|
||||
import static pp.util.FloatMath.sin;
|
||||
import static pp.util.FloatMath.sqr;
|
||||
import static pp.util.FloatMath.sqrt;
|
||||
|
||||
/**
|
||||
* Class for creating graphical primitives.
|
||||
*/
|
||||
public class Draw {
|
||||
private static final Logger LOGGER = System.getLogger(Draw.class.getName());
|
||||
private static final int NUM = 10;
|
||||
private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS
|
||||
private static final String COLOR = "Color"; //NON-NLS
|
||||
private final AssetManager am;
|
||||
private final Map<ColorRGBA, Geometry> lineMap = new HashMap<>();
|
||||
private final Map<ColorRGBA, Geometry> rectangleMap = new HashMap<>();
|
||||
private final Map<ColorRGBA, Geometry> circleMap = new HashMap<>();
|
||||
private Mesh lineMesh;
|
||||
private Mesh circleMesh;
|
||||
|
||||
/**
|
||||
* Creates an in stance of the Draw class with the specified
|
||||
* asset manager.
|
||||
*
|
||||
* @param assetManager the specified asset manager
|
||||
*/
|
||||
public Draw(AssetManager assetManager) {
|
||||
am = assetManager;
|
||||
}
|
||||
|
||||
private Geometry makeLine(ColorRGBA color) {
|
||||
LOGGER.log(Level.DEBUG, "create line with color {0}", color); //NON-NLS
|
||||
if (lineMesh == null) {
|
||||
lineMesh = new Mesh();
|
||||
lineMesh.setMode(Mesh.Mode.Lines);
|
||||
lineMesh.setBuffer(VertexBuffer.Type.Position, 3, new float[]{0, 0, 0, 0, 1, 0});
|
||||
lineMesh.setBuffer(VertexBuffer.Type.Index, 2, new short[]{0, 1});
|
||||
}
|
||||
final Geometry lineGeom = new Geometry("lineMesh", lineMesh.clone());
|
||||
Material matWireframe = new Material(am, UNSHADED); //NON-NLS
|
||||
matWireframe.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
matWireframe.setColor(COLOR, color); //NON-NLS
|
||||
lineGeom.setMaterial(matWireframe);
|
||||
|
||||
return lineGeom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a line for the specified segment.
|
||||
*
|
||||
* @param segment the segment with its start and end point
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
*/
|
||||
public Geometry makeLine(SegmentLike segment, float z, ColorRGBA color) {
|
||||
return makeLine(segment.from(), segment.to(), z, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a straight line between the specified points with the specified color.
|
||||
*
|
||||
* @param p1 start point
|
||||
* @param p2 end point
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
*/
|
||||
public Geometry makeLine(Position p1, Position p2, float z, ColorRGBA color) {
|
||||
return makeLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), z, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a straight line between the specified points with the specified color.
|
||||
*
|
||||
* @param x1 x-coordinate of the start point
|
||||
* @param y1 y-coordinate of the start point
|
||||
* @param x2 x-coordinate of the end point
|
||||
* @param y2 y-coordinate of the end point
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
*/
|
||||
public Geometry makeLine(float x1, float y1, float x2, float y2, float z, ColorRGBA color) {
|
||||
final Geometry line = lineMap.computeIfAbsent(color, this::makeLine).clone();
|
||||
line.lookAt(Vector3f.UNIT_Z, new Vector3f(x2 - x1, y2 - y1, 0));
|
||||
line.setLocalScale(sqrt(sqr(x2 - x1) + sqr(y2 - y1)));
|
||||
line.setLocalTranslation(x1, y1, z);
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a straight line between the specified points with the specified width and color.
|
||||
*
|
||||
* @param p1 start point
|
||||
* @param p2 end point
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
* @param width width of the line
|
||||
*/
|
||||
public Geometry makeFatLine(Position p1, Position p2, float z, ColorRGBA color, float width) {
|
||||
return makeFatLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), z, color, width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a straight line between the specified points with the specified width and color.
|
||||
*
|
||||
* @param x1 x-coordinate of the start point
|
||||
* @param y1 y-coordinate of the start point
|
||||
* @param x2 x-coordinate of the end point
|
||||
* @param y2 y-coordinate of the end point
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
* @param width width of the line
|
||||
*/
|
||||
public Geometry makeFatLine(float x1, float y1, float x2, float y2, float z, ColorRGBA color, float width) {
|
||||
final Geometry line = rectangleMap.computeIfAbsent(color, this::makeRectangle).clone();
|
||||
final float dx = x2 - x1;
|
||||
final float dy = y2 - y1;
|
||||
final float len = sqrt(dx * dx + dy * dy);
|
||||
line.setLocalScale(width, len + width, 1f);
|
||||
if (len <= FLT_EPSILON)
|
||||
line.setLocalTranslation(x1 - 0.5f * width, y1 - 0.5f * width, z);
|
||||
else {
|
||||
final float f = 0.5f * width / len;
|
||||
line.setLocalTranslation(x1 - f * (dy + dx), y1 - f * (dy - dx), z);
|
||||
line.getLocalRotation().lookAt(Vector3f.UNIT_Z, new Vector3f(dx, dy, 0f));
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
private Geometry makeRectangle(ColorRGBA color) {
|
||||
final Mesh quad = new Quad(1f, 1f);
|
||||
final Geometry rectangle = new Geometry("quad", quad); //NON-NLS
|
||||
Material mat = new Material(am, UNSHADED); //NON-NLS
|
||||
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
mat.setColor(COLOR, color); //NON-NLS
|
||||
rectangle.setMaterial(mat);
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an axis-parallel rectangle with the specified color.
|
||||
*
|
||||
* @param x x-coordinate of the bottom-left corner
|
||||
* @param y y-coordinate of the bottom-left corner
|
||||
* @param w width of the rectangle
|
||||
* @param h height of the rectangle
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
*/
|
||||
public Geometry makeRectangle(float x, float y, float z, float w, float h, ColorRGBA color) {
|
||||
final Geometry rectangle = rectangleMap.computeIfAbsent(color, this::makeRectangle).clone();
|
||||
rectangle.setLocalScale(w, h, 1f);
|
||||
rectangle.setLocalTranslation(x, y, z);
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
private Geometry makeCircle(ColorRGBA color) {
|
||||
if (circleMesh == null) {
|
||||
circleMesh = new Mesh();
|
||||
circleMesh.setMode(Mesh.Mode.LineLoop);
|
||||
final float[] pointBuffer = new float[3 * NUM];
|
||||
final short[] indexBuffer = new short[NUM];
|
||||
int j = 0;
|
||||
for (short i = 0; i < NUM; i++) {
|
||||
final float a = TWO_PI / NUM * i;
|
||||
pointBuffer[j++] = 0.5f * cos(a);
|
||||
pointBuffer[j++] = 0.5f * sin(a);
|
||||
pointBuffer[j++] = 0f;
|
||||
indexBuffer[i] = i;
|
||||
}
|
||||
circleMesh.setBuffer(VertexBuffer.Type.Position, 3, pointBuffer);
|
||||
circleMesh.setBuffer(VertexBuffer.Type.Index, 2, indexBuffer);
|
||||
}
|
||||
|
||||
final Geometry circle = new Geometry("circleMesh", circleMesh.clone());
|
||||
Material matWireframe = new Material(am, UNSHADED); //NON-NLS
|
||||
matWireframe.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
matWireframe.setColor(COLOR, color); //NON-NLS
|
||||
circle.setMaterial(matWireframe);
|
||||
|
||||
return circle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ellipse with the specified color.
|
||||
*
|
||||
* @param x x-coordinate of the center point
|
||||
* @param y y-coordinate of the center point
|
||||
* @param w width of the ellipse
|
||||
* @param h height of the ellipse
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
*/
|
||||
public Geometry makeEllipse(float x, float y, float z, float w, float h, ColorRGBA color) {
|
||||
final Geometry ellipse = circleMap.computeIfAbsent(color, this::makeCircle).clone();
|
||||
ellipse.setLocalScale(w, h, 1f);
|
||||
ellipse.setLocalTranslation(x, y, z);
|
||||
return ellipse;
|
||||
}
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.view;
|
||||
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract base class for keeping the scene graph (=view) in sync with the model.
|
||||
*/
|
||||
public abstract class ModelViewSynchronizer<I> {
|
||||
private static final Logger LOGGER = System.getLogger(ModelViewSynchronizer.class.getName());
|
||||
private final Node itemNode = new Node("items"); //NON-NLS
|
||||
private final Map<I, Spatial> itemMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Saves the game state and the node.
|
||||
*
|
||||
* @param root particular node
|
||||
*/
|
||||
protected ModelViewSynchronizer(Node root) {
|
||||
root.attachChild(itemNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the spatial representing the specified item,
|
||||
* or null if the item has no view counterpart.
|
||||
*/
|
||||
public Spatial getSpatial(I item) {
|
||||
return itemMap.get(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* removes spatial from map
|
||||
*
|
||||
* @param item spatial that should be removed
|
||||
*/
|
||||
public void delete(I item) {
|
||||
final Spatial spatial = itemMap.remove(item);
|
||||
if (spatial != null) {
|
||||
spatial.removeFromParent();
|
||||
LOGGER.log(Level.DEBUG, "removed spatial for {0} in {1}", item, this); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add spatial to map
|
||||
*
|
||||
* @param item spatial that schuld be added
|
||||
*/
|
||||
public void add(I item) {
|
||||
if (itemMap.containsKey(item)) {
|
||||
LOGGER.log(Level.WARNING, "Item {0} already managed by {1}", item, this); //NON-NLS
|
||||
return;
|
||||
}
|
||||
final Spatial spatial = translate(item);
|
||||
itemMap.put(item, spatial);
|
||||
LOGGER.log(Level.DEBUG, "added spatial for {0} in {1}", item, this); //NON-NLS
|
||||
if (spatial != null)
|
||||
itemNode.attachChild(spatial);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed every item
|
||||
*/
|
||||
public void clear() {
|
||||
LOGGER.log(Level.DEBUG, "clear"); //NON-NLS
|
||||
itemMap.clear();
|
||||
itemNode.detachAllChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the spatial for the specified item. Implementations may decide to return null. This
|
||||
* means that the item shall not be represented in the scene graph.
|
||||
*
|
||||
* @param item the item whose representing spatial is asked for
|
||||
* @return the spatial of the item, or null if the item shall not be represented by a spatial.
|
||||
*/
|
||||
protected abstract Spatial translate(I item);
|
||||
}
|
Reference in New Issue
Block a user