JavaFX Glitches animating non standard widget - java

I am trying to prove out an expandable widget with JavaFX that differs from a TitledPane in that the border surrounding the label grows with the widget as it expands. I have found a way of doing it.
However I have a weird glitch that shows up after expanding/collapsing once or twice.
This is a link to a short video where the problem appears at the third and fourth expansion:
Short Youtube Concept / Glitch Video
I've run out of things I could try to get it to behave.
The code is as follows, apologies for the wackiness of it, wanted to get it working before I refactor it.
class ExpansionManager {
enum LayoutState {
INITIALIZE,
ANIMATING,
IDLE,
REQUEST_ANIMATION
}
LayoutState layoutState = LayoutState.INITIALIZE;
Double fromWidth = 0.0;
Double fromHeight = 0.0;
Double stepWidth = 0.0;
Double stepHeight = 0.0;
Double toWidth = 0.0;
Double toHeight = 0.0;
}
public class ExpandableTitledList extends VBox {
private Label title = new Label();
private ListProperty<String> listItem = new SimpleListProperty<>();
private ListView listView = new ListView<>(listItem);
Timeline timeline;
WritableValue<Double> writableHeight = new WritableValue<Double>() {
#Override
public Double getValue() {
return expansionManager.stepHeight;
}
#Override
public void setValue(Double value) {
expansionManager.stepHeight = value;
requestLayout();
}
};
WritableValue<Double> writableWidth = new WritableValue<Double>() {
#Override
public Double getValue() {
return expansionManager.stepWidth;
}
#Override
public void setValue(Double value) {
expansionManager.stepWidth = value;
requestLayout();
}
};
private boolean expanded = false;
ExpansionManager expansionManager = new ExpansionManager();
// private Dimension2D contractedDimension;
// private Dimension2D expandedDimension;
public ExpandableTitledList() {
setTitle("boom");
// title.layout();
// System.out.println(title.getLayoutBounds().getWidth());
// set down right caret
listItem.setValue(FXCollections.observableArrayList("one", "two"));
Insets theInsets = new Insets(-3, -5, -3, -5);
Border theBorder = new Border(
new BorderStroke(
Color.BLACK,
BorderStrokeStyle.SOLID,
new CornerRadii(4),
new BorderWidths(2),
theInsets
)
);
// expandedDimension = new Dimension2D(200,200);
setBorder(theBorder);
getChildren().addAll(title);
title.setOnMouseClicked((event) -> {
System.out.println("mouse clicked");
if (this.expanded) contract();
else expand();
});
}
#Override
protected void layoutChildren() {
System.out.println(expansionManager.layoutState);
if (expansionManager.layoutState == ExpansionManager.LayoutState.INITIALIZE) {
super.layoutChildren();
expansionManager.layoutState = ExpansionManager.LayoutState.IDLE;
} else if (expansionManager.layoutState == ExpansionManager.LayoutState.ANIMATING) {
super.layoutChildren();
} else if (expansionManager.layoutState == ExpansionManager.LayoutState.REQUEST_ANIMATION) {
setCache(false);
listView.setCache(false);
expansionManager.layoutState = ExpansionManager.LayoutState.ANIMATING;
System.out.println("from : " + expansionManager.fromWidth + ", "+ expansionManager.fromHeight);
System.out.println("to : " + expansionManager.toWidth + ", "+ expansionManager.toHeight);
timeline = new Timeline();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO,
new KeyValue(writableHeight, expansionManager.fromHeight),
new KeyValue(writableWidth, expansionManager.fromWidth)),
new KeyFrame(Duration.millis(100),
new KeyValue(writableHeight, expansionManager.toHeight),
new KeyValue(writableWidth, expansionManager.toWidth))
);
timeline.play();
timeline.setOnFinished((done) -> {
System.out.println("done");
expansionManager.layoutState = ExpansionManager.LayoutState.IDLE;
timeline = null;
});
} else {
System.out.println("idle");
super.layoutChildren();
}
}
#Override
protected double computePrefHeight(double width) {
if (expansionManager.layoutState == ExpansionManager.LayoutState.INITIALIZE) {
expansionManager.fromHeight = super.computePrefHeight(width);
return expansionManager.fromHeight;
} else if (expansionManager.layoutState == ExpansionManager.LayoutState.ANIMATING) {
return expansionManager.stepHeight;
} else if (expansionManager.layoutState == ExpansionManager.LayoutState.REQUEST_ANIMATION) {
expansionManager.fromHeight = getHeight();
expansionManager.stepHeight = expansionManager.fromHeight;
expansionManager.toHeight = super.computePrefHeight(width);
return expansionManager.fromHeight;
} else {
return expansionManager.toHeight;
}
}
#Override
protected double computePrefWidth(double height) {
if (expansionManager.layoutState == ExpansionManager.LayoutState.INITIALIZE) {
expansionManager.fromWidth = super.computePrefWidth(height);
return expansionManager.fromWidth;
} else if (expansionManager.layoutState == ExpansionManager.LayoutState.ANIMATING) {
return expansionManager.stepWidth;
} else if (expansionManager.layoutState == ExpansionManager.LayoutState.REQUEST_ANIMATION) {
expansionManager.fromWidth = getWidth();
expansionManager.stepWidth = expansionManager.fromWidth;
expansionManager.toWidth = super.computePrefWidth(height);
return expansionManager.fromWidth;
} else {
System.out.println("BANG BANG BANG");
return expansionManager.toWidth;
}
}
// #Override
// protected double computeMinWidth(double height) {
// return computePrefWidth(height);
// }
//
// #Override
// protected double computeMinHeight(double width) {
// return computePrefHeight(width);
// }
//
// #Override
// protected double computeMaxWidth(double height) {
// return computePrefWidth(height);
// }
//
// #Override
// protected double computeMaxHeight(double width) {
// return computePrefHeight(width);
// }
private void expand() {
System.out.println(expansionManager.layoutState);
// if(contractedDimension == null)
// contractedDimension = new Dimension2D(this.getWidth(), this.getHeight());
// setPrefSize(expandedDimension.getWidth(), expandedDimension.getHeight());
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
this.getChildren().setAll(title, listView);
expanded = true;
}
private void contract() {
// this.setPrefSize(contractedDimension.getWidth(), contractedDimension.getHeight());
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
this.getChildren().setAll(title);
expanded = false;
}
public String getTitle() {
return title.getText();
}
public void setTitle(String title) {
this.title.setText(title);
}
}

I could figure out that the ListView is the reason why there's a glitch.
Basically, except for the first time, everytime you add the list to the VBox you can see the list at its full size for a really brief instant, outside its container, and then when the timeline starts, it's properly resized.
In fact, you can add a delay (one second for instance) to the timeline:
timeline.setDelay(Duration.millis(1000));
and you'll see the problem for the whole second if you expand the box for the second time:
The list is visible outside the VBox, because it isn't resized to fit in it. When the animation starts, it's resized and the problem is gone.
I've tried several approaches to resize the list at that point, without success. Maybe you can solve it...
One ugly solution will be creating a new instance of the list every time you expand the box:
private void expand() {
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
// this works...
listView = new ListView<>(listItem);
this.getChildren().setAll(title,listView);
expanded = true;
}
Looking for other altenatives, I've bound the box disableProperty() to the timeline, so you can't click while the titled list is being expanded or contracted.
So another solution is binding the list visibleProperty() to the timeline, but you won't see the nice growing effect.
And there's a third solution, that will also be in line with the animation: set the opacity to 0 right before adding the list to the box:
private void expand() {
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
// this will avoid seeing the unresized listView
listView.setOpacity(0);
this.getChildren().setAll(title,listView);
expanded = true;
}
and add a new KeyValue increasing the list opacityProperty() from 0 to 1 to the timeline:
timeline.getKeyFrames().setAll(
new KeyFrame(Duration.ZERO,
new KeyValue(listView.opacityProperty(), 0),
new KeyValue(writableHeight, expansionManager.fromHeight.get()),
new KeyValue(writableWidth, expansionManager.fromWidth.get())),
new KeyFrame(Duration.millis(300),
new KeyValue(listView.opacityProperty(), 1),
new KeyValue(writableHeight, expansionManager.toHeight.get()),
new KeyValue(writableWidth, expansionManager.toWidth.get()))
);
Now you won't see the glitch, and the list will be showing up nicely while the box is resized. In fact I'll increase the duration of the second keyframe.
EDIT
I have another alternative to avoid the 'glitch'. Also it could improve the animation, since the list will be visible also when the titled list is contracted.
When you contract the titled list, first of all you remove the list, so super.computePrefHeight(width) and super.computePrefWidth(height) get the new size of the small box. This has the clear drawback that on the next expand, the list has to be added again, and the glitch happens.
In order to avoid this, we won't remove the list. First, we create two new fields on ExpansionManager:
Double minWidth = 0.0;
Double minHeight = 0.0;
Then we get the minimun size of the box (on the first expansion), and use it for every contraction:
#Override
protected double computePrefHeight(double width) {
...
if (expansionManager.layoutState == ExpansionManager.LayoutState.REQUEST_ANIMATION) {
if(expansionManager.minHeight==0d){
expansionManager.minHeight=getHeight();
}
expansionManager.fromHeight = getHeight();
expansionManager.stepHeight = expansionManager.fromHeight;
expansionManager.toHeight = expanded?super.computePrefHeight(width):
expansionManager.minHeight;
return expansionManager.fromHeight;
}
}
#Override
protected double computePrefWidth(double height) {
...
if (expansionManager.layoutState == ExpansionManager.LayoutState.REQUEST_ANIMATION) {
if(expansionManager.minWidth==0d){
expansionManager.minWidth=getWidth();
}
expansionManager.fromWidth = getWidth();
expansionManager.stepWidth = expansionManager.fromWidth;
expansionManager.toWidth = expanded?super.computePrefWidth(height):
expansionManager.minWidth;
return expansionManager.fromWidth;
}
}
Finally, we need to hide the list after any contraction, otherwise a small border will be seen, and change the expand() and contract() methods to call requestLayout(), given that the box children list is no longer modified (except on the first call):
#Override
protected void layoutChildren() {
timeline.setOnFinished((done) -> {
expansionManager.layoutState = ExpansionManager.LayoutState.IDLE;
listView.setVisible(expanded);
timeline = null;
});
}
private void expand() {
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
expanded = true;
listView.setVisible(true);
if(this.getChildren().size()==1){
this.getChildren().add(listView);
}
requestLayout();
}
private void contract() {
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
expanded = false;
requestLayout();
}

Related

Javafx Custom Pane Layout Children

I am trying to create a custom pane in javafx that holds a group of buttons.
The pane auto lays out the buttons so that they take up the maximum allowable width.
The pane works great, the problem I am having is that when the scene is set on the stage, the buttons lay out, but are off the screen to the bottom.
Any action I make on the stage (including clicking another windows and de-focusing the stage) causes them to lay out correctly.
I can't figure out what is going on.
I want to post a screenshot of what is happening, the problem is as mentioned when I go to take the screenshot, the window de-focuses and the bar then lays out correctly.
public class ButtonBar extends Pane {
//Add to auto generated buttons
protected String buttonPrefix = "F";
protected String buttonSuffix = "";
//Container sizing
protected DoubleProperty prefHeight = new SimpleDoubleProperty();
//Button Sizing
protected DoubleProperty buttonWidth = new SimpleDoubleProperty();
protected DoubleProperty buttonHeight = new SimpleDoubleProperty();
//Button Positioning
protected ArrayList<DoubleProperty> buttonXLocation;
//Static Button sizing
protected double interButtonMargin = 1;
protected double endButtonMargin = 10;
//Store whether initialized
protected boolean initialized = false;
//Listen for change to relayout
InvalidationListener layoutListener;
public int getNumberButtons() {
return numberButtons;
}
public void setNumberButtons(int numberButtons) {
this.numberButtons = numberButtons;
}
//Number of buttons
protected int numberButtons = 12;
protected boolean respectApsectRatio = true;
public ButtonBar() {
this(12);
}
public ButtonBar(List<Button> buttons) {
this.numberButtons = buttons.size();
getChildren().addAll(buttons);
}
public ButtonBar(int numButtons) {
this.numberButtons = numButtons;
for (int i = 0; i < numButtons; i++) {
Button button = new Button(String.valueOf(i + 1));
button.setStyle("-fx-background-radius: 7; -fx-font-size: 30px; -fx-background-color: #ffb366");
getChildren().add(button);
}
}
protected void init() {
buttonXLocation = new ArrayList<DoubleProperty>(numberButtons);
List<Node> children = getChildren();
//Calculate button width minus paddings
buttonWidth.bind(
widthProperty()
.subtract(interButtonMargin * (numberButtons - 1))
.subtract(endButtonMargin * 2)
.divide(numberButtons)
);
if (!respectApsectRatio) {
buttonHeight.bind(heightProperty());
prefHeight.bind(heightProperty());
} else {
buttonHeight.bind(buttonWidth);
prefHeight.bind(buttonHeight);
}
int i = 0;
for (Node n : getChildren()) {
Button b = (Button) n;
DoubleProperty buttonX = new SimpleDoubleProperty();
buttonX.bind(buttonWidth
.multiply(i)
.add(endButtonMargin)
.add(interButtonMargin * i)
);
buttonXLocation.add(i, buttonX);
//Set button text
b.setText(buttonPrefix + b.getText() + buttonSuffix);
i++;
}
setNeedsLayout(true);
this.initialized = true;
layoutListener = new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
setNeedsLayout(true);
layout();
layoutChildren();
}
};
widthProperty().addListener(layoutListener);
heightProperty().addListener(layoutListener);
}
#Override
protected void layoutChildren() {
//Only initialize once
if( !initialized ) init();
super.layoutChildren();
setPrefHeight(prefHeight.doubleValue());
List<Node> buttons = getChildren();
for (int i = 0; i < buttons.size(); i++) {
Button button = (Button) buttons.get(i);
button.setPrefWidth(buttonWidth.doubleValue());
button.setPrefHeight(buttonHeight.doubleValue());
button.setLayoutX(buttonXLocation.get(i).doubleValue());
button.setLayoutY(0);
}
super.layoutChildren();
}`

Why is my simple java2d Space Invaders game lagging?

I'm currently making a space invaders-esque game for my software engineering course. I've already got everything working that satisfies the requirements, so this isn't a 'solve my homework' kind of question. My problem is that the game will lag (at what seems like random times & intervals) to the point where it becomes too frustrating to play. Some things I think might be causing this - though I'm not positive - are as follows:
Problem with timer event every 10 ms (I doubt this because of the very limited resources required for this game).
Problem with collision detection (checking for collision with every visible enemy every 10 ms seems like it would take up a large chunk of resources)
Problem with repainting? This seems unlikely to me however...
#SuppressWarnings("serial")
public class SIpanel extends JPanel {
private SIpanel panel;
private Timer timer;
private int score, invaderPace, pulseRate, mysteryCount, distanceToEdge;
private ArrayList<SIthing> cast;
private ArrayList<SIinvader> invaders, dead;
private ArrayList<SImissile> missileBase, missileInvader;
private SIinvader[] bottomRow;
private SIbase base;
private Dimension panelDimension;
private SImystery mysteryShip;
private boolean gameOver, left, right, mysteryDirection, space, waveDirection;
private boolean runningTimer;
private Music sound;
private void pulse() {
pace();
processInputs();
if (gameOver) gameOver();
repaint();
}
private void pace() {
// IF invaders still live
if (!invaders.isEmpty()) {
invaderPace++;
// Switch back manager
if (distanceToEdge <= 10) {
switchBack();
pulseRate = (pulseRate >= 16) ? (int) (pulseRate*(0.8)) : pulseRate;
waveDirection = !waveDirection;
distanceToEdge = calculateDistanceToEdge();
}
// Move invaders left/right
else if (invaderPace >= pulseRate) {
invaderPace = 0;
distanceToEdge = calculateDistanceToEdge();
moveAI();
invadersFire();
if (!dead.isEmpty()) removeDead();
if (mysteryCount < 1) tryInitMysteryShip();
}
// All invaders are kill, create new wave
} else if (missileBase.isEmpty() && missileInvader.isEmpty() && !cast.contains(mysteryShip)) {
// System.out.println("New Wave!");
newWave();
}
// Every pace
if (!missileBase.isEmpty()) moveMissileBase();
// Every two paces
if (invaderPace % 2 == 0) {
if (!missileInvader.isEmpty()) moveMissileInvader();
if (mysteryCount > 0) moveMysteryShip();
}
}
private void processInputs() {
if (left) move(left);
if (right) move(!right);
if (space) fireMissile(base, true);
}
protected void fireMissile(SIship ship, boolean isBase) {
if(isBase && missileBase.isEmpty()) {
base.playSound();
SImissile m = new SImissile(ship.getX()+(ship.getWidth()/2), ship.getY()-(ship.getHeight()/4));
missileBase.add(m);
cast.add(m);
} else if (!isBase && missileInvader.size()<3) {
base.playSound();
SImissile m = new SImissile(ship.getX()+(ship.getWidth()/2), ship.getY()+(ship.getHeight()/4));
missileInvader.add(m);
cast.add(m);
}
}
private void newWave() {
pulseRate = 50;
int defaultY=60, defaultX=120, defaultWidth=30, defaultHeight=24;
for(int i=0; i<5; i++) {
for(int j=0; j<10; j++) {
if (i<1) invaders.add(new SItop((j*defaultWidth)+defaultX, (i*defaultHeight)+defaultY, defaultWidth, defaultHeight));
else if (i<3) invaders.add(new SImiddle((j*defaultWidth)+defaultX, (i*defaultHeight)+defaultY, defaultWidth, defaultHeight));
else if (i<5) invaders.add(new SIbottom((j*defaultWidth)+defaultX, (i*defaultHeight)+defaultY, defaultWidth, defaultHeight));
}
}
for (SIinvader s: invaders) {
cast.add(s);
}
if (!cast.contains(base)) {
cast.add(base);
}
bottomRow = getBottomRow();
}
private void tryInitMysteryShip() {
Random rand = new Random();
int x=rand.nextInt(1000);
if (x<=3) {
mysteryCount = 1;
if (rand.nextBoolean()) {
mysteryDirection = true;
}
if (mysteryDirection) {
mysteryShip = new SImystery(0, 60, 36, 18);
} else {
mysteryShip = new SImystery(480, 60, 36, 18);
}
cast.add(mysteryShip);
}
}
private void moveMysteryShip() {
int distance = 0;
if (mysteryDirection) {
mysteryShip.moveRight(5);
distance = getWidth() - mysteryShip.getX();
} else {
mysteryShip.moveLeft(5);
distance = 30+mysteryShip.getX()-mysteryShip.getWidth();
}
if (distance <= 5) {
dead.add(mysteryShip);
mysteryShip = null;
mysteryCount = 0;
}
}
private void removeDead() {
#SuppressWarnings("unchecked")
ArrayList<SIinvader> temp = (ArrayList<SIinvader>) dead.clone();
dead.clear();
for (SIinvader s : temp) {
invaders.remove(s);
cast.remove(s);
}
bottomRow = getBottomRow();
}
private void invadersFire() {
int[] p = new int[bottomRow.length];
for (int i=0; i<p.length; i++) {
for (int j=0; j<p.length; j++) {
p[j] = j;
}
Random rand = new Random();
int a=rand.nextInt(101);
if (a>=20) {
int b=rand.nextInt(p.length);
fireMissile(bottomRow[b], false);
}
}
}
private int calculateDistanceToEdge() {
int distance = 0;
SIinvader[] outliers = getOutliers();
if (waveDirection) {
distance = getWidth() - outliers[0].getX()-outliers[0].getWidth();
} else {
distance = outliers[1].getX();
}
return distance;
}
private SIinvader[] getOutliers() {
SIinvader leftMost = invaders.get(0), rightMost = invaders.get(0);
for (SIinvader s : invaders) {
if (s.getX() < leftMost.getX()) {
leftMost = s;
}
if (s.getX() > rightMost.getX()) {
rightMost = s;
}
}
return new SIinvader[] { rightMost, leftMost };
}
private SIinvader[] getBottomRow() {
SIinvader[] x = new SIinvader[(invaders.size()>10)?10:invaders.size()];
for (int i=0; i<x.length; i++) {
x[i] = invaders.get(i);
for (SIinvader s:invaders) {
if (s.getX() == x[i].getX()) {
if (s.getY() > x[i].getY()) {
x[i] = s;
}
}
}
}
return x;
}
private void move(boolean b) {
int defaultX = 5;
if (b) base.moveLeft(defaultX);
else base.moveRight(defaultX);
}
private void moveAI() {
for(SIinvader s : invaders) {
s.changeImage();
int defaultX = 5;
if (waveDirection) s.moveRight(defaultX);
else s.moveLeft(defaultX);
}
}
private void moveMissileBase() {
if (invaders.isEmpty()) return;
int movement = -5, bound = 0;
SImissile missile = missileBase.get(0);
missile.moveDown(movement);
SIinvader lowestInvader = getLowestInvader();
if (missile.getY() < (lowestInvader.getY() + lowestInvader.getHeight())) {
for (SIinvader s:bottomRow) {
if (checkCollision(missile, s)) {
s.setHit();
dead.add(s);
cast.remove(missile);
missileBase.clear();
score += s.value;
return;
}
}
if (mysteryCount > 0) {
if (checkCollision(missile, mysteryShip)) {
mysteryShip.setHit();
dead.add(mysteryShip);
cast.remove(missile);
missileBase.clear();
score += mysteryShip.value;
return;
}
}
if (missile.getY() < bound) {
missileBase.remove(missile);
cast.remove(missile);
}
}
}
private SIinvader getLowestInvader() {
SIinvader lowest = bottomRow[0];
for (SIinvader invader : bottomRow) {
if (invader.getY() > lowest.getY()) {
lowest = invader;
}
}
return lowest;
}
private void moveMissileInvader() {
int movement = 5, bound = (int) panelDimension.getHeight();
for (SImissile missile : missileInvader) {
missile.moveDown(movement);
if(missile.getY() >= base.getY()) {
if (checkCollision(missile, base)) {
base.setHit();
gameOver = true;;
missileInvader.remove(missile);
cast.remove(missile);
return;
} else if (missile.getY() >= bound-25) {
missileInvader.remove(missile);
cast.remove(missile);
return;
}
}
}
}
private boolean checkCollision(SIthing missile, SIthing ship) {
Rectangle2D rect1 = new Rectangle2D.Double(
missile.getX(),
missile.getY(),
missile.getWidth(),
missile.getHeight()
);
Rectangle2D rect2 = new Rectangle2D.Double(
ship.getX(),
ship.getY(),
ship.getWidth(),
ship.getHeight()
);
return rect1.intersects(rect2);
}
private void switchBack() {
int defaultY = 12;
for (SIinvader s : invaders) {
if (s.getY() > getHeight()) {
gameOver = true;
return;
}
s.moveDown(defaultY);
}
}
private void gameOver() {
pause(true);
SI.setGameOverLabelVisibile(true);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.GREEN);
Font font = new Font("Arial", 0, 20);
setFont(font);
String score = "Score: "+this.score;
Rectangle2D rect = font.getStringBounds(score, g2.getFontRenderContext());
int screenWidth = 0;
try { screenWidth = (int) panelDimension.getWidth(); }
catch (NullPointerException e) {}
g2.setColor(Color.GREEN);
g2.drawString(score, (int) (screenWidth - (10 + rect.getWidth())), 20);
for(SIthing a:cast) {
a.paint(g);
}
}
public SIpanel() {
super();
setBackground(Color.BLACK);
cast = new ArrayList<SIthing>();
missileBase = new ArrayList<SImissile>();
score = invaderPace = mysteryCount = pulseRate = 0;
sound = new Music("AmbientMusic.wav");
panel = this;
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT : left = true; break;
case KeyEvent.VK_RIGHT : right = true; break;
case KeyEvent.VK_SPACE : space = true; break;
}
}
#Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT : left = false; break;
case KeyEvent.VK_RIGHT : right = false; break;
case KeyEvent.VK_SPACE : space = false; break;
}
}
});
setFocusable(true);
timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
pulse();
}
});
}
public void reset() {
SI.setGameOverLabelVisibile(false);
score = invaderPace = mysteryCount = 0;
pulseRate = 50;
cast = new ArrayList<SIthing>();
invaders = new ArrayList<SIinvader>();
dead = new ArrayList<SIinvader>();
missileBase = new ArrayList<SImissile>();
missileInvader = new ArrayList<SImissile>();
base = new SIbase(230, 370, 26, 20);
waveDirection = true;
gameOver = false;
sound.stop();
sound.loop();
panelDimension = SI.getFrameDimensions();
bottomRow = getBottomRow();
newWave();
timer.start();
runningTimer=true;
}
public SIpanel getPanel() {
return this.panel;
}
public void pause(boolean paused) {
if (paused) timer.stop();
else timer.start();
}
}
I believe that collision detection may be the reason for lagging and you should simply investigate it by trying to increase and decrease count of enemies or missiles drastically to see if that makes a difference.
Consider garbage collector your enemy. In your checkCollision method you are instantiating two (very simple) objects. It may not seem like a lot, but consider that your might be creating them for each collision check, and that at 60fps it adds up until it may reach critical mass when GC says "stop the world" and you see noticeable lag.
If that is the case, possible solution to that would be to not instantiate any objects in a method called so frequently. You may create Rectangle2D once, and then update its position, instead of creating a new one each time, so you will avoid unnecessary memory allocation.

JavaFX animation problems

I'm currently working on an application with animation in JavaFX. The application is used by human corrector, who is correcting computer-generated subtitles. In the animation there is a floating text. My problem is that the animation sometimes shutters. You can see the following image for demonstration:
This flaw occurs mainly after resizing. When the animation breaks, it never gets to the fully functioning state again.
I use the JFXpanel which is in inserted in Swing UI. I use it this way because I've created quite a lot of code in Swing and I didn't want to toss it all away. I don't use Swing for animation because I wasn't able to create an animation that is smooth enough.
Here is the animation-related code:
public class AnimationPanel extends JFXPanel {
public MyAnimationTimer animationTimer;
public EditObject editObject;
public Color colorHOST1;
public Color colorHOST2;
public Color colorGUEST1;
public Color colorGUEST2;
public Color colorUSER;
public Color colorSIGNING;
public Color basicColor = Color.WHITE;
public Color currentColor = Color.WHITE;
public AnimationPanel(EditObject editObject) {
super();
this.editObject = editObject;
Group group = new Group();
this.animationTimer = new MyAnimationTimer((List<MyText>)(List<?>)group.getChildren(), this);
final Scene scene = new Scene(group, 800, 600, Color.BLACK);
this.setScene(scene);
this.animationTimer.start();
/* // Update animation when component is resized
this.addComponentListener(new ComponentListener() {
#Override
public void componentResized(ComponentEvent e) {
animationTimer.updateAnimations();
}
#Override
public void componentMoved(ComponentEvent e) {
}
#Override
public void componentShown(ComponentEvent e) {
}
#Override
public void componentHidden(ComponentEvent e) {
}
});*/
}
public void setColors(Gui g) {
this.colorHOST1 = Color.rgb(g.colorHOST1.getRed(), g.colorHOST1.getGreen(), g.colorHOST1.getBlue(), g.colorHOST1.getAlpha()/255.0);
this.colorHOST2 = Color.rgb(g.colorHOST2.getRed(), g.colorHOST2.getGreen(), g.colorHOST2.getBlue(), g.colorHOST2.getAlpha()/255.0);
this.colorGUEST1 = Color.rgb(g.colorGUEST1.getRed(), g.colorGUEST1.getGreen(), g.colorGUEST1.getBlue(), g.colorGUEST1.getAlpha()/255.0);
this.colorGUEST2 = Color.rgb(g.colorGUEST2.getRed(), g.colorGUEST2.getGreen(), g.colorGUEST2.getBlue(), g.colorGUEST2.getAlpha()/255.0);
this.colorUSER = Color.rgb(g.colorUSER.getRed(), g.colorUSER.getGreen(), g.colorUSER.getBlue(), g.colorUSER.getAlpha()/255.0);
this.colorSIGNING = Color.rgb(g.colorSIGNING.getRed(), g.colorSIGNING.getGreen(), g.colorSIGNING.getBlue(), g.colorSIGNING.getAlpha()/255.0);
}
}
public class MyAnimationTimer extends AnimationTimer {
private List<MyText> nodes;
private long subtitle_max_time_in_app;
private AnimationPanel animationPanel;
private boolean stopAtTheEnd = false;
private boolean isAtTheEnd = false;
private int currentPos = 0;
public MyAnimationTimer(List<MyText> nodes, AnimationPanel animationPanel) {
super();
this.nodes = nodes;
this.animationPanel = animationPanel;
}
#Override
public void handle(long now) {
MyText node;
if(this.stopAtTheEnd) {
if(this.isAtTheEnd) {
for (int i = this.currentPos; i < this.nodes.size(); i += 2) {
node = nodes.get(i);
if(this.collides(nodes.get(i-2), node)) {
node.setTranslateXforTextandSubText(nodes.get(i-2).getBoundsInParent().getWidth() + nodes.get(i-2).getTranslateX() + 10);
this.currentPos+=2;
}
node.setTranslateXforTextandSubText(node.getTranslateX() - node.getVelocity());
}
} else {
if(nodes.size()!=0) {
node = nodes.get(0);
if((node.getTranslateX() - node.getVelocity()) < 0) {
node.setTranslateXforTextandSubText(0);
this.isAtTheEnd = true;
this.currentPos = 2;
} else {
for (int i = 0; i < this.nodes.size(); i += 2) {
node = nodes.get(i);
node.setTranslateXforTextandSubText(node.getTranslateX() - node.getVelocity());
}
}
}
}
} else {
for (int i = 0; i < this.nodes.size(); i += 2) {
node = nodes.get(i);
node.setTranslateXforTextandSubText(node.getTranslateX() - node.getVelocity());
}
}
}
private boolean collides(MyText node1, MyText node2) {
return (node1.getBoundsInParent().getWidth() + node1.getTranslateX() - node2.getTranslateX()) + 7 >= 0;
}
public void addNode(final MyText node) {
Platform.runLater(() -> {
node.setTranslateYforTextandSubText(animationPanel.getHeight() / 2);
node.setTranslateXforTextandSubText(animationPanel.getWidth());
node.setVelocity(this.getVelocity());
nodes.add(node);
nodes.add(node.id);
// Check for overlaying
if(nodes.size()>=4) {
int size = nodes.size();
double overlaying = (nodes.get(size-4).getBoundsInParent().getWidth() + nodes.get(size-4).getTranslateX() - nodes.get(size-2).getTranslateX()) + 7;
if(overlaying>0) {
nodes.get(size-2).setTranslateXforTextandSubText(nodes.get(size-2).getTranslateX()+overlaying);
}
}
});
}
public void recalculateGaps() {
Platform.runLater(() -> {
if (nodes.size() >= 4) {
double overlaying;
// System.out.println("Size: " + nodes.size());
for (int i = nodes.size() - 2; i > 0; i -= 2) {
overlaying = (nodes.get(i - 2).getBoundsInParent().getWidth() + nodes.get(i - 2).getTranslateX() - nodes.get(i).getTranslateX()) + 7;
if (overlaying > 0) {
nodes.get(i - 2).setTranslateXforTextandSubText(nodes.get(i - 2).getTranslateX() - overlaying);
}
}
}
});
}
public void removeNodesBehindTheScene() {
Platform.runLater(() -> {
MyText node;
for (int i=0; i<nodes.size(); i+=2) {
node = nodes.get(i);
if(node.getTranslateX() > 0) {
break;
} else {
if(!node.isOverdue()) {
animationPanel.editObject.setMessageToBeSendSoon(node);
}
nodes.remove(i);
nodes.remove(i);
i-=2;
}
}
});
}
/* public void updateAnimations() {
// This method is called when the window is resized.
for (int i=0; i<this.nodes.size(); i+=2) {
nodes.get(i).setTranslateYforTextandSubText(animationPanel.getHeight()/2);
}
this.setVelocity();
}*/
public double getVelocity() {
return (this.animationPanel.getWidth()/4)*3/((double) this.subtitle_max_time_in_app)*1000/60;
}
public void setSubtitle_max_time_in_app(long subtitle_max_time_in_app) {
this.subtitle_max_time_in_app = subtitle_max_time_in_app;
}
public void setStopAtTheEnd(boolean stopAtTheEnd) {
// Remove all overdue
if(stopAtTheEnd) {
Platform.runLater(() -> {
for (int i = 0; i < nodes.size(); i += 2) {
if (nodes.get(i).isOverdue()) {
nodes.remove(i);
// Remove ID number
nodes.remove(i);
i -= 2;
} else {
break;
}
}
});
this.isAtTheEnd = false;
this.currentPos = 0;
}
this.stopAtTheEnd = stopAtTheEnd;
}
public void removeUpToNode(MyText node) {
Platform.runLater(() -> {
if(nodes.contains(node)) {
for (int i = 0; i < nodes.size(); i += 2) {
if (nodes.get(i) == node) {
nodes.remove(i);
nodes.remove(i);
break;
}
else {
nodes.remove(i);
nodes.remove(i);
i-=2;
}
}
}
});
}
public void addNodesAtTheBeginning(List<MyText> nodes_list, double nodeposition) {
Platform.runLater(() -> {
MyText node;
double position;
for (int i = nodes_list.size() - 1; i >= 0; i--) {
node = nodes_list.get(i);
node.setTranslateYforTextandSubText(animationPanel.getHeight() / 2);
if(nodes.size()!=0) {
position = this.nodes.get(0).getTranslateX() - node.getBoundsInParent().getWidth() - 10;
} else {
position = animationPanel.getWidth();
}
if(i==(nodes_list.size() - 1)) {
double exactposition = nodeposition - node.getBoundsInParent().getWidth();
if(exactposition < position) {
node.setTranslateXforTextandSubText(exactposition);
} else {
node.setTranslateXforTextandSubText(position);
}
} else {
node.setTranslateXforTextandSubText(position);
}
node.setVelocity(this.getVelocity());
nodes.add(0, node.id);
nodes.add(0, node);
}
});
}
}
I've tested various versions of JavaFX(including the one packed in JDK9), but with no result. Thanks in advance
Finally I fixed the bug. The problem was that I was setting a property of an existing node from my own thread instead of JavaFX thread. Putting it in Platform.runLater method fixed it. I didn't notice the bug immediately because it didn't throw the illegal thread exception as it does when you try to add node. I should have red the documentation more thoroughly.
Thanks

Android:About AndEngine updateThread and uiThread

I recently explore on AndEngine. I write a simple test demo, in which I implement IOnSceneTouchListener and the scene registers a TimerHandler to change one ChangableText.
And I set the engine runOnUpdateThread true.
So the problem is: when I touched the scene a while, the activity paused and crashed. And the Logcat showed the same text as before:"org.anddev.andengine.util.pool.PoolUpdateHandler$1 was exhausted, with 1 item not yet recycled. Allocated 1 more."
If anyone can solve my problem, so thankful I will be!
PS: my code
public class TestActivity extends BaseGameActivity implements IOnSceneTouchListener, IOnMenuItemClickListener {
...
...
private TimerHandler mTimeUpdateHandler = new TimerHandler(1.f, true, new ITimerCallback() {
#Override
public void onTimePassed(TimerHandler arg0) {
runOnUpdateThread(new Runnable() {
#Override
public void run() {
if (mElapsedText != null && mAttempts > 0) {
mElapsedText.setText("Time: "
+ (ParticlyActivity.this.mEngine.getSecondsElapsedTotal() - mCurrentTotalSeconds),
false);
}
}
});
}
});
...
...
// #Override
public Engine onLoadEngine() {
this.mCamera = new BoundCamera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
final EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE,
new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), mCamera).setNeedsSound(true);
engineOptions.getTouchOptions().setRunOnUpdateThread(true);
this.mEngine = new Engine(engineOptions);
return this.mEngine;
}
public Scene onLoadScene(){
...
// Text
mElapsedText = new ChangeableText(20, 12, this.mFont, "Time:00.00");
mScene.getFirstChild().attachChild(mElapsedText);
mScene.registerUpdateHandler(mTimeUpdateHandler);
...
}
#Override
public boolean onSceneTouchEvent(final Scene pScene, final TouchEvent pSceneTouchEvent) {
if ((pSceneTouchEvent.isActionMove() || pSceneTouchEvent.isActionDown()) && mAttempts < MaxBullets) {
double angle = 0;
if ((pSceneTouchEvent.getX() - StartX) == 0) {
angle = 90;
} else {
angle = Math
.abs(Math.atan((StartY - pSceneTouchEvent.getY()) / (StartX - pSceneTouchEvent.getX())) / 3.14 * 180);
}
if (angle > 90) {
angle = 90;
} else if (angle < 0) {
angle = 0;
}
mGun.setRotation((float) -angle);
mGun.setStrength(pSceneTouchEvent.getX());
} else if (pSceneTouchEvent.isActionUp() && mAttempts < MaxBullets) {
...
}
}
return true;
}
}
TimerHandler:
private TimerHandler mTimeUpdateHandler = new TimerHandler(1.f, true, new ITimerCallback() {
#Override
public void onTimePassed(TimerHandler arg0) {
runOnUpdateThread(new Runnable() {
#Override
public void run() {
if (mElapsedText != null && mAttempts > 0) {
mElapsedText.setText("Time: " + (ParticlyActivity.this.mEngine.getSecondsElapsedTotal() - mCurrentTotalSeconds), false);
}
}
});
}
});
UIThread is the one responsible for drawing , UpdateThread gets executed every frame
if you need to change Text values , do that on the UpdateThread [just stay away from the UIThread in general]

Selection and hover overrides Cell background color in an SWT Table component

I'm using SWT (and Eclipse RCP) to render a table. My problem is that if I change the background of a cell (a ViewerCell in fact) I can see that it has the new color.
My problem is that if I select a row in my Table or if I hover over the row containing my cell in question then the selection/hover background overrides my cell color. How can I override this?
Problem solved with StyledCellLabelProvider. Tell me if you want to see some code.
Edit:
We use it do display validation errors so ignore the validation stuff here:
public class ValidationCellLabelProvider extends StyledCellLabelProvider {
private static final int DELAY = 200;
private static final int SHIFT_X = 5;
private static final int SHIFT_Y = 5;
private static final int DISPLAY = 5000;
private CellLabelProvider provider;
private String propertyName;
private final StyleRange[] styleRanges = new StyleRange[1];
/**
* Default constructor.
* #param provider provider
* #param propertyName propertyName
*/
public ValidationCellLabelProvider(CellLabelProvider provider, String propertyName) {
super(StyledCellLabelProvider.COLORS_ON_SELECTION);
this.provider = provider;
this.propertyName = propertyName;
this.setOwnerDrawEnabled(true);
}
#Override
public void initialize(ColumnViewer viewer, ViewerColumn column) {
super.initialize(viewer, column);
final StyleRange styleRange = new StyleRange();
styleRange.foreground = Display.getCurrent().getSystemColor(SWT.COLOR_WHITE);
styleRange.background = Display.getCurrent().getSystemColor(SWT.COLOR_RED);
styleRanges[0] = styleRange;
}
#Override
public void update(ViewerCell cell) {
provider.update(cell);
if (cell.getStyleRanges() == null) {
cell.setStyleRanges(styleRanges);
}
if (cell.getElement() instanceof IValidable) {
IValidable model = (IValidable) cell.getElement();
if (!ControllerRegistry.getCurrentViolations().getViolations(model.getModelId(), propertyName).isEmpty()) {
if (cell.getText().isEmpty()) {
cell.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_RED));
cell.setImage(FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_ERROR).getImage());
} else {
if (styleRanges[0].length < cell.getText().length()) {
styleRanges[0].length = cell.getText().length();
}
}
} else {
if (cell.getImage() != null) {
cell.setImage(null);
}
cell.setStyleRanges(null);
}
}
super.update(cell);
}
//mine
#Override
protected void paint(Event event, Object element) {
if (element instanceof IValidable) {
IValidable model = (IValidable) element;
if (!ControllerRegistry.getCurrentViolations().getViolations(model.getModelId(), propertyName).isEmpty()) {
int width = 1000;
int x = event.x;
int y = event.y;
int height = event.height - 1;
GC gc = event.gc;
Color oldBackground = gc.getBackground();
gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_DARK_RED));
gc.fillRectangle(x, y, width, height);
gc.setBackground(oldBackground);
}
}
super.paint(event, element);
}
//-----
#Override
public String getToolTipText(Object element) {
String ret = null;
if (element instanceof IValidable) {
List<ConstraintViolation> constraintViolations = ControllerRegistry.getCurrentViolations().getViolations(
((IValidable) element).getModelId(), propertyName);
if (!constraintViolations.isEmpty()) {
ret = ValidationHelper.getMessage(constraintViolations);
}
}
if (ret != null) {
ret = ret.length() > 0 ? ret.toString() : null;
}
return ret;
}
#Override
public int getToolTipDisplayDelayTime(Object object) {
return DELAY;
}
#Override
public Point getToolTipShift(Object object) {
return new Point(SHIFT_X, SHIFT_Y);
}
#Override
public int getToolTipTimeDisplayed(Object object) {
return DISPLAY;
}
}
The only option I see would be use an OwnerDrawLabelProvider and paint the whole cell yourself.
There is a way to prevent the table from drawing its selection background but the font color will still change to its selection color, so depending on your OS you might end up with white text on white background when a row is selected.

Categories