Jan Beneš
Jan Beneš

Reputation: 722

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:

enter image description here

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

Upvotes: 1

Views: 468

Answers (1)

Jan Beneš
Jan Beneš

Reputation: 722

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

Upvotes: 2

Related Questions