From 310356b1eb0411319ca63da6353c5b3cf3c71cd3 Mon Sep 17 00:00:00 2001 From: Markil3 <75867393+Markil3@users.noreply.github.com> Date: Fri, 18 Dec 2020 10:28:11 -0700 Subject: [PATCH] Moves many of the GUI tasks for CalibrateInputScreen to their own class. I plan on reusing them in JoystickPreviewScreen. --- .../java/markil3/controller/GUIUtils.java | 200 +++++++++++++++++ .../controller/CalibrateInputScreen.java | 211 +++++++----------- 2 files changed, 275 insertions(+), 136 deletions(-) create mode 100644 library/src/main/java/markil3/controller/GUIUtils.java diff --git a/library/src/main/java/markil3/controller/GUIUtils.java b/library/src/main/java/markil3/controller/GUIUtils.java new file mode 100644 index 0000000..005bc26 --- /dev/null +++ b/library/src/main/java/markil3/controller/GUIUtils.java @@ -0,0 +1,200 @@ +package markil3.controller; + +import com.jme3.asset.AssetManager; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.font.Rectangle; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Ray; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; + +/** + * A collection of utility methods for the screens used here. + * @author Markil 3 + * @version 1.1 + */ +public class GUIUtils +{ + /** + * The color that a button takes on when the mouse is down on it. Feel + * free to change it to suit your needs. + */ + static ColorRGBA BUTTON_COLOR_ON = new ColorRGBA(0F, 0.1F, 0F, 1F); + /** + * The default color that a button. Feel free to change it to suit your + * needs. + */ + static ColorRGBA BUTTON_COLOR_OFF = new ColorRGBA(0F, 0.75F, 0.25F, 1F); + + private static final String BUTTON_ID = "button"; + + /** + * Creates a button. This button will have special user data fields that + * can be detected by {@link #handleButtonPress(Node, Vector2f, boolean)} + * to trigger events. + * @param assets - The application asset manager. + * @param guiFont - The font to use for the button. + * @param id - The button ID. This is used to identify which button was + * pressed later on. + * @param content - The text that will display in the button. + * @return A node containing the button elements. + */ + public static Node createButton(AssetManager assets, BitmapFont guiFont, + String id, String content) + { + BitmapText buttonText; + Geometry buttonBackground; + Node button = new Node(); + buttonText = guiFont.createLabel(content); + buttonText.setUserData(BUTTON_ID, id); + buttonText.setBox(new Rectangle(0, 0, buttonText.getLineWidth(), + buttonText.getLineHeight())); + buttonText.setAlignment(BitmapFont.Align.Center); + buttonText.setVerticalAlignment(BitmapFont.VAlign.Center); + buttonBackground = new Geometry("button-" + id, + new Quad(buttonText.getLineWidth() + 10, + buttonText.getHeight() + 5)); + buttonBackground.setMaterial( + new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md")); + buttonBackground.getMaterial().setColor("Color", BUTTON_COLOR_OFF); + buttonBackground.setUserData(BUTTON_ID, id); + buttonBackground + .setLocalTranslation(-2.5F, -buttonText.getLineHeight() - 2.5F, + 0); + button.setUserData(BUTTON_ID, id); + button.attachChild(buttonBackground); + button.attachChild(buttonText); + return button; + } + + /** + * Call this method from a mouse listener to search the provided node for + * any buttons created with + * {@link #createButton(AssetManager, BitmapFont, String, String)} and + * returns their ID. + * @param gui - The GUI to search. + * @param cursor - The position of the mouse cursor. + * @param isPressed - Whether or not the cursor is pressed. This will + * change how the button is displayed. + * @return The ID of the button that was clicked, or null if none were + * clicked. + */ + public static String handleButtonPress(Node gui, Vector2f cursor, + boolean isPressed) + { + final String KEY_CURRENT = "currentButton"; + CollisionResults results; + Ray ray; + Geometry button; + String buttonId = null; + + /* + * We generally mark buttons on mouse down, and trigger that same + * button on release. If we haven't even marked a button by the time + * we release the mouse, we just won't bother. + */ + if (!isPressed) + { + button = gui.getUserData(KEY_CURRENT); + if (button == null) + { + return null; + } + } + + results = new CollisionResults(); + ray = new Ray(new Vector3f(cursor.x, cursor.y, 0), + new Vector3f(cursor.x, cursor.y, 1)); + gui.collideWith(ray, results); + for (CollisionResult result : results) + { + button = result.getGeometry(); + buttonId = button.getUserData(BUTTON_ID); + if (buttonId != null) + { + if (isPressed) + { + /* + * Trigger the press graphic + */ + button.getMaterial().setColor("Color", BUTTON_COLOR_ON); + gui.setUserData(KEY_CURRENT, button); + break; + } + else + { + /* + * If the button we released isn't the same as the button + * we originally pressed, don't trigger either. + */ + button = gui.getUserData(KEY_CURRENT); + if (button != null) + { + if (button.getUserData(BUTTON_ID).equals(buttonId)) + { + break; + } + else + { + buttonId = null; + } + } + } + } + } + if (!isPressed) + { + /* + * Make sure the originally-clicked button was reset. + */ + button = gui.getUserData(KEY_CURRENT); + if (button != null) + { + button.getMaterial().setColor("Color", BUTTON_COLOR_OFF); + gui.setUserData(KEY_CURRENT, null); + } + } + return buttonId; + } + + /** + * Aligns the contents in a container so that all {@link BitmapText} and + * button elements will align in a column. + * @param cont - The container to align. + * @param width - The width of the screen. + * @param height - The height of the screen. + * @return The total height of the container. This is useful in + * positioning the container itself. + */ + public static float alignContainer(Node cont, int width, int height) + { + float totalHeight = 0; + for (Spatial node : cont.getChildren()) + { + if (node instanceof BitmapText) + { + node.setLocalTranslation( + -((BitmapText) node).getLineWidth() / 2F, -totalHeight, + 0); + totalHeight += ((BitmapText) node).getHeight(); + } + else if (node.getUserData(BUTTON_ID) != null) + { + Quad quad = ((Quad) ((Geometry) ((Node) node).getChild(0)) + .getMesh()); + node.setLocalTranslation(-quad.getWidth() / 2F, + -totalHeight - 10, 0); + totalHeight += quad.getHeight() + 10; + } + } + return totalHeight; + } +} diff --git a/src/main/java/markil3/controller/CalibrateInputScreen.java b/src/main/java/markil3/controller/CalibrateInputScreen.java index 1179638..395afda 100644 --- a/src/main/java/markil3/controller/CalibrateInputScreen.java +++ b/src/main/java/markil3/controller/CalibrateInputScreen.java @@ -3,7 +3,6 @@ package markil3.controller; import com.jme3.app.Application; import com.jme3.app.SimpleApplication; import com.jme3.app.state.BaseAppState; -import com.jme3.collision.CollisionResult; import com.jme3.collision.CollisionResults; import com.jme3.font.BitmapFont; import com.jme3.font.BitmapText; @@ -21,11 +20,9 @@ import com.jme3.input.event.KeyInputEvent; import com.jme3.input.event.MouseButtonEvent; import com.jme3.input.event.MouseMotionEvent; import com.jme3.input.event.TouchEvent; -import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.Ray; import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.scene.Geometry; import com.jme3.scene.Node; @@ -62,6 +59,7 @@ import static markil3.controller.Main.CALIBRATION_FILE; /** * Provides a series of prompts that will build a controller calibration file. * @author Markil3 + * @version 1.1 */ public class CalibrateInputScreen extends BaseAppState implements RawInputListener, ActionListener @@ -155,32 +153,6 @@ public class CalibrateInputScreen extends BaseAppState private HashMap defaultValues = new HashMap<>(); - private Node createButton(String id, String content) - { - BitmapText buttonText; - Geometry buttonBackground; - Node button = new Node(); - buttonText = this.guiFont.createLabel(content); - buttonText.setUserData("button", id); - buttonText.setBox(new Rectangle(0, 0, buttonText.getLineWidth(), buttonText.getLineHeight())); - buttonText.setAlignment(BitmapFont.Align.Center); - buttonText.setVerticalAlignment(BitmapFont.VAlign.Center); - buttonBackground = new Geometry("button-" + id, - new Quad(buttonText.getLineWidth() + 10, - buttonText.getLineHeight() + 5)); - buttonBackground.setMaterial( - new Material(this.getApplication().getAssetManager(), - "Common/MatDefs/Misc/Unshaded.j3md")); - buttonBackground.getMaterial() - .setColor("Color", HIGHLIGHTED_BUTTON_COLOR); - buttonBackground.setUserData("button", id); - buttonBackground.setLocalTranslation(-2.5F, -buttonText.getLineHeight() - 2.5F, 0); - button.setUserData("button", id); - button.attachChild(buttonBackground); - button.attachChild(buttonText); - return button; - } - @Override protected void initialize(Application app) { @@ -207,7 +179,9 @@ public class CalibrateInputScreen extends BaseAppState text.setVerticalAlignment(BitmapFont.VAlign.Center); this.introCont.attachChild(text); - this.startButton = this.createButton("start", "Start"); + this.startButton = + GUIUtils.createButton(app.getAssetManager(), this.guiFont, + "start", "Start"); // this.startButton.addClickCommands(this); this.introCont.attachChild(this.startButton); @@ -217,12 +191,18 @@ public class CalibrateInputScreen extends BaseAppState "First, press any button or axis\n" + "on the controller you want to calibrate.")); - this.skipButton = this.createButton("skip", "Skip"); + this.skipButton = + GUIUtils.createButton(app.getAssetManager(), this.guiFont, + "skip", "Skip"); - this.cancelButton = this.createButton("cancel", "Cancel"); + this.cancelButton = + GUIUtils.createButton(app.getAssetManager(), this.guiFont, + "cancel", "Cancel"); this.introCont.attachChild(this.cancelButton); - this.restartButton = this.createButton("close", "Close Application"); + this.restartButton = + GUIUtils.createButton(app.getAssetManager(), this.guiFont, + "close", "Close Application"); this.gui.attachChild(this.introCont); @@ -356,46 +336,6 @@ public class CalibrateInputScreen extends BaseAppState this.resize(camera.getWidth(), camera.getHeight()); } - private float alignContainer(Node cont, int width, int height) - { - float totalHeight = 0; - Vector2f prevButtonDimensions = null; - for (Spatial node : cont.getChildren()) - { - if (node instanceof BitmapText) - { -// ((BitmapText) node).setAlignment(BitmapFont.Align.Center); -// ((BitmapText) node).setVerticalAlignment(BitmapFont.VAlign -// .Center); - node.setLocalTranslation( - -((BitmapText) node).getLineWidth() / 2F, -totalHeight, - 0); - totalHeight += ((BitmapText) node).getHeight(); - prevButtonDimensions = null; - } - else if (node.getUserData("button") != null) - { - Quad quad = ((Quad) ((Geometry) ((Node) node).getChild(0)) - .getMesh()); - if (prevButtonDimensions != null) - { - node.setLocalTranslation( - -quad.getWidth() / 2F + prevButtonDimensions.x + 10, - -totalHeight + prevButtonDimensions.y, 0); - } - else - { - node.setLocalTranslation(-quad.getWidth() / 2F, - -totalHeight - 10, 0); - totalHeight += quad.getHeight() + 10; - } -// prevButtonDimensions = new Vector2f(quad.getWidth(), quad -// .getHeight()); - } - } - return totalHeight; - } - /** * Scales and positions elements of this screen. * @param width - The width to scale to. @@ -403,8 +343,8 @@ public class CalibrateInputScreen extends BaseAppState */ protected void resize(int width, int height) { - float introHeight = this.alignContainer(this.introCont, width, height); - float mainHeight = this.alignContainer(this.mainOptions, width, height); + float introHeight = GUIUtils.alignContainer(this.introCont, width, height); + float mainHeight = GUIUtils.alignContainer(this.mainOptions, width, height); this.gui.setLocalTranslation(0, height / 2F, 0); this.introCont.setLocalTranslation((width) / 2F, (introHeight) / 2F, 0); this.mainOptions @@ -433,60 +373,50 @@ public class CalibrateInputScreen extends BaseAppState @Override public void onAction(String name, boolean isPressed, float tpf) { - CollisionResults results; - Ray ray; - Vector2f cursor; String buttonId; - if (name.equals(CLICK_MAPPING) && !isPressed) + if (name.equals(CLICK_MAPPING)) { - cursor = - this.getApplication().getInputManager().getCursorPosition(); - results = new CollisionResults(); - ray = new Ray(new Vector3f(cursor.x, cursor.y, 0), - new Vector3f(cursor.x, cursor.y, 1)); - this.gui.collideWith(ray, results); - for (CollisionResult result : results) + buttonId = GUIUtils.handleButtonPress(this.gui, + this.getApplication().getInputManager().getCursorPosition(), + isPressed); + if (!isPressed && buttonId != null) { - buttonId = result.getGeometry().getUserData("button"); - if (buttonId != null) + switch (buttonId) { - switch (buttonId) - { - case "start": - this.introCont.removeFromParent(); - this.gui.attachChild(this.mainOptions); - this.gui.attachChild(this.gamepad); - this.resize(); + case "start": + this.introCont.removeFromParent(); + this.gui.attachChild(this.mainOptions); + this.gui.attachChild(this.gamepad); + this.resize(); - this.listeningRaw = true; - this.getApplication().getInputManager() - .addRawInputListener(this); - break; - case "skip": - if (this.currentStage != null) + this.listeningRaw = true; + this.getApplication().getInputManager() + .addRawInputListener(this); + break; + case "skip": + if (this.currentStage != null) + { + this.timeHeld = -1; + this.focusedJoyElement = null; + this.focusValue = 0; + if (this.currentStage == PromptStage.BUTTON) { - this.timeHeld = -1; - this.focusedJoyElement = null; - this.focusValue = 0; - if (this.currentStage == PromptStage.BUTTON) - { - this.calibrateNextButton(); - } - else - { - this.calibrateNextAxis(); - } + this.calibrateNextButton(); + } + else + { + this.calibrateNextAxis(); } - break; - case "cancel": - this.getStateManager().detach(this); - this.getStateManager() - .attach(new NewJoystickPreviewScreen()); - break; - case "close": - this.getApplication().stop(); - break; } + break; + case "cancel": + this.getStateManager().detach(this); + this.getStateManager() + .attach(new NewJoystickPreviewScreen()); + break; + case "close": + this.getApplication().stop(); + break; } } } @@ -501,8 +431,11 @@ public class CalibrateInputScreen extends BaseAppState private void setJoystick(Joystick joystick) { this.joystick = joystick; - this.currentJoystick = this.guiFont.createLabel(this.joystick.getName()); - this.currentJoystick.setBox(new Rectangle(0, 0, this.currentJoystick.getLineWidth(), this.currentJoystick.getHeight())); + this.currentJoystick = + this.guiFont.createLabel(this.joystick.getName()); + this.currentJoystick + .setBox(new Rectangle(0, 0, this.currentJoystick.getLineWidth(), + this.currentJoystick.getHeight())); this.currentJoystick.setAlignment(BitmapFont.Align.Center); this.gui.attachChild(this.currentJoystick); this.calibrationIter = BUTTON_PROMPTS.keySet().iterator(); @@ -700,6 +633,9 @@ public class CalibrateInputScreen extends BaseAppState */ private void recordFile() { + /* + * Enable this in JME 3.4. + */ boolean perComponentEnabled = false; JoystickAxis axis; JoystickButton button; @@ -715,17 +651,17 @@ public class CalibrateInputScreen extends BaseAppState { if (!CALIBRATION_FILE.createNewFile()) { - throw new IOException( - "Could not create calibration file."); + throw new IOException("Could not create calibration file."); } } catch (IOException ioe) { this.introCont.detachAllChildren(); - this.introCont.attachChild( - this.guiFont.createLabel("Could not create Calibration File:")); + this.introCont.attachChild(this.guiFont + .createLabel("Could not create Calibration File:")); this.introCont.attachChild(this.cancelButton); - this.introCont.attachChild(this.guiFont.createLabel("Error Details:")); + this.introCont.attachChild( + this.guiFont.createLabel("Error Details:")); this.listStack(this.introCont, ioe, 0); this.gui.attachChild(this.introCont); this.resize(); @@ -771,11 +707,12 @@ public class CalibrateInputScreen extends BaseAppState { props.store(output, "Joystick Calibration File"); this.introCont.detachAllChildren(); + this.introCont.attachChild(this.guiFont + .createLabel("Calibration completed successfully.")); + this.introCont.attachChild(this.guiFont.createLabel( + "Close the application and restart to load")); this.introCont.attachChild( - this.guiFont.createLabel("Calibration completed successfully.")); - this.introCont.attachChild( - this.guiFont.createLabel("Close the application and restart to load")); - this.introCont.attachChild(this.guiFont.createLabel("the new settings.")); + this.guiFont.createLabel("the new settings.")); this.introCont.attachChild(this.restartButton); this.gui.attachChild(this.introCont); this.resize(); @@ -783,10 +720,11 @@ public class CalibrateInputScreen extends BaseAppState catch (IOException ioe) { this.introCont.detachAllChildren(); - this.introCont.attachChild( - this.guiFont.createLabel("Could not create Calibration File:")); + this.introCont.attachChild(this.guiFont + .createLabel("Could not create Calibration File:")); this.introCont.attachChild(this.cancelButton); - this.introCont.attachChild(this.guiFont.createLabel("Error Details:")); + this.introCont.attachChild( + this.guiFont.createLabel("Error Details:")); this.listStack(this.introCont, ioe, 0); this.gui.attachChild(this.introCont); this.resize(); @@ -820,7 +758,8 @@ public class CalibrateInputScreen extends BaseAppState errorBuilder.append('\n'); cause = error.getCause(); } - errorCont.attachChild(this.guiFont.createLabel(errorBuilder.toString())); + errorCont + .attachChild(this.guiFont.createLabel(errorBuilder.toString())); } @Override