Java: Graphics2D - JPopupMenu - Color Changing - java

I’m writing a simple graphics editor, thus far I can draw some figures, move and enlarge them. I’m trying to allow user to change color of figure. After I right click in a shape, there appears a popup menu with colors to choose. But no matter what I do - the shape’s color doesn’t change. :/ I hope to get help, I spent a of of time on it but no idea how to solve it. :/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
public class PaintPanel extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener
{
public RadioMenu radio = new RadioMenu();
private ArrayList <Point2D.Double> points = new ArrayList<>();
private ArrayList <Shape> figures = new ArrayList<>();
private Color mainColor = Color.blue;
private Color bgColor = Color.white;
private Color special = Color.red;
private double scrollSpeed = 5;
private int pointsize = 4;
private int near = 15;
private int index;
private ColorMenu colorMenu = new ColorMenu();
public PaintPanel()
{
super();
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
setLayout(new BorderLayout());
setBackground(bgColor);
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
drawGraphics(g2d);
}
private void drawGraphics(Graphics2D g2d)
{
int i = 0;
for (Shape s : figures)
{
g2d.setColor(mainColor);
if (s instanceof MyEllipse2D)
{
g2d.setColor(((MyEllipse2D) s).color);
System.out.println(g2d.getColor());
}
else if (s instanceof MyRectangle2D)
{
g2d.setColor(((MyRectangle2D) s).color);
System.out.println(g2d.getColor());
}
else if (s instanceof MyPolygon2D)
{
g2d.setColor(((MyPolygon2D) s).color);
System.out.println(g2d.getColor());
}
if (g2d.getColor() != bgColor)
{
g2d.setColor(mainColor);
} else
{
g2d.setColor(mainColor);
g2d.draw(s);
}
++i;
}
i = 0;
for (Point2D.Double p : points)
{
if (i == 0)
{
g2d.setColor(special);
g2d.fillOval((int) p.getX(), (int) p.getY(), pointsize, pointsize);
g2d.setColor(mainColor);
} else
{
g2d.fillOval((int) p.getX(), (int) p.getY(), pointsize, pointsize);
}
++i;
}
}
#Override
public void mousePressed(MouseEvent e)
{
if(e.getButton()==MouseEvent.BUTTON1)
switch (radio.getChoice())
{
case "Okrag":
points.clear();
points.add(new Point2D.Double(e.getX(),e.getY()));
repaint();
break;
case "Prostokat":
points.clear();
points.add(new Point2D.Double(e.getX(),e.getY()));
repaint();
break;
case "Edycja":
index = isSelected(e);
break;
}
else if(e.getButton()==MouseEvent.BUTTON3 && radio.getChoice().equals("Edycja"))
{
index = isSelected(e);
if(index >= 0)
{
colorMenu.doPop(e);
}
}
}
#Override
public void mouseReleased(MouseEvent e)
{
if(e.getButton()==MouseEvent.BUTTON1)
switch (radio.getChoice())
{
case "Okrag":
points.add(new Point2D.Double(e.getX(),e.getY()));
figures.add(new MyEllipse2D(points.get(0),points.get(1),bgColor));
points.clear();
break;
case "Prostokat":
points.add(new Point2D.Double(e.getX(),e.getY()));
figures.add(new MyRectangle2D(points.get(0),points.get(1),bgColor));
points.clear();
break;
case "Wielokat":
if(points.size() != 0 && points.get(0).distance(e.getX(),e.getY())<=near)
{
figures.add(new MyPolygon2D(points,bgColor));
points.clear();
}
else
{
points.add(new Point2D.Double(e.getX(),e.getY()));
}
break;
case "Edycja":
points.clear();
}
repaint();
}
#Override
public void mouseDragged(MouseEvent e)
{
if(index>=0 && radio.getChoice().equals("Edycja"))
{
if (figures.get(index) instanceof MyEllipse2D)
{
((MyEllipse2D) figures.get(index)).move(new Point2D.Double(e.getX(),e.getY()));
}
else if (figures.get(index) instanceof MyRectangle2D)
{
((MyRectangle2D) figures.get(index)).move(new Point2D.Double(e.getX(),e.getY()));
}
else if(figures.get(index) instanceof MyPolygon2D)
{
((MyPolygon2D) figures.get(index)).move(new Point2D.Double(e.getX(),e.getY()));
}
repaint();
}
}
#Override
public void mouseWheelMoved(MouseWheelEvent e)
{
index = isSelected(e);
if(radio.getChoice().equals("Edycja"))
{
if (index>=0)
{
if (figures.get(index) instanceof MyEllipse2D)
{
((MyEllipse2D) figures.get(index)).scale(e.getPreciseWheelRotation(), scrollSpeed);
}
else if (figures.get(index) instanceof MyRectangle2D)
{
((MyRectangle2D) figures.get(index)).scale(e.getPreciseWheelRotation(), scrollSpeed);
}
else if(figures.get(index) instanceof MyPolygon2D)
{
((MyPolygon2D) figures.get(index)).scale(e.getPreciseWheelRotation(), scrollSpeed);
}
repaint();
}
}
}
private int isSelected(MouseEvent e)
{
int i;
for(i=figures.size()-1;i>=0;--i)
{
if(figures.get(i).contains(e.getPoint()))
{
return i;
}
}
return -1;
}
#Override
public void mouseClicked(MouseEvent e)
{
index = isSelected(e);
if(index >= 0 )
{
colorMenu.doPop(e);
if(e.getButton()==MouseEvent.BUTTON3 && radio.getChoice().equals("Edycja"))
{
colorMenu.doPop(e);
if(figures.get(index) instanceof MyEllipse2D)
((MyEllipse2D) figures.get(index)).color = colorMenu.color;
else if(figures.get(index) instanceof MyRectangle2D)
((MyRectangle2D) figures.get(index)).color = colorMenu.color;
else if(figures.get(index) instanceof MyPolygon2D)
((MyPolygon2D) figures.get(index)).color = colorMenu.color;
System.out.println(colorMenu.color);
//colorMenu.color = bgColor;
}
repaint();
}
}
#Override
public void mouseEntered(MouseEvent e)
{
}
#Override
public void mouseExited(MouseEvent e)
{
}
#Override
public void mouseMoved(MouseEvent e)
{
}
}
ColorMenu
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
public class ColorMenu extends JPopupMenu implements ActionListener
{
private ArrayList<JMenuItem> items = new ArrayList<JMenuItem>();
private ArrayList<Color> colors = new ArrayList<Color>();
public Color color;
public ColorMenu()
{
super();
colors.add(Color.black);
colors.add(Color.blue);
colors.add(Color.cyan);
colors.add(Color.gray);
colors.add(Color.green);
colors.add(Color.magenta);
colors.add(Color.orange);
colors.add(Color.red);
colors.add(Color.yellow);
colors.add(Color.white);
for (Color c : colors)
{
items.add(new JMenuItem(c.toString()));
}
for(JMenuItem i: items)
{
i.addActionListener(this);
add(i);
}
}
public void doPop(MouseEvent e)
{
show(e.getComponent(), e.getX(), e.getY());
}
#Override
public void actionPerformed(ActionEvent e)
{
Object source = e.getSource();
int j=0;
for(JMenuItem i: items)
{
if(i == source)
{
break;
}
++j;
}
this.color = colors.get(j);
}
}
MyRectangle2D
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class MyRectangle2D extends Rectangle2D.Double implements Shape
{
private Point2D.Double center;
private double width,hight;
public Color color;
public MyRectangle2D()
{}
public MyRectangle2D(Point2D.Double p1, Point2D.Double p2, Color color)
{
super();
this.color = color;
double x1 = p1.getX();
double y1 = p1.getY();
double x2 = p2.getX();
double y2 = p2.getY();
if(x1<=x2 && y1>=y2)
{
width=x2-x1;
hight=y1-y2;
setRect(x1,y2,width,hight);
}
else if(x1<=x2 && y1<=y2)
{
width=x2-x1;
hight=y2-y1;
setRect(x1,y1,width,hight);
}
else if (x1>=x2 && y1<=y2)
{
width=x1-x2;
hight=y2-y1;
setRect(x2,y1,width,hight);
}
else if(x1>=x2 && y1>=y2)
{
width=x1-x2;
hight=y1-y2;
setRect(x2,y2,width,hight);
}
center = new Point2D.Double(x1 + (x2-x1)/2,y1+(y2-y1)/2);
}
public void scale(double amount, double scale)
{
double change = -1*amount*scale;
width += change;
hight += change;
setRect(center.getX()-width/2,center.getY()-hight/2,width,hight);
}
public void move (Point2D.Double p)
{
center = p;
setRect();
}
private void setRect()
{
setRect(center.getX()-width/2,center.getY()-hight/2,width,hight);
}
}
MyPolygon2D
import java.awt.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
public class MyPolygon2D extends Polygon implements Shape
{
private ArrayList<MyVector> vectors = new ArrayList<>();
private Point2D.Double center;
private int size;
public Color color;
public MyPolygon2D()
{}
public MyPolygon2D(ArrayList<Point2D.Double> points, Color color)
{
super();
this.color = color;
size = points.size();
for(int i=0; i<size;++i)
{
addPoint((int)points.get(i).getX(),(int)points.get(i).getY());
}
center();
setVectors();
}
public void scale(double amount, double scale)
{
double change = -1*amount*scale;
for (int i=0;i<size;++i)
{
vectors.get(i).x *= (100.0+change)/100.0;
vectors.get(i).y *= (100.0+change)/100.0;
Point2D.Double curr = new Point2D.Double(center.getX()+vectors.get(i).x,center.getY()+vectors.get(i).y);
xpoints[i] = (int)curr.getX();
ypoints[i] = (int)curr.getY();
}
invalidate();
}
public void move (Point2D.Double p)
{
MyVector change = new MyVector(center,p);
center = p;
for(int i=0;i<size;++i)
{
xpoints[i] += (int)change.x;
ypoints[i] += (int)change.y;
}
invalidate();
}
public void setColor(Color color)
{
this.color = color;
}
public Color getColor()
{
return this.color;
}
#Override
public boolean contains(Point p)
{
int maxx=0, maxy=0, minx=Integer.MAX_VALUE, miny=Integer.MAX_VALUE;
for (int i=0;i<size;++i)
{
if(xpoints[i]>=maxx)
maxx = xpoints[i];
if(xpoints[i]<=minx)
minx = xpoints[i];
if(ypoints[i]>=maxy)
maxy = ypoints[i];
if(ypoints[i]<=miny)
miny = ypoints[i];
}
if(p.getX() <= maxx && p.getX() >= minx && p.getY() <= maxy && p.getY() >=miny)
return true;
else
return false;
}
private void setVectors()
{
for(int i=0; i<size;++i)
{
vectors.add(new MyVector(center,new Point2D.Double(xpoints[i],ypoints[i])));
}
}
private void center()
{
center = new Point2D.Double(getBounds2D().getX()+getBounds2D().getWidth()/2,getBounds2D().getY()+getBounds2D().getHeight()/2);
}
private class MyVector
{
public double x, y;
public MyVector(Point2D.Double p1, Point2D.Double p2)
{
x=p2.getX()-p1.getX();
y=p2.getY() - p1.getY();
}
}
}
MyEllipse2D
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
public class MyEllipse2D extends Ellipse2D.Double implements Shape
{
private double radius;
private Point2D.Double center;
public Color color;
public MyEllipse2D(Point2D.Double p1, Point2D.Double p2, Color color)
{
super();
this.color = color;
center = p1;
radius = (p1.distance(p2));
setFrame();
}
public void scale(double amount, double scale)
{
double change = -1*amount*scale;
radius += change;
setFrame();
}
public void move (Point2D.Double p)
{
center = p;
setFrame();
}
public void setColor(Color color)
{
this.color = color;
System.out.println(this.color);
}
public Color getColor()
{
return this.color;
}
private void setFrame()
{
setFrame(center.getX()-radius,center.getY()-radius,2*radius,2*radius);
}
}
RadioMenu
import javax.swing.*;
import java.awt.*;
public class RadioMenu extends JPanel
{
private int amount = 4;
private JRadioButton[] options = new JRadioButton[amount];
private ButtonGroup group = new ButtonGroup();
private String[] names = {"Okrag","Prostokat","Wielokat","Edycja"};
private Font font = new Font("Times New Roman",Font.BOLD,16);
public RadioMenu()
{
super();
setLayout(new GridLayout(1,amount));
for(int i=0;i<amount;++i)
{
if(i!=0)
options[i] = new JRadioButton(names[i],false);
else
options[i] = new JRadioButton(names[i],true);
group.add(options[i]);
add(options[i]);
options[i].setFont(font);
}
}
public String getChoice()
{
for(int i=0; i<amount; ++i)
{
if(options[i].isSelected())
return options[i].getText();
}
return "";
}
}
Frame
import javax.swing.*;
import java.awt.*;
public class Frame extends JFrame
{
private Dimension prefsize = new Dimension(800,600);
private Dimension minSize = new Dimension(400,200);
private Menu menu = new Menu();
private PaintPanel panel = new PaintPanel();
public Frame()
{
super();
setVisible(true);
setPreferredSize(prefsize);
setMinimumSize(minSize);
setLayout(new BorderLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
add(panel,BorderLayout.CENTER);
JPanel upper = new JPanel();
upper.setLayout(new GridLayout(2,1));
upper.add(menu);
upper.add(panel.radio);
add(upper,BorderLayout.NORTH);
pack();
}
}
MyAplet
import javax.swing.*;
public class MyAplet extends JApplet
{
public void init()
{
Frame main = new Frame();
}
}
Menu
import javax.swing.*;
public class Menu extends JMenuBar
{
private JMenu info;
//private JMenuItem x;
public Menu()
{
super();
info = new JMenu("info");
//info.add(x);
add(info);
}
}

First, you should be displaying a JPopupMenu in response to only one type of event. You are currently calling colorMenu.doPop(e) in the mousePressed method and twice in the mouseClicked method.
To make sure you only display a JPopupMenu when you’re supposed to, you should use the JPopupMenu.isPopupTrigger method. This way, the menu will be displayed on mousePress or mouseClick, but never both. You do not want to display a JPopupMenu more than once in response to a single mouse action!
private int selectedIndex = -1;
// ...
#Override
public void mousePressed(MouseEvent e)
{
selectedIndex = isSelected(e);
if (colorMenu.isPopupTrigger(e)) {
colorMenu.doPop(e);
} else if (e.getButton() == MouseEvent.BUTTON1) {
// ...
#Override
public void mouseClicked(MouseEvent e)
{
if (selectedIndex >= 0 && colorMenu.isPopupTrigger(e) && radio.getChoice().equals("Edycja"))
{
colorMenu.doPop(e);
The next problem is that you’re trying to read colorMenu.color before the user has made a color selection. When you call colorMenu.doPopup(e), the menu is displayed and the method returns immediately. It does not wait for the user to make a selection and dismiss the menu. Your code is trying to immediately make use of colorMenu.color, but it hasn’t been set yet.
The only way to know when the user has selected a color is with an ActionListener. Your ColorMenu class has an ActionListener, but currently there is no way for the PaintPanel class to know when the ActionListener has been triggered.
You can give your ColorMenu class the capability of notifying listeners in other classes, using the inherited listenerList object:
public void addChangeListener(ChangeListener listener) {
listenerList.add(ChangeListener.class, listener);
}
public void removeChangeListener(ChangeListener listener) {
listenerList.remove(ChangeListener.class, listener);
}
public ChangeListener[] getChangeListeners() {
return listenerList.getListeners(ChangeListener.class);
}
protected void fireChangeListeners() {
ChangeEvent event = new ChangeEvent(this);
for (ChangeListener listener : getChangeListeners()) {
listener.stateChanged(event);
}
}
(ChangeListener and ChangeEvent are in the javax.swing.event package.)
This allows other classes to listen for a user selection. You want to change ColorMenu to actually notify those listeners:
#Override
public void actionPerformed(ActionEvent e)
{
// (other code) ...
this.color = colors.get(j);
fireChangeListeners();
}
Now, you can make PaintPanel listen for a user selection:
public PaintPanel()
{
super();
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
setLayout(new BorderLayout());
setBackground(bgColor);
colorMenu.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent event) {
if (selectedIndex >= 0)
{
Shape figure = figures.get(selectedIndex);
if (figure instanceof MyEllipse2D)
((MyEllipse2D) figure).color = colorMenu.color;
else if (figure instanceof MyRectangle2D)
((MyRectangle2D) figure).color = colorMenu.color;
else if (figure instanceof MyPolygon2D)
((MyPolygon2D) figure).color = colorMenu.color;
}
}
});
}
You should remove that color changing code from mouseClicked, as it will never be valid to call it there, since the user hasn’t selected a color yet:
#Override
public void mouseClicked(MouseEvent e)
{
if (selectedIndex >= 0 && colorMenu.isPopupTrigger(e) && radio.getChoice().equals("Edycja"))
{
colorMenu.doPop(e);
// Nothing else to do here, since the user has not selected a color yet.
}
}

The basic problem is, you need to know when the color is selected from the menu. The solution to the problem can be achieved in a number of different ways.
You could continue to use a MouseListener to monitor the mousePressed, mouseReleased and mouseClicked events, but you should be making using of the MouseEvent#isPopupTrigger property to determine when the popup should be displayed, as the triggers are different for different platforms.
Another solution would be to use JComponent#setComponentPopupMenu all the component to take care of the action itself. This, however, would require a slight change in design.
This approach places the onus back onto the component, rather then been separated into a different class, like you have the ColorMenu right now.
To start with, we need to know the selected shape, so, to the PaintPanel, we need to add a new property
private Shape selectedShape;
All this does is determines which shape was selected by the user.
Next we need someway to update the shape color. This can be achieved by simply adding a new method to the paintPanel method, which is called when the color is to be changed.
public void setShapeColor(Color color) {
//...
}
Next, we take advantage of the Action API and create a simple ColorAction.
This is self contained unit of work, which contains the information need to display it on a menu (or button) and which determines the actions to take when triggered.
public class ColorAction extends AbstractAction {
private Color color;
public ColorAction(String name, Color color) {
super(name);
this.color = color;
}
public Color getColor() {
return color;
}
#Override
public boolean isEnabled() {
return getSelectedShape() != null;
}
#Override
public void actionPerformed(ActionEvent e) {
setShapeColor(color);
}
}
In this case, this simple inner class to PaintPanel will call setShapeColor when triggered.
"But wait", I hear you call, "The colors can only be selected when a shape is selected!" Ah, we can control the Action through the isEnabled method, which will only return true when a shape is actually selected.
This might seem somewhat counterintuitive, but by not displaying a popup menu when nothing is displayed, it causes the user to think that no popup menu will be displayed ever, at least this way, we can display a popup menu with it's options disabled.
The next trick is trying to determine what is selected. Unfortunately, during my testing, the OS consumed the mousePressed event, which would have been most useful to make this determination, however, not all is lost.
The JComponent will call getPopupLocation just before the popup is made visible, passing in the MouseEvent which triggered it, we can take advantage of this to determine which shape would be selected
#Override
public Point getPopupLocation(MouseEvent event) {
selectedShape = null;
for (int i = figures.size() - 1; i >= 0; --i) {
if (figures.get(i).contains(event.getPoint())) {
selectedShape = figures.get(i);
}
}
return super.getPopupLocation(event);
}
For a more complete example...
public class PaintPanel extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener {
private ArrayList<Point2D.Double> points = new ArrayList<>();
private ArrayList<Shape> figures = new ArrayList<>();
private Shape selectedShape;
private Color mainColor = Color.blue;
private Color bgColor = Color.white;
private Color special = Color.red;
private double scrollSpeed = 5;
private int pointsize = 4;
private int near = 15;
private int index;
public PaintPanel() {
super();
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
setLayout(new BorderLayout());
setBackground(bgColor);
setComponentPopupMenu(makePopupMenu());
}
protected JPopupMenu makePopupMenu() {
JPopupMenu menu = new JPopupMenu();
menu.add(new ColorAction("Black", Color.black));
menu.add(new ColorAction("Blue", Color.blue));
menu.add(new ColorAction("Cyan", Color.cyan));
menu.add(new ColorAction("Grey", Color.gray));
menu.add(new ColorAction("Green", Color.green));
menu.add(new ColorAction("Megenta", Color.magenta));
menu.add(new ColorAction("Orange", Color.orange));
menu.add(new ColorAction("Red", Color.red));
menu.add(new ColorAction("Yellow", Color.yellow));
menu.add(new ColorAction("White", Color.white));
return menu;
}
public Shape getSelectedShape() {
return selectedShape;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public Point getPopupLocation(MouseEvent event) {
selectedShape = null;
for (int i = figures.size() - 1; i >= 0; --i) {
if (figures.get(i).contains(event.getPoint())) {
selectedShape = figures.get(i);
}
}
return super.getPopupLocation(event);
}
#Override
public void mouseClicked(MouseEvent e) {
//...
}
#Override
public void mousePressed(MouseEvent e) {
//...
}
#Override
public void mouseReleased(MouseEvent e) {
//...
}
#Override
public void mouseEntered(MouseEvent e) {
//...
}
#Override
public void mouseExited(MouseEvent e) {
//...
}
#Override
public void mouseDragged(MouseEvent e) {
//...
}
#Override
public void mouseMoved(MouseEvent e) {
//...
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
//...
}
public void setShapeColor(Color color) {
//...
}
public class ColorAction extends AbstractAction {
private Color color;
public ColorAction(String name, Color color) {
super(name);
this.color = color;
}
public Color getColor() {
return color;
}
#Override
public boolean isEnabled() {
return getSelectedShape() != null;
}
#Override
public void actionPerformed(ActionEvent e) {
setShapeColor(color);
}
}
}
There are a couple of other ways you might achieve this, but I'd be using these basic principles are the cornerstone of the design.
For example, you could have a "color model" which get's passed to the popup menu class, which would be updated when a color is selected

Related

JTable AutoScrolling like IntelliJ Console

I want to add a Scroll To End (Autoscroll) function to a jtable with maximum up to 50 rows.
AutoScroll function should work as IntelliJ Scroll To End.
Initially there are 0 rows. In several milliseconds (say 100 ms.) a few item (1-5 items) is added to the end of the table. If the table row count reaches to the max (50), the first row will be deleted before the next insertion.
When user scrolls to the end with mouse dragging or scroll wheel, scroll to end checkbox selected and function will be enabled. From that moment, the scrollpane should be scrolled to the bottom of the table like:
scrollRectToVisible(getCellRect(getRowCount() - 1, 0, true));
When user changes scrollbar position other than the bottom of viewport by dragging or mouse scroll wheel, scroll to end checkbox will deselected and function will be disabled.
While scroll to end checkbox is not selected, the table will react to scroll bar movements which are sourced by dragging or mouse wheel. But the viewport should show same data.
If it is impossible to show same data in the viewport because of deletions of the consequent first rows, the visible first data in the viewport will go to the up.
This is the exact behavior of the IntelliJ console log screen.
I wrote Scroll To End enable functionality (90% percent working).
I could not write Scroll To End disable functionality, I don't know to preserve current viewport as stated above.
Thanks for your help.
package org.example.table;
import com.bsbls.home.gui.test.GuiTester;
import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class MyTable extends JTable {
JScrollPane scrollPane;
boolean autoScroll;
private Consumer<Boolean> scrollToEndListener;
private Object lastValue;
private ScheduledFuture<?> future;
public MyTable(int max) {
this.setFillsViewportHeight(true);
this.setRowHeight(24);
this.setModel(new MyTableModel(max));
this.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
if (autoScroll) {
scrollToEnd();
}
}
});
}
public MyTableModel getTableModel() {
return (MyTableModel) getModel();
}
private void scrollToEnd() {
scrollRectToVisible(getCellRect(getRowCount() - 1, 0, true));
}
public void setScrollToEnd(boolean autoScroll) {
this.autoScroll = autoScroll;
if (autoScroll) {
scrollToEnd();
}
if (scrollToEndListener != null) {
scrollToEndListener.accept(autoScroll);
}
}
public void setScrollToEndListener(Consumer<Boolean> scrollToEndListener) {
this.scrollToEndListener = scrollToEndListener;
}
public JScrollPane wrap() {
if (scrollPane == null) {
scrollPane = new JScrollPane(this);
scrollPane.getViewport().setScrollMode(JViewport.BACKINGSTORE_SCROLL_MODE);
JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
scrollBar.addAdjustmentListener(e -> {
if (e.getValue() + scrollBar.getVisibleAmount() == scrollBar.getMaximum()) {
setScrollToEnd(true);
} else {
setScrollToEnd(false);
}
});
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scrollPane.addMouseWheelListener(new MouseWheelListener() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (future != null) {
future.cancel(false);
}
future = scheduler.schedule(() -> {
EventQueue.invokeLater(() -> {
System.out.println("Yes");
JViewport viewport = scrollPane.getViewport();
Point p = viewport.getViewPosition();
Dimension extentSize = viewport.getExtentSize();
//p.translate(extentSize.width, extentSize.height);
int rowIndex = rowAtPoint(p);
if (rowIndex >= 0) {
lastValue = getValueAt(rowIndex, 0);
}
});
}, 500, TimeUnit.MILLISECONDS);
}
});
scrollBar.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
//System.out.println(e);
}
#Override
public void mousePressed(MouseEvent e) {
//System.out.println(e);
lastValue = null;
}
#Override
public void mouseReleased(MouseEvent e) {
//System.out.println(e);
JViewport viewport = scrollPane.getViewport();
Point p = viewport.getViewPosition();
Dimension extentSize = viewport.getExtentSize();
//p.translate(extentSize.width, extentSize.height);
int rowIndex = rowAtPoint(p);
if (rowIndex >= 0) {
lastValue = getValueAt(rowIndex, 0);
}
}
#Override
public void mouseEntered(MouseEvent e) {
//System.out.println(e);
}
#Override
public void mouseExited(MouseEvent e) {
//System.out.println(e);
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
//System.out.println(e);
}
#Override
public void mouseDragged(MouseEvent e) {
// System.out.println(e);
}
#Override
public void mouseMoved(MouseEvent e) {
//System.out.println(e);
}
});
getTableModel().addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if (lastValue != null && !autoScroll) {
int rowCount = getRowCount();
int newIndex = -1;
for (int i = 0; i < rowCount; i++) {
Object indexValue = getValueAt(i, 0);
if (indexValue == lastValue) {
newIndex = i;
break;
}
}
System.out.println(lastValue + " " + newIndex);
if (newIndex > 1) {
scrollRectToVisible(getCellRect(newIndex - 1, 0, true));
} else {
lastValue = null;
}
}
}
});
}
return scrollPane;
}
static int counter;
public static void main(String[] args) {
GuiTester.test(f -> {
JPanel panel = new JPanel(new BorderLayout());
MyTable table = new MyTable(50);
MyTableModel model = table.getTableModel();
Random random = new Random();
Timer timer = new Timer(100, e -> {
Data data = new Data();
data.setName(++counter + "");
data.setX(random.nextInt());
data.setY(random.nextInt());
data.setZ(random.nextInt());
data.setFlag(random.nextBoolean());
model.addRow(data.toObjectArray());
});
timer.start();
DataPanel dataPanel = new DataPanel();
table.getSelectionModel().addListSelectionListener(e -> {
int index = e.getFirstIndex();
if (index >= 0) {
Data data = (Data) table.getValueAt(index, 5);
dataPanel.setData(data);
}
});
JCheckBox checkBox = new JCheckBox("Scroll To End");
table.setScrollToEndListener(flag -> {
checkBox.setSelected(flag);
});
checkBox.addItemListener(e -> {
table.setScrollToEnd(checkBox.isSelected());
});
panel.add(checkBox, BorderLayout.NORTH);
panel.add(table.wrap(), BorderLayout.CENTER);
panel.add(dataPanel.getPanel(), BorderLayout.EAST);
return panel;
});
}
}
Table Model
package org.example.table;
import javax.swing.table.DefaultTableModel;
public class MyTableModel extends DefaultTableModel {
private int max = -1;
public MyTableModel() {
this(-1);
}
public MyTableModel(int max) {
super(new Object[]{
"Name", "Flag", "X", "Y", "Z", "Data"
}, 0);
this.max = max;
}
#Override
public void addRow(Object[] rowData) {
if (getRowCount() == max) {
super.removeRow(0);
}
super.addRow(rowData);
}
}
Dummy data
package org.example.table;
public class Data {
private String name;
private boolean flag;
private int x;
private int y;
private int z;
public Object[] toObjectArray() {
Object[] array = new Object[6];
array[0] = name;
array[1] = flag;
array[2] = x;
array[3] = y;
array[4] = z;
array[5] = this;
return array;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
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 int getZ() {
return z;
}
public void setZ(int z) {
this.z = z;
}
}
DatPanel which is a dummy IntelliJ form:
package org.example.table;
import javax.swing.*;
public class DataPanel {
private JTextField fieldName;
private JCheckBox flagCheckBox;
private JTextField fieldX;
private JTextField fieldY;
private JTextField fieldZ;
private JPanel panel;
public JPanel getPanel() {
return panel;
}
public void setData(Data data) {
fieldName.setText(data.getName());
fieldX.setText(data.getX() + "");
fieldY.setText(data.getY() + "");
fieldZ.setText(data.getZ() + " ");
flagCheckBox.setSelected(data.isFlag());
}
}
EDIT:
I have added to mouse and mouse wheel listeners, and find the first visible row.
And changed scroll mode to simple or backingstore.
scrollPane.getViewport().setScrollMode(JViewport.BACKINGSTORE_SCROLL_MODE);
In that case I have got a better function.
I am still open for better approaches.

Program not painting screen properly

I've been constructing a short program that basically draws a spaceship on a JPanel and listens for keys that tell the program to shoot a bullet. The problem is that it's not even painting the spaceship or the bullets on the screen. I also suspect that the KeyBindings may not be working as that was a previous problem (that I may or may not have fixed), but the main issue at hand is still the fact that my screen isn't being painted. Here is my code:
public enum Direction {
LEFT, RIGHT, SPACE
}
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame frame;
Ship s1;
Shoot shoot;
// Set the frame up
frame = new JFrame();
frame.setSize(400, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setVisible(true);
// Get some more necessary objects
s1 = new Ship();
shoot = new Shoot(s1);
frame.getContentPane().add(shoot);
s1.setShoot(shoot);
// Threads
Thread ship = new Thread(s1);
ship.start();
}
}
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class Shoot extends JPanel {
Ship s1;
public Shoot(Ship s1) {
this.s1 = s1;
addKeyBinding(KeyEvent.VK_LEFT, "left.pressed", new MoveAction(true, s1, Direction.LEFT), true);
addKeyBinding(KeyEvent.VK_LEFT, "left.released", new MoveAction(false, s1, Direction.LEFT), false);
addKeyBinding(KeyEvent.VK_RIGHT, "right.pressed", new MoveAction(true, s1, Direction.RIGHT), true);
addKeyBinding(KeyEvent.VK_RIGHT, "right.released", new MoveAction(false, s1, Direction.RIGHT), false);
addKeyBinding(KeyEvent.VK_SPACE, "space.pressed", new MoveAction(true, s1, Direction.SPACE), true);
addKeyBinding(KeyEvent.VK_SPACE, "space.released", new MoveAction(false, s1, Direction.SPACE), false);
setDoubleBuffered(true);
}
#Override
public void paintComponent(Graphics g) {
// Draw the ship
super.paintComponent(g);
s1.draw(g);
g.fill3DRect(40, 50, 10, 10, false);
}
protected void addKeyBinding(int keyCode, String name, Action action, boolean keyPressed) {
if (keyPressed) {
addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0, false), name, action);
} else {
addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0, true), name, action);
}
}
protected void addKeyBinding(KeyStroke keyStroke, String name, Action action) {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(keyStroke, name);
actionMap.put(name, action);
}
}
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
public class Ship implements Runnable {
int x, y, xDirection, bx, by;
boolean readyToFire, shooting = false;
Rectangle bullet;
Shoot shoot;
public Ship() {
x = 175;
y = 275;
bullet = new Rectangle(0, 0, 3, 5);
}
public void draw(Graphics g) {
// System.out.println("draw() called");
g.setColor(Color.BLUE);
g.fillRect(x, y, 40, 10);
g.fillRect(x + 18, y - 7, 4, 7);
if (shooting) {
g.setColor(Color.RED);
g.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
}
shoot.repaint();
}
public void move() {
x += xDirection;
if (x <= 0)
x = 0;
if (x >= 360)
x = 360;
shoot.repaint();
}
public void shoot() {
if (shooting) {
bullet.y--;
shoot.repaint();
}
}
public void setXDirection(int xdir) {
xDirection = xdir;
}
public void setShoot(Shoot shoot) {
this.shoot = shoot;
}
#Override
public void run() {
try {
while (true) {
shoot();
move();
Thread.sleep(5);
}
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.util.HashSet;
import javax.swing.AbstractAction;
public class MoveAction extends AbstractAction {
boolean pressed;
Ship s1;
Direction dir;
private Set<Direction> movement;
public MoveAction(boolean pressed, Ship s1, Direction dir) {
System.out.println("moveaction class");
movement = new HashSet<Direction>();
this.pressed = pressed;
this.s1 = s1;
this.dir = dir;
}
#Override
public void actionPerformed(ActionEvent e) {
try {
if (movement.contains(Direction.LEFT)) {
if (pressed) {
s1.setXDirection(-1);
} else {
s1.setXDirection(0);
}
} else if (movement.contains(Direction.RIGHT)) {
if (pressed) {
s1.setXDirection(1);
} else {
s1.setXDirection(0);
}
} else if (movement.contains(Direction.SPACE)) {
if (pressed) {
if (s1.bullet == null)
s1.readyToFire = true;
if (s1.readyToFire) {
s1.bullet.x = s1.x + 18;
s1.bullet.y = s1.y - 7;
s1.shooting = true;
}
} else {
s1.readyToFire = false;
if (s1.bullet.y <= -7) {
s1.bullet = null;
s1.shooting = false;
s1.bullet = null;
s1.bullet = new Rectangle(0, 0, 0, 0);
s1.readyToFire = true;
}
}
}
} catch (NullPointerException ex) {
System.out.println("NullPointerException");
}
}
So, there are a number of issues...
You should call setVisible on the JFrame last, this will ensure that components are laid out
Your keybindings issue seem to related to the fact that you're using the movement Set, but you never actually add anything to it. Instead you should be checking the dir value.
And probably a bunch of other things. Your code is tightly coupled and there isn't any centralised management of the state.
I'd start by having a better understand of the Model-View-Controller paradigm and separate the code into appropriate areas of responsibility.
The "data" for the game should separate from the "rendering" of the game, which should be separate from the decisions about how the game is to be updated.
What I'm about to present is an oversimplification intended to spark ideas, rather than been a concrete solution, as there are a number of ways you could achieve the physical implementation...
So, what we need is, a concept of something in the game, AKA an "entity", entities take many forms, but I'm going to focus on renderable/displayable entities. You need some kind of model that is responsible for modeling the current state of the game and which is responsible for implementing the rules. You need some kind of view, which is responsible for rendering the model and responding to user input. You need some kind of controller which controls how the model and the view bridged.
It's always a good idea to start with a series of interfaces which define the contractual expectations and outlines the expected means by which elements are expected to communicate with each other. Again, I've taken a simple, direct approach, but it's by no means the only...
public static enum Direction {
LEFT,
RIGHT,
SPACE
}
public interface Entity {
public void paint(Graphics2D g2d);
public Point getLocation();
public void setLocation(Point p);
public Dimension getSize();
}
public interface GameModel {
public Entity[] getEntities();
public void update(Rectangle bounds, Set<Direction> keys);
}
public interface GameController {
public Entity[] getEntities();
public void setDirection(Direction direction, boolean pressed);
public void start();
}
public interface GameView {
public void setController(GameController controller);
public GameController getController();
public Rectangle getViewBounds();
public void repaint();
}
Let's take a look at the implementation of the entities. This example has two entities, a Player and a Bullet...
public abstract class AbstractEntity implements Entity {
private final Point location = new Point(0, 0);
#Override
public Point getLocation() {
return new Point(location);
}
#Override
public void setLocation(Point p) {
location.setLocation(p);
}
}
public class Player extends AbstractEntity {
public Player(Rectangle bounds) {
int x = bounds.x + ((bounds.width - getSize().width) / 2);
int y = bounds.y + (bounds.height - getSize().height);
setLocation(new Point(x, y));
}
#Override
public Dimension getSize() {
return new Dimension(40, 17);
}
#Override
public void paint(Graphics2D g2d) {
Point p = getLocation();
Dimension size = getSize();
g2d.setColor(Color.BLUE);
g2d.fillRect(p.x, p.y + 7, size.width, 10);
g2d.fillRect(p.x + 18, p.y, 4, 7);
}
}
public class Bullet extends AbstractEntity {
#Override
public void paint(Graphics2D g2d) {
Rectangle bullet = new Rectangle(getLocation(), getSize());
g2d.setColor(Color.RED);
g2d.fill(bullet);
}
#Override
public Dimension getSize() {
return new Dimension(4, 8);
}
}
Nothing spectacular, but they each define their parameters and can render their states when asked.
Next, we have the model, controller and view. This example uses the controller as the primary game loop, responsible for updating the game (model) state and scheduling repaints. This is done with the use of a Swing Timer as this prevents possible race conditions between the update loop and the painting loop. A more complex implementation would need to take over the painting process through the use of a BufferStrategy and BufferStrategy and BufferCapabilities.
The model simply takes the current view boundaries and the current state of the keys and updates the state of the entities.
The view monitors user input, notifying the controller, and renders the current state of the game.
You'll note that the view and model never communicate directly with each other, this is the domain of the controller.
public class DefaultGameModel implements GameModel {
private final List<Entity> entities;
private Player player;
private Long lastShot;
public DefaultGameModel() {
entities = new ArrayList<>(25);
}
#Override
public Entity[] getEntities() {
return entities.toArray(new Entity[0]);
}
#Override
public void update(Rectangle bounds, Set<Direction> keys) {
if (player == null) {
player = new Player(bounds);
entities.add(player);
}
Point p = player.getLocation();
int xDelta = 0;
if (keys.contains(Direction.LEFT)) {
xDelta = -4;
} else if (keys.contains(Direction.RIGHT)) {
xDelta = 4;
}
p.x += xDelta;
if (p.x <= bounds.x) {
p.x = bounds.x;
} else if (p.x + player.getSize().width >= bounds.x + bounds.width) {
p.x = bounds.width - player.getSize().width;
}
player.setLocation(p);
Iterator<Entity> it = entities.iterator();
while (it.hasNext()) {
Entity entity = it.next();
if (entity instanceof Bullet) {
Point location = entity.getLocation();
Dimension size = entity.getSize();
location.y -= size.height;
if (location.y + size.height < bounds.y) {
it.remove();
} else {
entity.setLocation(location);
}
}
}
if (keys.contains(Direction.SPACE)) {
if (lastShot == null || System.currentTimeMillis() - lastShot > 100) {
lastShot = System.currentTimeMillis();
Bullet bullet = new Bullet();
int x = p.x + ((player.getSize().width - bullet.getSize().width) / 2);
int y = p.y - bullet.getSize().height;
bullet.setLocation(new Point(x, y));
entities.add(bullet);
}
}
}
}
public class DefaultGameController implements GameController {
private GameModel model;
private GameView view;
private Timer timer;
private Set<Direction> keys = new HashSet<>(25);
public DefaultGameController(GameModel gameModel, GameView gameView) {
gameView.setController(this);
view = gameView;
model = gameModel;
}
#Override
public Entity[] getEntities() {
return model.getEntities();
}
#Override
public void setDirection(Direction direction, boolean pressed) {
if (pressed) {
keys.add(direction);
} else {
keys.remove(direction);
}
}
#Override
public void start() {
if (timer != null && timer.isRunning()) {
timer.stop();
}
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.update(view.getViewBounds(), Collections.unmodifiableSet(keys));
view.repaint();
}
});
timer.start();
}
}
public class DefaultGameView extends JPanel implements GameView {
private GameController controller;
public DefaultGameView() {
addKeyBinding("left.pressed", KeyEvent.VK_LEFT, true, new DirectionAction(Direction.LEFT, true));
addKeyBinding("left.released", KeyEvent.VK_LEFT, false, new DirectionAction(Direction.LEFT, false));
addKeyBinding("right.pressed", KeyEvent.VK_RIGHT, true, new DirectionAction(Direction.RIGHT, true));
addKeyBinding("right.released", KeyEvent.VK_RIGHT, false, new DirectionAction(Direction.RIGHT, false));
addKeyBinding("space.pressed", KeyEvent.VK_SPACE, true, new DirectionAction(Direction.SPACE, true));
addKeyBinding("space.released", KeyEvent.VK_SPACE, false, new DirectionAction(Direction.SPACE, false));
}
protected void addKeyBinding(String name, int keyEvent, boolean pressed, DirectionAction action) {
addKeyBinding(name, KeyStroke.getKeyStroke(keyEvent, 0, !pressed), action);
}
protected void addKeyBinding(String name, KeyStroke keyStroke, DirectionAction action) {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(keyStroke, name);
actionMap.put(name, action);
}
#Override
public void setController(GameController controller) {
this.controller = controller;
}
#Override
public GameController getController() {
return controller;
}
#Override
public Rectangle getViewBounds() {
return new Rectangle(new Point(0, 0), getSize());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
GameController controller = getController();
for (Entity entity : controller.getEntities()) {
// I don't trust you
Graphics2D g2d = (Graphics2D) g.create();
entity.paint(g2d);
g2d.dispose();
}
}
public class DirectionAction extends AbstractAction {
private Direction direction;
private boolean pressed;
public DirectionAction(Direction direction, boolean pressed) {
this.direction = direction;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
getController().setDirection(direction, pressed);
}
}
}
Okay, but that's all fine and good, but how do you use it? Something like this for example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
GameModel model = new DefaultGameModel();
DefaultGameView view = new DefaultGameView();
GameController controller = new DefaultGameController(model, view);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(view);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
controller.start();
}
});
}
public static enum Direction {
LEFT,
RIGHT,
SPACE
}
public interface Entity {
public void paint(Graphics2D g2d);
public Point getLocation();
public void setLocation(Point p);
public Dimension getSize();
}
public interface GameModel {
public Entity[] getEntities();
public void update(Rectangle bounds, Set<Direction> keys);
}
public interface GameController {
public Entity[] getEntities();
public void setDirection(Direction direction, boolean pressed);
public void start();
}
public interface GameView {
public void setController(GameController controller);
public GameController getController();
public Rectangle getViewBounds();
public void repaint();
}
public class DefaultGameModel implements GameModel {
private final List<Entity> entities;
private Player player;
private Long lastShot;
public DefaultGameModel() {
entities = new ArrayList<>(25);
}
#Override
public Entity[] getEntities() {
return entities.toArray(new Entity[0]);
}
#Override
public void update(Rectangle bounds, Set<Direction> keys) {
if (player == null) {
player = new Player(bounds);
entities.add(player);
}
Point p = player.getLocation();
int xDelta = 0;
if (keys.contains(Direction.LEFT)) {
xDelta = -4;
} else if (keys.contains(Direction.RIGHT)) {
xDelta = 4;
}
p.x += xDelta;
if (p.x <= bounds.x) {
p.x = bounds.x;
} else if (p.x + player.getSize().width >= bounds.x + bounds.width) {
p.x = bounds.width - player.getSize().width;
}
player.setLocation(p);
Iterator<Entity> it = entities.iterator();
while (it.hasNext()) {
Entity entity = it.next();
if (entity instanceof Bullet) {
Point location = entity.getLocation();
Dimension size = entity.getSize();
location.y -= size.height;
if (location.y + size.height < bounds.y) {
it.remove();
} else {
entity.setLocation(location);
}
}
}
if (keys.contains(Direction.SPACE)) {
if (lastShot == null || System.currentTimeMillis() - lastShot > 100) {
lastShot = System.currentTimeMillis();
Bullet bullet = new Bullet();
int x = p.x + ((player.getSize().width - bullet.getSize().width) / 2);
int y = p.y - bullet.getSize().height;
bullet.setLocation(new Point(x, y));
entities.add(bullet);
}
}
}
}
public class DefaultGameController implements GameController {
private GameModel model;
private GameView view;
private Timer timer;
private Set<Direction> keys = new HashSet<>(25);
public DefaultGameController(GameModel gameModel, GameView gameView) {
gameView.setController(this);
view = gameView;
model = gameModel;
}
#Override
public Entity[] getEntities() {
return model.getEntities();
}
#Override
public void setDirection(Direction direction, boolean pressed) {
if (pressed) {
keys.add(direction);
} else {
keys.remove(direction);
}
}
#Override
public void start() {
if (timer != null && timer.isRunning()) {
timer.stop();
}
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.update(view.getViewBounds(), Collections.unmodifiableSet(keys));
view.repaint();
}
});
timer.start();
}
}
public abstract class AbstractEntity implements Entity {
private final Point location = new Point(0, 0);
#Override
public Point getLocation() {
return new Point(location);
}
#Override
public void setLocation(Point p) {
location.setLocation(p);
}
}
public class Player extends AbstractEntity {
public Player(Rectangle bounds) {
int x = bounds.x + ((bounds.width - getSize().width) / 2);
int y = bounds.y + (bounds.height - getSize().height);
setLocation(new Point(x, y));
}
#Override
public Dimension getSize() {
return new Dimension(40, 17);
}
#Override
public void paint(Graphics2D g2d) {
Point p = getLocation();
Dimension size = getSize();
g2d.setColor(Color.BLUE);
g2d.fillRect(p.x, p.y + 7, size.width, 10);
g2d.fillRect(p.x + 18, p.y, 4, 7);
}
}
public class Bullet extends AbstractEntity {
#Override
public void paint(Graphics2D g2d) {
Rectangle bullet = new Rectangle(getLocation(), getSize());
g2d.setColor(Color.RED);
g2d.fill(bullet);
}
#Override
public Dimension getSize() {
return new Dimension(4, 8);
}
}
public class DefaultGameView extends JPanel implements GameView {
private GameController controller;
public DefaultGameView() {
addKeyBinding("left.pressed", KeyEvent.VK_LEFT, true, new DirectionAction(Direction.LEFT, true));
addKeyBinding("left.released", KeyEvent.VK_LEFT, false, new DirectionAction(Direction.LEFT, false));
addKeyBinding("right.pressed", KeyEvent.VK_RIGHT, true, new DirectionAction(Direction.RIGHT, true));
addKeyBinding("right.released", KeyEvent.VK_RIGHT, false, new DirectionAction(Direction.RIGHT, false));
addKeyBinding("space.pressed", KeyEvent.VK_SPACE, true, new DirectionAction(Direction.SPACE, true));
addKeyBinding("space.released", KeyEvent.VK_SPACE, false, new DirectionAction(Direction.SPACE, false));
}
protected void addKeyBinding(String name, int keyEvent, boolean pressed, DirectionAction action) {
addKeyBinding(name, KeyStroke.getKeyStroke(keyEvent, 0, !pressed), action);
}
protected void addKeyBinding(String name, KeyStroke keyStroke, DirectionAction action) {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(keyStroke, name);
actionMap.put(name, action);
}
#Override
public void setController(GameController controller) {
this.controller = controller;
}
#Override
public GameController getController() {
return controller;
}
#Override
public Rectangle getViewBounds() {
return new Rectangle(new Point(0, 0), getSize());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
GameController controller = getController();
for (Entity entity : controller.getEntities()) {
// I don't trust you
Graphics2D g2d = (Graphics2D) g.create();
entity.paint(g2d);
g2d.dispose();
}
}
public class DirectionAction extends AbstractAction {
private Direction direction;
private boolean pressed;
public DirectionAction(Direction direction, boolean pressed) {
this.direction = direction;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
getController().setDirection(direction, pressed);
}
}
}
}
Again, you're going to need to go away and do some more research, but this is the general idea
Your drawing depends on the boolean variable shooting; there is one place where shooting is being set to true; if the key operations dont work the flow of the program may never reach there and that may never happen.
So I sugest you minimize your project to a screen that will draw the graphic without depending on pressing keys.
If you can see the graphics then you can add gradually the keys

Draw on JPanel from other class

In my program I try to paint on a JPanel when the mouse is pressed. The mousePressed method is just to test the painting from another class. Later on the spawn method will be called by other class methods. When I press the mouse button spawnPedestrian() is called, but no Pedestrian is painted. Below is a running example with code from my project. If you create a project Roundabout and paste this code in it, you should be able to run it (images are hotlinked).
How to fix the spawnPedestrian() method?
public class Roundabout extends JFrame {
public static Surface surface;
public Roundabout() {
initUI();
}
private void initUI() {
setTitle("Roundabout");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
surface = new Surface();
add(surface);
this.addMouseListener(new MouseAdapter() {// empty implementation of all
// MouseListener`s methods
#Override
public void mousePressed(MouseEvent e) {
//Spawn
Spawn sp = new Spawn();
sp.spawnPedestrian(300, 100);
}
});
setSize(1618, 850);
setLocationRelativeTo(null);
}
public static JPanel getSurface() {
return surface;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Roundabout roundabout = new Roundabout();
roundabout.setVisible(true);
}
});
}
//Track class
class Track {
BufferedImage track;
Point trackPosition;
Point TRACK_POS = new Point(0, 0);
public Track() {
try {
track = ImageIO.read(new URL("http://i.stack.imgur.com/2U3j5.png"));
} catch (Exception ex) {
System.out.println("Problem loading track image: " + ex);
}
trackPosition = new Point(TRACK_POS.x, TRACK_POS.y);
}
public void paint(Graphics g) {
g.drawImage(track, TRACK_POS.x, TRACK_POS.y, null);
}
}
//Surface class
public class Surface extends JPanel {
Track track = new Track();
public List<Vehicle> toDraw = new ArrayList<>();
public Surface() {
Pedestrian p = new Pedestrian(100, 100);
toDraw.add(p);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//setLayout(null);
track.paint(g);
//Make sure the track is painted first
for (Vehicle v : toDraw) {
v.paint(g);
}
}
}
class Pedestrian extends Vehicle {
BufferedImage pedestrian;
Point pedestrianPosition;
double pedestrianRotation = 0;
int pedestrianW, pedestrianH;
public Pedestrian(int x, int y) {
try {
pedestrian = ImageIO.read(new URL("http://i.stack.imgur.com/wm0I5.png"));
} catch (IOException e) {
System.out.println("Problem loading pedestrian images: " + e);
}
pedestrianPosition = new Point(x, y);
pedestrianW = pedestrian.getWidth();
pedestrianH = pedestrian.getHeight();
}
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.rotate(Math.toRadians(pedestrianRotation), pedestrianPosition.x, pedestrianPosition.y);
g2d.drawImage(pedestrian, pedestrianPosition.x, pedestrianPosition.y, null);
}
#Override
public void setPath(List<Point> path) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void update(double i) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
//Spawn class
class Spawn {
public void spawnPedestrian(int x, int y) {
//Create a new pedestrian.
System.out.println("Spawn a pedestrian.");
Pedestrian p = new Pedestrian(x, y);
Roundabout.surface.toDraw.add(p);
Roundabout.surface.revalidate();
Roundabout.surface.repaint();
}
}
public abstract class Vehicle {
public abstract void setPath(List<Point> path);
public abstract void update(double i);
public abstract void paint(Graphics g);
}
}
EDIT: It works now Pedestrian is spawned on mouse click.
Basically, you want to decouple your code and centralise the responsible to the classes. So the "data" should be maintained by a model of some kind, the rendering should be handle by some kind of view and the updates to the model and view should be handled by some kind of controller.
This makes it easier to swap out any one part with out requiring a whole bunch of new code or other changes. It also means that each class has a defined domain of responsibility and discourages you from trying to, for example, make changes to state from within the view which should be handled by the model (which could put the state into disarray)
Let's start with something that what's to be painted
public interface Sprite {
public void paint(Graphics2D g2d);
}
public interface MoveableSprite extends Sprite {
public void update(Container container);
}
These represent either a static sprite (like a tree for example) or a sprite which is moving (and wants to be updated on a regular bases)
These are contained within a model
public interface GameModel {
public List<Sprite> getSprites();
public void setObserver(Observer<MoveableSprite> observer);
public Observer<MoveableSprite> getObserver();
public void spawnSprite();
}
Which provides some means by which it can notify (in this case, a single) interested party about some kind of state change. For this example, that means a new MoveableSprite has become available
The Observer is pretty basic and just has a single call back...
public interface Observer<T> {
public void stateChanged(T parent);
}
And an "engine" to help drive it...
public class GameEngine {
private GameModel model;
private SurfacePane surface;
private Timer timer;
public GameEngine(GameModel model, SurfacePane surface) {
this.model = model;
this.surface = surface;
model.setObserver(new Observer<MoveableSprite>() {
#Override
public void stateChanged(MoveableSprite sprite) {
sprite.update(getSurface());
}
});
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Sprite sprite : getModel().getSprites()) {
if (sprite instanceof MoveableSprite) {
((MoveableSprite) sprite).update(getSurface());
}
}
getSurface().repaint();
}
});
}
public GameModel getModel() {
return model;
}
public SurfacePane getSurface() {
return surface;
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
}
This is a pretty basic example, but basically, it updates the position of MoveableSprite and asks the surface to repaint itself. It's also observing the GameModel for any new sprites and it will update their position immediately, so they don't appear in some "weird" place
Okay, now we actually need to implement some of this to make it work
public class DefaultGameModel implements GameModel {
private Observer<MoveableSprite> observer;
private List<Sprite> sprites;
public DefaultGameModel() {
sprites = new ArrayList<>(25);
for (int index = 0; index < 10; index++) {
spawnSprite();
}
}
#Override
public List<Sprite> getSprites() {
return Collections.unmodifiableList(sprites);
}
public void spawnSprite() {
try {
ZombieSprite sprite = new ZombieSprite();
sprites.add(sprite);
Observer<MoveableSprite> observer = getObserver();
if (observer != null) {
observer.stateChanged(sprite);
}
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void setObserver(Observer<MoveableSprite> observer) {
this.observer = observer;
}
#Override
public Observer<MoveableSprite> getObserver() {
return observer;
}
}
public class ZombieSprite implements MoveableSprite {
private int x;
private int y;
private int xDelta;
private int yDelta;
private BufferedImage img;
private Observer<Sprite> observer;
private boolean initialised = false;
public ZombieSprite() throws IOException {
img = ImageIO.read(getClass().getResource("/LogoZombi.png"));
}
#Override
public void update(Container container) {
if (!initialised) {
x = (int) (Math.random() * container.getWidth());
y = (int) (Math.random() * container.getHeight());
Random rnd = new Random();
xDelta = rnd.nextBoolean() ? 1 : -1;
yDelta = rnd.nextBoolean() ? 1 : -1;
initialised = true;
}
x += xDelta;
y += yDelta;
if (x < 0) {
x = 0;
xDelta *= -1;
} else if (x + img.getWidth() > container.getWidth()) {
x = container.getWidth() - img.getWidth();
xDelta *= -1;
}
if (y < 0) {
y = 0;
yDelta *= -1;
} else if (y + img.getHeight() > container.getHeight()) {
y = container.getHeight() - img.getHeight();
yDelta *= -1;
}
}
#Override
public void paint(Graphics2D g2d) {
g2d.drawImage(img, x, y, null);
}
}
These two classes implement the GameModel and MoveableSprite interfaces. We use interfaces to decouple the code, which makes it easier to change the way in which things work and provides a jumping off point for agreed to contracts and exceptions of the implemenations
And finally, something that actually paints the current state...
public class SurfacePane extends JPanel {
private GameModel model;
public SurfacePane(GameModel model) {
this.model = model;
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
getModel().spawnSprite();
}
});
}
public GameModel getModel() {
return model;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
GameModel model = getModel();
for (Sprite sprite : model.getSprites()) {
sprite.paint(g2d);
}
g2d.dispose();
}
}
You'll not that this class has the MouseListener, this is kind of deliberate, as other components which might be added to this container could prevent the MouseListener from been notified, so don't do that. But the MouseListener just calls the model to spawn another zombie...
And finally, we need to plumb it altogether...
GameModel model = new DefaultGameModel();
SurfacePane surfacePane = new SurfacePane(model);
GameEngine engine = new GameEngine(model, surfacePane);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(surfacePane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
engine.start();
And just because I know that's a lot of disjointed concepts to put together, a complete example...
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeListener;
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();
}
GameModel model = new DefaultGameModel();
SurfacePane surfacePane = new SurfacePane(model);
GameEngine engine = new GameEngine(model, surfacePane);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(surfacePane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
engine.start();
}
});
}
public class SurfacePane extends JPanel {
private GameModel model;
public SurfacePane(GameModel model) {
this.model = model;
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
getModel().spawnSprite();
}
});
}
public GameModel getModel() {
return model;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
GameModel model = getModel();
for (Sprite sprite : model.getSprites()) {
sprite.paint(g2d);
}
g2d.dispose();
}
}
public class GameEngine {
private GameModel model;
private SurfacePane surface;
private Timer timer;
public GameEngine(GameModel model, SurfacePane surface) {
this.model = model;
this.surface = surface;
model.setObserver(new Observer<MoveableSprite>() {
#Override
public void stateChanged(MoveableSprite sprite) {
sprite.update(getSurface());
}
});
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Sprite sprite : getModel().getSprites()) {
if (sprite instanceof MoveableSprite) {
((MoveableSprite) sprite).update(getSurface());
}
}
getSurface().repaint();
}
});
}
public GameModel getModel() {
return model;
}
public SurfacePane getSurface() {
return surface;
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
}
public interface Observer<T> {
public void stateChanged(T parent);
}
public interface Sprite {
public void paint(Graphics2D g2d);
}
public interface MoveableSprite extends Sprite {
public void update(Container container);
}
public interface GameModel {
public List<Sprite> getSprites();
public void setObserver(Observer<MoveableSprite> observer);
public Observer<MoveableSprite> getObserver();
public void spawnSprite();
}
public class DefaultGameModel implements GameModel {
private Observer<MoveableSprite> observer;
private List<Sprite> sprites;
public DefaultGameModel() {
sprites = new ArrayList<>(25);
for (int index = 0; index < 10; index++) {
spawnSprite();
}
}
#Override
public List<Sprite> getSprites() {
return Collections.unmodifiableList(sprites);
}
public void spawnSprite() {
try {
ZombieSprite sprite = new ZombieSprite();
sprites.add(sprite);
Observer<MoveableSprite> observer = getObserver();
if (observer != null) {
observer.stateChanged(sprite);
}
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void setObserver(Observer<MoveableSprite> observer) {
this.observer = observer;
}
#Override
public Observer<MoveableSprite> getObserver() {
return observer;
}
}
public class ZombieSprite implements MoveableSprite {
private int x;
private int y;
private int xDelta;
private int yDelta;
private BufferedImage img;
private Observer<Sprite> observer;
private boolean initialised = false;
public ZombieSprite() throws IOException {
img = ImageIO.read(getClass().getResource("/LogoZombi.png"));
}
#Override
public void update(Container container) {
if (!initialised) {
x = (int) (Math.random() * container.getWidth());
y = (int) (Math.random() * container.getHeight());
Random rnd = new Random();
xDelta = rnd.nextBoolean() ? 1 : -1;
yDelta = rnd.nextBoolean() ? 1 : -1;
initialised = true;
}
x += xDelta;
y += yDelta;
if (x < 0) {
x = 0;
xDelta *= -1;
} else if (x + img.getWidth() > container.getWidth()) {
x = container.getWidth() - img.getWidth();
xDelta *= -1;
}
if (y < 0) {
y = 0;
yDelta *= -1;
} else if (y + img.getHeight() > container.getHeight()) {
y = container.getHeight() - img.getHeight();
yDelta *= -1;
}
}
#Override
public void paint(Graphics2D g2d) {
g2d.drawImage(img, x, y, null);
}
}
}

Make this cube's movement smoother?

I've been working the movement of this cube, however, its movement is pretty ugly and sudden, so is there anyway that I could make it "smooth" and "clean"?
Here is my code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Main extends JPanel implements KeyListener
{
Environment environment = new Environment ();
Cube cube = new Cube ();
JFrame frame = new JFrame ();
int cubeX = cube.cube.x;
int cubeY = cube.cube.y;
// Paint method used to repaint the cube's location
public void paint (Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
environment.createBox (g2d);
cube.createCube (g2d);
}
// Getting pressed keys to move cube
#Override
public void keyPressed (KeyEvent e)
{
if (e.getKeyCode () == KeyEvent.VK_UP)
{
try
{
cube.isCubeMoving = true;
cube.moveCube ();
Thread.sleep (10);
frame.repaint ();
}
catch (InterruptedException ie)
{
ie.printStackTrace ();
}
}
else if (e.getKeyCode () == KeyEvent.VK_DOWN)
{
cube.cube.y = cube.cube.y + 100;
if (cube.cube.y > 620)
{
cube.cube.y = 620;
}
try
{
Thread.sleep (10);
frame.repaint ();
}
catch (InterruptedException e1)
{
e1.printStackTrace ();
}
}
}
#Override
public void keyReleased (KeyEvent arg0)
{
}
#Override
public void keyTyped (KeyEvent arg0)
{
}
// Main method
public static void main (String[] args) throws InterruptedException
{
Main m = new Main ();
m.frame.add (m);
m.frame.addKeyListener (m);
m.frame.setSize (700, 1000);
m.frame.setVisible (true);
m.frame.setTitle ("The Cube");
m.frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
m.frame.setResizable (true);
m.frame.setLocationRelativeTo (null);
m.frame.setBackground (new Color (240, 84, 84));
while (true)
{
m.frame.repaint ();
Thread.sleep (3);
}
}
}
Here's the Cube class:
import java.awt.*;
public class Cube extends Thread
{
public int x = 200;
public int y = 620;
public boolean isCubeMoving = true;
int whereCubeStops = 440;
Runnable r = new Runnable ()
{
public void run ()
{
while (isCubeMoving == true)
{
cube.setLocation (x, y -= 10);
System.out.println (y);
if (y == whereCubeStops)
{
try
{
isCubeMoving = false;
cube.setLocation (x, y = 620);
Thread.sleep (100);
}
catch (InterruptedException e)
{
e.printStackTrace ();
}
}
try
{
Thread.sleep (10);
}
catch (InterruptedException e)
{
e.printStackTrace ();
}
}
}
};
Rectangle cube = new Rectangle (x, y, 80, 80);
public void createCube (Graphics2D g2d)
{
g2d.setColor (new Color (148, 235, 148));
g2d.fill (cube);
}
public void moveCube ()
{
new Thread (r).start ();
}
}
Thanks very much for all your help!
:)
Well, for this, we have to write a proper game loop. Let's start one by one:
Here's my GameFrame:
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public abstract class GameFrame extends JFrame
{
private GamePanel gamePanel;
public GameFrame (String gameTitle, GamePanel gamePanel)
{
super (gameTitle);
this.gamePanel = gamePanel;
setDefaultCloseOperation (WindowConstants.EXIT_ON_CLOSE);
addWindowListener (new FrameListener ());
getContentPane ().setLayout (new GridBagLayout ());
getContentPane ().add (gamePanel);
pack ();
setLocationRelativeTo (null);
setResizable (false);
setVisible (true);
}
public class FrameListener extends WindowAdapter
{
public void windowActivated (WindowEvent event)
{
gamePanel.setWindowPaused (false);
}
public void windowDeactivated (WindowEvent event)
{
gamePanel.setWindowPaused (true);
}
public void windowDeiconified (WindowEvent event)
{
gamePanel.setWindowPaused (false);
}
public void windowIconified (WindowEvent event)
{
gamePanel.setWindowPaused (true);
}
public void windowClosing (WindowEvent event)
{
gamePanel.stopGame ();
}
}
}
Its an abstract class, and all it does is put a GamePanel in it, and make itself visible when initialised.
Here's my GamePanel which implements a game loop:
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public abstract class GamePanel extends JPanel implements Runnable
{
private int panelWidth;
private int panelHeight;
private Thread animator;
private volatile boolean running = false;
private volatile boolean isUserPaused = false;
private volatile boolean isWindowPaused = false;
private Graphics2D dbg;
private Image dbImage = null;
private static final int NO_DELAYS_PER_YIELD = 16;
private static final int MAX_FRAME_SKIPS = 5;
private Color backgroundColor;
private long period;
public GamePanel (int width, int height, long fps, Color backgroundColor)
{
this.panelWidth = width;
this.panelHeight = height;
this.backgroundColor = backgroundColor;
this.period = 1000000L * (long) 1000.0 / fps;
setBackground (backgroundColor);
setPreferredSize (new Dimension (panelWidth, panelHeight));
setFocusable (true);
requestFocus ();
readyForPause ();
addKeyListener (new KeyAdapter ()
{
public void keyPressed (KeyEvent e)
{
consumeKeyPressed (e.getKeyCode ());
}
});
}
protected abstract void consumeKeyPressed (int keyCode);
protected abstract void renderGame (Graphics2D graphics);
protected abstract void updateGame ();
#Override
public void addNotify ()
{
super.addNotify ();
startGame ();
}
protected void startGame ()
{
if (animator == null || ! running)
{
animator = new Thread (this);
animator.start ();
}
}
protected void stopGame ()
{
running = false;
}
private void readyForPause ()
{
addKeyListener (new KeyAdapter ()
{
public void keyPressed (KeyEvent e)
{
int keyCode = e.getKeyCode ();
if ((keyCode == KeyEvent.VK_ESCAPE) || (keyCode == KeyEvent.VK_Q)
|| (keyCode == KeyEvent.VK_END) || (keyCode == KeyEvent.VK_P)
|| ((keyCode == KeyEvent.VK_C) && e.isControlDown ()))
{
setUserPaused (! isUserPaused);
}
}
});
}
public void run ()
{
long beforeTime, afterTime, timeDiff, sleepTime;
long overSleepTime = 0L;
int noDelays = 0;
long excess = 0L;
beforeTime = System.nanoTime ();
running = true;
while (running)
{
requestFocus ();
gameUpdate ();
gameRender ();
paintScreen ();
afterTime = System.nanoTime ();
timeDiff = afterTime - beforeTime;
sleepTime = (period - timeDiff) - overSleepTime;
if (sleepTime > 0)
{
try
{
Thread.sleep (sleepTime / 1000000L);
}
catch (InterruptedException ignored)
{
}
overSleepTime = (System.nanoTime () - afterTime - sleepTime);
}
else
{
excess -= sleepTime;
overSleepTime = 0L;
if (++ noDelays >= NO_DELAYS_PER_YIELD)
{
Thread.yield ();
noDelays = 0;
}
}
beforeTime = System.nanoTime ();
int skips = 0;
while ((excess > period) && (skips < MAX_FRAME_SKIPS))
{
excess -= period;
gameUpdate ();
skips++;
}
}
System.exit (0);
}
private void gameUpdate ()
{
if (! isUserPaused && ! isWindowPaused)
{
updateGame ();
}
}
private void gameRender ()
{
if (dbImage == null)
{
dbImage = createImage (panelWidth, panelHeight);
if (dbImage == null)
{
System.out.println ("Image is null.");
return;
}
else
{
dbg = (Graphics2D) dbImage.getGraphics ();
}
}
dbg.setColor (backgroundColor);
dbg.fillRect (0, 0, panelWidth, panelHeight);
renderGame (dbg);
}
private void paintScreen ()
{
Graphics2D g;
try
{
g = (Graphics2D) this.getGraphics ();
if ((g != null) && (dbImage != null))
{
g.drawImage (dbImage, 0, 0, null);
}
Toolkit.getDefaultToolkit ().sync ();
if (g != null)
{
g.dispose ();
}
}
catch (Exception e)
{
System.out.println ("Graphics context error : " + e);
}
}
public void setWindowPaused (boolean isPaused)
{
isWindowPaused = isPaused;
}
public void setUserPaused (boolean isPaused)
{
isUserPaused = isPaused;
}
}
This again is an abstract class. Its abstract for reusability purpose. You need not know the exact implementation of my game loop. You can create your own custom game panel that inherits from it, and implement the abstract methods of it, and everything will be good to go.
Lets create a Box now:
import java.awt.*;
public class Box extends Rectangle
{
private Color color;
private Direction currentDirection = Direction.None;
private int speed;
public Box (int size, int speed, Color color)
{
super (size, size);
this.speed = speed;
this.color = color;
}
public void update ()
{
switch (currentDirection)
{
case None:
break;
case North:
y -= speed;
break;
case South:
y += speed;
break;
case East:
x += speed;
break;
case West:
x -= speed;
break;
}
}
public void draw (Graphics2D graphics)
{
graphics.setColor (color);
graphics.fill (this);
}
public void setDirection (Direction direction)
{
currentDirection = direction;
}
}
Nothing out of the ordinary here. Its a Rectangle shape, which has update method to update its state based on the Direction it has and a draw method that renders it on screen using the graphics object as context.
The Direction enum used in Box looks as follows:
public enum Direction
{
None,
North,
South,
East,
West
}
So now its time to create our own custom BoxPanel that will inherit from GamePanel. Here's how it looks like:
import java.awt.*;
import java.awt.event.KeyEvent;
public class BoxPanel extends GamePanel
{
private Box box;
public BoxPanel ()
{
super (800, 600, 60, Color.lightGray);
box = new Box (80, 5, Color.darkGray);
}
#Override
protected void consumeKeyPressed (int keyCode)
{
switch (keyCode)
{
case KeyEvent.VK_W:
box.setDirection (Direction.North);
break;
case KeyEvent.VK_S:
box.setDirection (Direction.South);
break;
case KeyEvent.VK_D:
box.setDirection (Direction.East);
break;
case KeyEvent.VK_A:
box.setDirection (Direction.West);
break;
case KeyEvent.VK_SPACE:
box.setDirection (Direction.None);
break;
}
}
#Override
protected void renderGame (Graphics2D graphics)
{
box.draw (graphics);
}
#Override
protected void updateGame ()
{
box.update ();
}
}
It basically creates a box and implements the abstract methods of the GamePanel where all it does is update the box and render the box using appropriate methods present.
The consumeKeyPressed is all about handling key presses, where all I do is set the direction of the box appropriately.
And so finally comes my BoxFrame which wraps everything together into a runnable demonstration:
public class BoxFrame extends GameFrame
{
public BoxFrame ()
{
super ("Box Demo", new BoxPanel ());
}
public static void main (String[] args)
{
new BoxFrame ();
}
}
That's it!
You can use GameFrame and GamePanel in your own projects too. Abstraction really pays off. Doesn't it?
You need not worry if you don't understand part of the game loop or anything. By repeated reading of the code, you'll eventually understand it.
This is a runnable demonstration that shows how you can create smooth movements. I'd suggest you also look into interpolation for creating smooth movements.
You can tweak the FPS value while creating the BoxPanel to vary the smooth factor of the game.
Run the code, read it, re-read it, understand it. Then write it yourself as practice.
FYI, my game loop uses a concept called Double Buffering for rendering the objects smoothly on screen.
You can create other objects too that have update and draw method, and can put their calls in the updateGame and renderGame method of your custom panel, and those objects will appropriately be rendered as well.

java graphics2d animated gif update

The applet plays an animated gif file:
public class Applet1 extends Applet {
private Image m_image=null;
public void init() {
m_image=getImage(getDocumentBase(), "001.gif");
}
public void paint(Graphics g) {
g.drawImage(m_image,0,0,this);
}
public boolean imageUpdate( Image img, int flags, int x, int y, int w, int h ) {
System.out.println("Image update: flags="+flags+" x="+x+" y="+y+" w="+w+" h="+h);
repaint();
return true;
}
}
I need to add the updated image in another program:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
public class HighlightExample {
public static void main(String[] args) {
JFrame f = new JFrame("Highlight example");
final JTextPane textPane = new JTextPane();
JPanel pane = new JPanel();
pane.setLayout(new BorderLayout());
final JTextField tf = new JTextField();
pane.add(tf, "Center");
f.getContentPane().add(pane, "South");
f.getContentPane().add(new JScrollPane(textPane), "Center");
textPane.setText("abсdefghijkl lmnop12345678");
final WordSearcher searcher = new WordSearcher(textPane);
tf.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
word = tf.getText().trim();
int offset = searcher.search(word);
if (offset != -1) {
try {
textPane.scrollRectToVisible(textPane.modelToView(offset));
} catch (BadLocationException e) {}
}
}
});
textPane.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent evt) {
searcher.search(word);
}
public void removeUpdate(DocumentEvent evt) {
searcher.search(word);
}
public void changedUpdate(DocumentEvent evt) {
}
});
f.setSize(400, 400);
f.setVisible(true);
}
public static String word;
}
class WordSearcher {
protected JTextComponent comp;
protected Highlighter.HighlightPainter painter;
public WordSearcher(JTextComponent comp) {
this.comp = comp;
this.painter = new UnderlineHighlighter.UnderlineHighlightPainter(Color.red);
}
public int search(String word) {
int firstOffset = -1;
Highlighter highlighter = comp.getHighlighter();
Highlighter.Highlight[] highlights = highlighter.getHighlights();
for (int i = 0; i < highlights.length; i++) {
Highlighter.Highlight h = highlights[i];
if (h.getPainter() instanceof UnderlineHighlighter.UnderlineHighlightPainter) {
highlighter.removeHighlight(h);
}
}
if (word == null || word.equals("")) {
return -1;
}
String content;
try {
Document d = comp.getDocument();
content = d.getText(0, d.getLength()).toLowerCase();
} catch (BadLocationException e) {
return -1;
}
word = word.toLowerCase();
int lastIndex = 0;
int wordSize = word.length();
while ((lastIndex = content.indexOf(word, lastIndex)) != -1) {
int endIndex = lastIndex + wordSize;
try {
highlighter.addHighlight(lastIndex, endIndex, painter);
} catch (BadLocationException e) {}
if (firstOffset == -1) {
firstOffset = lastIndex;
}
lastIndex = endIndex;
}
return firstOffset;
}
}
class UnderlineHighlighter extends DefaultHighlighter{
protected static final Highlighter.HighlightPainter sharedPainter = new UnderlineHighlightPainter(null);
protected Highlighter.HighlightPainter painter;
public static class UnderlineHighlightPainter extends LayeredHighlighter.LayerPainter {
protected Color color; // The color for the underline
public UnderlineHighlightPainter(Color c) {
color = c;
}
public void paint(final Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
}
public Shape paintLayer(final Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c, View view) {
g.setColor(color == null ? c.getSelectionColor() : color);
Rectangle alloc;
try {
Shape shape = view.modelToView(offs0,Position.Bias.Forward, offs1,Position.Bias.Backward, bounds);
alloc = (shape instanceof Rectangle) ? (Rectangle) shape : shape.getBounds();
} catch (BadLocationException e) {
return null;
}
FontMetrics fm = c.getFontMetrics(c.getFont());
int baseline = alloc.y + alloc.height - fm.getDescent() + 1;
g.drawLine(alloc.x, baseline, alloc.x + alloc.width, baseline);
Toolkit kit=Toolkit.getDefaultToolkit();
Image im3 =kit.getImage("001.gif");
g.drawImage(im3,alloc.x+15,alloc.y-6,null);
return alloc;
}
}
}
In this program always displays only the first frame of the image im3. Sorry for a lot of code, it is fully compillable.
It seems like the problem is that you are passing null as the ImageObserver when drawing the image (near the end of your code):
g.drawImage(im3,alloc.x+15,alloc.y-6,null);
So, the image will fire an update when a new frame is ready, but that update just gets ignored because there are no listeners. You probably want to pass c as the ImageObserver so that it will repaint itself when the image changes.
Edit:
This approach will c entirely. If you wanted to be clever, you could create your own ImageObserver that remembers the exact rectangle in c that the icon was painted in, and repaints just that rectangle when notified of an update.

Categories

Resources