I am trying to take screeshots of any file open currently on screen. After google all i got is to take screenshot of scene only in javafx. I don't want to use any AWT or Swing component. So is there any way ?
Currently javafx.scene.robot.Robot is available, which can take screen captures. I don’t know if it was when the question was originally asked. The method is:
javafx.scene.robot.Robot robot = new Robot();
WritableImage imgReturn = robot.getScreenCapture(imgOut, new Rectangle2D());
The optional rectangle specifies the region of the screen to capture. If not supplied, the whole screen is included. imgReturn can be assigned the resulting writable image. imgOut is an writable image output parameter that can also store the result, if you’ve defined it previously and it’s the correct dimensions. Otherwise, leave it null.
The method must be run on the JavaFX Application thread (you can use Platform.runLater() to ensure this if it’s invoked from another thread).
If you’re looking to take a screenshot of a specific application window, you could analyze the capture of the whole screen and look for the region containing the window you’re looking for.
If you know the window coordinates and dimensions, you can plug that in for the second argument in getScreenCapture.
You can also use the semitransparent overlay stage method from #GOXR3PLUS in which the user can define a rectangle (by clicking and dragging to define upper-left and lower-right corners, for example). Then you can hide the overlay stage and take a screenshot of the region within that rectangle.
This is the main mechanism I have used: A transparent Stage with Transparent Canvas which has a BorderPane with Opacity 0.1
Here is the simple example (it is just for selecting areas...):
WHEN YOU START THE APP THE ONLY WAY TO CLOSE IT IS USING ESCAPE
Tester class:
import javafx.application.Application;
import javafx.stage.Screen;
import javafx.stage.Stage;
public class Tester extends Application{
#Override
public void start(Stage primaryStage) throws Exception {
CaptureWindow window = new CaptureWindow(Screen.getPrimary().getBounds().getWidth(),Screen.getPrimary().getBounds().getHeight(), primaryStage);
window.show();
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Capture Window class:
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
/**
* Is used to capture an area of the screen.
*
* #author GOXR3PLUS
*/
public class CaptureWindow extends Stage {
/** The border pane. */
// BorderPane and Canvas
BorderPane borderPane = new BorderPane();
/** The canvas. */
Canvas canvas = new Canvas();
/** The gc. */
GraphicsContext gc = canvas.getGraphicsContext2D();
/** The stage. */
Stage stage;
/** The width. */
// Variables
int width;
/** The height. */
int height;
/** The x pressed. */
int xPressed = 0;
/** The y pressed. */
int yPressed = 0;
/** The x now. */
int xNow = 0;
/** The y now. */
int yNow = 0;
/** The foreground. */
Color foreground = Color.rgb(255, 167, 0);
/** The background. */
Color background = Color.rgb(0, 0, 0, 0.3);
/**
* Constructor.
*
* #param screenWidth the screen width
* #param screenHeight the screen height
* #param primary the primary
*/
public CaptureWindow(double screenWidth, double screenHeight, Stage primary) {
stage = primary;
setX(0);
setY(0);
setWidth(screenWidth);
setHeight(screenHeight);
initOwner(primary);
initStyle(StageStyle.TRANSPARENT);
setAlwaysOnTop(true);
// BorderPane
borderPane.setStyle("-fx-background-color:rgb(0,0,0,0.1);");
// Canvas
canvas.setWidth(screenWidth);
canvas.setHeight(screenHeight);
canvas.setOnMousePressed(m -> {
xPressed = (int) m.getScreenX();
yPressed = (int) m.getScreenY();
});
canvas.setOnMouseDragged(m -> {
xNow = (int) m.getScreenX();
yNow = (int) m.getScreenY();
repaintCanvas();
});
borderPane.setCenter(canvas);
// Scene
setScene(new Scene(borderPane, Color.TRANSPARENT));
getScene().setCursor(Cursor.CROSSHAIR);
getScene().setOnKeyReleased(key -> {
if (key.getCode() == KeyCode.B) {
close();
System.out.println("Key Released....");
}else if(key.getCode() == KeyCode.ESCAPE)
close();
});
// gc
gc.setLineDashes(6);
gc.setFont(Font.font("null", FontWeight.BOLD, 14));
}
/**
* Repaints the canvas *.
*/
protected void repaintCanvas() {
gc.clearRect(0, 0, getWidth(), getHeight());
gc.setStroke(foreground);
gc.setFill(background);
gc.setLineWidth(3);
if (xNow > xPressed && yNow > yPressed) { // Right and Down
calculateWidthAndHeight(xNow - xPressed, yNow - yPressed);
gc.strokeRect(xPressed, yPressed, width, height);
gc.fillRect(xPressed, yPressed, width, height);
} else if (xNow < xPressed && yNow < yPressed) { // Left and Up
calculateWidthAndHeight(xPressed - xNow, yPressed - yNow);
gc.strokeRect(xNow, yNow, width, height);
gc.fillRect(xNow, yNow, width, height);
} else if (xNow > xPressed && yNow < yPressed) { // Right and Up
calculateWidthAndHeight(xNow - xPressed, yPressed - yNow);
gc.strokeRect(xPressed, yNow, width, height);
gc.fillRect(xPressed, yNow, width, height);
} else if (xNow < xPressed && yNow > yPressed) { // Left and Down
calculateWidthAndHeight(xPressed - xNow, yNow - yPressed);
gc.strokeRect(xNow, yPressed, width, height);
gc.fillRect(xNow, yPressed, width, height);
}
}
/**
* Show the window.
*/
public void showWindow() {
xNow = 0;
yNow = 0;
xPressed = 0;
yPressed = 0;
repaintCanvas();
show();
}
/**
* Calculates the width and height of the rectangle.
*
* #param w the w
* #param h the h
*/
private final void calculateWidthAndHeight(int w, int h) {
width = w;
height = h;
}
/**
* Selects whole Screen.
*/
public void selectWholeScreen() {
xPressed = 0;
yPressed = 0;
xNow = (int) getWidth();
yNow = (int) getHeight();
}
/**
* Return an array witch contains the (UPPER_LEFT) Point2D of the rectangle
* and the width and height of the rectangle.
*
* #return the int[]
*/
public int[] calculatedRectangle() {
if (xNow > xPressed) { // Right
if (yNow > yPressed) // and DOWN
return new int[] { xPressed, yPressed, xNow - xPressed, yNow - yPressed };
else if (yNow < yPressed) // and UP
return new int[] { xPressed, yNow, xNow - xPressed, yPressed - yNow };
} else if (xNow < xPressed) { // LEFT
if (yNow > yPressed) // and DOWN
return new int[] { xNow, yPressed, xPressed - xNow, yNow - yPressed };
else if (yNow < yPressed) // and UP
return new int[] { xNow, yNow, xPressed - xNow, yPressed - yNow };
}
return new int[] { xPressed, yPressed, xNow, yNow };
}
Here is a full advanced example.It is part of a GitHub Project Here . You can clone the project and modify on your needs.
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import org.controlsfx.control.Notifications;
import application.Main;
import application.SFileChooser;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
/**
* This is the Window which is used from the user to draw the rectangle
* representing an area on the screen to be captured.
*
* #author GOXR3PLUS
*/
public class CaptureWindowController extends Stage {
/** The stack pane. */
#FXML
private StackPane stackPane;
/** The main canvas. */
#FXML
private Canvas mainCanvas;
// -----------------------------
/**
* The Model of the CaptureWindow
*/
CaptureWindowModel model = new CaptureWindowModel();
/** The file saver. */
SFileChooser fileSaver = new SFileChooser();
/** The capture service. */
final CaptureService captureService = new CaptureService();
/** The graphics context of the canvas */
GraphicsContext gc;
/**
* When a key is being pressed into the capture window then this Animation
* Timer is doing it's magic.
*/
AnimationTimer yPressedAnimation = new AnimationTimer() {
private long nextSecond = 0L;
// private static final long ONE_SECOND_NANOS = 1_000_000_000L
private long precisionLevel;
#Override
public void start() {
nextSecond = 0L;
precisionLevel = (long) ( settingsWindowController.getPrecisionSlider().getValue() * 1_000_000L );
super.start();
}
#Override
public void handle(long nanos) {
System.out.println("TimeStamp: " + nanos + " Current: " + nextSecond);
System.out.println("Milliseconds Delay: " + precisionLevel / 1_000_000);
if (nanos >= nextSecond) {
nextSecond = nanos + precisionLevel;
// With special key pressed
// (we want [LEFT] and [DOWN] side of the rectangle to be
// movable)
// No Special Key is Pressed
// (we want [RIGHT] and [UP] side of the rectangle to be
// movable)
// ------------------------------
if (model.rightPressed.get()) {
if (model.shiftPressed.get()) { // Special Key?
if (model.mouseXNow > model.mouseXPressed) { // Mouse gone Right?
model.mouseXPressed += 1;
} else {
model.mouseXNow += 1;
}
} else {
if (model.mouseXNow > model.mouseXPressed) { // Mouse gone Right?
model.mouseXNow += 1;
} else {
model.mouseXPressed += 1;
}
}
}
if (model.leftPressed.get()) {
if (model.shiftPressed.get()) { // Special Key?
if (model.mouseXNow > model.mouseXPressed) { // Mouse gone Right?
model.mouseXPressed -= 1;
} else {
model.mouseXNow -= 1;
}
} else {
if (model.mouseXNow > model.mouseXPressed) { // Mouse gone Right?
model.mouseXNow -= 1;
} else {
model.mouseXPressed -= 1;
}
}
}
if (model.upPressed.get()) {
if (model.shiftPressed.get()) { // Special Key?
if (model.mouseYNow > model.mouseYPressed) { // Mouse gone UP?
model.mouseYNow -= 1;
} else {
model.mouseYPressed -= 1;
}
} else {
if (model.mouseYNow > model.mouseYPressed) { // Mouse gone UP?
model.mouseYPressed -= 1;
} else {
model.mouseYNow -= 1;
}
}
}
if (model.downPressed.get()) {
if (model.shiftPressed.get()) { // Special Key?
if (model.mouseYNow > model.mouseYPressed) { // Mouse gone UP?
model.mouseYNow += 1;
} else {
model.mouseYPressed += 1;
}
} else {
if (model.mouseYNow > model.mouseYPressed) { // Mouse gone UP?
model.mouseYPressed += 1;
} else {
model.mouseYNow += 1;
}
}
}
repaintCanvas();
}
}
};
/**
* This AnimationTimer waits until the canvas is cleared before it can
* capture the screen.
*/
AnimationTimer waitFrameRender = new AnimationTimer() {
private int frameCount = 0;
#Override
public void start() {
frameCount = 0;
super.start();
}
#Override
public void handle(long timestamp) {
frameCount++;
if (frameCount >= 5) {
stop();
// Capture the Image
BufferedImage image;
int[] rect = getRectangleBounds();
try {
image = new Robot().createScreenCapture(new Rectangle(rect[0], rect[1], rect[2], rect[3]));
} catch (AWTException ex) {
Logger.getLogger(getClass().getName()).log(Level.INFO, null, ex);
return;
} finally {
mainCanvas.setDisable(false);
}
// System.out.println("Starting Service")
// Start the Service
captureService.startService(image);
}
}
};
/** The counting thread. */
Thread countingThread;
/** The main window controller. */
MainWindowController mainWindowController;
/** The settings window controller. */
SettingsWindowController settingsWindowController;
/**
* Constructor.
*/
public CaptureWindowController() {
setX(0);
setY(0);
getIcons().add(new Image(getClass().getResourceAsStream("/image/icon.png")));
initStyle(StageStyle.TRANSPARENT);
setAlwaysOnTop(true);
}
/**
* Add the needed references from the other controllers.
*
* #param mainWindowController the main window controller
* #param settingsWindowController the settings window controller
*/
#SuppressWarnings("hiding")
public void addControllerReferences(MainWindowController mainWindowController ,
SettingsWindowController settingsWindowController) {
this.mainWindowController = mainWindowController;
this.settingsWindowController = settingsWindowController;
}
/**
* Will be called as soon as FXML file is loaded.
*/
#FXML
public void initialize() {
// System.out.println("CaptureWindow initialized")
// Scene
Scene scene = new Scene(stackPane, model.screenWidth, model.screenHeight, Color.TRANSPARENT);
scene.setCursor(Cursor.NONE);
setScene(scene);
addKeyHandlers();
// Canvas
mainCanvas.setWidth(model.screenWidth);
mainCanvas.setHeight(model.screenHeight);
mainCanvas.setOnMousePressed(m -> {
if (m.getButton() == MouseButton.PRIMARY) {
model.mouseXPressed = (int) m.getScreenX();
model.mouseYPressed = (int) m.getScreenY();
}
});
mainCanvas.setOnMouseDragged(m -> {
if (m.getButton() == MouseButton.PRIMARY) {
model.mouseXNow = (int) m.getScreenX();
model.mouseYNow = (int) m.getScreenY();
repaintCanvas();
}
});
// graphics context 2D
gc = mainCanvas.getGraphicsContext2D();
gc.setLineDashes(6);
gc.setFont(Font.font("null", FontWeight.BOLD, 14));
// HideFeaturesPressed
model.hideExtraFeatures.addListener((observable , oldValue , newValue) -> repaintCanvas());
}
/**
* Adds the KeyHandlers to the Scene.
*/
private void addKeyHandlers() {
// -------------Read the below to understand the Code-------------------
// the default prototype of the below code is
// 1->when the user is pressing RIGHT ARROW -> The rectangle is
// increasing from the RIGHT side
// 2->when the user is pressing LEFT ARROW -> The rectangle is
// decreasing from the RIGHT side
// 3->when the user is pressing UP ARROW -> The rectangle is increasing
// from the UP side
// 4->when the user is pressing DOWN ARROW -> The rectangle is
// decreasing from the UP side
// when ->LEFT KEY <- is pressed
// 1->when the user is pressing RIGHT ARROW -> The rectangle is
// increasing from the LEFT side
// 2->when the user is pressing LEFT ARROW -> The rectangle is
// decreasing from the LEFT side
// 3->when the user is pressing UP ARROW -> The rectangle is increasing
// from the DOWN side
// 4->when the user is pressing DOWN ARROW -> The rectangle is
// decreasing from the DOWN side
// kemodel.yPressed
getScene().setOnKeyPressed(key -> {
if (key.isShiftDown())
model.shiftPressed.set(true);
if (key.getCode() == KeyCode.LEFT)
model.leftPressed.set(true);
if (key.getCode() == KeyCode.RIGHT)
model.rightPressed.set(true);
if (key.getCode() == KeyCode.UP)
model.upPressed.set(true);
if (key.getCode() == KeyCode.DOWN)
model.downPressed.set(true);
if (key.getCode() == KeyCode.H)
model.hideExtraFeatures.set(true);
});
// keyReleased
getScene().setOnKeyReleased(key -> {
if (key.getCode() == KeyCode.SHIFT)
model.shiftPressed.set(false);
if (key.getCode() == KeyCode.RIGHT) {
if (key.isControlDown()) {
model.mouseXNow = (int) getWidth();
repaintCanvas();
}
model.rightPressed.set(false);
}
if (key.getCode() == KeyCode.LEFT) {
if (key.isControlDown()) {
model.mouseXPressed = 0;
repaintCanvas();
}
model.leftPressed.set(false);
}
if (key.getCode() == KeyCode.UP) {
if (key.isControlDown()) {
model.mouseYPressed = 0;
repaintCanvas();
}
model.upPressed.set(false);
}
if (key.getCode() == KeyCode.DOWN) {
if (key.isControlDown()) {
model.mouseYNow = (int) getHeight();
repaintCanvas();
}
model.downPressed.set(false);
}
if (key.getCode() == KeyCode.A && key.isControlDown())
selectWholeScreen();
if (key.getCode() == KeyCode.H)
model.hideExtraFeatures.set(false);
if (key.getCode() == KeyCode.ESCAPE || key.getCode() == KeyCode.BACK_SPACE) {
// Stop Counting Thread
if (countingThread != null)
countingThread.interrupt();
// Stop MaryTTS
Main.textToSpeech.stopSpeaking();
// Deactivate all keys
deActivateAllKeys();
// show the appropriate windows
Main.stage.show();
close();
} else if (key.getCode() == KeyCode.ENTER || key.getCode() == KeyCode.SPACE) {
// Stop MaryTTS
Main.textToSpeech.stopSpeaking();
// Deactivate all keys
deActivateAllKeys();
// Capture Selected Area
prepareImage();
}
});
model.anyPressed.addListener((obs , wasPressed , isNowPressed) ->
{
if (isNowPressed.booleanValue()) {
yPressedAnimation.start();
} else {
yPressedAnimation.stop();
}
});
}
/**
* Deactivates the keys contained into this method.
*/
private void deActivateAllKeys() {
model.shiftPressed.set(false);
model.upPressed.set(false);
model.rightPressed.set(false);
model.downPressed.set(false);
model.leftPressed.set(false);
model.hideExtraFeatures.set(false);
}
/**
* Creates and saves the image.
*/
public void prepareImage() {
// return if it is alive
if ( ( countingThread != null && countingThread.isAlive() ) || captureService.isRunning())
return;
countingThread = new Thread(() -> {
mainCanvas.setDisable(true);
boolean interrupted = false;
// CountDown
if (!mainWindowController.getTimeSlider().isDisabled()) {
for (int i = (int) mainWindowController.getTimeSlider().getValue(); i > 0; i--) {
final int a = i;
// Lock until it has been refreshed from JavaFX
// Application Thread
CountDownLatch count = new CountDownLatch(1);
// Repaint the Canvas
Platform.runLater(() -> {
gc.clearRect(0, 0, getWidth(), getHeight());
gc.setFill(model.background);
gc.fillRect(0, 0, getWidth(), getHeight());
gc.setFill(Color.BLACK);
gc.fillOval(getWidth() / 2 - 90, getHeight() / 2 - 165, 250, 250);
gc.setFill(Color.WHITE);
gc.setFont(Font.font("", FontWeight.BOLD, 120));
gc.fillText(Integer.toString(a), getWidth() / 2, getHeight() / 2);
// Unlock the Parent Thread
count.countDown();
});
try {
// Wait JavaFX Application Thread
count.await();
// MaryTTS
if (settingsWindowController.getMarryTTSToggle().isSelected())
Main.textToSpeech.speak(i);
// Sleep 1 seconds after that
Thread.sleep(980);
} catch (InterruptedException ex) {
interrupted = true;
mainCanvas.setDisable(false);
countingThread.interrupt();
Logger.getLogger(getClass().getName()).log(Level.INFO, null, ex);
break;
}
}
}
// !interrupted?
if (!Thread.interrupted()) {
// MaryTTS
if (settingsWindowController.getMarryTTSToggle().isSelected())
Main.textToSpeech.speak("Select where the image will be saved.");
Platform.runLater(() -> {
// Clear the canvas
gc.clearRect(0, 0, getWidth(), getHeight());
// Wait for frame Render
waitFrameRender.start();
});
} // !interrupted?
});
countingThread.setDaemon(true);
countingThread.start();
}
/**
* Repaint the canvas of the capture window.
*/
protected void repaintCanvas() {
gc.clearRect(0, 0, getWidth(), getHeight());
gc.setFont(model.font);
// draw the actual rectangle
gc.setStroke(Color.AQUA);
gc.setFill(model.background);
gc.setLineWidth(1);
// smart calculation of where the mouse has been dragged
model.rectWidth = ( model.mouseXNow > model.mouseXPressed ) ? model.mouseXNow - model.mouseXPressed // RIGHT
: model.mouseXPressed - model.mouseXNow // LEFT
;
model.rectHeight = ( model.mouseYNow > model.mouseYPressed ) ? model.mouseYNow - model.mouseYPressed // DOWN
: model.mouseYPressed - model.mouseYNow // UP
;
model.rectUpperLeftX = // -------->UPPER_LEFT_X
( model.mouseXNow > model.mouseXPressed ) ? model.mouseXPressed // RIGHT
: model.mouseXNow// LEFT
;
model.rectUpperLeftY = // -------->UPPER_LEFT_Y
( model.mouseYNow > model.mouseYPressed ) ? model.mouseYPressed // DOWN
: model.mouseYNow // UP
;
gc.strokeRect(model.rectUpperLeftX - 1.00, model.rectUpperLeftY - 1.00, model.rectWidth + 2.00, model.rectHeight + 2.00);
gc.fillRect(model.rectUpperLeftX, model.rectUpperLeftY, model.rectWidth, model.rectHeight);
// draw the circles
if (!model.hideExtraFeatures.getValue()) {
// Show the Size
double middle = model.rectUpperLeftX + model.rectWidth / 2.00;
gc.setLineWidth(1);
gc.setStroke(Color.AQUA);
gc.strokeRect(middle - 78, model.rectUpperLeftY < 25 ? model.rectUpperLeftY + 2 : model.rectUpperLeftY - 26.00, 79, 25);
gc.setFill(Color.rgb(0, 0, 00, 0.9));
gc.fillRect(middle - 77, model.rectUpperLeftY < 25 ? model.rectUpperLeftY + 2 : model.rectUpperLeftY - 25.00, 77, 23);
gc.setFill(Color.WHITE);
gc.fillText(model.rectWidth + "," + model.rectHeight, middle - 77 + 9,
model.rectUpperLeftY < 25 ? model.rectUpperLeftY + 17.00 : model.rectUpperLeftY - 6.00);
}
}
/**
* Selects whole Screen.
*/
private void selectWholeScreen() {
model.mouseXPressed = 0;
model.mouseYPressed = 0;
model.mouseXNow = (int) getWidth();
model.mouseYNow = (int) getHeight();
repaintCanvas();
}
/**
* Prepares the Window for the User.
*/
public void prepareForCapture() {
show();
repaintCanvas();
Main.stage.close();
settingsWindowController.close();
if (settingsWindowController.getMarryTTSToggle().isSelected())
Main.textToSpeech.speak("Select an area of the screen dragging your mouse and then press Enter or Space");
}
/**
* Return an array witch contains the (UPPER_LEFT) Point2D of the rectangle
* and the width and height of the rectangle.
*
* #return An array witch contains the (UPPER_LEFT) Point2D of the
* rectangle
* and the width and height of the rectangle
*/
public int[] getRectangleBounds() {
return new int[]{ model.rectUpperLeftX , model.rectUpperLeftY , model.rectWidth , model.rectHeight };
}
/**
* The work of the Service is to capture the Image based on the rectangle
* that user drawn of the Screen.
*
* #author GOXR3PLUS
*/
public class CaptureService extends Service<Boolean> {
/** The file path. */
String filePath;
/** The image. */
BufferedImage image;
/**
* Constructor.
*/
public CaptureService() {
setOnSucceeded(s -> done());
setOnCancelled(c -> done());
setOnFailed(f -> done());
}
/**
* Starts the Service.
*
* #param image2 The image to be saved.
*/
public void startService(BufferedImage image2) {
if (!isRunning()) {
this.image = image2;
// Show the SaveDialog
fileSaver.get().setInitialFileName("ScreenShot" + model.random.nextInt(50000));
File file = fileSaver.get().showSaveDialog(CaptureWindowController.this);
if (file != null) {
filePath = file.getAbsolutePath();
reset();
start();
} else
repaintCanvas();
}
}
/**
* Service has been done.
*/
private void done() {
Main.stage.show();
close();
if (getValue()) // successful?
Notifications.create().title("Successfull Capturing").text("Image is being saved at:\n" + filePath)
.showInformation();
else
Notifications.create().title("Error").text("Failed to capture the Screen!").showError();
}
/* (non-Javadoc)
* #see javafx.concurrent.Service#createTask() */
#Override
protected Task<Boolean> createTask() {
return new Task<Boolean>() {
#Override
protected Boolean call() throws Exception {
boolean written = false;
// Try to write the file to the disc
try {
written = ImageIO.write(image, fileSaver.get().getSelectedExtensionFilter().getDescription(),
new File(filePath));
} catch (IOException ex) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex);
return written;
}
return written;
}
};
}
}
}
Related
I am trying to use one MainActivity.java class in Android Studio to control an RC car, I am still in the works as this is an early prototype code.
I am currently struggling on why my surfaceHolder.getSurface().isValid() is returning false every time. I cannot find any help besides implementing a Callback class, however, previously I have been able to use precisely the same lines of code mimicking my surfaceHolder and never needed a Callback class, the only exception was the class extended SurfaceView which this one does not because I can only extend one class. Below is my code:
package com.example.hughman.cccontrol;
import android.graphics.Point;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.widget.ImageButton;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
public class MainActivity extends AppCompatActivity implements View.OnClickListener, Runnable{
private ImageButton breakButton, connectButton;
private Bitmap left_thumb, left_thumb_resized, right_thumb_resized;
private Context context;
private int screenW, screenH, thumb_x, thumb_y, drag_x, drag_y, current_thumb = -1;
private Canvas canvas;
private Paint paint = new Paint();
private SurfaceHolder surfaceHolder;
private Thread thread;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
SurfaceView surfaceView = new SurfaceView(this);
paint.setAntiAlias(true);
//puts the app in full screen mode
requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
surfaceHolder = surfaceView.getHolder();
//initalize screen size
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
screenH = size.x;
screenW = size.x;
//initialize buttons
breakButton = findViewById(R.id.breakbutton);
connectButton = findViewById(R.id.connectbutton);
breakButton.setOnClickListener(this);
connectButton.setOnClickListener(this);
left_thumb = BitmapFactory.decodeResource(this.getResources(), R.drawable.thumbstick);
left_thumb_resized = Bitmap.createScaledBitmap(left_thumb, 500, 500, false);
right_thumb_resized = Bitmap.createScaledBitmap(left_thumb, 500, 500, false);
thread = new Thread(this);
thread.start();
}
#Override
public void run() {
if(current_thumb == -1) {
drawInitialBitmap();
}
else
{
reDraw(drag_x, drag_y);
}
System.out.println("Run Test");
setFPS();
}
public void drawInitialBitmap()
{
thumb_x = 800;
thumb_y = (screenH / 2) + 250;
if (surfaceHolder.getSurface().isValid()) {
System.out.println("Hello");
canvas = surfaceHolder.lockCanvas();
canvas.drawBitmap(left_thumb_resized, thumb_x, thumb_y, paint);
canvas.drawBitmap(right_thumb_resized, thumb_x * 2, thumb_y, paint);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
public void reDraw(int x, int y)
{
if (surfaceHolder.getSurface().isValid()) {
canvas = surfaceHolder.lockCanvas();
if (current_thumb == 1) {
canvas.drawBitmap(left_thumb_resized, x, y, paint);
} else if (current_thumb == 2) {
canvas.drawBitmap(right_thumb_resized, x, y, paint);
}
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
//method for when user selects an xml button
public void onClick(View v) {
if (v == connectButton) {
//prompts to connect to Bluetooth
System.out.println("ConectButton");
}
else if(v == breakButton)
{
System.out.println("BreakButton");
//halt all motors
}
}
public boolean onTouchEvent(MotionEvent event){
int action = event.getAction();
int drag_x = (int) event.getX(); // or getRawX();
int drag_y = (int) event.getY();
switch(action){
case MotionEvent.ACTION_DOWN:
if (drag_x >= thumb_x && drag_x < (thumb_x + left_thumb_resized.getWidth())
&& drag_y >= thumb_y && drag_y < (thumb_y + left_thumb_resized.getHeight())) {
current_thumb = 1;
return true;
}
else if (drag_x >= thumb_x * 2 && drag_x < ((thumb_x * 2) + right_thumb_resized.getWidth())
&& drag_y >= thumb_y && drag_y < (thumb_y + right_thumb_resized.getHeight())) {
current_thumb = 2;
return true;
}
break;
case MotionEvent.ACTION_MOVE:
if(current_thumb != -1)
{
System.out.println(Math.sqrt(Math.pow(drag_x - thumb_x, 2) + Math.pow(drag_y - thumb_y, 2)));
System.out.println(Math.sqrt(Math.pow(drag_x - (thumb_x * 2), 2) + Math.pow(drag_y - thumb_y, 2)));
//move left thumb
return true;
}
break;
case MotionEvent.ACTION_UP:
current_thumb = -1;
drawInitialBitmap();
break;
default:
}
return false;
}
public void setFPS() {
try {
thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Everything runs fine and builds properly, but neither thumb sticks are being drawn. I haven't restricted the movement of the thumb sticks, but later on I will add that. As for now, I want to be able to drag them in the direction I want to move, using the distance function to simply figure out how far I ideally want to be able to drag them.
Is it possible to do this without extending SufaceView? Or making a Callback class? Why? Below I will add the code from my previous project where I did not use a Callback class, keep in mind the code is messy as we had multiple people working on the project and it had a required deadline of 24 hours.
package com.example.michael.doyouknowdeway;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageButton;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.MediaPlayer;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Created by michael on 1/27/18.
* The big class that does most of the work for the code, sets the overall view of the game, getting
* the character, tiles, and player motions all together for a working game. Took us the most time
* to work on with many bugs occuring along de wae.
*/
public class GameView extends SurfaceView implements Runnable{
//
volatile boolean isPlaying = true, init = true, isPassOver = true;
private Thread gameThread = null;
private SurfaceHolder surfaceHolder;
private Canvas canvas;
private Context context;
private Activity activity;
private int screenWidth = 0, screenHeight = 0, move_const = 1;
private Player player;
private MediaPlayer jumpNoise, eatNoise;
private Bitmap backgroundImage;
private MediaPlayer backgroundMusic;
private MediaPlayer endGameSound;
private Bitmap backgroundImageResized;
Tile currentTile, nextTile;
private ScheduledExecutorService executorService;
private Paint paint = new Paint();
private Paint textPaint = new Paint();
private FireBall fireball;
private int scoreCount = 0, passOver;
private Bitmap endImage;
private Bitmap endImageResized;
private Bitmap run1, podCount;
private Bitmap run1Resized, podCountResized;
private Bitmap run2;
private Bitmap playerJumpImage;
private boolean isRun1 = false;
private ImageButton redoButton;
//starts of the program, creating the background for the game based on the dimensions of the
//phone being used
public GameView(Context context, int screenX, int screenY) {
super(context);
//load images into game
backgroundImage = BitmapFactory.decodeResource(context.getResources(), R.drawable.background_sky);
backgroundImageResized = Bitmap.createScaledBitmap(backgroundImage, screenX, screenY, false);
podCount = BitmapFactory.decodeResource(context.getResources(), R.drawable.detergent_pod);
run1 = BitmapFactory.decodeResource(context.getResources(), R.drawable.knuckles_run);
run1Resized = Bitmap.createScaledBitmap(run1, 200, 200, false);
podCountResized = Bitmap.createScaledBitmap(podCount, 100, 100, false);
run2 = BitmapFactory.decodeResource(context.getResources(), R.drawable.ugandan_knuckle);
playerJumpImage = BitmapFactory.decodeResource(context.getResources(), R.drawable.knucklesjump);
//load sounds into game
jumpNoise = MediaPlayer.create(context, R.raw.jump_takeoff);
eatNoise = MediaPlayer.create(context, R.raw.eat_1);
backgroundMusic = MediaPlayer.create(context, R.raw.music_baby);
endGameSound = MediaPlayer.create(context, R.raw.end_game);
//initialize other important stuff
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(72);
textPaint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
screenWidth = screenX;
screenHeight = screenY;
activity = (Activity) context;
backgroundMusic.start();
this.context = context;
player = new Player(context, screenX, screenY);
fireball = new FireBall(context, screenX, screenY);
currentTile = new Tile(context, 3, screenWidth + 400, screenHeight);
currentTile.fillTile();
surfaceHolder = getHolder();
//controls "running" animation
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
if(!player.isJumping) {
if (!isRun1) {
run1Resized = Bitmap.createScaledBitmap(run1, 200, 200, false);
isRun1 = true;
} else {
run1Resized = Bitmap.createScaledBitmap(run2, 200, 200, false);
isRun1 = false;
}
}
}
}, 0, 200, TimeUnit.MILLISECONDS); //can change "speed" of run by altering the second param
}
/**
* Main game loop
*/
public void run() {
while (isPlaying) {
update();
draw();
setFPS();
}
}
/**
* Redraws the screen in the new positions, creating continuous movement for the game
*/
public void draw() {
if (surfaceHolder.getSurface().isValid()) {
canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(backgroundImageResized, 0, 0, paint);
canvas.drawBitmap(podCountResized, 0, 0, paint);
String scoreCountStr = Integer.toString(scoreCount);
if(0 >= (currentTile.getLength() * 100) - move_const)
{
currentTile = new Tile(nextTile);
isPassOver = true;
nextTile = null;
move_const = 0;
}
if(init) {
init = false;
for (int i = 0; i < currentTile.getLength(); i++) {
for (int j = currentTile.getHeight() - 1; j >= 0; j--) {
if (currentTile.getBlock(i, j) != null) {
canvas.drawBitmap(currentTile.getBlock(i, j).getImage(), (i * 100), (j * 100) + 10, paint);
}
}
}
}
else
{
for (int i = 0; i < currentTile.getLength(); i++) {
for (int j = currentTile.getHeight() - 1; j >= 0; j--) {
if (currentTile.getBlock(i, j) != null) {
canvas.drawBitmap(currentTile.getBlock(i, j).getImage(), (i * 100) - move_const, (j * 100) + 10, paint);
}
if (nextTile != null) {
if (i < nextTile.getLength() && j < nextTile.getHeight()) {
if (nextTile.getBlock(i, j) != null) {
canvas.drawBitmap(nextTile.getBlock(i, j).getImage(), ((i + currentTile.getLength()) * 100) - move_const, (j * 100) + 10, paint);
}
}
}
}
}
move_const += 10;
}
if(fireball.isShooting) {
canvas.drawBitmap(fireball.getImage(), fireball.getXVal(), fireball.getYVal(), paint);
}
canvas.drawBitmap(run1Resized,player.getXVal(), player.getYVal(), paint);
canvas.drawText(scoreCountStr, 120, 80, textPaint);
//releases the canvas to be redrawn again
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
/**
* updates the positions of everything on the screen if needed
*/
public void update() {
player.update();
if(fireball.isShooting) {
fireball.update(player);
}
if((((currentTile.getLength() - (screenWidth/100) )* 100) - move_const <= 200) && nextTile == null){
nextTile = currentTile.getNextTile();
}
if(player.getYVal() >= screenHeight || player.getXVal() <= 0){
gameOver();
}
detectCollisions();
}
//initially sets player to not be colliding - changed almost instantly
static boolean isColliding = false;
//
/**
* Detects collisions so the player can collect tide pods and jump off of surfaces.
*/
public void detectCollisions(){
int highestY = 9;
int currentX = (250 + move_const)/100;
if(currentX >= currentTile.getLength() && isPassOver)
{
passOver = -10;
isPassOver = false;
}
else
{
passOver= -25; //arbitrary
}
for(int i = 0; i < currentTile.getHeight(); i++)
{
if(currentX >= currentTile.getLength())
{
passOver += 10;
if(nextTile.getBlock(passOver/100, i) != null)
{
if(!nextTile.getBlock(passOver/100, i).isPod())
{
highestY = i;
break;
}
}
}
else if(currentTile.getBlock(currentX, i) != null)
{
if(!currentTile.getBlock(currentX, i).isPod())
{
highestY = i;
break;
}
}
else
{
highestY = -1;
}
}
checkGroundCollision(highestY);
checkTidePodCollision(currentTile, nextTile);
checkForwardCollision(nextTile, currentX, highestY, passOver);
}
/**
* Method used to check if the player has hit a wall in front of them.
* #param next - the next tile
* #param x - player x position
* #param y - player y position
* #param passOver - the location being passed over.
*/
public void checkForwardCollision(Tile next, int x, int y, int passOver)
{
boolean collision = false;
Rect rect = new Rect();
if(next != null && passOver >= 0)
{
rect.top = y * 100;
rect.bottom = screenHeight;
rect.left = passOver + (player.getBitmap().getWidth() / 2);
rect.right = passOver + (player.getBitmap().getWidth() / 2) + 100;
collision = Rect.intersects(player.getHitBox(), rect);
}
else
{
rect.top = y * 100;
rect.bottom = screenHeight;
rect.left = (x+1)* 100;
rect.right = (x+2) * 100;
collision = Rect.intersects(player.getHitBox(), rect);
}
//if collision is true, half player movement until its not true
}
/**
* Method that checks if a player has hit a tidepod, if so, adds to score count
* #param current - the current tile
* #param next - the next tile
*/
public void checkTidePodCollision(Tile current, Tile next)
{
if(next != null && !isPassOver)
{
for(double iter: next.getTidePods())
{
int x = (int) iter;
int y = (int) (iter - x)*10;
boolean hit = podCollision(x, y);
if(hit)
{
eatNoise.start();
scoreCount += 10;
System.out.println("Cur Next: " + x + " || " + y);
nextTile.setNullBlock(x, y);
return;
}
}
}
else
{
for(double iter: current.getTidePods())
{
int x = (int) iter;
double temp = x;
int y = (int) ((iter - temp)*10.00);
boolean hit = podCollision(x, y);
if(hit)
{
eatNoise.start();
scoreCount += 10;
System.out.println("Current: " + x + " || " + y);
currentTile.setNullBlock(x, y);
return;
}
}
}
}
//compliment to the previous method
private boolean podCollision(int x, int y) {
Rect tideRect = new Rect();
if(isPassOver && x < 3)
{
System.out.println("TEST: " + x + " || " + y);
tideRect.top = (y * 100);
tideRect.left = x * 100 + passOver + 10;
tideRect.right = (x + 2) * 100 + passOver;
tideRect.bottom = (y + 2) * 100 + 10;
}
else {
tideRect.top = y * 100;
tideRect.left = x * 100 - move_const + 10;
tideRect.right = (x + 2) * 100 - move_const;
tideRect.bottom = (y + 2) * 100 + 10;
}
return Rect.intersects(player.getHitBox(), tideRect);
}
/**
* Method used for jumping off of the ground.
* #param highestY
*/
private void checkGroundCollision(int highestY) {
Rect blockRect = new Rect();
boolean GroundCollision;
if(highestY >= 0) {
blockRect.top = (highestY) * 100 - 25;
blockRect.left = 200;
blockRect.right = 300;
//changed this valued -- this is to remind myself
blockRect.bottom = highestY * 100 + 25; //still needs work //make player hitbox just his feet
GroundCollision = Rect.intersects(player.getFeetBox(), blockRect);
System.out.println("WWWWWW : " + GroundCollision);
}
else
{
GroundCollision = false;
}
if(GroundCollision){
isColliding = true;
} else {
player.isFalling = true;
isColliding = false;
}
}
/**
* Sets the FPS to roughly 60 fps
*/
public void setFPS() {
try {
gameThread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Method that
*/
public void gameOver() {
//end image is not currently working
endImage = BitmapFactory.decodeResource(context.getResources(), R.drawable.end_game);
endImageResized = Bitmap.createScaledBitmap(endImage, 100, 200, false);
canvas.drawBitmap(endImageResized, screenWidth/2, screenHeight/2, paint);
//free up memory from bitmaps
backgroundImage.recycle();
backgroundImage = null;
backgroundImageResized.recycle();
backgroundImageResized = null;
podCount.recycle();
podCount = null;
podCountResized.recycle();
podCountResized = null;
backgroundImage.recycle();
backgroundImage = null;
run1.recycle();
run1 = null;
run1Resized.recycle();
run1Resized = null;
run2.recycle();
run2 = null;
playerJumpImage.recycle();
playerJumpImage = null;
backgroundMusic.stop();
Runtime.getRuntime().gc(); //manually run garbage collector
endGameSound.start();
context.startActivity(new Intent(context,MainActivity.class));
}
/**
* Method used to dictate what to do when the android screen is touched.
* #param event - the type of touch on the screen
* #return - true when screen is touched
*/
public boolean onTouchEvent(MotionEvent event){
int touchAction = event.getAction();
if(touchAction == MotionEvent.ACTION_DOWN){
if(event.getX() < (screenWidth / 2)) {
jumpNoise.start();
if(!player.isJumping && !player.isFalling) {
player.setYval(player.getYVal());
player.isJumping = true;
run1Resized = Bitmap.createScaledBitmap(playerJumpImage, 200, 200, false);
}
else if(player.getJumpCount() < 1)
{
player.setYval(player.getYVal());
player.incrJump();
player.isJumping = true;
run1Resized = Bitmap.createScaledBitmap(playerJumpImage, 200, 200, false);
}
} else {
fireball.setOnScreen(true);
}
}
return true;
}
/**
* Method that pauses the game when necessary (i.e when home button is pressed)
*/
public void pause() {
isPlaying = false;
backgroundMusic.pause();
try {
gameThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Resumes the game after a pause
*/
public void resume() {
isPlaying = true;
backgroundMusic.start();
gameThread = new Thread(this);
gameThread.start();
}
}
Thank you for your time & any help is appreciated!
EDIT: Also using bitmaps to redraw etc. as that is what I am used to.
I want to create a button that has an icon with text on the top and arrow on the corner. Upon clicking on it, a menu is displayed. I was able to achieve all of above except for the arrow part. I know I can have an an image file that has the icon and arrow saved in one png file. However, I don't want to alter the icon file. Here is the code I have so far.
How can I add an arrow ( whether programmtically or from another arrow image file to the button)
public class JButtonMenu extends JToggleButton {
JPopupMenu popup;
public JButtonMenu(ImageIcon img, String title, String []list) {
super(name);
this.popup = new JPopupMenu();
this.buttonId = buttonId;
this.setMenuList(list); //This is another method
setIcon(img);
setVerticalTextPosition(SwingConstants.TOP);
setHorizontalTextPosition(SwingConstants.CENTER);
}
public void setMenuList(String[]list){
if(list == null){
return;
}
for(String item:list){
popup.add(new JMenuItem(new AbstractAction(item) {
public void actionPerformed(ActionEvent e) {
JMenuItem menuItem = (JMenuItem)e.getSource();
int index= popup.getComponentIndex(menuItem);
menuItemListener.itemSelectedListener(buttonId,index, menuItem.getText());
}
}));
}
}
}
This is basically a watered down version of this implementation of a split button but which focuses on the need for painting an additional image as well as some of the other functionality you'll need to implement to ensure that the original text and icon are offset accurtaly.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
SplitButton btn = new SplitButton();
btn.setText("This is a split button");
JFrame frame = new JFrame("Testing");
frame.setLayout(new GridBagLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(btn);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class SplitButton extends JButton {
private int separatorSpacing = 4;
private int splitWidth = 30;
private int arrowSize = 8;
private Rectangle splitRectangle;
private Color arrowColor = Color.BLACK;
private Color disabledArrowColor = Color.GRAY;
private Image image;
public SplitButton() {
super();
}
#Override
public Insets getInsets() {
Insets insets = (Insets) super.getInsets().clone();
insets.right += splitWidth;
return insets;
}
#Override
public Insets getInsets(Insets insets) {
Insets insets1 = getInsets();
insets.left = insets1.left;
insets.right = insets1.right;
insets.bottom = insets1.bottom;
insets.top = insets1.top;
return insets1;
}
/**
* Returns the separatorSpacing. Separator spacing is the space above and
* below the separator( the line drawn when you hover your mouse over the
* split part of the button).
*
* #return separatorSpacingimage = null; //to repaint the image with the new
* size
*/
public int getSeparatorSpacing() {
return separatorSpacing;
}
/**
* Sets the separatorSpacing.Separator spacing is the space above and below
* the separator( the line drawn when you hover your mouse over the split
* part of the button).
*
* #param spacing
*/
public void setSeparatorSpacing(int spacing) {
if (spacing != separatorSpacing && spacing >= 0) {
int old = separatorSpacing;
this.separatorSpacing = spacing;
image = null;
firePropertyChange("separatorSpacing", old, separatorSpacing);
revalidate();
repaint();
}
}
/**
* Gets the color of the arrow.
*
* #return arrowColor
*/
public Color getArrowColor() {
return arrowColor;
}
/**
* Set the arrow color.
*
* #param color
*/
public void setArrowColor(Color color) {
if (arrowColor != color) {
Color old = arrowColor;
this.arrowColor = color;
image = null;
firePropertyChange("arrowColor", old, arrowColor);
repaint();
}
}
/**
* gets the disabled arrow color
*
* #return disabledArrowColor color of the arrow if no popup attached.
*/
public Color getDisabledArrowColor() {
return disabledArrowColor;
}
/**
* sets the disabled arrow color
*
* #param color color of the arrow if no popup attached.
*/
public void setDisabledArrowColor(Color color) {
if (disabledArrowColor != color) {
Color old = disabledArrowColor;
this.disabledArrowColor = color;
image = null; //to repaint the image with the new color
firePropertyChange("disabledArrowColor", old, disabledArrowColor);
}
}
/**
* Splitwidth is the width of the split part of the button.
*
* #return splitWidth
*/
public int getSplitWidth() {
return splitWidth;
}
/**
* Splitwidth is the width of the split part of the button.
*
* #param width
*/
public void setSplitWidth(int width) {
if (splitWidth != width) {
int old = splitWidth;
this.splitWidth = width;
firePropertyChange("splitWidth", old, splitWidth);
revalidate();
repaint();
}
}
/**
* gets the size of the arrow.
*
* #return size of the arrow
*/
public int getArrowSize() {
return arrowSize;
}
/**
* sets the size of the arrow
*
* #param size
*/
public void setArrowSize(int size) {
if (arrowSize != size) {
int old = arrowSize;
this.arrowSize = size;
image = null; //to repaint the image with the new size
firePropertyChange("setArrowSize", old, arrowSize);
revalidate();
repaint();
}
}
/**
* Gets the image to be drawn in the split part. If no is set, a new image
* is created with the triangle.
*
* #return image
*/
public Image getImage() {
if (image == null) {
Graphics2D g = null;
BufferedImage img = new BufferedImage(arrowSize, arrowSize, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) img.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
g.setColor(isEnabled() ? arrowColor : disabledArrowColor);
//this creates a triangle facing right >
g.fillPolygon(new int[]{0, 0, arrowSize / 2}, new int[]{0, arrowSize, arrowSize / 2}, 3);
g.dispose();
//rotate it to face downwards
img = rotate(img, 90);
BufferedImage dimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
g = (Graphics2D) dimg.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(img, null, 0, 0);
g.dispose();
for (int i = 0; i < dimg.getHeight(); i++) {
for (int j = 0; j < dimg.getWidth(); j++) {
if (dimg.getRGB(j, i) == Color.WHITE.getRGB()) {
dimg.setRGB(j, i, 0x8F1C1C);
}
}
}
image = Toolkit.getDefaultToolkit().createImage(dimg.getSource());
}
return image;
}
/**
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Graphics gClone = g.create();//EDIT: Hervé Guillaume
Color oldColor = g.getColor();
splitRectangle = new Rectangle(getWidth() - splitWidth, 0, splitWidth, getHeight());
g.translate(splitRectangle.x, splitRectangle.y);
int mh = getHeight() / 2;
int mw = splitWidth / 2;
g.drawImage(getImage(), mw - arrowSize / 2, mh + 2 - arrowSize / 2, null);
if (getModel().isRollover() || isFocusable()) {
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.background"));
g.drawLine(1, separatorSpacing + 2, 1, getHeight() - separatorSpacing - 2);
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.shadow"));
g.drawLine(2, separatorSpacing + 2, 2, getHeight() - separatorSpacing - 2);
}
g.setColor(oldColor);
g.translate(-splitRectangle.x, -splitRectangle.y);
}
/**
* Rotates the given image with the specified angle.
*
* #param img image to rotate
* #param angle angle of rotation
* #return rotated image
*/
private BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.rotate(Math.toRadians(angle), w / 2, h / 2);
g.drawImage(img, null, 0, 0);
return dimg;
}
}
}
Swing has a well defined and documented painting process, in order to perform custom painting you need to work within the constraints of the API otherwise you will end up with no end ot issues.
Take a look at Painting in AWT and Swing and Performing Custom Painting for more details
A quick way to do it would be
Image img1=imageIcon1.getImage();
Image img2=imageIcon2.getImage();
Graphics g=img1.getGraphics();
g.drawImage(img2, x, y, sizex, sizey, null)
where x, y is where you put the second icon (arrow) on the first icon and sizex, sizey is the reduced size of the second icon. You can change these by trying.
You have to have another method
public JButtonMenu(ImageIcon imageIcon1, ImageIcon imageIcon2, String title, String []list) {
// above segment here
// continue with the rest of the code
}
I need to find the way to show several actions inside of another action like Netbeans does with Run Main Project icon.
You can see there is a default action Run Main Project and if you click in the little arrow next to the green play icon, you can select a specific action like Run .
I was checking the code of Netbeans but I can't find the code to make this in my application.
Ah, (one of) the holy grails of UI components, a split button. Over a number of years I've tried to find one which would perform well under multiple look and feels and failed dismally.
Many used multiple buttons or just resorted to using a JComboBox
Like many things, I stumbled across one which was pretty well done, but which I had to modify to suit my needs, unfortunately, I don't remember the original version or author, sorry. (If you believe this code is based on yours, please leave a comment with a link to the original and I will evaluate and provide appropriate credit)
Basically, if you click the button, it will run the "default" action (Bananas) otherwise you can select one of the sub elements and it will execute it
public class SplitButton extends JButton {
private int separatorSpacing = 4;
private int splitWidth = 22;
private int arrowSize = 8;
private boolean onSplit;
private Rectangle splitRectangle;
private JFrame popupMenu;
private boolean alwaysDropDown;
private Color arrowColor = Color.BLACK;
private Color disabledArrowColor = Color.GRAY;
private Image image;
private MouseHandler mouseHandler;
private boolean toolBarButton;
private PopupWindowEventHandler popupWindowEventHandler;
/**
* Creates a button with initial text and an icon.
*
* #param text the text of the button
* #param icon the Icon image to display on the button
*/
public SplitButton() {
super();
addMouseMotionListener(getMouseHandler());
addMouseListener(getMouseHandler());
// Default for no "default" action...
setAlwaysDropDown(true);
InputMap im = getInputMap(WHEN_FOCUSED);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "PopupMenu.close");
am.put("PopupMenu.close", new ClosePopupAction());
}
public SplitButton(Action defaultAction, Action... actions) {
this();
setAction(defaultAction);
for (Action action : actions) {
addAction(action);
}
}
public SplitButton(String text, Icon icon, Action... actions) {
this((Action) null, actions);
setText(text);
setIcon(icon);
}
public SplitButton(String text, Action... actions) {
this((Action) null, actions);
setText(text);
}
public JSplitButton(Icon icon, Action... actions) {
this((Action) null, actions);
setIcon(icon);
}
#Override
public void setAction(Action a) {
super.setAction(a);
if (a != null) {
setAlwaysDropDown(false);
}
}
/**
* Creates a pre-configured button suitable for being used on a JToolBar
*
* #param defaultAction
* #param actions
* #return
*/
public static SplitButton createToolBarButton(Action defaultAction, Action... actions) {
JSplitButton btn = new JSplitButton(defaultAction, actions);
btn.configureForToolBar();
return btn;
}
/**
* Creates a pre-configured "options only" button suitable for being used on
* a JToolBar
*
* #param text
* #param icon
* #param actions
* #return
*/
public static SplitButton createToolBarButton(String text, Icon icon, Action... actions) {
JSplitButton btn = new JSplitButton(icon, actions);
btn.setToolTipText(text);
btn.configureForToolBar();
return btn;
}
/**
* Used to determine if the button is begin configured for use on a tool bar
*
* #return
*/
public boolean isToolBarButton() {
return toolBarButton;
}
/**
* Configures this button for use on a tool bar...
*/
public void configureForToolBar() {
toolBarButton = true;
if (getIcon() != null) {
setHideActionText(true);
}
setHorizontalTextPosition(JButton.CENTER);
setVerticalTextPosition(JButton.BOTTOM);
setFocusable(false);
}
protected MouseHandler getMouseHandler() {
if (mouseHandler == null) {
mouseHandler = new MouseHandler();
}
return mouseHandler;
}
protected AbstractButton getButtonFor(Action action) {
Container parent = ((JFrame) getPopupWindow()).getContentPane();
AbstractButton btn = null;
for (Component comp : parent.getComponents()) {
if (comp instanceof AbstractButton) {
Action childAction = ((AbstractButton) comp).getAction();
if (action.equals(childAction)) {
btn = (AbstractButton) comp;
break;
}
}
}
return btn;
}
/**
* Returns the index of the specified action within the popup window or -1
* of it does not exist
*
* #param action
* #return
*/
public int indexOfAction(Action action) {
Container parent = ((JFrame) getPopupWindow()).getContentPane();
AbstractButton btn = getButtonFor(action);
return btn == null ? -1 : parent.getComponentZOrder(btn);
}
/**
* Adds the specified action to the popup menu...
*
* This simply calls getPopupWindow().add(action)
*
* #param action Add
*/
public void addAction(Action action) {
addActionAt(action, -1);
}
protected int getOptionsCount() {
return ((JFrame) getPopupWindow()).getContentPane().getComponentCount();
}
protected void addActionAt(Action action, int index) {
if (index < 0 || index >= getOptionsCount()) {
getPopupWindow().add(createMenuItem(action));
} else {
getPopupWindow().add(createMenuItem(action), index);
}
}
protected void removeAction(Action action) {
AbstractButton btn = getButtonFor(action);
if (btn != null) {
getPopupWindow().remove(btn);
}
}
/**
* Creates a new JMenuItem from the supplied Action. This is used to
* provided the ability for subclasses to either change the type of menu
* item used by the button or add additional functionality (like listeners)
* should they be required
*
* #param action
* #return
*/
protected JMenuItem createMenuItem(Action action) {
return new JMenuItem(action);
}
#Override
public Insets getInsets() {
Insets insets = (Insets) super.getInsets().clone();
insets.right += splitWidth;
return insets;
}
#Override
public Insets getInsets(Insets insets) {
Insets insets1 = getInsets();
insets.left = insets1.left;
insets.right = insets1.right;
insets.bottom = insets1.bottom;
insets.top = insets1.top;
return insets1;
}
/**
* Returns the window that acts as the buttons popup window
*
* #return
*/
public Window getPopupWindow() {
if (popupMenu == null) {
popupMenu = new JFrame();
popupMenu.setFocusableWindowState(false);
popupMenu.setUndecorated(true);
popupMenu.setContentPane(createPopupWindowContentPane());
popupMenu.setAlwaysOnTop(true);
DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if ("focusOwner".equalsIgnoreCase(name)
|| "permanentFocusOwner".equalsIgnoreCase(name)
|| "focusedWindow".equalsIgnoreCase(name)
|| "activeWindow".equalsIgnoreCase(name)) {
Window focusedWindow = DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
if (!popupMenu.equals(focusedWindow)) {
closePopupWinodw();
}
}
}
});
}
return popupMenu;
}
protected Container createPopupWindowContentPane() {
return new DefaultMenuPane();
}
protected void closePopupWinodw() {
getPopupWindow().setVisible(false);
if (popupWindowEventHandler != null) {
Toolkit.getDefaultToolkit().removeAWTEventListener(popupWindowEventHandler);
}
}
protected void showPopupWindow() {
Window popup = getPopupWindow();
popup.pack();
Point pos = getLocationOnScreen();
popup.setLocation(pos.x + (getWidth() - popup.getWidth()), pos.y + getHeight());
popup.setVisible(true);
if (popupWindowEventHandler == null) {
popupWindowEventHandler = new PopupWindowEventHandler();
}
Toolkit.getDefaultToolkit().addAWTEventListener(popupWindowEventHandler, AWTEvent.MOUSE_EVENT_MASK);
}
/**
* Returns the separatorSpacing. Separator spacing is the space above and
* below the separator( the line drawn when you hover your mouse over the
* split part of the button).
*
* #return separatorSpacingimage = null; //to repaint the image with the new
* size
*/
public int getSeparatorSpacing() {
return separatorSpacing;
}
/**
* Sets the separatorSpacing.Separator spacing is the space above and below
* the separator( the line drawn when you hover your mouse over the split
* part of the button).
*
* #param spacing
*/
public void setSeparatorSpacing(int spacing) {
if (spacing != separatorSpacing && spacing >= 0) {
int old = separatorSpacing;
this.separatorSpacing = spacing;
image = null;
firePropertyChange("separatorSpacing", old, separatorSpacing);
revalidate();
repaint();
}
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* #return true if alwaysDropdown, false otherwise.
*/
public boolean isAlwaysDropDown() {
return alwaysDropDown;
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* If true, this will prevent the button from raising any actionPerformed
* events for itself
*
* #param value true to show the attached dropdown even if the button part
* is clicked, false otherwise
*/
public void setAlwaysDropDown(boolean value) {
if (alwaysDropDown != value) {
this.alwaysDropDown = value;
firePropertyChange("alwaysDropDown", !alwaysDropDown, alwaysDropDown);
}
}
/**
* Gets the color of the arrow.
*
* #return arrowColor
*/
public Color getArrowColor() {
return arrowColor;
}
/**
* Set the arrow color.
*
* #param color
*/
public void setArrowColor(Color color) {
if (arrowColor != color) {
Color old = arrowColor;
this.arrowColor = color;
image = null;
firePropertyChange("arrowColor", old, arrowColor);
repaint();
}
}
/**
* gets the disabled arrow color
*
* #return disabledArrowColor color of the arrow if no popup attached.
*/
public Color getDisabledArrowColor() {
return disabledArrowColor;
}
/**
* sets the disabled arrow color
*
* #param color color of the arrow if no popup attached.
*/
public void setDisabledArrowColor(Color color) {
if (disabledArrowColor != color) {
Color old = disabledArrowColor;
this.disabledArrowColor = color;
image = null; //to repaint the image with the new color
firePropertyChange("disabledArrowColor", old, disabledArrowColor);
}
}
/**
* Splitwidth is the width of the split part of the button.
*
* #return splitWidth
*/
public int getSplitWidth() {
return splitWidth;
}
/**
* Splitwidth is the width of the split part of the button.
*
* #param width
*/
public void setSplitWidth(int width) {
if (splitWidth != width) {
int old = splitWidth;
this.splitWidth = width;
firePropertyChange("splitWidth", old, splitWidth);
revalidate();
repaint();
}
}
/**
* gets the size of the arrow.
*
* #return size of the arrow
*/
public int getArrowSize() {
return arrowSize;
}
/**
* sets the size of the arrow
*
* #param size
*/
public void setArrowSize(int size) {
if (arrowSize != size) {
int old = arrowSize;
this.arrowSize = size;
image = null; //to repaint the image with the new size
firePropertyChange("setArrowSize", old, arrowSize);
revalidate();
repaint();
}
}
/**
* Gets the image to be drawn in the split part. If no is set, a new image
* is created with the triangle.
*
* #return image
*/
public Image getImage() {
if (image == null) {
Graphics2D g = null;
BufferedImage img = new BufferedImage(arrowSize, arrowSize, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) img.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
g.setColor(popupMenu != null ? arrowColor : disabledArrowColor);
//this creates a triangle facing right >
g.fillPolygon(new int[]{0, 0, arrowSize / 2}, new int[]{0, arrowSize, arrowSize / 2}, 3);
g.dispose();
//rotate it to face downwards
img = rotate(img, 90);
BufferedImage dimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
g = (Graphics2D) dimg.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(img, null, 0, 0);
g.dispose();
for (int i = 0; i < dimg.getHeight(); i++) {
for (int j = 0; j < dimg.getWidth(); j++) {
if (dimg.getRGB(j, i) == Color.WHITE.getRGB()) {
dimg.setRGB(j, i, 0x8F1C1C);
}
}
}
image = Toolkit.getDefaultToolkit().createImage(dimg.getSource());
}
return image;
}
/**
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Graphics gClone = g.create();//EDIT: Hervé Guillaume
Color oldColor = g.getColor();
splitRectangle = new Rectangle(getWidth() - splitWidth, 0, splitWidth, getHeight());
g.translate(splitRectangle.x, splitRectangle.y);
int mh = getHeight() / 2;
int mw = splitWidth / 2;
g.drawImage(getImage(), mw - arrowSize / 2, mh + 2 - arrowSize / 2, null);
if (!alwaysDropDown) {
if (getModel().isRollover() || isFocusable()) {
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.background"));
g.drawLine(1, separatorSpacing + 2, 1, getHeight() - separatorSpacing - 2);
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.shadow"));
g.drawLine(2, separatorSpacing + 2, 2, getHeight() - separatorSpacing - 2);
}
}
g.setColor(oldColor);
g.translate(-splitRectangle.x, -splitRectangle.y);
}
/**
* Rotates the given image with the specified angle.
*
* #param img image to rotate
* #param angle angle of rotation
* #return rotated image
*/
private BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.rotate(Math.toRadians(angle), w / 2, h / 2);
g.drawImage(img, null, 0, 0);
return dimg;
}
#Override
protected void fireActionPerformed(ActionEvent event) {
// This is a little bit of a nasty trick. Basically this is where
// we try and decide if the buttons "default" action should
// be fired or not. We don't want it firing if the button
// is in "options only" mode or the user clicked on
// on the "drop down arrow"....
if (onSplit || isAlwaysDropDown()) {
showPopupWindow();
} else {
super.fireActionPerformed(event);
}
}
protected class MouseHandler extends MouseAdapter {
#Override
public void mouseExited(MouseEvent e) {
onSplit = false;
repaint(splitRectangle);
}
#Override
public void mouseMoved(MouseEvent e) {
if (splitRectangle.contains(e.getPoint())) {
onSplit = true;
} else {
onSplit = false;
}
repaint(splitRectangle);
}
}
protected class PopupWindowEventHandler implements AWTEventListener {
#Override
public void eventDispatched(AWTEvent event) {
if (popupMenu.isVisible()) {
switch (event.getID()) {
case MouseEvent.MOUSE_RELEASED:
Object source = event.getSource();
if (source instanceof Component) {
Window win = SwingUtilities.getWindowAncestor((Component) source);
if (!popupMenu.equals(win)) {
closePopupWinodw();
}
}
break;
}
}
}
}
protected class ClosePopupAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
closePopupWinodw();
}
}
protected class DefaultMenuPane extends JPanel {
public DefaultMenuPane() {
setBorder(UIManager.getBorder("PopupMenu.border"));
setBackground(UIManager.getColor("PopupMenu.background"));
setLayout(new GridLayout(0, 1));
}
}
}
It would be configured something like ...
SplitButton btn = new SplitButton();
btn.setAction(new FruitAction("Banana", new BananaIcon(32, 32)));
btn.addAction(new FruitAction("Apple", new AppleIcon(32, 32)));
btn.addAction(new FruitAction("Black Berry", new BlackBerriesIcon(32, 32)));
btn.addAction(new FruitAction("Grapes", new GrapesIcon(32, 32)));
btn.addAction(new FruitAction("Peach", new PeachIcon(32, 32)));
btn.addAction(new FruitAction("Strewberry", new StrewberriesIcon(32, 32)));
And, for reference, the fruit action looks like...
public class FruitAction extends AbstractAction {
public FruitAction(String text, Icon icon) {
putValue(NAME, text);
putValue(SMALL_ICON, icon);
putValue(SHORT_DESCRIPTION, text);
}
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "I am " + getValue(NAME), "Fruit", JOptionPane.INFORMATION_MESSAGE);
}
}
This is use a custom vector based icon library, so obviously, I won't be including that, but it gives you an idea of how to configure it
I too have been looking for a decent JSplitButton for a while, but everything I found as a standalone component was rather disappointing.
MadProgrammer's answer looked very promising, but it did not work all that well for me. I'm not 100% sure what it might be caused by, whether it's by design or because of the L&F I'm using but the provided component had focus issues. Specifically, the items of the popup did not show any hover indicators, indicating they weren't receiving focus. Also upon clicking an item in the popup the popup did not close itself.
Anyway, I rewrote parts of the component and made it use a JPopupMenu instead of a custom JFrame to avoid handling focus myself. The component set's the popup menu as its popup menu using the JComponent.setComponentPopupMenu() and then just invoking the popup menu upon clicking the dropdown arrow. This also makes it that it's possible to right-click the button to show the popup directly.
The popup has focus and behaves like a regular JPopupMenu, supporting adding stuff like separators and whatnot.
Note: The L&F used in the image is flatlaf
The button's default action can be set like a normal JButton using addActionListener() or using actions by calling setAction().
SplitButton btn = new SplitButton("Click me");
btn.addActionListener((e) -> {
System.out.println("Button clicked");
});
SplitButton btn2 = new SplitButton(new AbstractAction("Click me") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked");
}
});
The popup menu can be created separately and then assigned to the button using setPopupMenu() or you can add individual items as actions to the menu without the need of creating it yourself using addAction and addActionAt.
JPopupMenu popup = new JPopupMenu();
popup.add(new JMenuItem("A popup option"));
popup.add(new JMenuItem("JMenuItem with icon", Icons.deleteBin));
popup.addSeparator();
btn.setPopupMenu(popup);
btn.addAction(new AbstractAction("Or don't", Icons.alert) {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Or don't clicked");
}
});
btn.addAction(new AbstractAction("Click me in a different way") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Click me in a different way clicked");
}
});
The popup menu can be retrieved simply by using getPopupMenu().
The full code
Again, original by MadProgrammer and whoever he got it from :D
/**
* A swing split button implementation.
* A JButton that has an additional section with an arrow icon on the right that when clicked
* shows a JPopupMenu that is positioned flush with the button.
*
* The implementation sets the buttons pop-up menu using setComponentPopupMenu()
* meaning that in addition to clicking the drop-down arrow, user can also right click
* the button to open the pop-up menu.
*
* Author: DUDSS - 21.02.2020
* I modified the button to use a JPopupMenu instead of a custom JFrame to avoid hacky
* focus workarounds and fix focus issues.
*
* Credit:
* Modified version of a split button by MadProgrammer.
* https://stackoverflow.com/questions/36352707/actions-inside-of-another-action-like-netbeans
* It's original author seems to be unknown.
*
*/
public class SplitButton extends JButton {
private int separatorSpacing = 4;
private int splitWidth = 22;
private int arrowSize = 8;
private boolean onSplit;
private Rectangle splitRectangle;
private boolean alwaysDropDown;
private Color arrowColor = Color.BLACK;
private Color disabledArrowColor = Color.GRAY;
private Image image;
private MouseHandler mouseHandler;
private boolean toolBarButton;
private JPopupMenu jpopupMenu;
/**
* Creates a button with initial text and an icon.
*
* #param text the text of the button
* #param icon the Icon image to display on the button
*/
public SplitButton() {
super();
addMouseMotionListener(getMouseHandler());
addMouseListener(getMouseHandler());
// Default for no "default" action...
setAlwaysDropDown(true);
InputMap im = getInputMap(WHEN_FOCUSED);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "PopupMenu.close");
am.put("PopupMenu.close", new ClosePopupAction());
}
public SplitButton(Action defaultAction) {
this();
setAction(defaultAction);
}
public SplitButton(Action defaultAction, JPopupMenu popup) {
this();
setAction(defaultAction);
setPopupMenu(popup);
}
public SplitButton(Action defaultAction, Action... actions) {
this();
setAction(defaultAction);
for (Action a : actions) {
addAction(a);
}
}
public SplitButton(String text) {
this();
setText(text);
}
public SplitButton(String text, Icon icon) {
this();
setText(text);
setIcon(icon);
}
public SplitButton(String text, JPopupMenu popup) {
this();
setText(text);
setPopupMenu(popup);
}
public SplitButton(String text, Icon icon, JPopupMenu popup) {
this();
setText(text);
setIcon(icon);
setPopupMenu(popup);
}
/**
* Creates a pre-configured button suitable for being used on a JToolBar
*
* #param defaultAction
* #param actions
* #return
*/
public static SplitButton createToolBarButton(Action defaultAction, Action... actions) {
SplitButton btn = new SplitButton(defaultAction, actions);
btn.configureForToolBar();
return btn;
}
/**
* Creates a pre-configured "options only" button suitable for being used on
* a JToolBar
*
* #param text
* #param icon
* #param actions
* #return
*/
public static SplitButton createToolBarButton(String text, Icon icon, JPopupMenu popupMenu) {
SplitButton btn = new SplitButton(text, icon);
btn.setPopupMenu(popupMenu);
btn.setToolTipText(text);
btn.configureForToolBar();
return btn;
}
#Override
public void addActionListener(ActionListener l) {
if (l != null) {
setAlwaysDropDown(false);
}
super.addActionListener(l);
}
#Override
public void setAction(Action a) {
super.setAction(a);
if (a != null) {
setAlwaysDropDown(false);
}
}
public void addActionAt(Action a, int index) {
getPopupMenu().insert(a, index);
}
public void addAction(Action a) {
getPopupMenu().add(a);
}
public void setPopupMenu(JPopupMenu popup) {
jpopupMenu = popup;
this.setComponentPopupMenu(popup);
}
/**
* Returns the buttons popup menu.
*
* #return
*/
public JPopupMenu getPopupMenu() {
if (jpopupMenu == null) {
jpopupMenu = new JPopupMenu();
}
return jpopupMenu;
}
/**
* Used to determine if the button is begin configured for use on a tool bar
*
* #return
*/
public boolean isToolBarButton() {
return toolBarButton;
}
/**
* Configures this button for use on a tool bar...
*/
public void configureForToolBar() {
toolBarButton = true;
if (getIcon() != null) {
setHideActionText(true);
}
setHorizontalTextPosition(JButton.CENTER);
setVerticalTextPosition(JButton.BOTTOM);
setFocusable(false);
}
protected MouseHandler getMouseHandler() {
if (mouseHandler == null) {
mouseHandler = new MouseHandler();
}
return mouseHandler;
}
protected int getOptionsCount() {
return getPopupMenu().getComponentCount();
}
/*protected void addActionAt(Action action, int index) {
if (index < 0 || index >= getOptionsCount()) {
getPopupWindow().add(createMenuItem(action));
} else {
getPopupWindow().add(createMenuItem(action), index);
}
}*/
/*protected void removeAction(Action action) {
AbstractButton btn = getButtonFor(action);
if (btn != null) {
getPopupWindow().remove(btn);
}
}*/
#Override
public Insets getInsets() {
Insets insets = (Insets) super.getInsets().clone();
insets.right += splitWidth;
return insets;
}
#Override
public Insets getInsets(Insets insets) {
Insets insets1 = getInsets();
insets.left = insets1.left;
insets.right = insets1.right;
insets.bottom = insets1.bottom;
insets.top = insets1.top;
return insets1;
}
protected void closePopupMenu() {
getPopupMenu().setVisible(false);
}
protected void showPopupMenu() {
if (getOptionsCount() > 0) {
JPopupMenu menu = getPopupMenu();
menu.setVisible(true); //Necessary to calculate pop-up menu width the first time it's displayed.
menu.show(this, (getWidth() - menu.getWidth()), getHeight());
}
}
/**
* Returns the separatorSpacing. Separator spacing is the space above and
* below the separator( the line drawn when you hover your mouse over the
* split part of the button).
*
* #return separatorSpacingimage = null; //to repaint the image with the new
* size
*/
public int getSeparatorSpacing() {
return separatorSpacing;
}
/**
* Sets the separatorSpacing.Separator spacing is the space above and below
* the separator( the line drawn when you hover your mouse over the split
* part of the button).
*
* #param spacing
*/
public void setSeparatorSpacing(int spacing) {
if (spacing != separatorSpacing && spacing >= 0) {
int old = separatorSpacing;
this.separatorSpacing = spacing;
image = null;
firePropertyChange("separatorSpacing", old, separatorSpacing);
revalidate();
repaint();
}
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* #return true if alwaysDropdown, false otherwise.
*/
public boolean isAlwaysDropDown() {
return alwaysDropDown;
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* If true, this will prevent the button from raising any actionPerformed
* events for itself
*
* #param value true to show the attached dropdown even if the button part
* is clicked, false otherwise
*/
public void setAlwaysDropDown(boolean value) {
if (alwaysDropDown != value) {
this.alwaysDropDown = value;
firePropertyChange("alwaysDropDown", !alwaysDropDown, alwaysDropDown);
}
}
/**
* Gets the color of the arrow.
*
* #return arrowColor
*/
public Color getArrowColor() {
return arrowColor;
}
/**
* Set the arrow color.
*
* #param color
*/
public void setArrowColor(Color color) {
if (arrowColor != color) {
Color old = arrowColor;
this.arrowColor = color;
image = null;
firePropertyChange("arrowColor", old, arrowColor);
repaint();
}
}
/**
* gets the disabled arrow color
*
* #return disabledArrowColor color of the arrow if no popup attached.
*/
public Color getDisabledArrowColor() {
return disabledArrowColor;
}
/**
* sets the disabled arrow color
*
* #param color color of the arrow if no popup attached.
*/
public void setDisabledArrowColor(Color color) {
if (disabledArrowColor != color) {
Color old = disabledArrowColor;
this.disabledArrowColor = color;
image = null; //to repaint the image with the new color
firePropertyChange("disabledArrowColor", old, disabledArrowColor);
}
}
/**
* Splitwidth is the width of the split part of the button.
*
* #return splitWidth
*/
public int getSplitWidth() {
return splitWidth;
}
/**
* Splitwidth is the width of the split part of the button.
*
* #param width
*/
public void setSplitWidth(int width) {
if (splitWidth != width) {
int old = splitWidth;
this.splitWidth = width;
firePropertyChange("splitWidth", old, splitWidth);
revalidate();
repaint();
}
}
/**
* gets the size of the arrow.
*
* #return size of the arrow
*/
public int getArrowSize() {
return arrowSize;
}
/**
* sets the size of the arrow
*
* #param size
*/
public void setArrowSize(int size) {
if (arrowSize != size) {
int old = arrowSize;
this.arrowSize = size;
image = null; //to repaint the image with the new size
firePropertyChange("setArrowSize", old, arrowSize);
revalidate();
repaint();
}
}
/**
* Gets the image to be drawn in the split part. If no is set, a new image
* is created with the triangle.
*
* #return image
*/
public Image getImage() {
if (image == null) {
Graphics2D g = null;
BufferedImage img = new BufferedImage(arrowSize, arrowSize, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) img.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
g.setColor(jpopupMenu != null ? arrowColor : disabledArrowColor);
//this creates a triangle facing right >
g.fillPolygon(new int[]{0, 0, arrowSize / 2}, new int[]{0, arrowSize, arrowSize / 2}, 3);
g.dispose();
//rotate it to face downwards
img = rotate(img, 90);
BufferedImage dimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
g = (Graphics2D) dimg.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(img, null, 0, 0);
g.dispose();
for (int i = 0; i < dimg.getHeight(); i++) {
for (int j = 0; j < dimg.getWidth(); j++) {
if (dimg.getRGB(j, i) == Color.WHITE.getRGB()) {
dimg.setRGB(j, i, 0x8F1C1C);
}
}
}
image = Toolkit.getDefaultToolkit().createImage(dimg.getSource());
}
return image;
}
/**
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Graphics gClone = g.create();//EDIT: Hervé Guillaume
Color oldColor = g.getColor();
splitRectangle = new Rectangle(getWidth() - splitWidth, 0, splitWidth, getHeight());
g.translate(splitRectangle.x, splitRectangle.y);
int mh = getHeight() / 2;
int mw = splitWidth / 2;
g.drawImage(getImage(), mw - arrowSize / 2, mh + 2 - arrowSize / 2, null);
if (!alwaysDropDown) {
if (getModel().isRollover() || isFocusable()) {
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.background"));
g.drawLine(1, separatorSpacing + 2, 1, getHeight() - separatorSpacing - 2);
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.shadow"));
g.drawLine(2, separatorSpacing + 2, 2, getHeight() - separatorSpacing - 2);
}
}
g.setColor(oldColor);
g.translate(-splitRectangle.x, -splitRectangle.y);
}
/**
* Rotates the given image with the specified angle.
*
* #param img image to rotate
* #param angle angle of rotation
* #return rotated image
*/
private BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.rotate(Math.toRadians(angle), w / 2, h / 2);
g.drawImage(img, null, 0, 0);
return dimg;
}
#Override
protected void fireActionPerformed(ActionEvent event) {
// This is a little bit of a nasty trick. Basically this is where
// we try and decide if the buttons "default" action should
// be fired or not. We don't want it firing if the button
// is in "options only" mode or the user clicked on
// on the "drop down arrow"....
if (onSplit || isAlwaysDropDown()) {
showPopupMenu();
} else {
super.fireActionPerformed(event);
}
}
protected class MouseHandler extends MouseAdapter {
#Override
public void mouseExited(MouseEvent e) {
onSplit = false;
repaint(splitRectangle);
}
#Override
public void mouseMoved(MouseEvent e) {
if (splitRectangle.contains(e.getPoint())) {
onSplit = true;
} else {
onSplit = false;
}
repaint(splitRectangle);
}
}
protected class ClosePopupAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
closePopupMenu();
}
}
}
I've been working my way through Developing Games in Java by David Brackeen and have come across a problem. I am modifying the game and pulling it apart to better understand how it works. I have no idea however how to add a menu like the one that was demonstrated in chapter Chapter 3. Any guidance is appreciated.
I have been looking in the ScreenManager and the GameManager for an idea on how to do this. I know how the game gets the screen size, I just need a push on how to get a menu to display.
Here is the code:
ScreenManager.java
package com.brackeen.javagamebook.graphics;
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;
import javax.swing.JLabel;
/**
The ScreenManager class manages initializing and displaying
full screen graphics modes.
*/
public class ScreenManager {
private GraphicsDevice device;
/**
Creates a new ScreenManager object.
*/
public ScreenManager() {
GraphicsEnvironment environment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
device = environment.getDefaultScreenDevice();
}
/**
Returns a list of compatible display modes for the
default device on the system.
*/
public DisplayMode[] getCompatibleDisplayModes() {
return device.getDisplayModes();
}
/**
Returns the first compatible mode in a list of modes.
Returns null if no modes are compatible.
*/
public DisplayMode findFirstCompatibleMode(
DisplayMode modes[])
{
DisplayMode goodModes[] = device.getDisplayModes();
for (int i = 0; i < modes.length; i++) {
for (int j = 0; j < goodModes.length; j++) {
if (displayModesMatch(modes[i], goodModes[j])) {
return modes[i];
}
}
}
return null;
}
/**
Returns the current display mode.
*/
public DisplayMode getCurrentDisplayMode() {
return device.getDisplayMode();
}
/**
Determines if two display modes "match". Two display
modes match if they have the same resolution, bit depth,
and refresh rate. The bit depth is ignored if one of the
modes has a bit depth of DisplayMode.BIT_DEPTH_MULTI.
Likewise, the refresh rate is ignored if one of the
modes has a refresh rate of
DisplayMode.REFRESH_RATE_UNKNOWN.
*/
public boolean displayModesMatch(DisplayMode mode1,
DisplayMode mode2)
{
if (mode1.getWidth() != mode2.getWidth() ||
mode1.getHeight() != mode2.getHeight())
{
return false;
}
if (mode1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI &&
mode2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI &&
mode1.getBitDepth() != mode2.getBitDepth())
{
return false;
}
if (mode1.getRefreshRate() !=
DisplayMode.REFRESH_RATE_UNKNOWN &&
mode2.getRefreshRate() !=
DisplayMode.REFRESH_RATE_UNKNOWN &&
mode1.getRefreshRate() != mode2.getRefreshRate())
{
return false;
}
return true;
}
/**
Enters full screen mode and changes the display mode.
If the specified display mode is null or not compatible
with this device, or if the display mode cannot be
changed on this system, the current display mode is used.
<p>
The display uses a BufferStrategy with 2 buffers.
*/
public void setFullScreen(DisplayMode displayMode) {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setUndecorated(true);
frame.setIgnoreRepaint(true);
frame.setResizable(false);
device.setFullScreenWindow(frame);
if (displayMode != null &&
device.isDisplayChangeSupported())
{
try {
device.setDisplayMode(displayMode);
}
catch (IllegalArgumentException ex) { }
// fix for mac os x
frame.setSize(displayMode.getWidth(),
displayMode.getHeight());
}
// avoid potential deadlock in 1.4.1_02
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
frame.createBufferStrategy(2);
}
});
}
catch (InterruptedException ex) {
// ignore
}
catch (InvocationTargetException ex) {
// ignore
}
}
/**
Gets the graphics context for the display. The
ScreenManager uses double buffering, so applications must
call update() to show any graphics drawn.
<p>
The application must dispose of the graphics object.
*/
public Graphics2D getGraphics() {
Window window = device.getFullScreenWindow();
if (window != null) {
BufferStrategy strategy = window.getBufferStrategy();
return (Graphics2D)strategy.getDrawGraphics();
}
else {
return null;
}
}
/**
Updates the display.
*/
public void update() {
Window window = device.getFullScreenWindow();
if (window != null) {
BufferStrategy strategy = window.getBufferStrategy();
if (!strategy.contentsLost()) {
strategy.show();
}
}
// Sync the display on some systems.
// (on Linux, this fixes event queue problems)
Toolkit.getDefaultToolkit().sync();
}
/**
Returns the window currently used in full screen mode.
Returns null if the device is not in full screen mode.
*/
public JFrame getFullScreenWindow() {
return (JFrame)device.getFullScreenWindow();
}
/**
Returns the width of the window currently used in full
screen mode. Returns 0 if the device is not in full
screen mode.
*/
public int getWidth() {
Window window = device.getFullScreenWindow();
if (window != null) {
return window.getWidth();
}
else {
return 0;
}
}
/**
Returns the height of the window currently used in full
screen mode. Returns 0 if the device is not in full
screen mode.
*/
public int getHeight() {
Window window = device.getFullScreenWindow();
if (window != null) {
return window.getHeight();
}
else {
return 0;
}
}
/**
Restores the screen's display mode.
*/
public void restoreScreen() {
Window window = device.getFullScreenWindow();
if (window != null) {
window.dispose();
}
device.setFullScreenWindow(null);
}
/**
Creates an image compatible with the current display.
*/
public BufferedImage createCompatibleImage(int w, int h,
int transparancy)
{
Window window = device.getFullScreenWindow();
if (window != null) {
GraphicsConfiguration gc =
window.getGraphicsConfiguration();
return gc.createCompatibleImage(w, h, transparancy);
}
return null;
}
}
GameManager.java
package com.brackeen.javagamebook.tilegame;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.Iterator;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.sampled.AudioFormat;
import com.brackeen.javagamebook.graphics.*;
import com.brackeen.javagamebook.sound.*;
import com.brackeen.javagamebook.input.*;
import com.brackeen.javagamebook.test.GameCore;
import com.brackeen.javagamebook.tilegame.sprites.*;
/**
GameManager manages all parts of the game.
*/
public class GameManager extends GameCore {
public static void main(String[] args) {
new GameManager().run();
}
// uncompressed, 44100Hz, 16-bit, mono, signed, little-endian
private static final AudioFormat PLAYBACK_FORMAT =
new AudioFormat(44100, 16, 1, true, false);
private static final int DRUM_TRACK = 1;
public static final float GRAVITY = 0.002f;
private Point pointCache = new Point();
private TileMap map;
private MidiPlayer midiPlayer;
private SoundManager soundManager;
private ResourceManager resourceManager;
private Sound prizeSound;
private Sound boopSound;
private Sound jacksound;
private InputManager inputManager;
private TileMapRenderer renderer;
public Boolean panimation = false;
public Boolean playing = false;
private int points;
private GameAction moveLeft;
private GameAction moveRight;
private GameAction jump;
private GameAction exit;
private GameAction pause;
public void init() {
super.init();
// set up input manager
initInput();
// start resource manager
resourceManager = new ResourceManager(
screen.getFullScreenWindow().getGraphicsConfiguration());
// load resources
renderer = new TileMapRenderer();
renderer.setBackground(
resourceManager.loadImage("background.png"));
// load first map
map = resourceManager.loadNextMap();
// load sounds
soundManager = new SoundManager(PLAYBACK_FORMAT);
prizeSound = soundManager.getSound("sounds/prizes.wav");
boopSound = soundManager.getSound("sounds/boop2.wav");
jacksound = soundManager.getSound("sounds/jack.wav");
// start music
midiPlayer = new MidiPlayer();
Sequence sequence =
midiPlayer.getSequence("sounds/music.midi");
midiPlayer.play(sequence, true);
toggleDrumPlayback();
}
/**
Closes any resurces used by the GameManager.
*/
public void stop() {
super.stop();
midiPlayer.close();
soundManager.close();
}
public void pause() {
if(!panimation){
panimation = true;
soundManager.setPaused(true);
midiPlayer.setPaused(true);
}
else{
panimation = false;
soundManager.setPaused(false);
midiPlayer.setPaused(false);
}
}
private void initInput() {
moveLeft = new GameAction("moveLeft");
moveRight = new GameAction("moveRight");
jump = new GameAction("jump",
GameAction.DETECT_INITAL_PRESS_ONLY);
exit = new GameAction("exit",
GameAction.DETECT_INITAL_PRESS_ONLY);
pause = new GameAction("pause",
GameAction.DETECT_INITAL_PRESS_ONLY);
inputManager = new InputManager(
screen.getFullScreenWindow());
inputManager.setCursor(InputManager.INVISIBLE_CURSOR);
inputManager.mapToKey(moveLeft, KeyEvent.VK_LEFT);
inputManager.mapToKey(moveRight, KeyEvent.VK_RIGHT);
inputManager.mapToKey(jump, KeyEvent.VK_SPACE);
inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);
inputManager.mapToKey(pause, KeyEvent.VK_F1);
}
private void checkInput(long elapsedTime) {
if (exit.isPressed()) {
stop();
String spoints = Integer.toString(points);
System.out.println("Points: " + spoints);
}
if (pause.isPressed()){
pause();
}
Player player = (Player)map.getPlayer();
if (player.isAlive()) {
float velocityX = 0;
if (moveLeft.isPressed()) {
velocityX-=player.getMaxSpeed();
}
if (moveRight.isPressed()) {
velocityX+=player.getMaxSpeed();
}
if (jump.isPressed()) {
player.jump(false);
}
player.setVelocityX(velocityX);
}
}
public void draw(Graphics2D g) {
renderer.draw(g, map,
screen.getWidth(), screen.getHeight());
}
/**
Gets the current map.
*/
public TileMap getMap() {
return map;
}
/**
Turns on/off drum playback in the midi music (track 1).
*/
public void toggleDrumPlayback() {
Sequencer sequencer = midiPlayer.getSequencer();
if (sequencer != null) {
sequencer.setTrackMute(DRUM_TRACK,
!sequencer.getTrackMute(DRUM_TRACK));
}
}
/**
Gets the tile that a Sprites collides with. Only the
Sprite's X or Y should be changed, not both. Returns null
if no collision is detected.
*/
public Point getTileCollision(Sprite sprite,
float newX, float newY)
{
float fromX = Math.min(sprite.getX(), newX);
float fromY = Math.min(sprite.getY(), newY);
float toX = Math.max(sprite.getX(), newX);
float toY = Math.max(sprite.getY(), newY);
// get the tile locations
int fromTileX = TileMapRenderer.pixelsToTiles(fromX);
int fromTileY = TileMapRenderer.pixelsToTiles(fromY);
int toTileX = TileMapRenderer.pixelsToTiles(
toX + sprite.getWidth() - 1);
int toTileY = TileMapRenderer.pixelsToTiles(
toY + sprite.getHeight() - 1);
// check each tile for a collision
for (int x=fromTileX; x<=toTileX; x++) {
for (int y=fromTileY; y<=toTileY; y++) {
if (x < 0 || x >= map.getWidth() ||
map.getTile(x, y) != null)
{
// collision found, return the tile
pointCache.setLocation(x, y);
return pointCache;
}
}
}
// no collision found
return null;
}
/**
Checks if two Sprites collide with one another. Returns
false if the two Sprites are the same. Returns false if
one of the Sprites is a Creature that is not alive.
*/
public boolean isCollision(Sprite s1, Sprite s2) {
// if the Sprites are the same, return false
if (s1 == s2) {
return false;
}
// if one of the Sprites is a dead Creature, return false
if (s1 instanceof Creature && !((Creature)s1).isAlive()) {
return false;
}
if (s2 instanceof Creature && !((Creature)s2).isAlive()) {
return false;
}
// get the pixel location of the Sprites
int s1x = Math.round(s1.getX());
int s1y = Math.round(s1.getY());
int s2x = Math.round(s2.getX());
int s2y = Math.round(s2.getY());
// check if the two sprites' boundaries intersect
return (s1x < s2x + s2.getWidth() &&
s2x < s1x + s1.getWidth() &&
s1y < s2y + s2.getHeight() &&
s2y < s1y + s1.getHeight());
}
/**
Gets the Sprite that collides with the specified Sprite,
or null if no Sprite collides with the specified Sprite.
*/
public Sprite getSpriteCollision(Sprite sprite) {
// run through the list of Sprites
Iterator i = map.getSprites();
while (i.hasNext()) {
Sprite otherSprite = (Sprite)i.next();
if (isCollision(sprite, otherSprite)) {
// collision found, return the Sprite
return otherSprite;
}
}
// no collision found
return null;
}
/**
Updates Animation, position, and velocity of all Sprites
in the current map.
*/
public void update(long elapsedTime) {
Creature player = (Creature)map.getPlayer();
// player is dead! start map over
if (player.getState() == Creature.STATE_DEAD) {
map = resourceManager.reloadMap();
return;
}
// get keyboard/mouse input
checkInput(elapsedTime);
if (!panimation) { //If game is paused, it stops the updating of the animation.
// update player
updateCreature(player, elapsedTime);
player.update(elapsedTime);
// update other sprites
Iterator i = map.getSprites();
while (i.hasNext()) {
Sprite sprite = (Sprite)i.next();
if (sprite instanceof Creature) {
Creature creature = (Creature)sprite;
if (creature.getState() == Creature.STATE_DEAD) {
i.remove();
}
else {
updateCreature(creature, elapsedTime);
}
}
// normal update
sprite.update(elapsedTime);
}
}
}
/**
Updates the creature, applying gravity for creatures that
aren't flying, and checks collisions.
*/
private void updateCreature(Creature creature,
long elapsedTime)
{
// apply gravity
if (!creature.isFlying()) {
creature.setVelocityY(creature.getVelocityY() +
GRAVITY * elapsedTime);
}
// change x
float dx = creature.getVelocityX();
float oldX = creature.getX();
float newX = oldX + dx * elapsedTime;
Point tile =
getTileCollision(creature, newX, creature.getY());
if (tile == null) {
creature.setX(newX);
}
else {
// line up with the tile boundary
if (dx > 0) {
creature.setX(
TileMapRenderer.tilesToPixels(tile.x) -
creature.getWidth());
}
else if (dx < 0) {
creature.setX(
TileMapRenderer.tilesToPixels(tile.x + 1));
}
creature.collideHorizontal();
}
if (creature instanceof Player) {
checkPlayerCollision((Player)creature, false);
}
// change y
float dy = creature.getVelocityY();
float oldY = creature.getY();
float newY = oldY + dy * elapsedTime;
tile = getTileCollision(creature, creature.getX(), newY);
if (tile == null) {
creature.setY(newY);
}
else {
// line up with the tile boundary
if (dy > 0) {
creature.setY(
TileMapRenderer.tilesToPixels(tile.y) -
creature.getHeight());
}
else if (dy < 0) {
creature.setY(
TileMapRenderer.tilesToPixels(tile.y + 1));
}
creature.collideVertical();
}
if (creature instanceof Player) {
boolean canKill = (oldY < creature.getY());
checkPlayerCollision((Player)creature, canKill);
}
}
/**
Checks for Player collision with other Sprites. If
canKill is true, collisions with Creatures will kill
them.
*/
public void checkPlayerCollision(Player player,
boolean canKill)
{
if (!player.isAlive()) {
return;
}
// check for player collision with other sprites
Sprite collisionSprite = getSpriteCollision(player);
if (collisionSprite instanceof PowerUp) {
acquirePowerUp((PowerUp)collisionSprite);
}
else if (collisionSprite instanceof Creature) {
Creature badguy = (Creature)collisionSprite;
if (canKill) {
// kill the badguy and make player bounce
soundManager.play(boopSound);
badguy.setState(Creature.STATE_DYING);
player.setY(badguy.getY() - player.getHeight());
player.jump(true);
}
else {
// player dies!
player.setState(Creature.STATE_DYING);
points = 0;
}
}
}
/**
Gives the player the speicifed power up and removes it
from the map.
*/
public void acquirePowerUp(PowerUp powerUp) {
// remove it from the map
map.removeSprite(powerUp);
Player player = (Player)map.getPlayer();
if (powerUp instanceof PowerUp.Star) {
// do something here, like give the player points
soundManager.play(prizeSound);
points = points + 5;
}
else if (powerUp instanceof PowerUp.Music) {
// change the music
player.setMaxSpeed();
if (! playing){
soundManager.play(jacksound);
playing = true;
}
else
{
//Do nothing
}
toggleDrumPlayback();
}
else if (powerUp instanceof PowerUp.Goal) {
// advance to next map
soundManager.play(prizeSound,
new EchoFilter(2000, .7f), false);
map = resourceManager.loadNextMap();
}
}
}
The book has an example menu in the chapter.
MenuTest.java
package com.brackeen.javagamebook.tilegame;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import com.brackeen.javagamebook.graphics.*;
import com.brackeen.javagamebook.sound.*;
import com.brackeen.javagamebook.input.*;
import com.brackeen.javagamebook.test.GameCore;
import com.brackeen.javagamebook.tilegame.sprites.*;
/**
Extends the InputManagerTest demo and adds Swing buttons
for pause, config and quit.
*/
public class Menu extends InputManager
implements ActionListener
{
public static void main(String[] args) {
new Menu().run();
}
protected GameAction configAction;
private JButton playButton;
private JButton configButton;
private JButton quitButton;
private JButton pauseButton;
private JPanel playButtonSpace;
public void init() {
super.init();
// make sure Swing components don't paint themselves
NullRepaintManager.install();
// create an addtional GameAction for "config"
configAction = new GameAction("config");
// create buttons
quitButton = createButton("quit", "Quit");
playButton = createButton("play", "Continue");
pauseButton = createButton("pause", "Pause");
configButton = createButton("config", "Change Settings");
// create the space where the play/pause buttons go.
playButtonSpace = new JPanel();
playButtonSpace.setOpaque(false);
playButtonSpace.add(pauseButton);
JFrame frame = super.screen.getFullScreenWindow();
Container contentPane = frame.getContentPane();
// make sure the content pane is transparent
if (contentPane instanceof JComponent) {
((JComponent)contentPane).setOpaque(false);
}
// add components to the screen's content pane
contentPane.setLayout(new FlowLayout(FlowLayout.LEFT));
contentPane.add(playButtonSpace);
contentPane.add(configButton);
contentPane.add(quitButton);
// explicitly layout components (needed on some systems)
frame.validate();
}
/**
Extends InputManagerTest's functionality to draw all
Swing components.
*/
public void draw(Graphics2D g) {
super.draw(g);
JFrame frame = super.screen.getFullScreenWindow();
// the layered pane contains things like popups (tooltips,
// popup menus) and the content pane.
frame.getLayeredPane().paintComponents(g);
}
/**
Changes the pause/play button whenever the pause state
changes.
*/
public void setPaused(boolean p) {
super.setPaused(p);
playButtonSpace.removeAll();
if (isPaused()) {
playButtonSpace.add(playButton);
}
else {
playButtonSpace.add(pauseButton);
}
}
/**
Called by the AWT event dispatch thread when a button is
pressed.
*/
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if (src == quitButton) {
// fire the "exit" gameAction
super.exit.tap();
}
else if (src == configButton) {
// doesn't do anything (for now)
configAction.tap();
}
else if (src == playButton || src == pauseButton) {
// fire the "pause" gameAction
super.pause.tap();
}
}
/**
Creates a Swing JButton. The image used for the button is
located at "../images/menu/" + name + ".png". The image is
modified to create a "default" look (translucent) and a
"pressed" look (moved down and to the right).
<p>The button doesn't use Swing's look-and-feel and
instead just uses the image.
*/
public JButton createButton(String name, String toolTip) {
// load the image
String imagePath = "../images/menu/" + name + ".png";
ImageIcon iconRollover = new ImageIcon(imagePath);
int w = iconRollover.getIconWidth();
int h = iconRollover.getIconHeight();
// get the cursor for this button
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
// make translucent default image
Image image = screen.createCompatibleImage(w, h,
Transparency.TRANSLUCENT);
Graphics2D g = (Graphics2D)image.getGraphics();
Composite alpha = AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, .5f);
g.setComposite(alpha);
g.drawImage(iconRollover.getImage(), 0, 0, null);
g.dispose();
ImageIcon iconDefault = new ImageIcon(image);
// make a pressed iamge
image = screen.createCompatibleImage(w, h,
Transparency.TRANSLUCENT);
g = (Graphics2D)image.getGraphics();
g.drawImage(iconRollover.getImage(), 2, 2, null);
g.dispose();
ImageIcon iconPressed = new ImageIcon(image);
// create the button
JButton button = new JButton();
button.addActionListener(this);
button.setIgnoreRepaint(true);
button.setFocusable(false);
button.setToolTipText(toolTip);
button.setBorder(null);
button.setContentAreaFilled(false);
button.setCursor(cursor);
button.setIcon(iconDefault);
button.setRolloverIcon(iconRollover);
button.setPressedIcon(iconPressed);
return button;
}
}
What is the best way drawing a hero and moving it? I just need the best code for doing that. Before writing this i found a way, but when i made the surface holder transparent, i realised that the code is drawing new bitmap in the front of the old one every milisecond. That way looks kind of laggy to me, but maymie i'm not right. Please help me. Actualy i'm kind of confused...
Anyway, here's the code that i think is laggy:
MainThread.java
/**
*
*/
package com.workspace.pockethero;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
/**
* #author impaler
*
* The Main thread which contains the game loop. The thread must have access to
* the surface view and holder to trigger events every game tick.
*/
public class MainThread extends Thread {
private static final String TAG = MainThread.class.getSimpleName();
// Surface holder that can access the physical surface
private SurfaceHolder surfaceHolder;
// The actual view that handles inputs
// and draws to the surface
private MainGamePanel gamePanel;
// flag to hold game state
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#Override
public void run() {
Canvas canvas;
canvas = this.surfaceHolder.lockCanvas();
Log.d(TAG, "Starting game loop");
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
synchronized (surfaceHolder) {
// update game state
this.gamePanel.update();
// render state to the screen
// draws the canvas on the panel
this.gamePanel.render(canvas);
}
} finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
}
MainGamePanel.java
/**
*
*/
package com.workspace.pockethero;
import com.workspace.pockethero.model.Droid;
import com.workspace.pockethero.buttons.*;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
/**
* #author impaler
* This is the main surface that handles the ontouch events and draws
* the image to the screen.
*/
public class MainGamePanel extends SurfaceView implements
SurfaceHolder.Callback {
private static final String TAG = MainGamePanel.class.getSimpleName();
private MainThread thread;
public Droid droid;
public Butt butt;
public Butt1 butt1;
public Butt2 butt2;
public Butt3 butt3;
public Buttz buttz;
public Buttz1 buttz1;
public Buttz2 buttz2;
public Buttz3 buttz3;
public Buttx buttx;
public Build build;
public int decentreX;
public int decentreY;
public int debottomA;
public boolean moved;
public boolean moved1;
public boolean moved2;
public boolean moved3;
public boolean moved4;
public boolean moved5;
public boolean moved6;
public boolean moved7;
private Drawable myImage;
public boolean mapPainted;
public MainGamePanel(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// create droid and load bitmap
decentreX = PocketHero.centreX;
decentreY = PocketHero.centreY;
debottomA = PocketHero.bottomA;
droid = new Droid(BitmapFactory.decodeResource(getResources(), R.drawable.herod), decentreX, decentreY);
butt = new Butt(BitmapFactory.decodeResource(getResources(), R.drawable.button), 110, debottomA - 70);
butt1 = new Butt1(BitmapFactory.decodeResource(getResources(), R.drawable.button1), 70, debottomA - 110);
butt2 = new Butt2(BitmapFactory.decodeResource(getResources(), R.drawable.button2), 30, debottomA - 70);
butt3 = new Butt3(BitmapFactory.decodeResource(getResources(), R.drawable.button3), 70, debottomA - 30);
buttz = new Buttz(BitmapFactory.decodeResource(getResources(), R.drawable.zbutton), 110, debottomA - 110);
buttz1 = new Buttz1(BitmapFactory.decodeResource(getResources(), R.drawable.zbutton1), 30, debottomA - 110);
buttz2 = new Buttz2(BitmapFactory.decodeResource(getResources(), R.drawable.zbutton2), 30, debottomA - 30);
buttz3 = new Buttz3(BitmapFactory.decodeResource(getResources(), R.drawable.zbutton3), 110, debottomA - 30);
buttx = new Buttx(BitmapFactory.decodeResource(getResources(), R.drawable.xbutton), 70, debottomA - 70);
build = new Build(BitmapFactory.decodeResource(getResources(), R.drawable.building), 500, 200);
// create the game loop thread
//300 indicates start position of bitmapfield on screen
thread = new MainThread(getHolder(), this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
this.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View V, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// delegating event handling to the droid
handleActionDown((int)event.getX(), (int)event.getY());
} if (event.getAction() == MotionEvent.ACTION_MOVE) {
handleActionDown((int)event.getX(), (int)event.getY());
// the gestures
} if (event.getAction() == MotionEvent.ACTION_UP) {
// touch was released
if (droid.touched) {
droid.setTouched(false);
}
if (droid.touched1) {
droid.setTouched1(false);
}
if (droid.touched2) {
droid.setTouched2(false);
}
if (droid.touched3) {
droid.setTouched3(false);
}
}
return true;
}
});
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
public void surfaceCreated(SurfaceHolder holder) {
// at this point the surface is created and
// we can safely start the game loop
thread.setRunning(true);
thread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Surface is being destroyed");
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
Log.d(TAG, "Thread was shut down cleanly");
}
public void render(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
droid.draw(canvas);
butt.draw(canvas);
butt1.draw(canvas);
butt2.draw(canvas);
butt3.draw(canvas);
buttz.draw(canvas);
buttz1.draw(canvas);
buttz2.draw(canvas);
buttz3.draw(canvas);
buttx.draw(canvas);
}
/**
* This is the game update method. It iterates through all the objects
* and calls their update method if they have one or calls specific
* engine's update method.
*/
public void update() {
droid.update();
}
public void handleActionDown(int eventX, int eventY) {
if (eventX >= (butt.x - butt.bitmap.getWidth() / 2) && (eventX <= (butt.x + butt.bitmap.getWidth()/2))) {
if (eventY >= (buttz.y - buttz.bitmap.getHeight() / 2) && (eventY <= (buttz3.y + buttz3.bitmap.getHeight() / 2))) {
// droid touched
droid.setTouched(true);
} else {
droid.setTouched(false);
}
} else {
droid.setTouched(false);
}
if (eventX >= (buttz1.x - buttz1.bitmap.getWidth() / 2) && (eventX <= (buttz.x + buttz.bitmap.getWidth()/2))) {
if (eventY >= (butt1.y - butt1.bitmap.getHeight() / 2) && (eventY <= (butt1.y + butt1.bitmap.getHeight() / 2))) {
// droid touched
droid.setTouched1(true);
} else {
droid.setTouched1(false);
}
}else {
droid.setTouched1(false);
}
if (eventX >= (butt2.x - butt2.bitmap.getWidth() / 2) && (eventX <= (butt2.x + butt2.bitmap.getWidth()/2))) {
if (eventY >= (buttz1.y - buttz1.bitmap.getHeight() / 2) && (eventY <= (buttz2.y + buttz2.bitmap.getHeight() / 2))) {
// droid touched
droid.setTouched2(true);
} else {
droid.setTouched2(false);
}
}else {
droid.setTouched2(false);
}
if (eventX >= (buttz2.x - buttz2.bitmap.getWidth() / 2) && (eventX <= (buttz3.x + buttz3.bitmap.getWidth()/2))) {
if (eventY >= (butt3.y - butt3.bitmap.getHeight() / 2) && (eventY <= (butt3.y + butt3.bitmap.getHeight() / 2))) {
// droid touched
droid.setTouched3(true);
} else {
droid.setTouched3(false);
}
}else {
droid.setTouched3(false);
}
if (droid.touched & !droid.touched1 & !droid.touched3) {
if (!moved) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.heror);
moved = true;
}
}else {
moved = false;
}if (droid.touched1 & !droid.touched & !droid.touched2){
if (!moved1) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herou);
moved1 = true;
}
}else {
moved1 = false;
} if (droid.touched2 & !droid.touched1 & !droid.touched3){
if (!moved2) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herol);
moved2 = true;
}
}else {
moved2 = false;
} if (droid.touched3 & !droid.touched2 & !droid.touched){
if (!moved7) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herod);
moved7 = true;
}
}else {
moved7 = false;
} if (droid.touched & droid.touched1){
if (!moved3) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herour);
moved3 = true;
}
}else {
moved3 = false;
} if (droid.touched1 & droid.touched2){
if (!moved4) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.heroul);
moved4 = true;
}
}else {
moved4 = false;
} if (droid.touched2 & droid.touched3){
if (!moved5) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herodl);
moved5 = true;
}
}else {
moved5 = false;
} if (droid.touched3 & droid.touched){
if (!moved6) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herodr);
moved6 = true;
}
}else {
moved6 = false;
}
}
}
and the Droid.java
/**
*
*/
package com.workspace.pockethero.model;
import android.graphics.Bitmap;
import android.graphics.Canvas;
/**
* This is a test droid that is dragged, dropped, moved, smashed against
* the wall and done other terrible things with.
* Wait till it gets a weapon!
*
* #author impaler
*
*/
public class Droid {
public Bitmap bitmap; // the actual bitmap
public int x; // the X coordinate
public int y; // the Y coordinate
public boolean touched; // if droid is touched/picked up
public boolean touched1; // if droid is touched/picked up
public boolean touched2; // if droid is touched/picked up
public boolean touched3; // if droid is touched/picked up
public Droid(Bitmap bitmap, int x, int y) {
this.bitmap = bitmap;
this.x = x;
this.y = y;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public void draw(Canvas canvas) {
canvas.drawBitmap(bitmap, x - (bitmap.getWidth() / 2), y - (bitmap.getHeight() / 2), null);
}
/**
* Method which updates the droid's internal state every tick
*/
public void setTouched(boolean touched) {
this.touched = touched;
}
public boolean isTouched() {
return touched;
}
public void setTouched1(boolean touched) {
this.touched1 = touched;
}
public boolean isTouched1() {
return touched1;
}
public void setTouched2(boolean touched) {
this.touched2 = touched;
}
public boolean isTouched2() {
return touched2;
}
public void setTouched3(boolean touched) {
this.touched3 = touched;
}
public boolean isTouched3() {
return touched3;
}
public void update() {
if (touched & !touched1 & !touched3) {
x += 1;
}else if (touched1 & !touched & !touched2){
y -= 1;
}else if (touched2 & !touched1 & !touched3){
x -= 1;
}else if (touched3 & !touched2 & !touched){
y += 1;
}else if (touched & touched1){
x += 1;
y -= 1;
}else if (touched1 & touched2){
x -= 1;
y -= 1;
}else if (touched2 & touched3){
x -= 1;
y += 1;
}else if (touched3 & touched){
x += 1;
y += 1;
}
}
/**
* Handles the {#link MotionEvent.ACTION_DOWN} event. If the event happens on the
* bitmap surface then the touched state is set to <code>true</code> otherwise to <code>false</code>
* #param eventX - the event's X coordinate
* #param eventY - the event's Y coordinate
*/
}
there are also other classes created for each button, but i didnt pasted them here, because the are practicly the same as droid.java
Re-drawing the whole frame each loop is the correct way to draw/move sprites and perform anything with canvas.
The buffer is cleared each frame and you need to re-draw the background and all objects at their specified position.
In your code render() and update() will not be called more frequently than approximately every 16milliseconds~ (60 frames per second), so all you have to think about is drawing the onscreen scene.
I'm not sure what you mean by 'laggy', maybe you have a performance issue related to the size of your Bitmaps or phone performance, but I hope this is close to what you were looking for.