Custom Swing arrow button repaint() erases customiztion - java

I have custom arrow class, which extends BasicArrowButton. It is constructed and displayed the way I need, but then, when it is repainted (mouse over, other tab clicked, etc) arrow disappeares. How can I fix it?
public class CustomArrow extends BasicArrowButton {
private transient Color shadow = new Color(241, 241, 241);
private transient Color dark = new Color(150, 150, 150);
private static int defaultSize = 10;
/** The Polygon that points up. */
private static Polygon upIcon = new Polygon(new int[] { 0, 5, 9 },
new int[] { 7, 2, 7 }, 3);
/** The Polygon that points down. */
private static Polygon downIcon = new Polygon(new int[] { 1, 8, 19 },
new int[] { 3, 13, 3 }, 3);
/** The Polygon that points left. */
private static Polygon leftIcon = new Polygon(new int[] { 7, 3, 7 },
new int[] { 1, 5, 9 }, 3);
/** The Polygon that points right. */
private static Polygon rightIcon = new Polygon(new int[] { 3, 7, 3 },
new int[] { 1, 5, 9 }, 3);
private transient Border buttonBorder = new Border()
{
public Insets getBorderInsets(Component c)
{
return new Insets(2, 2, 2, 2);
}
public boolean isBorderOpaque()
{
return true;
}
public void paintBorder(Component c, Graphics g, int x, int y, int w, int h)
{
Color saved = g.getColor();
g.setColor(shadow);
g.drawLine(x + 1, y, x + w - 1, y);
g.setColor(dark);
g.drawLine(x, y, x, y + h + 2);
g.setColor(shadow);
g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1);
g.setColor(shadow);
g.drawLine(x + 1, y + h - 1, x + w, y + h - 1);
g.setColor(saved);
}
};
#Override
public synchronized void addKeyListener(KeyListener l) {
super.addKeyListener(l);
}
#Override
public void addActionListener(ActionListener l) {
super.addActionListener(l);
}
public CustomArrow(int direction)
{
super(direction);
setBorder(buttonBorder);
setDirection(direction);
this.setRolloverEnabled(false);
}
public CustomArrow(int direction, Color background, Color shadow, Color darkShadow, Color highlight)
{
this(direction);
setBackground(background);
}
#Override
public void paintTriangle(Graphics g, int x, int y, int size, int direction, boolean isEnabled)
{
Polygon arrow = null;
switch (direction)
{
case NORTH:
arrow = upIcon;
break;
case SOUTH:
arrow = downIcon;
break;
case EAST:
case RIGHT:
arrow = rightIcon;
break;
case WEST:
case LEFT:
arrow = leftIcon;
break;
}
int[] xPoints = arrow.xpoints;
int[] yPoints = arrow.ypoints;
int x1;
int y1;
int x2;
int y2;
x1 = y1 = x2 = y2 = 0;
x = x - 1;
if (size != defaultSize)
{
float scale = size * 1f / defaultSize;
for (int i = 0; i < 3; i++)
{
xPoints[i] *= scale;
yPoints[i] *= scale;
}
}
g.translate(x, y);
switch (direction)
{
case NORTH:
x1 = xPoints[0] + 2;
y1 = yPoints[0];
y2 = y1;
x2 = xPoints[2] - 1;
break;
case SOUTH:
x1 = xPoints[1];
y1 = yPoints[1] + 1;
x2 = xPoints[2] - 1;
y2 = yPoints[2];
break;
case LEFT:
case WEST:
x1 = xPoints[0] + 1;
y1 = yPoints[0] + 1;
x2 = x1;
y2 = yPoints[2] + 1;
break;
case RIGHT:
case EAST:
x1 = xPoints[2];
y1 = yPoints[2] + 1;
x2 = xPoints[1] - 1;
y2 = yPoints[1] + 1;
break;
}
Color saved = g.getColor();
g.setColor(dark);
if (arrow != null) {
g.fillPolygon(xPoints, yPoints, 3);
}
g.setColor(saved);
g.translate(-x, -y);
}
public static void main(String[] args) {
// Resize the frame to reproduce
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new CustomArrow(SwingConstants.NORTH));
frame.setSize(400, 400);
frame.setVisible(true);
}
}

Array variables hold references to the array, not the array itself (just like Object variables). When you do something like
int[] xPoints = arrow.xpoints;
int[] yPoints = arrow.ypoints;
you are copying the reference, not the data, meaning that xPoints and arrow.xpoints still point at the same data and modifying either will affect the other. When you later scale these points, you're changing how the arrow will look every future time it is drawn.
If you want to copy the array data to avoid this, you can use System.arraycopy:
int[] xPoints = new int[3]; //arrow.xpoints;
int[] yPoints = new int[3]; //arrow.ypoints;
System.arraycopy(arrow.xpoints, 0, xPoints, 0, 3);
System.arraycopy(arrow.ypoints, 0, yPoints, 0, 3);
However, you can simply scale the graphics object instead of scaling your reference points:
Graphics2D g2 = (Graphics2D) g;
float scale = size * 1f / defaultSize;
g2.scale(scale, scale);

Related

Color Picker not Visible

I have an issue where I'm unable to see Color Picker.
I'm trying to generate a lot of lines to make patterns to make me feel accomplished. It's a side project.
I'm not the best at coding so I need help. Also you have to minimize the window and open it back up to make it work when you are running it by the way.
*update I've figured out that the color picker is there but just behind all of my lines that I've drawn.
Here is my code.
import controlP5.ColorPicker;
import controlP5.ControlEvent;
import controlP5.ControlP5;
import processing.core.PApplet;
public class main extends PApplet {
ControlP5 cp5;
ColorPicker cp;
boolean guiState;
int col;
public static void main(String[] args) {
PApplet.main("main");
}
void drawLines() {
float lineLength = sqrt(height * height + width * width);
float nsHueStart = random((float) 10);
float nsSatStart = random((float) 10);
float nsRotStart = random((float) 10);
float nsHStart = random((float) 10);
float nsWStart = random((float) 10);
int lineWidthMax = 50;
for (int lineWidth = 1; lineWidth <= lineWidthMax; ++lineWidth) {
strokeWeight((5 * pow(lineWidth, 2)));
nsHueStart += 001;
float nsHue = nsHueStart;
float nsSat = nsSatStart;
float nsH = nsHStart;
float divH;
float divW;
for (float idxH = 0; idxH < height; idxH += divH) {
divH = map(noise(nsH), 0, 1, 80, 200);
float nsRot = nsRotStart;
float nsW = nsWStart;
for (float idxW = 0; idxW < width; idxW += divW) {
divW = map(noise(nsW), 0, 1, 0, 5); // do not use 2D noise
float brushHue = map(noise(nsHue), 0, 1, 0, 720) % 360; // various colors
float brushSat = map(noise(nsSat), 0, 1, 20, 70) / map(lineWidth, 1, lineWidthMax, 1, (float) 1.8);
float brushBri = map(noise(nsSat), 0, 1, 8, 15) / map(lineWidth, 1, lineWidthMax, 1, (float) (lineWidthMax * 2.8));
float brushAlp = 100;
float brushSiz = lineLength;
float brushRot = map(noise(nsRot), 0, 1, -60, 60);
pushMatrix();
translate(idxW, idxH);
canvasRotation(brushRot);
stroke(brushHue, brushSat, brushBri, brushAlp);
line(-brushSiz, 0, brushSiz, 0);
popMatrix();
nsHue += 002;
nsRot += 005;
nsW += 01 + noise(nsH) / 10; // not to be same shape
}
nsSat += 5;
nsH += 8;
}
}
}
public void settings() {
size(2560, 1395);
smooth(8);
}
public void setup() {
noStroke();
cp5 = new ControlP5(this);
cp = cp5.addColorPicker("picker")
.setPosition(60, 100)
.setColorValue(color(0, 128, 255, 128))
;
colorMode(HSB, 360, 100, 100, 100);
blendMode(SCREEN);
cp5.show();
frameRate(60);
}
public void draw() {
background(cp.getColorValue());
fill(0, 80);
rect(50, 90, 275, 80);
translate(0, 0);
drawLines();
}
public void controlEvent(ControlEvent c) {
if (c.isFrom(cp)) {
float r = (c.getArrayValue(0));
float g = (c.getArrayValue(1));
float b = (c.getArrayValue(2));
float a = (c.getArrayValue(3));
col = color(r, g, b, a);
println("event\talpha:"+a+"\tred:"+r+"\tgreen:"+g+"\tblue:"+b+"\tcol"+col);
}
}
void canvasRotation(float degrees) {
rotate(radians(degrees));
}
public void toggleGUI(boolean state) {
if (state) {
cp5.show();
} else {
cp5.hide();
background(200);
}
guiState = state;
}
public void keyPressed() {
if (keyCode == LEFT) toggleGUI(guiState);
switch (key) {
case ('1'):
cp.setArrayValue(new float[]{120, 0, 120, 255});
break;
case ('2'):
cp.setColorValue(color(255, 0, 0, 255));
break;
}
}
}

Drag-resizing rectangle with fixed aspect ratio northwest corner

I have a Java app where the user can crop a subimage from its original self. The crop area is selected by drawing a rectangle over the original image. The rectangle can then be resized diagonally. And so far, everything works!
The user also has an option to lock the aspect ratio of the rectangle to 4:3. I can achieve this simply by setting the width to w = h / 4 * 3;
However, when it comes to resizing with locked ratio, the rectangle behaves strangely and is no longer stationary when dragging from the northwest corner (see gif below). Had the same problem with southwest corner, but that could be fixed by instead setting height to h = w / 3 * 4; but I can't figure out how to do this mathematically for the northwest corner. I have provided a copy-pastable demo for experimentation:
public class CropDemo {
public static void main(String[] args) {
CropPanel cropPanel = new CropPanel();
cropPanel.setPreferredSize(new Dimension(640, 480));
JFrame jFrame = new JFrame("Crop Panel");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.getContentPane().add(cropPanel);
jFrame.setResizable(false);
jFrame.pack();
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
}
}
class CropPanel extends JPanel {
private static final long serialVersionUID = 1L;
private boolean fixedRatio = true;
private Rectangle rectangle;
private Point clickPoint;
private static final int HOVERING = 0;
private static final int MOVING = 1;
private static final int RESIZING = 2;
public CropPanel() {
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
MouseAdapter mouseHandler = new MouseAdapter() {
private Point startPoint = null;
#Override
public void mouseClicked(MouseEvent e) {
if (rectangle != null && getCursorState() == HOVERING) {
rectangle = null;
repaint();
}
}
#Override
public void mousePressed(MouseEvent e) {
clickPoint = e.getPoint();
startPoint = e.getPoint();
}
#Override
public void mouseMoved(MouseEvent e) {
if (rectangle != null) {
Point mouse = e.getPoint();
int width = rectangle.x + rectangle.width;
int height = rectangle.y + rectangle.height;
final int off = 5;
if (mouse.x > rectangle.x - off && mouse.x < width + off && mouse.y > rectangle.y - off
&& mouse.y < height + off) {
if (mouse.x <= rectangle.x + off && mouse.y >= height - off) {
setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
} else if (mouse.x >= width - off && mouse.y >= height - off) {
setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
} else if (mouse.x <= rectangle.x + off && mouse.y <= rectangle.y + off) {
setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
} else if (mouse.x >= width - off && mouse.y <= rectangle.y + off) {
setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
} else {
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
} else {
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (clickPoint != null) {
Point mouse = e.getPoint();
if (getCursorState() == MOVING) {
int dx = rectangle.x + mouse.x - clickPoint.x;
int dy = rectangle.y + mouse.y - clickPoint.y;
rectangle.setLocation(dx, dy);
clickPoint = e.getPoint();
} else if (getCursorState() == RESIZING) {
int dx = mouse.x - startPoint.x;
int dy = mouse.y - startPoint.y;
int height = rectangle.height;
int width = rectangle.width;
int x = 0;
int y = 0;
int w = 0;
int h = 0;
switch (getCursor().getType()) {
case Cursor.SW_RESIZE_CURSOR:
x = mouse.x + dx;
y = rectangle.y;
w = width - dx;
h = height + dy;
if (fixedRatio) {
h = w / 3 * 4;
}
break;
case Cursor.SE_RESIZE_CURSOR:
x = rectangle.x;
y = rectangle.y;
w = width + dx;
h = height + dy;
if (fixedRatio) {
w = h / 4 * 3;
}
break;
case Cursor.NW_RESIZE_CURSOR:
x = mouse.x + dx;
y = mouse.y + dy;
w = width - dx;
h = height - dy;
// This is where I'm lost
// something else needs to be done
if (fixedRatio) {
w = h / 4 * 3;
}
break;
case Cursor.NE_RESIZE_CURSOR:
x = rectangle.x;
y = mouse.y + dy;
w = width + dx;
h = height - dy;
if (fixedRatio) {
w = h / 4 * 3;
}
break;
}
rectangle.setBounds(x, y, w, h);
startPoint = mouse;
} else {
int x = Math.min(clickPoint.x, mouse.x);
int y = Math.min(clickPoint.y, mouse.y);
int w = Math.max(clickPoint.x - mouse.x, mouse.x - clickPoint.x);
int h = Math.max(clickPoint.y - mouse.y, mouse.y - clickPoint.y);
if (rectangle == null) {
rectangle = new Rectangle(x, y, w, h);
} else {
rectangle.setBounds(x, y, w, h);
}
}
repaint();
}
}
};
addMouseListener(mouseHandler);
addMouseMotionListener(mouseHandler);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, getWidth(), getHeight());
Graphics2D graphics2D = (Graphics2D) g.create();
if (rectangle != null) {
Area fill = new Area(new Rectangle(new Point(0, 0), getSize()));
fill.subtract(new Area(rectangle));
if (clickPoint != null) {
graphics2D.setColor(new Color(0, 0, 0, 0));
} else {
graphics2D.setColor(new Color(0, 0, 0, 200));
}
int x = rectangle.x;
int y = rectangle.y;
int w = rectangle.width;
int h = rectangle.height;
graphics2D.fill(fill);
graphics2D.setColor(Color.WHITE);
graphics2D.setStroke(
new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 6 }, 0));
graphics2D.drawRect(x, y, w, h);
if (w >= 30 && h >= 30) {
graphics2D.setStroke(new BasicStroke(3));
graphics2D.drawLine(x + 1, y + 1, x + 8, y + 1);
graphics2D.drawLine(x + 1, y + 1, x + 1, y + 8);
graphics2D.drawLine(x + w - 1, y + 1, x + w - 8, y + 1);
graphics2D.drawLine(x + w - 1, y + 1, x + w - 1, y + 8);
graphics2D.drawLine(x + 1, y + h - 1, x + 8, y + h - 1);
graphics2D.drawLine(x + 1, y + h - 1, x + 1, y + h - 8);
graphics2D.drawLine(x + w - 1, y + h - 1, x + w - 8, y + h - 1);
graphics2D.drawLine(x + w - 1, y + h - 1, x + w - 1, y + h - 8);
}
}
graphics2D.dispose();
g.dispose();
}
private int getCursorState() {
switch (getCursor().getType()) {
case Cursor.CROSSHAIR_CURSOR:
return HOVERING;
case Cursor.MOVE_CURSOR:
return MOVING;
case Cursor.SW_RESIZE_CURSOR:
case Cursor.SE_RESIZE_CURSOR:
case Cursor.NW_RESIZE_CURSOR:
case Cursor.NE_RESIZE_CURSOR:
case Cursor.N_RESIZE_CURSOR:
case Cursor.S_RESIZE_CURSOR:
case Cursor.W_RESIZE_CURSOR:
case Cursor.E_RESIZE_CURSOR:
return RESIZING;
default:
return -1;
}
}
}
Firstly just to note, the aspect ratio you are using is 3:4 not 4:3:
3:4 means that for every 3 units of width there are 4 units of height.
4:3 means that for every 4 units of width, there are 3 units of height.
w = h / 4 * 3 is calculating 3:4, not 4:3.
w = h / 3 * 4 or h = w / 4 * 3 calculates 4:3
Moving on to why your resizing breaks, when you create a Rectangle you provide the x, y coordinates of it's top left corner, and it's width and height:
Rectangle rectangle = new Rectangle(x, y, width, height)
The rectangle will then be drawn from x, y to x + width, y + height
The resizing part of your code works fine, when the mouse is dragged you update x, y, width, and height correctly.
The reason why applying the aspect ratio breaks it, is because you are updating width, and height, but you are not updating x and y.
Lets say the user performed a Northwest resize, and you now have a rectangle as follows:
x => 10
y => 10
width => 5
height => 10
You then apply your aspect ratio w = h / 4 * 3:
x => 10
y => 10
width => 8
height => 10
Because you are drawing from the top left corner, the rectangle has now grown from left to right, but you want it to grow from right to left. When you resize in the Northwest direction, you always want the bottom right corner of the rectangle to remain in the same place. The reason why this does not happen with your code is because when you apply the aspect ratio to the rectangle's width, you do not then update the start x, y point of the rectangle.
Using the above example, x and y should be updated as follows:
x => 7
y => 10
width => 8
height => 10
Here is a solution that I came up with:
else if (getCursorState() == RESIZING) {
Point startPoint = null;
Point endPoint = null;
switch(getCursor().getType()) {
case Cursor.SW_RESIZE_CURSOR:
startPoint = new Point((int) mouse.getX(), (int) rectangle.getMinY());
endPoint = new Point((int) rectangle.getMaxX(), (int) mouse.getY());
break;
case Cursor.NW_RESIZE_CURSOR:
startPoint = new Point((int) mouse.getX(), (int) mouse.getY());
endPoint = new Point((int) rectangle.getMaxX(), (int) rectangle.getMaxY());
break;
case Cursor.NE_RESIZE_CURSOR:
startPoint = new Point((int) rectangle.getMinX(), (int) mouse.getY());
endPoint = new Point((int) mouse.getX(), (int) rectangle.getMaxY());
break;
case Cursor.SE_RESIZE_CURSOR:
startPoint = new Point((int) rectangle.getMinX(), (int) rectangle.getMinY());
endPoint = new Point((int) mouse.getX(), (int) mouse.getY());
break;
}
rectangle.setFrameFromDiagonal(startPoint, endPoint);
if (fixedRatio) {
// Calculate 3:4 aspect ratio
rectangle.height = rectangle.width / 3 * 4;
// If this is a NW or NE resize, we need to adjust the start y coordinate to account for the new height
// This keeps the bottom right corner in the same place for a NW resize
// and the bottom left corner in the same place for a NE resize
if (getCursor().getType() == Cursor.NW_RESIZE_CURSOR || getCursor().getType() == Cursor.NE_RESIZE_CURSOR) {
rectangle.y = endPoint.y - rectangle.height;
}
}
}
So when the rectangle is resized in the Northwest or Northeast directions, and the aspect ratio is applied, I also update the rectangle's start y coordinate to account for the change in height.

Need help adding a class and animations to a simple pet drawing?

float x = 100;
float y = 100;
float p = 150;
float l = 10;
float a = 100;
float b = 100;
float n =20;
int value = 255;
int r = 150;
int t = 100;
int s = 100;
int w = 60;
int h = 60;
int z = 11;
int eyeSize = 10;
int pigNose = 30;
int pigBody = 30;
int pigEars = 35;
int pigTail = 20;
int otherpigTail = 200;
int speed = 1;
void setup () {
size (600, 600);
a = width/2.5;
b = height/2;
}
void draw() {
background(184, 233, 249);
//Draw legs
stroke(0);
fill(249, 137, 244);
rect(x+(2*w), y+h/3.5, z, 2*z);
rect(x+(w), y+h/3, z, 2*z);
rect(x+(1.5*w), y+h/3, z, 2*z);
rect(x+(2.5*w), y+h/3.5, z, 2*z);
////draw body
stroke(0);
fill(249, 137, 244);
ellipse(110+x,y-pigBody, p, p-20);
//draw tail
fill(0);
line(185+x, y-pigTail, x+otherpigTail, y-(2*pigTail));
// Draw payer's head
fill(249, 137, 244);
ellipse(x,y-pigNose,t,t);
// Draw player's eyes
fill(0);
ellipse(x-w/3+1,y-h/2,eyeSize,eyeSize);
ellipse(x+w/3-1,y-h/2,eyeSize,eyeSize);
//Draw nose
stroke(0);
fill(198, 105, 194);
ellipse(x, y, pigNose, pigNose);
//draw ears
stroke(0);
fill(198, 105, 194);
ellipse(x-(w/2),y-h, pigEars, pigEars);
ellipse(x+(w/2),y-h, pigEars, pigEars);
//draw obstacles
fill(value);
ellipse(a, b, s, s);
ellipse(300+a, 200+b, s, s);
ellipse(300-a, 400+b, s, s);
ellipse(300-a, 600+b, s, s);
ellipse(300-a, b, s, s);
ellipse(300+a, 800+b, s, s);
}
I need help turning this code into something similar to this:
/*
This is a very rudimentary virtual pet. It can sit,
lie down, and wag it's tail.
*/
class Pet {
int x, y;
int pose;
int WAG = 1, SLEEP = 2, SIT = 3;
float tailWag, wagSpeed;
Pet(int x, int y) {
this.x = x;
this.y = y;
pose = SIT;
}
// adjust pose and stop tail wagging
void sit() {
pose = SIT;
wagSpeed = 0;
tailWag = 0;
}
// adjust pose and start tail wagging
void wag() {
pose = WAG;
wagSpeed = .1;
}
// adjust pose and stop tail wagging
void sleep() {
pose = SLEEP;
wagSpeed = 0;
tailWag = 0;
}
// draw in selected pose
void draw() {
pushMatrix();
translate(x, y);
if (pose == SIT) {
drawSitting();
}
else if (pose == WAG) {
wagTail();
drawSitting();
}
else {
drawLaying();
}
popMatrix();
}
void drawLaying() {
// needs work :-)
ellipse(0, 0, 150, 60);
}
void wagTail() {
float maxTailWag = .5; // controls how much the tail wags back and forth
tailWag = tailWag + wagSpeed;
// reverse wag direction if the wag limit is reached
if (tailWag > maxTailWag || tailWag < -maxTailWag) {
wagSpeed = -wagSpeed;
}
}
// not pretty but gets the idea across
// origin is the center of the torso
void drawSitting() {
// torso
pushMatrix();
rotate(radians(-30));
ellipse(0, 0, 80, 120);
popMatrix();
ellipse(-20, -70, 60, 60); // head
// nose
pushMatrix();
translate(-55, -55);
rotate(radians(-15));
arc(0, 0, 40, 30, radians(20), radians(310), OPEN);
popMatrix();
// eyes
ellipse(-40, -85, 15, 15); // left eye
ellipse(-25, -80, 15, 15); // right eye
//ear
pushMatrix();
translate(15, -50);
rotate(radians(-20));
ellipse(0, 0, 20, 40);
popMatrix();
//tail
pushMatrix();
translate(40, 30);
rotate(radians(45)+tailWag);
arc(0, -35, 30, 60, radians(-220)-tailWag, radians(80), OPEN);
popMatrix();
// back leg
ellipse(0, 60, 50, 20);
// front leg
pushMatrix();
translate(-50, 30);
rotate(radians(15));
ellipse(0, 0, 30, 60);
popMatrix();
}
}
with classes and whatnot so that I can start working on adding in my own animations for the pet. I'm just not sure where to put everything/how to organize it like that using my drawing.
If I were you, I would start with something simpler. For example, here's a program that uses 4 variables to show a ball bouncing around:
float circleX = 50;
float circleY = 50;
float xSpeed = 1;
float ySpeed = 2;
void draw() {
background(200);
circleX += xSpeed;
if (circleX < 0 || circleX > width) {
xSpeed *= -1;
}
circleY += ySpeed;
if (circleY < 0 || circleY > height) {
ySpeed *= -1;
}
ellipse(circleX, circleY, 20, 20);
}
(source: happycoding.io)
From here, we can encapsulate those 4 variables into a class:
class Circle{
float x;
float y;
float xSpeed;
float ySpeed;
Circle(float x, float y, float xSpeed, float ySpeed){
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
}
}
Now that we have a class, we can use an instance of that class to control our ball.
Circle circle = new Circle(50, 50, 1, 2);
void draw() {
background(200);
circle.x += circle.xSpeed;
if (circle.x < 0 || circle.x > width) {
circle.xSpeed *= -1;
}
circle.y += circle.ySpeed;
if (circle.y < 0 || circle.y > height) {
circle.ySpeed *= -1;
}
ellipse(circle.x, circle.y, 20, 20);
}
class Circle{
float x;
float y;
float xSpeed;
float ySpeed;
Circle(float x, float y, float xSpeed, float ySpeed){
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
}
}
Your code would follow a similar pattern: create a class, encapsulate your data by moving your variables inside the class, and then create an instance of that class and call its methods to draw your figure. Start with something simpler, and just create a class that draws a single circle. Get that working first, and then add variables to that class to draw two circles (a head and a body), and keep working in small steps like that until you're drawing your whole figure.
I really suggest trying something out and posting an MCVE if you get stuck. Good luck.
Shameless self-promotion: I've written a tutorial on creating classes in Processing available here.

Java: How to detect a collision between a rectangle and falling circles of varying radius and speed?

to start off, I'm making a simple game in Java that involves a blue rectangle that can be moved with arrow keys and seven falling circles of varying color, radius, and falling speed. Essentially, whenever the rectangle comes in contact with one of these circles, the rectangle will "lose" a life, which will be indicated by 3 rectangles on the top right of a JFrame that I haven't drawn yet. Every time the rectangle is hit by one of these circles, one of the rectangles will disappear, and when the blue rectangle is hit once more, a red "Game Over" text will appear in the middle of the frame.
Now then, although I'm having trouble getting the colors and speed to randomize each time the circles hit the bottom, I'll leave those for a future question. My main concern is the hit detection between the circles and the blue rectangle. I know that I need to define a certain method, but I'm unsure on how to go about doing it, and how to test it for each of the seven circles over and over while they're falling and having their Y value constantly change.
How could I go about doing this? Anyway, here's my main and circles classes for this project. I'm aware that there's a lot of junk code that isn't being used in the main class as well. I'll clean it up after I just figure this out.
**Main class (Keyexample)
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.Random;
public class Keyexample extends JPanel implements ActionListener, KeyListener{
Timer t = new Timer(5, this);
private Circle[] Circles = new Circle[7];
private javax.swing.Timer t2;
private Circle newc, c1, c2, c3, c4, c5, c6, c7;
double x = 100, y = 100;
double changeX = 0, changeY = 0;
private int cx = 10, cy = 0;
private int newcx = 0, newcy = 0;
private Random rand = new Random();
private Random colorc = new Random();
private int n = rand.nextInt(8);
public keyExample() {
t.start();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
Random colorc = new Random();
Random radiusc = new Random();
int r1 = radiusc.nextInt(12);
int r2 = radiusc.nextInt(12);
int r3 = radiusc.nextInt(12);
int r4 = radiusc.nextInt(12);
int r5 = radiusc.nextInt(12);
int r6 = radiusc.nextInt(12);
int r7 = radiusc.nextInt(12);
Color cc1 = new Color(colorc.nextInt(255), colorc.nextInt(255),
colorc.nextInt(255));
Color cc2 = new Color(colorc.nextInt(255), colorc.nextInt(255),
colorc.nextInt(255));
Color cc3 = new Color(colorc.nextInt(255), colorc.nextInt(255),
colorc.nextInt(255));
Color cc4 = new Color(colorc.nextInt(255), colorc.nextInt(255),
colorc.nextInt(255));
Color cc5 = new Color(colorc.nextInt(255), colorc.nextInt(255),
colorc.nextInt(255));
Color cc6 = new Color(colorc.nextInt(255), colorc.nextInt(255),
colorc.nextInt(255));
Color cc7 = new Color(colorc.nextInt(255), colorc.nextInt(255),
colorc.nextInt(255));
//creating the 7 circles and spacing them out
c1 = new Circle(cx, cy, r1, cc1);
c2 = new Circle(cx + 50, cy, r2, cc2);
c3 = new Circle(cx + 100, cy, r3, cc3);
c4 = new Circle(cx + 150, cy, r4, cc4);
c5 = new Circle(cx + 200, cy, r5, cc5);
c6 = new Circle(cx + 300, cy, r6, cc6);
c7 = new Circle(cx + 400, cy, r7, cc7);
Circles[0] = c1;
Circles[1] = c2;
Circles[2] = c3;
Circles[3] = c4;
Circles[4] = c5;
Circles[5] = c6;
Circles[6] = c7;
t2 = new javax.swing.Timer(33, new CircleListener());
t2.start();
}
//painting rectangle and circles
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLUE);
g2.fill(new Rectangle2D.Double(x, y, 40, 40));
for (int i = 0; i < Circles.length; i++){
Color circlecolor = new Color(rand.nextInt(255), rand.nextInt(255),
rand.nextInt(255));
//circle color starts spazzing out here. constantly changing while falling
g2.setColor(circlecolor);
Circles[i].fill(g);
}
}
public void createCircle(){
}
public void actionPerformed(ActionEvent e) {
repaint();
x += changeX;
y += changeY;
changeX = 0;
changeY = 0;
}
private class CircleListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
Random rand = new Random();
int move = 2 + rand.nextInt(10);
int move2 =2 + rand.nextInt(10);
int move3 =2 + rand.nextInt(10);
int move4 =2 + rand.nextInt(10);
int move5 =2 + rand.nextInt(10);
int move6 =2 + rand.nextInt(10);
c1.move(0, n);
position(c1);
c2.move(0, move);
position(c2);
c3.move(0, move2);
position(c3);
c4.move(0, move3);
position(c4);
c5.move(0, move4);
position(c5);
c6.move(0, move5);
position(c6);
c7.move(0, move6);
position(c7);
repaint();
}
public void position(Circle cp) {
int height = getHeight();
int loc = cp.centerX;
int speed = 3 + rand.nextInt(10);
int radiuss = cp.radius;
Rectangle bound = cp.Bounds();
if (bound.topY + bound.width > height){
cp.centerY = 0;
//moving circle back to the top
cp.move(0, speed);
//randomizing speed of circle after moving to top, not working
cp.radius = 5 + rand.nextInt(20);
//randomizing radius of circle after moving to top, does work
}
}
}
public void up() {
if (y != 0){
changeY = -3.5;
changeX = 0;
}
}
public void down() {
if (y <= 350) {
changeY = 3.5;
changeX = 0;
}
}
public void left() {
if (x >=0) {
changeX = -3.5;
changeY = 0;
}
}
public void right() {
if (x <= 550) {
changeX = 3.5;
changeY = 0;
}
}
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if (code == KeyEvent.VK_UP) {
up();
}
if (code == KeyEvent.VK_DOWN) {
down();
}
if (code == KeyEvent.VK_RIGHT) {
right();
}
if (code == KeyEvent.VK_LEFT) {
left();
}
}
public void keyTyped(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
}
**Circle class
import java.awt.*;
import java.util.Random;
public class Circle{
public int centerX, centerY, radius;
public Color color;
public Circle (int x, int y, int r, Color c){
centerX = x;
centerY = y;
radius = r;
Random random = new Random();
}
public void draw(Graphics g){
Color oldColor = g.getColor();
g.setColor(color);
g.drawOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
g.setColor(oldColor);
}
public void fill(Graphics g){
Color oldColor = g.getColor();
g.setColor(color);
g.fillOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
g.setColor(oldColor);
}
public boolean containsPoint(int x, int y){
int xSquared = (x - centerX) * (x - centerX);
int ySquared = (y - centerY) * (y - centerY);
int radiusSquared = radius * radius;
return xSquared + ySquared - radiusSquared <=0;
}
public void move(int xAmount, int yAmount){
centerX = centerX + xAmount;
centerY = centerY + yAmount;
}
public Rectangle Bounds(){
int x = centerX - radius;
int y = centerY - radius;
return new Rectangle(x, y, radius * 2, radius * 2, Color.red);
}
}
You want to break the problem into two parts: the first - see if there is "definitely no collision". This happens when the center of the circle is more than a radius away from the edges. It's a very fast test, and will be true most of the time:
if(left > x + radius ||
right < x - radius ||
etc.) { // no collision }
The second - if you fail this test, you may still not hit. That depends on whether both x and y are sufficient for overlap. Within this, there are two situations:
A corner of the rectangle is inside the circle: easy to compute (distance from one corner to center of circle < r)
An edge is inside the circle. This means that in one direction (say X) the center lies between the edges; and in the other direction an edge is less than radius away.
This is general, without actual code. I assume you can write the code from here.
You can detect collisions by simple if else conditions on the circle's and rectangle's co ordinates on screen.
distance(circle, Rectangle) <= circle.radius + Rectangle.radius
You can implement distance helper function using the simple distance formula between two points.
link

Using paintThumb I've made my arrow fall off the JSlider when at 0 or 100, how can this be fixed?

The picture explains it all. I've painted a new Thumb and it goes off the JSlider area when at higher than 95 or below 5. I've tried padding the Track with no success.
Does anyone have any tips?
Here is my code. I believe my issue is fine turning the size of the thumb under the getThumbSize override.
private static class MySliderUI extends BasicSliderUI {
private int thumbHeight = 22;
private int thumbWidth = 22;
public MySliderUI(JSlider b) {
super(b);
// this.thumbHeight = slider.getHeight();
// this.thumbWidth = slider.getHeight();
}
#Override
protected Dimension getThumbSize() {
return new Dimension(thumbHeight, thumbWidth);
}
#Override
public void paintTrack(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Rectangle r = trackRect;
r.width = 40; // (int) (0.15 * r.getHeight());
float[] dist = { 0.1f, 0.5f, 0.9f };
Color[] colors = { new Color(34, 177, 76), new Color(255, 242, 0),
new Color(237, 28, 36) };
Point2D start = new Point2D.Float(r.x, r.y);
Point2D end = new Point2D.Float(r.x, r.y + r.height);
LinearGradientPaint p = new LinearGradientPaint(start, end, dist,
colors);
g2d.setPaint(p);
g2d.fill(r);
}
#Override
public void paintThumb(Graphics g) {
Graphics2D g1 = (Graphics2D) g;
// Make a triangle - Arrow on Meter
int[] x = new int[3];
int[] y = new int[3];
int n; // count of points
// Set Points for Arrow
Integer arrowX = thumbRect.x;
Integer arrowY = thumbRect.y;
x[0] = arrowX - 25;
x[1] = arrowX - 25;
x[2] = arrowX - 2;
y[0] = arrowY - 17; // Top Left
y[1] = arrowY + 27; // Bottom Left
y[2] = arrowY + 5; // Tip of Arrow
n = 3; // Number of points, 3 because its a triangle
// Draw Arrow Border
Polygon myTriShadow = new Polygon(x, y, n); // a triangle
g1.setPaint(Color.black);
g1.fill(myTriShadow);
// Set Points for Arrow Board
x[0] = x[0] + 2;
x[1] = x[1] + 2;
x[2] = x[2] - 3;
y[0] = y[0] + 5;
y[1] = y[1] - 5;
y[2] = y[2];
// Color colorMeter = robot.getPixelColor(x[2]+10, y[2]);
// Draw Arrow
Polygon myTri = new Polygon(x, y, n); // a triangle
// Color colr = new Color();
g1.setPaint(Color.yellow);
g1.fill(myTri);
// super.paintThumb(g); // replace with your fill()
}
}
Override getThumbSize(), returning the dimensions of the bounding rectangle of your thumb. If not symmetric, you should account for the slider's orientation.
Addendum: As an aside, the top of the thumb didn't "fall off"; it was clipped. See Painting in AWT and Swing for more about the clip rectangle.

Categories