Bouncing shape nested in another shape (What is wrong with this code) - java
I have an application that bounces Shapes around inside a JPanel. Whenever shapes hit a side they will bounce off in the other direction. I am trying to add a new shape called a NestingShape that contains zero or more Shapes that bounce around inside it, while the NestingShape bounces around in the JPanel. The children of a NestingShape instance can be either simple Shapes or other NestingShape instances.
Right now I am having trouble with moving the children of a NestingShape within a NestingShape with the move(width, height) method in the NestingShape subclass. I am also having trouble with developing a method in the Shape superclass that can find the parent any given shape. I'll copy and paste the code I have come up with so far for the Shape superclass and NestingShape subclass below and the test cases I am using to test the code so far:
Shape superclass:
NOTE: parent() and path() methods are the most relevant methods for this task and the parent() method is the one I'm having trouble implementing. There are a lot of little details such as fFill and count and such that are related to different Shapes that I have developed, and those can be ignored.
package bounce;
import java.awt.Color;
import java.util.List;
/**
* Abstract superclass to represent the general concept of a Shape. This class
* defines state common to all special kinds of Shape instances and implements
* a common movement algorithm. Shape subclasses must override method paint()
* to handle shape-specific painting.
*
* #author wadfsd
*
*/
public abstract class Shape {
// === Constants for default values. ===
protected static final int DEFAULT_X_POS = 0;
protected static final int DEFAULT_Y_POS = 0;
protected static final int DEFAULT_DELTA_X = 5;
protected static final int DEFAULT_DELTA_Y = 5;
protected static final int DEFAULT_HEIGHT = 35;
protected static final int DEFAULT_WIDTH = 25;
protected static final Color DEFAULT_COLOR = Color.black;
protected static final String DEFAULT_STRING = "";
// ===
// === Instance variables, accessible by subclasses.
protected int fX;
protected int fY;
protected int fDeltaX;
protected int fDeltaY;
protected int fWidth;
protected int fHeight;
protected boolean fFill;
protected Color fColor;
protected int count;
protected int fState;
protected int before;
protected String fString;
// ===
/**
* Creates a Shape object with default values for instance variables.
*/
public Shape() {
this(DEFAULT_X_POS, DEFAULT_Y_POS, DEFAULT_DELTA_X, DEFAULT_DELTA_Y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR, DEFAULT_STRING);
}
/**
* Creates a Shape object with a specified x and y position.
*/
public Shape(int x, int y) {
this(x, y, DEFAULT_DELTA_X, DEFAULT_DELTA_Y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR, DEFAULT_STRING);
}
public Shape(int x, int y, String str) {
this(x, y, DEFAULT_DELTA_X, DEFAULT_DELTA_Y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR, str);
}
/**
* Creates a Shape object with specified x, y, and color values.
*/
public Shape(int x, int y, Color c) {
this(x, y, DEFAULT_DELTA_X, DEFAULT_DELTA_Y, DEFAULT_WIDTH, DEFAULT_HEIGHT, c, DEFAULT_STRING);
}
public Shape(int x, int y, Color c, String str) {
this(x, y, DEFAULT_DELTA_X, DEFAULT_DELTA_Y, DEFAULT_WIDTH, DEFAULT_HEIGHT, c, str);
}
/**
* Creates a Shape instance with specified x, y, deltaX and deltaY values.
* The Shape object is created with a default width, height and color.
*/
public Shape(int x, int y, int deltaX, int deltaY) {
this(x, y, deltaX, deltaY, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR, DEFAULT_STRING);
}
public Shape(int x, int y, int deltaX, int deltaY, String str) {
this(x, y, deltaX, deltaY, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR, str);
}
/**
* Creates a Shape instance with specified x, y, deltaX, deltaY and color values.
* The Shape object is created with a default width and height.
*/
public Shape(int x, int y, int deltaX, int deltaY, Color c) {
this(x, y, deltaX, deltaY, DEFAULT_WIDTH, DEFAULT_HEIGHT, c, DEFAULT_STRING);
}
public Shape(int x, int y, int deltaX, int deltaY, Color c, String str) {
this(x, y, deltaX, deltaY, DEFAULT_WIDTH, DEFAULT_HEIGHT, c, str);
}
/**
* Creates a Shape instance with specified x, y, deltaX, deltaY, width and
* height values. The Shape object is created with a default color.
*/
public Shape(int x, int y, int deltaX, int deltaY, int width, int height) {
this(x, y, deltaX, deltaY, width, height, DEFAULT_COLOR, DEFAULT_STRING);
}
public Shape(int x, int y, int deltaX, int deltaY, int width, int height, String str) {
this(x, y, deltaX, deltaY, width, height, DEFAULT_COLOR, str);
}
public Shape(int x, int y, int deltaX, int deltaY, int width, int height, Color c) {
this(x, y, deltaX, deltaY, width, height, c, DEFAULT_STRING);
}
/**
* Creates a Shape instance with specified x, y, deltaX, deltaY, width,
* height and color values.
*/
public Shape(int x, int y, int deltaX, int deltaY, int width, int height, Color c, String str) {
fX = x;
fY = y;
fDeltaX = deltaX;
fDeltaY = deltaY;
fWidth = width;
fHeight = height;
fFill = false;
fColor = c;
count = 0;
fState = 1;
before = 0;
fString = str;
}
/**
* Moves this Shape object within the specified bounds. On hitting a
* boundary the Shape instance bounces off and back into the two-
* dimensional world and logs whether a vertical or horizontal wall
* was hit for the DynamicRectangleShape.
* #param width width of two-dimensional world.
* #param height height of two-dimensional world.
*/
public void move(int width, int height) {
int nextX = fX + fDeltaX;
int nextY = fY + fDeltaY;
if (nextY <= 0) {
nextY = 0;
fDeltaY = -fDeltaY;
fFill = false;
count++;
} else if (nextY + fHeight >= height) {
nextY = height - fHeight;
fDeltaY = -fDeltaY;
fFill = false;
count++;
}
// When Shape hits a corner the vertical wall fFill value overrides the horizontal
if (nextX <= 0) {
nextX = 0;
fDeltaX = -fDeltaX;
fFill = true;
count++;
} else if (nextX + fWidth >= width) {
nextX = width - fWidth;
fDeltaX = -fDeltaX;
fFill = true;
count++;
}
fX = nextX;
fY = nextY;
}
public void text(Painter painter, String str) {
painter.drawCentredText(str, fX, fY, fWidth, fHeight);
}
/**
* Returns the NestingShape that contains the Shape that method parent
* is called on. If the callee object is not a child within a
* NestingShape instance this method returns null.
*/
public NestingShape parent() {
// Related to NestingShape
}
/**
* Returns an ordered list of Shape objects. The first item within the
* list is the root NestingShape of the containment hierarchy. The last
* item within the list is the callee object (hence this method always
* returns a list with at least one item). Any intermediate items are
* NestingShapes that connect the root NestingShape to the callee Shape.
* E.g. given:
*
* NestingShape root = new NestingShape();
* NestingShape intermediate = new NestingShape();
* Shape oval = new OvalShape();
* root.add(intermediate);
* intermediate.add(oval);
*
* a call to oval.path() yields: [root,intermediate,oval]
*/
public List<Shape> path() {
// Related to NestingShape
}
/**
* Method to be implemented by concrete subclasses to handle subclass
* specific painting.
* #param painter the Painter object used for drawing.
*/
public abstract void paint(Painter painter);
/**
* Returns this Shape object's x position.
*/
public int x() {
return fX;
}
/**
* Returns this Shape object's y position.
*/
public int y() {
return fY;
}
/**
* Returns this Shape object's speed and direction.
*/
public int deltaX() {
return fDeltaX;
}
/**
* Returns this Shape object's speed and direction.
*/
public int deltaY() {
return fDeltaY;
}
/**
* Returns this Shape's width.
*/
public int width() {
return fWidth;
}
/**
* Returns this Shape's height.
*/
public int height() {
return fHeight;
}
/**
* Returns a String whose value is the fully qualified name of this class
* of object. E.g., when called on a RectangleShape instance, this method
* will return "bounce.RectangleShape".
*/
public String toString() {
return getClass().getName();
}
}
NestingShape subclass:
NOTE: Having trouble with the move() method
package bounce;
import java.util.ArrayList;
import java.util.List;
public class NestingShape extends Shape {
private List<Shape> nest = new ArrayList<Shape>();
/**
* Creates a NestingShape object with default values for state.
*/
public NestingShape() {
super();
}
/**
* Creates a NestingShape object with specified location values, default values for other
* state items.
*/
public NestingShape(int x, int y) {
super(x,y);
}
/**
* Creates a NestingShape with specified values for location, velocity and direction.
* Non-specified state items take on default values.
*/
public NestingShape(int x, int y, int deltaX, int deltaY) {
super(x,y,deltaX,deltaY);
}
/**
* Creates a NestingShape with specified values for location, velocity, direction, width, and
* height.
*/
public NestingShape(int x, int y, int deltaX, int deltaY, int width, int height) {
super(x,y,deltaX,deltaY,width,height);
}
/**
* Moves a NestingShape object (including its children) with the bounds specified by arguments
* width and height.
*/
public void move(int width, int height) {
int nextX = fX + fDeltaX;
int nextY = fY + fDeltaY;
if (nextY <= 0) {
nextY = 0;
fDeltaY = -fDeltaY;
fFill = false;
count++;
} else if (nextY + fHeight >= height) {
nextY = height - fHeight;
fDeltaY = -fDeltaY;
fFill = false;
count++;
}
if (nextX <= 0) {
nextX = 0;
fDeltaX = -fDeltaX;
fFill = true;
count++;
} else if (nextX + fWidth >= width) {
nextX = width - fWidth;
fDeltaX = -fDeltaX;
fFill = true;
count++;
}
fX = nextX;
fY = nextY;
// Move children
for (int i = 0; i < shapeCount(); i++) {
Shape shape = shapeAt(i);
int nextXChild = shape.fX + shape.fDeltaX;
int nextYChild = shape.fY + shape.fDeltaY;
if (nextYChild <= 0) {
nextYChild = 0;
shape.fDeltaY = -shape.fDeltaY;
} else if (nextYChild + shape.fHeight >= fHeight) {
nextYChild = fHeight - shape.fHeight;
shape.fDeltaY = -shape.fDeltaY;
}
if (nextXChild <= 0) {
nextXChild = 0;
shape.fDeltaX = -shape.fDeltaX;
} else if (nextXChild + fWidth >= width) {
nextXChild = fWidth - shape.fWidth;
shape.fDeltaX = -shape.fDeltaX;
}
shape.fX = nextXChild;
shape.fY = nextYChild;
}
}
/**
* Paints a NestingShape object by drawing a rectangle around the edge of its bounding box.
* The NestingShape object's children are then painted.
*/
public void paint(Painter painter) {
painter.drawRect(fX,fY,fWidth,fHeight);
painter.translate(fX,fY);
for (int i = 0; i < shapeCount(); i++) {
Shape shape = shapeAt(i);
shape.paint(painter);
}
painter.translate(0,0);
}
/**
* Attempts to add a Shape to a NestingShape object. If successful, a two-way link is
* established between the NestingShape and the newly added Shape. Note that this method
* has package visibility - for reasons that will become apparent in Bounce III.
* #param shape the shape to be added.
* #throws IllegalArgumentException if an attempt is made to add a Shape to a NestingShape
* instance where the Shape argument is already a child within a NestingShape instance. An
* IllegalArgumentException is also thrown when an attempt is made to add a Shape that will
* not fit within the bounds of the proposed NestingShape object.
*/
void add(Shape shape) throws IllegalArgumentException {
if (contains(shape)) {
throw new IllegalArgumentException();
} else if (shape.fWidth > fWidth || shape.fHeight > fHeight) {
throw new IllegalArgumentException();
} else {
nest.add(shape);
}
}
/**
* Removes a particular Shape from a NestingShape instance. Once removed, the two-way link
* between the NestingShape and its former child is destroyed. This method has no effect if
* the Shape specified to remove is not a child of the NestingShape. Note that this method
* has package visibility - for reasons that will become apparent in Bounce III.
* #param shape the shape to be removed.
*/
void remove(Shape shape) {
int index = indexOf(shape);
nest.remove(index);
}
/**
* Returns the Shape at a specified position within a NestingShape. If the position specified
* is less than zero or greater than the number of children stored in the NestingShape less
* one this method throws an IndexOutOfBoundsException.
* #param index the specified index position.
*/
public Shape shapeAt(int index) throws IndexOutOfBoundsException {
if (index < 0 || index >= shapeCount()) {
throw new IndexOutOfBoundsException();
} else {
Shape shape = nest.get(index);
return shape;
}
}
/**
* Returns the number of children contained within a NestingShape object. Note this method is
* not recursive - it simply returns the number of children at the top level within the callee
* NestingShape object.
*/
public int shapeCount() {
int number = nest.size();
return number;
}
/**
* Returns the index of a specified child within a NestingShape object. If the Shape specified
* is not actually a child of the NestingShape this method returns -1; otherwise the value
* returned is in the range 0 .. shapeCount() - 1.
* #param shape the shape whose index position within the NestingShape is requested.
*/
public int indexOf(Shape shape) {
int index = nest.indexOf(shape);
return index;
}
/**
* Returns true if the shape argument is a child of the NestingShape object on which this method
* is called, false otherwise.
*/
public boolean contains(Shape shape) {
boolean child = nest.contains(shape);
return child;
}
}
TestNestingShape test cases:
package bounce;
import java.util.List;
import junit.framework.TestCase;
/**
* Class to test class NestingShape according to its specification.
*/
public class TestNestingShape extends TestCase {
private NestingShape topLevelNest;
private NestingShape midLevelNest;
private NestingShape bottomLevelNest;
private Shape simpleShape;
public TestNestingShape(String name) {
super(name);
}
/**
* Creates a Shape composition hierarchy with the following structure:
* NestingShape (topLevelNest)
* |
* --- NestingShape (midLevelNest)
* |
* --- NestingShape (bottomLevelNest)
* |
* --- RectangleShape (simpleShape)
*/
protected void setUp() throws Exception {
topLevelNest = new NestingShape(0, 0, 2, 2, 100, 100);
midLevelNest = new NestingShape(0, 0, 2, 2, 50, 50);
bottomLevelNest = new NestingShape(5, 5, 2, 2, 10, 10);
simpleShape = new RectangleShape(1, 1, 1, 1, 5, 5);
midLevelNest.add(bottomLevelNest);
midLevelNest.add(simpleShape);
topLevelNest.add(midLevelNest);
}
/**
* Checks that methods move() and paint() correctly move and paint a
* NestingShape's contents.
*/
public void testBasicMovementAndPainting() {
Painter painter = new MockPainter();
topLevelNest.move(500, 500);
topLevelNest.paint(painter);
assertEquals("(rectangle 2,2,100,100)(rectangle 2,2,50,50)(rectangle 7,7,10,10)(rectangle 2,2,5,5)", painter.toString());
}
/**
* Checks that method add successfuly adds a valid Shape, supplied as
* argument, to a NestingShape instance.
*/
public void testAdd() {
// Check that topLevelNest and midLevelNest mutually reference each other.
assertSame(topLevelNest, midLevelNest.parent());
assertTrue(topLevelNest.contains(midLevelNest));
// Check that midLevelNest and bottomLevelNest mutually reference each other.
assertSame(midLevelNest, bottomLevelNest.parent());
assertTrue(midLevelNest.contains(bottomLevelNest));
}
/**
* Check that method add throws an IlegalArgumentException when an attempt
* is made to add a Shape to a NestingShape instance where the Shape
* argument is already part of some NestingShape instance.
*/
public void testAddWithArgumentThatIsAChildOfSomeOtherNestingShape() {
try {
topLevelNest.add(bottomLevelNest);
fail();
} catch(IllegalArgumentException e) {
// Expected action. Ensure the state of topLevelNest and
// bottomLevelNest has not been changed.
assertFalse(topLevelNest.contains(bottomLevelNest));
assertSame(midLevelNest, bottomLevelNest.parent());
}
}
/**
* Check that method add throws an IllegalArgumentException when an attempt
* is made to add a shape that will not fit within the bounds of the
* proposed NestingShape object.
*/
public void testAddWithOutOfBoundsArgument() {
Shape rectangle = new RectangleShape(80, 80, 2, 2, 50, 50);
try {
topLevelNest.add(rectangle);
fail();
} catch(IllegalArgumentException e) {
// Expected action. Ensure the state of topLevelNest and
// rectangle has not been changed.
assertFalse(topLevelNest.contains(rectangle));
assertNull(rectangle.parent());
}
}
/**
* Check that method remove breaks the two-way link between the Shape
* object that has been removed and the NestingShape it was once part of.
*/
public void testRemove() {
topLevelNest.remove(midLevelNest);
assertFalse(topLevelNest.contains(midLevelNest));
assertNull(midLevelNest.parent());
}
/**
* Check that method shapeAt returns the Shape object that is held at a
* specified position within a NestingShape instance.
*/
public void testShapeAt() {
assertSame(midLevelNest, topLevelNest.shapeAt(0));
}
/**
* Check that method shapeAt throws a IndexOutOfBoundsException when called
* with an invalid index argument.
*/
public void testShapeAtWithInvalidIndex() {
try {
topLevelNest.shapeAt(1);
fail();
} catch(IndexOutOfBoundsException e) {
// Expected action.
}
}
/**
* Check that method shapeCount returns zero when called on a NestingShape
* object without children.
*/
public void testShapeCountOnEmptyParent() {
assertEquals(0, bottomLevelNest.shapeCount());
}
/**
* Check that method shapeCount returns the number of children held within
* a NestingShape instance - where the number of children > 0.
*/
public void testShapeCountOnNonEmptyParent() {
assertEquals(2, midLevelNest.shapeCount());
}
/**
* Check that method indexOf returns the index position within a
* NestingShape instance of a Shape held within the NestingShape.
*/
public void testIndexOfWith() {
assertEquals(0, topLevelNest.indexOf(midLevelNest));
assertEquals(1, midLevelNest.indexOf(simpleShape));
}
/**
* Check that method indexOf returns -1 when called with an argument that
* is not part of the NestingShape callee object.
*/
public void testIndexOfWithNonExistingChild() {
assertEquals(-1, topLevelNest.indexOf(bottomLevelNest));
}
/**
* Check that Shape's path method correctly returns the path from the root
* NestingShape object through to the Shape object that path is called on.
*/
public void testPath() {
List<Shape> path = simpleShape.path();
assertEquals(3, path.size());
assertSame(topLevelNest, path.get(0));
assertSame(midLevelNest, path.get(1));
assertSame(simpleShape, path.get(2));
}
/**
* Check that Shape's path method correctly returns a singleton list
* containing only the callee object when this Shape object has no parent.
*/
public void testPathOnShapeWithoutParent() {
List<Shape> path = topLevelNest.path();
assertEquals(1, path.size());
assertSame(topLevelNest, path.get(0));
}
}
With the code I have so far when I run the test cases, I am unable to test the testAdd and testRemove related test cases to make sure I'm adding shapes properly because I haven't yet developed the parent() method in the Shape class. But I can't think of a way of implementing the parent method.
Whenever I testBasicMovementAndPainting, I also get a failed test because my current move() method (in the NestingShape class) only moves the children in the first NestingShape and doesn't move the children of the midLevelNest.
It's a bit of a long read and I'm not sure if that provides enough context as there are a lot of other classes in the package that I didn't include but if anyone could help I would really appreciate it.
Thanks.
For the "parent" problem: a Shape needs an extra Shape attribute that points to the outer nesting shape (it's container / parent):
private Shape parent = null;
you can either set it with a constructor or simply add getter/setter methods:
public Shape(Shape parent) {
this.parent = parent;
}
public void setParent(Shape parent) {
this.parent = parent;
}
public Shape parent() {
return parent;
}
Note the problem now is that any shape can be container for other shapes - it is not restricted to NestingShape. But if I declare the parent as a NestingShape, then we have the ugly situation, that Shape depends on NestingShape, its subclass.
Maybe you simply define an extra interface named ShapeContainer which adds container functionality to a Shape, like
public interface ShapeContainer {
public List<Shape> getChildren();
// .. more?
}
Then your class signature would look like this
public class NestingShape extends Shape implements ShapeContainer
and the type of the parent field in Shape would be ShapeContainer.
Related
How to print arrayList from another class using a Vaadin Label?
I have a Canvas.java class where I have a mousemove code to track the mouse position, and have it saved to an array. If I print this code out from this class itself, the mouse position is correctly printed. However, is there any way I can access this array from another class, and have it printed still (eg. I want it to appear as a Vaadin label in MainLayout.java)? It seems that the label is only showing '[]' in place of the coordinates I want printed. Canvas.java: package com.vaadin.starter.beveragebuddy.ui.components; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.HasSize; import com.vaadin.flow.component.HasStyle; import com.vaadin.flow.component.Tag; import com.vaadin.flow.component.html.Label; import com.vaadin.flow.dom.Element; import com.vaadin.flow.dom.ElementFactory; import elemental.json.JsonObject; import java.util.ArrayList; /** * Canvas component that you can draw shapes and images on. It's a Java wrapper * for the * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">HTML5 * canvas</a>. * <p> * Use {#link #getContext()} to get API for rendering shapes and images on the * canvas. * <p> */ #Tag("canvas") #SuppressWarnings("serial") public class Canvas extends Component implements HasStyle, HasSize { private CanvasRenderingContext2D context; private Element element; private boolean isDrawing = false; private boolean mouseIsDown = false; private double endX; private double endY; public static ArrayList <BoundingBox> arrayBoxes = new ArrayList<BoundingBox>(); public static ArrayList <MousePosition> mousePosArray = new ArrayList<MousePosition>(); public static ArrayList<BoundingBox> getArrayBoxes() { return arrayBoxes; } public static ArrayList<MousePosition> getMousePosArray() { return mousePosArray; } public static void setMousePosArray(ArrayList<MousePosition> mousePosArray) { Canvas.mousePosArray = mousePosArray; } /** * Creates a new canvas component with the given size. * <p> * Use the API provided by {#link #getContext()} to render graphics on the * canvas. * <p> * The width and height parameters will be used for the canvas' coordinate * system. They will determine the size of the component in pixels, unless * you explicitly set the component's size with {#link #setWidth(String)} or * {#link #setHeight(String)}. * * #param width * the width of the canvas * #param height * the height of the canvas */ public Canvas(int width, int height) { context = new CanvasRenderingContext2D(this); element = getElement(); element.getStyle().set("border", "1px solid"); getElement().setAttribute("width", String.valueOf(width)); getElement().setAttribute("height", String.valueOf(height)); element.addEventListener("mousedown", event -> { // Retrieve Starting Position on MouseDown Element boundingBoxResult = ElementFactory.createDiv(); element.appendChild(boundingBoxResult); JsonObject evtData = event.getEventData(); double xBox = evtData.getNumber("event.x"); double yBox = evtData.getNumber("event.y"); boundingBoxResult.setAttribute("data-x", String.format("%f", xBox)); boundingBoxResult.setAttribute("data-y", String.format("%f", yBox)); BoundingBox newBox = new BoundingBox(0, xBox, yBox, 0.0, 0.0); arrayBoxes.add(newBox); isDrawing = true; mouseIsDown=true; }).addEventData("event.x").addEventData("event.y"); element.addEventListener("mouseup", event -> { // Draw Box on MouseUp Element boundingBoxResult2 = ElementFactory.createDiv(); element.appendChild(boundingBoxResult2); JsonObject evtData2 = event.getEventData(); endX = evtData2.getNumber("event.x"); endY = evtData2.getNumber("event.y"); boundingBoxResult2.setAttribute("end-x", String.format("%f", endX)); boundingBoxResult2.setAttribute("end-y", String.format("%f", endY)); double xcoordi = 0; double ycoordi = 0; double boxWidth = 0; double boxHeight = 0; for (int i = 0; i < arrayBoxes.size(); i++) { arrayBoxes.get(i).setName(i + 1); arrayBoxes.get(i).setWidth(endX, arrayBoxes.get(i).xcoordi); arrayBoxes.get(i).setHeight(endY, arrayBoxes.get(i).ycoordi); xcoordi = arrayBoxes.get(i).getXcoordi(); ycoordi = arrayBoxes.get(i).getYcoordi(); boxWidth = arrayBoxes.get(i).getWidth(); boxHeight = arrayBoxes.get(i).getHeight(); } mouseIsDown=false; context.beginPath(); context.setFillStyle("limegreen"); context.setLineWidth(2); context.strokeRect(xcoordi, ycoordi, boxWidth, boxHeight); context.fill(); System.out.println(arrayBoxes.toString()); }).addEventData("event.x").addEventData("event.y"); element.addEventListener("mousemove", event -> { // Retrieve Mouse Position when Moving JsonObject mousePos = event.getEventData(); double mouseX = mousePos.getNumber("event.x"); double mouseY = mousePos.getNumber("event.y"); MousePosition currentPos = new MousePosition(mouseX, mouseY); mousePosArray.add(0, currentPos); setMousePosArray(mousePosArray); System.out.println(mousePosArray.get(0)); // System.out.println(mousePosArray.get(mousePosArray.size() -1)); }).addEventData("event.x").addEventData("event.y"); } MainLayout.java: package com.vaadin.starter.beveragebuddy.backend; import com.vaadin.flow.component.dependency.HtmlImport; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.Label; import com.vaadin.flow.component.html.NativeButton; import com.vaadin.flow.router.Route; import com.vaadin.starter.beveragebuddy.ui.components.BoundingBox; import com.vaadin.starter.beveragebuddy.ui.components.Canvas; import com.vaadin.starter.beveragebuddy.ui.components.CanvasRenderingContext2D; import com.vaadin.starter.beveragebuddy.ui.components.MousePosition; import com.vaadin.flow.component.textfield.TextField; import java.util.ArrayList; #HtmlImport("frontend://styles/shared-styles.html") #Route("") public class MainLayout extends Div { private CanvasRenderingContext2D ctx; private Canvas canvas; ArrayList<MousePosition> mousePosArray = Canvas.getMousePosArray(); ArrayList<BoundingBox> bb = Canvas.getArrayBoxes(); public MainLayout() { H2 title = new H2("Annotation UI"); title.addClassName("main-layout__title"); canvas = new Canvas(1580, 700); ctx = canvas.getContext(); Label label = new Label("Coordinates: " + mousePosArray); canvas.addComponent(label); add(label); } } CanvasRenderingContext2D.java: package com.vaadin.starter.beveragebuddy.ui.components; import java.io.Serializable; import java.util.ArrayList; /** * The context for rendering shapes and images on a canvas. * <p> * This is a Java wrapper for the <a href= * "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">same * client-side API</a>. */ public class CanvasRenderingContext2D { private Canvas canvas; public static ArrayList<BoundingBox> arrayBoxes = new ArrayList<BoundingBox>(); protected CanvasRenderingContext2D(Canvas canvas) { this.canvas = canvas; } public void setFillStyle(String fillStyle) { setProperty("fillStyle", fillStyle); } public void setStrokeStyle(String strokeStyle) { setProperty("fillStyle", strokeStyle); } public void setLineWidth(double lineWidth) { setProperty("lineWidth", lineWidth); } public void setFont(String font) { setProperty("font", font); } public void arc(double x, double y, double radius, double startAngle, double endAngle, boolean antiClockwise) { callJsMethod("arc", x, y, radius, startAngle, endAngle, antiClockwise); } public void beginPath() { callJsMethod("beginPath"); } public void clearRect(double x, double y, double width, double height) { callJsMethod("clearRect", x, y, width, height); Canvas.arrayBoxes.clear(); } public void closePath() { callJsMethod("closePath"); } /** * Fetches the image from the given location and draws it on the canvas. * <p> * <b>NOTE:</b> The drawing will happen asynchronously after the browser has * received the image. * * #param src * the url of the image to draw * #param x * the x-coordinate of the top-left corner of the image * #param y * the y-coordinate of the top-left corner of the image */ public void drawImage(String src, double x, double y) { runScript(String.format( "var zwKqdZ = new Image();" + "zwKqdZ.onload = function () {" + "$0.getContext('2d').drawImage(zwKqdZ, %s, %s);};" + "zwKqdZ.src='%s';", x, y, src)); } /** * Fetches the image from the given location and draws it on the canvas. * <p> * <b>NOTE:</b> The drawing will happen asynchronously after the browser has * received the image. * * #param src * the url of the image to draw * #param x * the x-coordinate of the top-left corner of the image * #param y * the y-coordinate of the top-left corner of the image * #param width * the width for the image * #param height * the height for the image */ public void drawImage(String src, double x, double y, double width, double height) { runScript(String.format("var zwKqdZ = new Image();" + "zwKqdZ.onload = function () {" + "$0.getContext('2d').drawImage(zwKqdZ, %s, %s, %s, %s);};" + "zwKqdZ.src='%s';", x, y, width, height, src)); } public void fill() { callJsMethod("fill"); } public void fillRect(double x, double y, double width, double height) { callJsMethod("fillRect", x, y, width, height); } public void fillText(String text, double x, double y) { callJsMethod("fillText", text, x, y); } public void lineTo(double x, double y) { callJsMethod("lineTo", x, y); } public void moveTo(double x, double y) { callJsMethod("moveTo", x, y); } public void rect(double x, double y, double width, double height) { callJsMethod("rect", x, y, width, height); } public void restore() { callJsMethod("restore"); } public void rotate(double angle) { callJsMethod("rotate", angle); } public void save() { callJsMethod("save"); } public void scale(double x, double y) { callJsMethod("scale", x, y); } public void stroke() { callJsMethod("stroke"); } public void strokeRect(double x, double y, double width, double height) { callJsMethod("strokeRect", x, y, width, height); } public void strokeText(String text, double x, double y) { callJsMethod("strokeText", text, x, y); } public void translate(double x, double y) { callJsMethod("translate", x, y); } protected void setProperty(String propertyName, Serializable value) { runScript(String.format("$0.getContext('2d').%s='%s'", propertyName, value)); } /** * Runs the given js so that the execution order works with callJsMethod(). * Any $0 in the script will refer to the canvas element. */ private void runScript(String script) { canvas.getElement().getNode().runWhenAttached( // This structure is needed to make the execution order work // with Element.callFunction() which is used in callJsMethod() ui -> ui.getInternals().getStateTree().beforeClientResponse( canvas.getElement().getNode(), context -> ui.getPage().executeJavaScript(script, canvas.getElement()))); } protected void callJsMethod(String methodName, Serializable... parameters) { canvas.getElement().callFunction("getContext('2d')." + methodName, parameters); } } BoundingBox.java: package com.vaadin.starter.beveragebuddy.ui.components; public class BoundingBox { public double xcoordi = 0; public double ycoordi = 0; public double boxWidth = 0; public double boxHeight = 0; public int name; public BoundingBox(int name, double xcoordi, double ycoordi, double boxWidth, double boxHeight) { this.name = name; this.xcoordi = xcoordi; this.ycoordi = ycoordi; this.boxWidth = boxWidth; this.boxHeight = boxHeight; } public int getName() { return name; } public void setName(int name) { this.name = name; } public double getXcoordi() { return xcoordi; } public void setXcoordi(double xcoordi) { this.xcoordi = xcoordi; } public double getYcoordi() { return ycoordi; } public void setYcoordi(double ycoordi) { this.ycoordi = ycoordi; } public double getWidth() { return boxWidth; } public void setWidth(double endX, double xcoordi) { boxWidth = endX - xcoordi; } public double getHeight() { return boxHeight; } public void setHeight(double endY, double ycoordi) { boxHeight = endY - ycoordi; } #Override public String toString() { return "{" + "Box=" + name + ", X=" + xcoordi + ", Y=" + ycoordi + ", Width=" + boxWidth + ", Height=" + boxHeight + '}'; } } MousePosition.java: package com.vaadin.starter.beveragebuddy.ui.components; public class MousePosition { public double mouseX; public double mouseY; public MousePosition(double mouseX, double mouseY) { this.mouseX = mouseX; this.mouseY = mouseY; } public double getMouseX() { return mouseX; } public void setMouseX(double mouseX) { this.mouseX = mouseX; } public double getMouseY() { return mouseY; } public void setMouseY(double mouseY) { this.mouseY = mouseY; } #Override public String toString() { return "MousePosition{" + "mouseX=" + mouseX + ", mouseY=" + mouseY + '}'; } } Any help is much appreciated, thank you!
Alright, with the updated code it seems your problem is that you never update the label. You set the label text initially, but it will not be automatically updated when the array changes. What you can do is allow adding a listener to the canvas, and notify it when the value changes. In the simplest case, a listener can be just a Java Runnable (or Supplier if you want to pass a value). My example returns a Vaadin Registration, which can be used to remove the listener by calling registration.remove(). In Canvas.java private List<Runnable> mouseMoveListeners = new ArrayList<>(0); ... public Registration addMouseMoveListener(Runnable listener) { mouseMoveListeners.add(listener); return () -> mouseMoveListeners.remove(listener); } And in your current mousemove listener // Your current code in the mousemove listener MousePosition currentPos = new MousePosition(mouseX, mouseY); mousePosArray.add(0, currentPos); setMousePosArray(mousePosArray); System.out.println(mousePosArray.get(0)); // Add this to run all the listeners that have been added to the canvas mouseMoveListeners.forEach(Runnable::run); Then at the end of your MainLayout constructor, something like this canvas.addMouseMoveListener(() -> label.setValue("Coordinates: " + mousePosArray)); This is untested code, but an approach similar to this should work. The point being that you somehow must notify the MainLayout when the array has changed.
Make touchable bg of touchpad [LibGdx]
Here's my touchpad. What I want is this: when I touch background of touchpad (not knob), knob move to this position. Code, which I use for touchpad from github/libgdx/gdx/src/com/badlogic/gdx/scenes/scene2d/ui/Touchpad.java package com.liketurbo.funnyGame; /******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ import com.badlogic.gdx.scenes.scene2d.Event; import com.badlogic.gdx.scenes.scene2d.EventListener; import com.badlogic.gdx.scenes.scene2d.ui.Widget; import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.math.Circle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener.ChangeEvent; import com.badlogic.gdx.scenes.scene2d.utils.Drawable; import com.badlogic.gdx.utils.Pools; /** An on-screen joystick. The movement area of the joystick is circular, centered on the touchpad, and its size determined by the * smaller touchpad dimension. * <p> * The preferred size of the touchpad is determined by the background. * <p> * {#link ChangeEvent} is fired when the touchpad knob is moved. Cancelling the event will move the knob to where it was * previously. * #author Josh Street */ public class Touchpad extends Widget { private TouchpadStyle style; boolean touched; boolean resetOnTouchUp = true; private float deadzoneRadius; private final Circle knobBounds = new Circle(0, 0, 0); private final Circle touchBounds = new Circle(0, 0, 0); private final Circle deadzoneBounds = new Circle(0, 0, 0); private final Vector2 knobPosition = new Vector2(); private final Vector2 knobPercent = new Vector2(); /** #param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */ public Touchpad (float deadzoneRadius, Skin skin) { this(deadzoneRadius, skin.get(TouchpadStyle.class)); } /** #param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */ public Touchpad (float deadzoneRadius, Skin skin, String styleName) { this(deadzoneRadius, skin.get(styleName, TouchpadStyle.class)); } /** #param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */ public Touchpad (float deadzoneRadius, TouchpadStyle style) { if (deadzoneRadius < 0) throw new IllegalArgumentException("deadzoneRadius must be > 0"); this.deadzoneRadius = deadzoneRadius; knobPosition.set(getWidth() / 2f, getHeight() / 2f); setStyle(style); setSize(getPrefWidth(), getPrefHeight()); addListener(new InputListener() { #Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { if (touched) return false; touched = true; calculatePositionAndValue(x, y, false); return true; } #Override public void touchDragged (InputEvent event, float x, float y, int pointer) { calculatePositionAndValue(x, y, false); } #Override public void touchUp (InputEvent event, float x, float y, int pointer, int button) { touched = false; calculatePositionAndValue(x, y, resetOnTouchUp); } }); } void calculatePositionAndValue (float x, float y, boolean isTouchUp) { float oldPositionX = knobPosition.x; float oldPositionY = knobPosition.y; float oldPercentX = knobPercent.x; float oldPercentY = knobPercent.y; float centerX = knobBounds.x; float centerY = knobBounds.y; y = centerY; knobPosition.set(centerX, centerY); knobPercent.set(0f, 0f); if (!isTouchUp) { if (!deadzoneBounds.contains(x, y)) { knobPercent.set((x - centerX) / knobBounds.radius*3.5f, 0); float length = knobPercent.len(); if (length > 1) knobPercent.scl(1 / length); if (knobBounds.contains(x, y)) { knobPosition.set(x, y); } else { knobPosition.set(knobPercent).nor().scl(knobBounds.radius*3.5f).add(knobBounds.x, knobBounds.y); } } } if (oldPercentX != knobPercent.x || oldPercentY != knobPercent.y) { ChangeEvent changeEvent = Pools.obtain(ChangeEvent.class); if (fire(changeEvent)) { knobPercent.set(oldPercentX, oldPercentY); knobPosition.set(oldPositionX, oldPositionY); } Pools.free(changeEvent); } } public void setStyle (TouchpadStyle style) { if (style == null) throw new IllegalArgumentException("style cannot be null"); this.style = style; invalidateHierarchy(); } /** Returns the touchpad's style. Modifying the returned style may not have an effect until {#link #setStyle(TouchpadStyle)} is * called. */ public TouchpadStyle getStyle () { return style; } #Override public Actor hit (float x, float y, boolean touchable) { return touchBounds.contains(x, y) ? this : null; } #Override public void layout () { // Recalc pad and deadzone bounds float halfWidth = getWidth() / 2; float halfHeight = getHeight() / 2; float radius = Math.min(halfWidth, halfHeight); touchBounds.set(halfWidth, halfHeight, radius); if (style.knob != null) radius -= Math.max(style.knob.getMinWidth(), style.knob.getMinHeight()) / 2; knobBounds.set(halfWidth, halfHeight, radius); deadzoneBounds.set(halfWidth, halfHeight, deadzoneRadius); // Recalc pad values and knob position knobPosition.set(halfWidth, halfHeight); knobPercent.set(0, 0); } #Override public void draw (Batch batch, float parentAlpha) { validate(); Color c = getColor(); batch.setColor(c.r, c.g, c.b, c.a * parentAlpha); float x = getX(); float y = getY(); float w = getWidth(); float h = getHeight(); final Drawable bg = style.background; if (bg != null) bg.draw(batch, x, y, w, h); final Drawable knob = style.knob; if (knob != null) { x += knobPosition.x - (knob.getMinWidth()*2) / 2f; y += knobPosition.y - (knob.getMinHeight()*2) / 2f; knob.draw(batch, x, y, knob.getMinWidth()*2, knob.getMinHeight()*2); } } #Override public float getPrefWidth () { return style.background != null ? style.background.getMinWidth() : 0; } #Override public float getPrefHeight () { return style.background != null ? style.background.getMinHeight() : 0; } public boolean isTouched () { return touched; } public boolean getResetOnTouchUp () { return resetOnTouchUp; } /** #param reset Whether to reset the knob to the center on touch up. */ public void setResetOnTouchUp (boolean reset) { this.resetOnTouchUp = reset; } /** #param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */ public void setDeadzone (float deadzoneRadius) { if (deadzoneRadius < 0) throw new IllegalArgumentException("deadzoneRadius must be > 0"); this.deadzoneRadius = deadzoneRadius; invalidate(); } /** Returns the x-position of the knob relative to the center of the widget. The positive direction is right. */ public float getKnobX () { return knobPosition.x; } /** Returns the y-position of the knob relative to the center of the widget. The positive direction is up. */ public float getKnobY () { return knobPosition.y; } /** Returns the x-position of the knob as a percentage from the center of the touchpad to the edge of the circular movement * area. The positive direction is right. */ public float getKnobPercentX () { return knobPercent.x; } /** Returns the y-position of the knob as a percentage from the center of the touchpad to the edge of the circular movement * area. The positive direction is up. */ public float getKnobPercentY () { return knobPercent.y; } /** The style for a {#link Touchpad}. * #author Josh Street */ public static class TouchpadStyle { /** Stretched in both directions. Optional. */ public Drawable background; /** Optional. */ public Drawable knob; public TouchpadStyle () { } public TouchpadStyle (Drawable background, Drawable knob) { this.background = background; this.knob = knob; } public TouchpadStyle (TouchpadStyle style) { this.background = style.background; this.knob = style.knob; } } } Line addListener(new InputListener() {...} works only for knob, if I can figure out how make it work for touchpad's background too, it solved my problem. Does somebody know how solve this problem? Thank you in advance.
You shouldn't add listener to your touchpad. Just limit touch area of the screen where touchpad can be used. Here is code to activate only on right half of a screen: private Touchpad touchpad; private Vector2 screenPos = new Vector2(); // position where touch occurs private Vector2 localPos = new Vector2(); // position in local coordinates private InputEvent fakeTouchDownEvent = new InputEvent(); // fake touch for correct work private Vector2 stagePos; // position to fire touch event private void getDirection() { if (Gdx.input.justTouched()) { // Get the touch point in screen coordinates. screenPos.set(Gdx.input.getX(), Gdx.input.getY()); if (screenPos.x > Gdx.graphics.getWidth() / 2) { // right half of a screen // Convert the touch point into local coordinates, place the touchpad and show it. localPos.set(screenPos); localPos = touchpad.getParent().screenToLocalCoordinates(localPos); touchpad.setPosition(localPos.x - touchpad.getWidth() / 2, localPos.y - touchpad.getHeight() / 2); // Fire a touch down event to get the touchpad working. Vector2 stagePos = touchpad.getStage().screenToStageCoordinates(screenPos); fakeTouchDownEvent.setStageX(stagePos.x); fakeTouchDownEvent.setStageY(stagePos.y); touchpad.fire(fakeTouchDownEvent); } } }
PopUp Class Seems To Speed Up Animations
I have a class called PopUp, which is a JPanel that, when activated, expands to a given size and location based on the parameters given from the center and contracts the same way when clicked. For some reason, when the PopUp is expanding and contracting, the animations sharing the same JPanel speed up. I've witnessed this on the two programs I've used my PopUp class on. Here is what I believe the relevant code is: /** * The {#code PopUp} class is a JPanel that expands to the rectangle * created from the given x, y, width and height that expands from * the center of the rectangle. * <p> * Here is an example of how the {#code PopUp} object can be initialized: * <blockquote><pre> * PopUp pu = new PopUp(25, 25, 575, 575, 25, Color.GRAY); * </pre></blockquote> * <p> * The class {#code PopUp} includes methods for drawing the pop-up; * choosing whether the pop-up is expanding or not; getting the * percentage that the pop-up is expanded; and getting the maximum x, y, * width, and height * * #author Gigi Bayte 2 */ public class PopUp extends JPanel implements MouseListener { private static final long serialVersionUID = 1L; /** * Expanded x coordinate */ private double x; /** * Expanded y coordinate */ private double y; /** * Expanded width value */ private double width; /** * Expanded height value */ private double height; /** * Number of steps until fully expanded */ private int steps; /** * This divided by steps is the percentage the pop-up is expanded */ private int expansionStage = 0; /** * Whether or not the pop-up is expansing */ private boolean isExpanding = false; /** * Color of the pop-up */ private Color color; /** * The rectangle that represents the bounds of the pop-up */ private Rectangle2D popUp; /** * Initializes a newly created {#code PopUp} with a uniform color * #param x The x coordinate of the expanded pop-up * #param y The y coordinate of the expanded pop-up * #param w The width of the expanded pop-up * #param h The height of the expanded pop-up * #param expansionSteps The number of steps until fully expanded * #param popUpColor The color of the pop-up */ public PopUp(double x, double y, double w, double h, int expansionSteps, Color popUpColor) { this.x = x; this.y = y; width = w; height = h; color = popUpColor; steps = expansionSteps; popUp = new Rectangle2D.Double(0, 0, width, height); addMouseListener(this); } /** * Draws the pop-up * #param g Graphics object from paintComponent */ public final void draw(Graphics g) { if(isExpanding) expansionStage = Math.min(expansionStage + 1, steps); else expansionStage = Math.max(expansionStage - 1, 0); Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); AffineTransform trans = new AffineTransform(); trans.translate(x + width/2 * (1 - (double) expansionStage/steps), y + height/2 * (1 - (double) expansionStage/steps)); trans.scale((double) expansionStage/steps, (double) expansionStage/steps); setBounds((int) trans.getTranslateX(), (int) trans.getTranslateY(), (int) (width * expansionStage/steps), (int) (height * expansionStage/steps)); g2d.setColor(color); Shape transformed = trans.createTransformedShape(popUp); g2d.fill(transformed); } /** * Sets whether the pop-up is expanding or not * #param expanding Whether or not the pop-up should expand */ public final void setExpanding(boolean expanding) { isExpanding = expanding; } #Override public final void mouseClicked(MouseEvent e) { isExpanding = false; } } Here is a test class to run: public class Test extends JPanel implements ActionListener, MouseListener { private static final long serialVersionUID = 1L; private static PopUp popUp; private int stringX = 610; private int stringCounter = 0; public static void main(String[] args) { JFrame frame = new JFrame(); frame.setSize(600, 600); Test t = new Test(); t.setBounds(0, 0, 600, 600); frame.add(t); t.setVisible(true); Timer timer = new Timer(5, t); popUp = new PopUp(100, 100, 400, 400, 100, Color.WHITE); frame.add(popUp); popUp.setVisible(true); timer.start(); frame.addMouseListener(t); frame.setLayout(null); frame.setUndecorated(true); frame.setVisible(true); } #Override public void actionPerformed(ActionEvent e) { repaint(); } #Override public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.BLACK); g.fillRect(0, 0, 600, 600); popUp.draw(g); g.setColor(Color.WHITE); g.drawString("This is a test", stringX, 580); if(++stringCounter % 3 == 0) { --stringX; stringCounter = 0; } if(stringX == -10 - g.getFontMetrics().stringWidth("This is a test")) stringX = 610; } #Override public void mouseClicked(MouseEvent e) { popUp.setExpanding(!popUp.getExpanding()); } #Override public void mousePressed(MouseEvent e) {} #Override public void mouseReleased(MouseEvent e) {} #Override public void mouseEntered(MouseEvent e) {} #Override public void mouseExited(MouseEvent e) {} } As can be seen in the above example, the text scrolling from right to left speeds up every time the pop-up is expanding or contracting.
That is the expected behavior when receiving repeated update events that invoke paintComponent(); resize this AnimationTest to reproduce the effect. Why exactly do the repeated update events cause this? What's the logic behind it? Each call to setBounds() in your draw() method "invalidates the component hierarchy." The Component API ensures that When the hierarchy gets invalidated, like after changing the bounds of components, or adding/removing components to/from containers, the whole hierarchy must be validated afterwards by means of the Container.validate() method invoked on the top-most invalid container of the hierarchy. Because the validate() method "may be a quite time-consuming operation," you can "postpone the validation of the hierarchy till a set of layout-related operations completes," as you show here; or you can pace the animation with a javax.swing.Timer, illustrated here.
Well, I found the problem. The culprit was the line here: setBounds((int) trans.getTranslateX(), (int) trans.getTranslateY(), (int) (width * expansionStage/steps), (int) (height * expansionStage/steps)); Apparently scaling a JPanel to different sizes in rapid succession causes some speed warping for reasons I do not know. I'd appreciate an edit to this answer with a better explanation for this phenomenon. I just set a static size for the JPanel and had the graphics do the rest of the work.
How to make a diff between 2 ArrayLists
I am making a webserver that generates checker-like tiles Images on java. The class that models the checker is: package app.app.image; import app.app.image.iterate.ImageElementVisitorInterface; import app.app.image.tiles.Tile; import java.awt.*; import java.util.ArrayList; import org.apache.commons.collections4.CollectionUtils; /** * Created by pcmagas on 5/10/2016. * * Methos that Implements a Checkerboard with multiple color tiles */ public class Checker implements ImageElement { /** * The tiles that can be used to Draw the checker */ private ArrayList<Tile> availableTiles=null; /** * The grid where the tiles are drawn */ private Tile[][] checkerGrid=null; /** * The checker width */ private int width=0; /** * The checker Height */ private int height=0; private int tileSize=0; /** * Creates a new Checker * #param width the checker's width * #param height the checker's height */ public Checker(int width, int height) { availableTiles= new ArrayList<Tile>(); this.setHeight(height); this.setWidth(width); } /** * #param height The checker's height */ public void setHeight(int height) { this.height=height; } /** * #return The checker's height */ public int getHeight() { return this.height; } /** * Seth the tile width * #param width */ public void setWidth(int width) { this.width=width; } /** * Returns how wide is the tile * #return */ public int getWidth() { return this.width; } /** * Method that Allows us to append a tile to the Checker */ public void addTile(Tile t) throws Exception { if(this.tileSize >0 && t.getSize()!= this.tileSize) { throw new Exception("The tile does not have the same size with the orthers"); } else if(!this.availableTiles.contains(t)) { this.availableTiles.add(t); this.tileSize=t.getSize(); } } /** * Method that initializes the grid for the checker */ private void initGrid() { if(this.checkerGrid==null) { int width = this.width/this.tileSize; int height = this.height/this.tileSize; this.checkerGrid=new Tile[height][width]; } } /** * Method that selects a tile during the grid generation * #param top The tile that is on top of the current tile * #param previous The tile that is on previously from the current tile * * #return The correctly selected tile */ private Tile selectTile(Tile top,Tile previous) { ArrayList<Tile> tilesNotToUse=new ArrayList<Tile>(); if(top==null) { tilesNotToUse.add(top); } if(previous==null) { tilesNotToUse.add(previous); } //TODO: Create a diff Between this.availableTiles and tilesNotToUse return new Tile(Color.black,10,this); } #Override public void draw(ImageElementVisitorInterface visitor) { this.initGrid(); for(int i=0;i<this.checkerGrid.length;i++) { for(int j=0;j<this.checkerGrid[i].length;j++) { Tile top=(i>0)?this.checkerGrid[i-1][j]:null; Tile previous=(j>0)?this.checkerGrid[i][j-1]:null; Tile currentTile=this.selectTile(top,previous); this.checkerGrid[i][j]= currentTile; currentTile.draw(visitor); } } } } Please I have some do-not-know-hot-to-do on this method: /** * Method that selects a tile during the grid generation * #param top The tile that is on top of the current tile * #param previous The tile that is on previously from the current tile * * #return The correctly selected tile */ private Tile selectTile(Tile top,Tile previous) { ArrayList<Tile> tilesNotToUse=new ArrayList<Tile>(); if(top==null) { tilesNotToUse.add(top); } if(previous==null) { tilesNotToUse.add(previous); } //TODO: Create a diff Between this.availableTiles and tilesNotToUse return new Tile(Color.black,10,this); } And I want to do a diff between this.availableTiles and tilesNotToUse. I have seen the CollectionUtils but I cannot figure out how to do it. I want to acheive similar result with php's http://php.net/manual/en/function.array-diff.php
You can use list1.removeAll(list2); Java Docs Thus your list1 will have only what belongs to list1 and not to list2. And the other way around if you want.
Image layering in Java
I'm new to the site, as well as to Java. I'm playing around in BlueJ for one of my programming classes and we created a drawing and a sunset using slowMoveVertical, but I can't get my sun to "set" behind the horizon... you continue to see it set above it. Is there a way to change the layering so I can get it to "set" behind the horizon? Here's the whole code for the "Picture" class. public class Picture { private Circle hill; private Square wall; private Square window; private Triangle roof; private Circle sun; /** * Constructor for objects of class Picture */ public Picture() { // nothing to do... instance variables are automatically set to null } /** * Draw this picture. */ public void draw() { wall = new Square(); wall.moveVertical(80); wall.changeSize(100); wall.makeVisible(); window = new Square(); window.changeColor("black"); window.moveHorizontal(20); window.moveVertical(100); window.makeVisible(); roof = new Triangle(); roof.changeSize(50, 140); roof.changeColor("blue"); roof.moveHorizontal(60); roof.moveVertical(70); roof.makeVisible(); sun = new Circle(); sun.changeColor("yellow"); sun.moveHorizontal(180); sun.moveVertical(-10); sun.changeSize(60); sun.makeVisible(); hill = new Circle(); hill.changeColor("green"); hill.moveHorizontal(-360); hill.moveVertical(160); hill.changeSize(1000); hill.makeVisible(); } /** * Change this picture to black/white display */ public void setBlackAndWhite() { if(wall != null) // only if it's painted already... { wall.changeColor("black"); window.changeColor("white"); roof.changeColor("black"); sun.changeColor("black"); hill.changeColor("black"); } } /** * Change this picture to use color display */ public void setColor() { if(wall != null) // only if it's painted already... { wall.changeColor("red"); window.changeColor("black"); roof.changeColor("blue"); sun.changeColor("yellow"); hill.changeColor("green"); } } /** * Change this picture to make the sun go down */ public void setSunset() { if(wall != null) // only if the sun is already up... { sun.slowMoveVertical(255); } And here is the code for class "Circle". public class Circle { private int diameter; private int xPosition; private int yPosition; private String color; private boolean isVisible; /** * Create a new circle at default position with default color. */ public Circle() { diameter = 30; xPosition = 20; yPosition = 60; color = "blue"; isVisible = false; } /** * Make this circle visible. If it was already visible, do nothing. */ public void makeVisible() { isVisible = true; draw(); } /** * Make this circle invisible. If it was already invisible, do nothing. */ public void makeInvisible() { erase(); isVisible = false; } /** * Move the circle a few pixels to the right. */ public void moveRight() { moveHorizontal(20); } /** * Move the circle a few pixels to the left. */ public void moveLeft() { moveHorizontal(-20); } /** * Move the circle a few pixels up. */ public void moveUp() { moveVertical(-20); } /** * Move the circle a few pixels down. */ public void moveDown() { moveVertical(20); } /** * Move the circle horizontally by 'distance' pixels. */ public void moveHorizontal(int distance) { erase(); xPosition += distance; draw(); } /** * Move the circle vertically by 'distance' pixels. */ public void moveVertical(int distance) { erase(); yPosition += distance; draw(); } /** * Slowly move the circle horizontally by 'distance' pixels. */ public void slowMoveHorizontal(int distance) { int delta; if(distance < 0) { delta = -1; distance = -distance; } else { delta = 1; } for(int i = 0; i < distance; i++) { xPosition += delta; draw(); } } /** * Slowly move the circle vertically by 'distance' pixels. */ public void slowMoveVertical(int distance) { int delta; if(distance < 0) { delta = -1; distance = -distance; } else { delta = 1; } for(int i = 0; i < distance; i++) { yPosition += delta; draw(); } } /** * Change the size to the new size (in pixels). Size must be >= 0. */ public void changeSize(int newDiameter) { erase(); diameter = newDiameter; draw(); } /** * Change the color. Valid colors are "red", "yellow", "blue", "green", * "magenta" and "black". */ public void changeColor(String newColor) { color = newColor; draw(); } /* * Draw the circle with current specifications on screen. */ private void draw() { if(isVisible) { Canvas canvas = Canvas.getCanvas(); canvas.draw(this, color, new Ellipse2D.Double(xPosition, yPosition, diameter, diameter)); canvas.wait(10); } } /* * Erase the circle on screen. */ private void erase() { if(isVisible) { Canvas canvas = Canvas.getCanvas(); canvas.erase(this); } }
The loop in slowMoveVertical pretty much locks you into drawing the sun in front. If your assignment allows you to bypass slowMoveVertical, then you can manually do the loop it does and render your sun and the hill yourself so the layering is right. for(int i = 0; i < 190; i++) { sun.moveHorizontal(1); hill.draw(); } edit... There's a wait() at the end of the draw call, which would really only be used on the last object in the "batch". Making another draw method with an optional wait call allows the caller to control it instead of the circle. example... public void draw() { draw(true); // retain current behavior { public void draw(boolean useWait) { if(isVisible) { Canvas canvas = Canvas.getCanvas(); canvas.draw(this, color, new Ellipse2D.Double(xPosition, yPosition, diameter, diameter)); if (useWait) { canvas.wait(10); } } } Now you can call sun.draw(false) and hill.draw(true) and it shouldn't flicker (or a at least a lot less).