By default, Buttons use pixel level checking for intersection tests. Use {@link ImageSprite#setPixelLevelChecks(boolean) } to disable this feature. */ public class Button extends ImageSprite { // For labeled button creation. See createLabeledButton(). private static final int MARGIN = 12; private static final int MIN_WIDTH = 72; private static final int NORMAL = 0; private static final int HOVER = 1; private static final int PRESSED = 2; private static final int PRESSED_BUT_OUTSIDE = 3; private static final int NUM_VISIBLE_STATES = 3; private final CoreImage[] images; private final boolean isToggleButton; private int[] keyBinding; private int state; private boolean isSelected; private boolean isClicked; private Group rootWhenPressed; /** @param images an array of three images: normal, hover, and pressed. */ public Button(CoreImage[] images, int x, int y) { this(images, x, y, false); } /** @param images an array of three images: normal, hover, and pressed. Use six images for toggle buttons (unselected and selected). */ public Button(CoreImage[] images, int x, int y, boolean isToggleButton) { super(images[0], x, y); this.isToggleButton = isToggleButton; this.images = new CoreImage[images.length]; init(images, isToggleButton); } /** @param images an array of three images: normal, hover, and pressed. */ public Button(CoreImage[] images, double x, double y) { this(images, x, y, false); } /** @param images an array of three images: normal, hover, and pressed. Use six images for toggle buttons (unselected and selected). */ public Button(CoreImage[] images, double x, double y, boolean isToggleButton) { super(images[0], x, y); this.isToggleButton = isToggleButton; this.images = new CoreImage[images.length]; init(images, isToggleButton); } private void init(CoreImage[] images, boolean isToggleButton) { if (images.length < (isToggleButton?6:3)) { throw new IllegalArgumentException("Not enough button images."); } System.arraycopy(images, 0, this.images, 0, images.length); setCursor(Input.CURSOR_HAND); } /** @return true if this button is a toggle button, false otherwise */ public boolean isToggleButton() { return isToggleButton; } /** Gets a copy of the key bindings for this button. A button has no key bindings by default. @return the key bindings for this button, or null if there are no key bindings. @see #setKeyBinding(int) @see #setKeyBinding(int[]) @see #clearKeyBinding() */ public int[] getKeyBinding() { return CoreSystem.arraycopy(keyBinding); } /** Clears the key binding for this button. @see #getKeyBinding() @see #setKeyBinding(int) @see #setKeyBinding(int[]) */ public void clearKeyBinding() { keyBinding = null; } /** Sets the key binding for this button to the specified key code. The button is considered "clicked" if that key is pressed and released. @see #getKeyBinding() @see #setKeyBinding(int[]) @see #clearKeyBinding() */ public void setKeyBinding(int keyCode) { setKeyBinding(new int[] { keyCode }); } /** Sets the key binding for this button to the specified key codes. The button is considered "clicked" if any of those keys are pressed and released. @see #getKeyBinding() @see #setKeyBinding(int) @see #clearKeyBinding() */ public void setKeyBinding(int[] keyCodes) { keyBinding = CoreSystem.arraycopy(keyCodes); } /** Sets whether this button is selected. For toggle buttons only. */ public void setSelected(boolean isSelected) { if (this.isSelected != isSelected) { this.isSelected = isSelected; // update the frame setState(state); setDirty(true); } } /** Determines if this button is selected. For toggle buttons only. */ public boolean isSelected() { return isSelected; } private void setState(int state) { this.state = state; int frame; if (!isEnabledAndVisible() || state == PRESSED_BUT_OUTSIDE) { frame = NORMAL; } else { frame = state; } if (isToggleButton && isSelected) { frame += NUM_VISIBLE_STATES; } if (images != null) { super.setImage(images[frame]); } } public void update(int elapsedTime) { super.update(elapsedTime); boolean imageChanged = false; for (int i = 0; i < images.length; i++) { imageChanged |= images[i].update(elapsedTime); } if (imageChanged) { setDirty(true); } isClicked = isClickedImpl(); if (state == PRESSED) { rootWhenPressed = getRoot(); } } /** Determines if this button was clicked since the last frame. @return true if this button was clicked since the last frame. */ public boolean isClicked() { return isClicked && getRoot() == rootWhenPressed; } /** Determines if this button was pressed and the mouse is still held down over the button. This method continues to returns true as long as the mouse is pressed and still over the button.
To detect a click, use {@link #isClicked()}.
*/ public boolean isPressedDown() { return state == PRESSED && getRoot() == rootWhenPressed; } private boolean isClickedImpl() { // Disabled button if (!isEnabledAndVisible()) { if (state != NORMAL) { setState(NORMAL); } return false; } // Handle key input if (keyBinding != null) { if (Input.isPressed(keyBinding)) { setState(PRESSED); return false; } else if (Input.isDown(keyBinding) && state == PRESSED) { return false; } else if (Input.isReleased(keyBinding) && state == PRESSED) { if (isToggleButton) { isSelected = !isSelected; } setState(NORMAL); return true; } } // Handle mouse input if (state == PRESSED) { if (isMouseReleased()) { if (isToggleButton) { isSelected = !isSelected; } setState(HOVER); return true; } else if (!isMouseDown()) { setState(PRESSED_BUT_OUTSIDE); } } else if (state == PRESSED_BUT_OUTSIDE) { if (isMouseDown()) { setState(PRESSED); } else if (!Input.isMouseDown()) { setState(NORMAL); } } else if (state == HOVER) { if (isMousePressedPick()) { setState(PRESSED); } else if (!isMouseHoverPick()) { setState(NORMAL); } } else { if (isMousePressedPick()) { setState(PRESSED); } else if (isMouseHoverPick()) { setState(HOVER); } } return false; } private boolean isMousePressedPick() { return Input.isMouseInside() && Input.isMousePressed() && isPickEnabledAndVisible(Input.getMousePressX(), Input.getMousePressY()); } private boolean isMouseHoverPick() { return Input.isMouseInside() && !Input.isMouseDown() && isPickEnabledAndVisible(Input.getMouseX(), Input.getMouseY()); } // // Convenience methods to create buttons with text labels. // public static Button createLabeledButton(String text, int x, int y) { return createLabeledButton(null, null, text, x, y); } public static Button createLabeledToggleButton(String text, int x, int y) { return createLabeledToggleButton(null, null, text, x, y); } public static Button createLabeledButton(CoreImage[] images, CoreFont font, String text, int x, int y) { if (font == null) { font = CoreFont.getSystemFont(); } int textX; int textY; if (images != null) { textX = images[0].getWidth() / 2; textY = images[0].getHeight() / 2; } else { font = font.tint(0xffffff); int textWidth = font.getStringWidth(text); int textHeight = font.getHeight(); int buttonWidth = Math.max(MIN_WIDTH, textWidth + MARGIN*2); int buttonHeight = textHeight + MARGIN; textX = buttonWidth / 2; textY = buttonHeight / 2; } return createLabeledButton(images, font, text, x, y, textX, textY, 0.5, 0.5, false, true); } /** @deprecated */ public static Button createLabeledButton(CoreImage[] images, CoreFont font, String text, int x, int y, int textX, int textY, int textAnchor, boolean offsetPressedText) { return createLabeledButton(images, font, text, x, y, textX, textY, textAnchor, false, offsetPressedText); } public static Button createLabeledButton(CoreImage[] images, CoreFont font, String text, int x, int y, int textX, int textY, double textAnchorX, double textAnchorY, boolean offsetPressedText) { return createLabeledButton(images, font, text, x, y, textX, textY, textAnchorX, textAnchorY, false, offsetPressedText); } public static Button createLabeledToggleButton(CoreImage[] images, CoreFont font, String text, int x, int y) { if (font == null) { font = CoreFont.getSystemFont(); } int textX; int textY; if (images != null) { textX = images[0].getWidth() / 2; textY = images[0].getHeight() / 2; } else { font = font.tint(0xffffff); int textWidth = font.getStringWidth(text); int textHeight = font.getHeight(); int buttonWidth = Math.max(MIN_WIDTH, textWidth + MARGIN*2); int buttonHeight = textHeight + MARGIN; textX = buttonWidth / 2; textY = buttonHeight / 2; } return createLabeledButton(images, font, text, x, y, textX, textY, 0.5, 0.5, true, true); } /** @deprecated */ public static Button createLabeledToggleButton(CoreImage[] images, CoreFont font, String text, int x, int y, int textX, int textY, int textAnchor, boolean offsetPressedText) { return createLabeledButton(images, font, text, x, y, textX, textY, textAnchor, true, offsetPressedText); } public static Button createLabeledToggleButton(CoreImage[] images, CoreFont font, String text, int x, int y, int textX, int textY, double textAnchorX, double textAnchorY, boolean offsetPressedText) { return createLabeledButton(images, font, text, x, y, textX, textY, textAnchorX, textAnchorY, true, offsetPressedText); } /** @param images the images to use. If null, simple gray images are created to fit the text @param font the font to use for rendering the text label. If null, the system font is used. @param offsetPressedText Set to true to offset the button's text when the button is pressed. @deprecated */ public static Button createLabeledButton(CoreImage[] images, CoreFont font, String text, int x, int y, int textX, int textY, int textAnchor, boolean isToggleButton, boolean offsetPressedText) { double textAnchorX = 0; double textAnchorY = 0; // Hard-coded values are old // hcenter/right/vcenter/bottom values from Sprite. if ((textAnchor & 4) != 0) { textAnchorX = 0.5; } else if ((textAnchor & 2) != 0) { textAnchorX = 1.0; } if ((textAnchor & 32) != 0) { textAnchorY = 0.5; } else if ((textAnchor & 16) != 0) { textAnchorY = 1.0; } return createLabeledButton(images, font, text, x, y, textX, textY, textAnchorX, textAnchorY, isToggleButton, offsetPressedText); } /** @param images the images to use. If null, simple gray images are created to fit the text @param font the font to use for rendering the text label. If null, the system font is used. @param offsetPressedText Set to true to offset the button's text when the button is pressed. */ public static Button createLabeledButton(CoreImage[] images, CoreFont font, String text, int x, int y, int textX, int textY, double textAnchorX, double textAnchorY, boolean isToggleButton, boolean offsetPressedText) { if (font == null) { font = CoreFont.getSystemFont(); } // Determine text label location int textWidth = font.getStringWidth(text); int textHeight = font.getHeight(); textX -= textAnchorX * textWidth; textY -= textAnchorY * textHeight; // Create button image, if needed if (images == null) { font = font.tint(0xffffff); images = new CoreImage[isToggleButton?6:3]; int buttonWidth = Math.max(MIN_WIDTH, textWidth + MARGIN*2); int buttonHeight = textHeight + MARGIN; for (int i = 0; i < images.length; i++) { images[i] = createButtonImage(buttonWidth, buttonHeight, i); } } // Determine bounds of the image after the text is added Rect imageBounds = new Rect(0, 0, images[0].getWidth(), images[0].getHeight()); Rect textBounds = new Rect(textX, textY, textWidth + 1, textHeight + 1); Rect bounds = new Rect(imageBounds); bounds.union(textBounds); boolean textInsideImage = bounds.equals(imageBounds); // Create new images CoreImage[] textImages = new CoreImage[images.length]; for (int i = 0; i < textImages.length; i++) { int offsetX = 0; int offsetY = 0; if (offsetPressedText && (i % NUM_VISIBLE_STATES) == PRESSED) { offsetX = 1; offsetY = 1; } textImages[i] = new CoreImage(bounds.width, bounds.height, false); CoreGraphics g = textImages[i].createGraphics(); g.setFont(font); g.drawImage(images[i], -bounds.x, -bounds.y); g.drawString(text, textX-bounds.x + offsetX, textY-bounds.y + offsetY); } Button button = new Button(textImages, x, y, isToggleButton); button.setPixelLevelChecks(textInsideImage); return button; } private static CoreImage createButtonImage(int buttonWidth, int buttonHeight, int type) { // Create the button image at 2x the dimensions, then scale down. // This way the corners look anti-aliased. int w = buttonWidth * 2; int h = buttonHeight * 2; int cornerSize = 3; CoreImage image = new CoreImage(w, h, false); CoreGraphics g = image.createGraphics(); Color topGradient = new Color(); Color bottomGradient = new Color(); // Web 2.0-style gradients if (type == 0 || type == 1) { // Normal, Hover topGradient.animate(0xff626365, 0xff484848, h/2); bottomGradient.animate(0xff030303, 0xff2e2e2e, h/2); } else if (type == 2) { // Pressed topGradient.animate(0xff2e2e2e, 0xff484848, h/2); bottomGradient.animate(0xff030303, 0xff2e2e2e, h/2); } else if (type == 3 || type == 4) { // Selected topGradient.animate(0xff3d4a8a, 0xff3d4a8a, h/2); bottomGradient.animate(0xff11194f, 0xff3d4a8a, h/2); } else { // Selected & pressed topGradient.animate(0xff273271, 0xff3d4a8a, h/2); bottomGradient.animate(0xff11194f, 0xff3d4a8a, h/2); } for (int i = 0; i < h/2; i++) { int lineWidth = Math.min(w, w + (i - cornerSize) * 2); int x = (w - lineWidth) / 2; int y1 = i; int y2 = h - 1 - i; topGradient.update(1); g.setColor(topGradient.get()); g.fillRect(x, y1, lineWidth, 1); bottomGradient.update(1); g.setColor(bottomGradient.get()); g.fillRect(x, y2, lineWidth, 1); } return image.halfSize(); } }