I'm going to create a moving circle for my later project, and the circle will keep moving, and it interior color will change like color emitting , the changing color will from little circle to larger circle in 5 levels, so how to keep each color change to stay a while and I hope these code present with thread, so I create two thread for the purpose, one control circle moving, another control the circle's interior color emit
here is my code:
import java.awt.*;
import static java.awt.Color.black;
import static java.awt.Color.yellow;
import static java.awt.FlowLayout.RIGHT;
import java.awt.event.*;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import static java.lang.Math.abs;
import java.util.Random;
import javax.swing.*;
import java.util.concurrent.ExecutorService;
class thepane extends JPanel implements Runnable{
public float x,y,r;
public float speedx,speedy;
thepane(float lx,float ly,float lr, float sx,float sy){
loadspeed(sx,sy);
load(lx,ly,lr);
for(int i=0;i<5;i++)
fc[i]=new Color(nd.nextInt(255),nd.nextInt(255),nd.nextInt(255));
}
public void load(float lx,float ly,float lr){
x=lx;y=ly;r=lr;
}
public void loadspeed(float sx,float sy){
speedx=sx;speedy=sy;
}
public void xmoving(){
x+=speedx;
}
public void ymoving(){
y-=speedy;
}
public void touchbond(){
if(x>getWidth()-r||x<0)
speedx*=-1;
if(y>getHeight()-r||y<0)
speedy*=-1;
if(x>getWidth()-r)
x=getWidth()-r;
else if(x<0)
x=0;
if(y>getHeight()-r)
y=getHeight()-r;
else if(y<0)
y=0;
}
Random nd=new Random();
int colorcount=0;
int emitcount=0;
boolean emit=false;
Color[] fc=new Color[5];
Graphics2D comp2D ;
Thread athread;
#Override
public void paintComponent(Graphics comp) {
comp2D = (Graphics2D) comp;
//create rectangle background
comp2D.setColor(Color.BLACK);
comp2D.fillRect(0, 0, getWidth(), getHeight());
//set information text
comp2D.setFont( new Font("Arial", Font.BOLD, 12));
comp2D.setColor(Color.WHITE);
comp2D.drawString("Centre("+(x+r/2)+' '+(y+r/2)+"), xspeed: "+speedx+" yspeed: "+speedy, 10f,10f );
comp2D.drawString("panel width "+getWidth()+" panel height "+getHeight()+" circle radius "
+r, 10f, 22f);
}
//thread run()
#Override
public void run() {
x=100;y=100;
System.out.println("thread in pane start!!!! (current colorcount = "+colorcount+')');
while(true){
circleEmit(fc[colorcount%5]);
repaint();
sleeping(1);
// comp2D=(Graphics2D)this.getGraphics();
// colorEmit(comp2D);
}
}
//wait method
public void waiting(){
try{wait();}
catch(Exception e){}}
public void waiting2D(int time){
try{comp2D.wait(time);}
catch(Exception e){}
}
public void waiting(int time){
try{wait(time);}
catch(Exception e){}
}
//sleep method
public void sleeping(int n){
try{
Thread.sleep(n);
}catch(Exception f){
System.out.print(f);
}
}
Ellipse2D.Float[] e=new Ellipse2D.Float[5];
public void loade(){
float centrex=x+r/2,centrey=y+r/2;
e[0]= new Ellipse2D.Float(centrex-r/10, centrey-r/10, r/5, r/5);
e[1]= new Ellipse2D.Float(centrex-r/5, centrey-r/5, 2*r/5, 2*r/5);
e[2]= new Ellipse2D.Float(centrex-3*r/10, centrey-3*r/10, 3*r/5, 3*r/5);
e[3]= new Ellipse2D.Float(centrex-2*r/5, centrey-2*r/5, 4*r/5, 4*r/5);
e[4]= new Ellipse2D.Float(centrex-r/2, centrey-r/2, r, r);
}
public Color ff;
public synchronized void circleEmit(Color fc){
comp2D=(Graphics2D)this.getGraphics();
loade();
comp2D.setColor(fc);
comp2D.fill(e[emitcount%5]);
waiting(5);
emitcount++;
}
public synchronized void callnotify(){
this.notify();
}
//iterative way to generate color emit
public void colorEmit(Graphics2D comp2D){
//create circle
//set circle property
float centrex=x+r/2,centrey=y+r/2;//so x=centrex-r/2;y=centrey+r/2
Ellipse2D.Float e1 = new Ellipse2D.Float(centrex-r/10, centrey-r/10, r/5, r/5);
Ellipse2D.Float e2 = new Ellipse2D.Float(centrex-r/5, centrey-r/5, 2*r/5, 2*r/5);
Ellipse2D.Float e3 = new Ellipse2D.Float(centrex-3*r/10, centrey-3*r/10, 3*r/5, 3*r/5);
Ellipse2D.Float e4 = new Ellipse2D.Float(centrex-2*r/5, centrey-2*r/5, 4*r/5, 4*r/5);
Ellipse2D.Float e5 = new Ellipse2D.Float(centrex-r/2, centrey-r/2, r, r);
if(colorcount>=4)
emit(comp2D,fc[(colorcount-4)%5],e5);
waiting(1000);
if(colorcount>=3)
emit(comp2D,fc[(colorcount-3)%5],e4);
waiting(1000);
if(colorcount>=2)
emit(comp2D,fc[(colorcount-2)%5],e3);
waiting(1000);
if(colorcount>=1)
emit(comp2D,fc[(colorcount-1)%5],e2);
waiting(1000);
emit(comp2D,fc[colorcount%5],e1);
waiting(1000);
colorcount++;
}
private void emit(Graphics2D comp,Color thecolor,Ellipse2D.Float f){
comp.setColor(thecolor);
comp.fill(f);
}
}
//------------------------------------------------------------------------------------
//main class
public class drawpanel extends Thread implements ActionListener{
JFrame frame=new JFrame();
thepane panel;
JButton FlyingBalls=new JButton("balls"),exit=new JButton("Exit"),stop=new JButton("Stop");
JButton slow=new JButton("slow down"),resume=new JButton("resume");
Float x,y,r;
public void sleeping(int n){
try{
Thread.sleep(n);
}catch(Exception f){
System.out.print(f);
}
}
Thread newthread,pthread;
Thread[] five=new Thread[5];
drawpanel(){
frame.setTitle("FlyingBalls");
frame.setLocation(100, 100);
frame.setLayout(null);
//x,y,r,speedx,speedy
panel=new thepane(nd.nextInt(800),nd.nextInt(500),40,nd.nextFloat()*20+1,nd.nextFloat()*10+1);
panel.setSize(800,500);
frame.setSize(810,580);
frame.add(panel);
FlyingBalls.setSize(80,30);exit.setSize(70,30);stop.setSize(70,30);slow.setSize(140,30);
resume.setSize(100,30);
FlyingBalls.addActionListener(this);
exit.addActionListener(this);
stop.addActionListener(this);slow.addActionListener(this);resume.addActionListener(this);
frame.add(FlyingBalls);frame.add(exit); frame.add(stop);frame.add(slow);frame.add(resume);
FlyingBalls.setLocation(20,500);exit.setLocation(190, 500);stop.setLocation(110,500);
slow.setLocation(270,500);resume.setLocation(420,500);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//control moving ball
newthread=new Thread(this);
//control color change
for(int i=0;i<5;i++){
five[i]=new Thread(panel);
}
// newthread.start();
panel.colorcount++;
five[0].start();
panel.colorcount=2;
// five[1].start();
panel.waiting(5);
}
public static void main(String[] arg){
drawpanel apanel=new drawpanel();
}
int bw=800,bh=500;
void setp(){
x=panel.x;y=panel.y;
}
void touchbond(){
System.out.println("width:"+panel.getWidth()+"Height:"+panel.getHeight());
System.out.println("xposition:"+x+"yposition:"+y);
if(x+r>panel.getWidth()){
panel.speedx*=-1;
x=bw-r;
}
else if(x-r<0){
panel.speedx*=-1;
x=r;
}
if(y-r<0){
panel.speedy*=-1;
y=r;
}
else if(y+r>panel.getHeight()){
panel.speedy*=-1;
y=bh-r;
}
panel.x=x;panel.y=y;
}
int T=10;
Random nd=new Random();
#Override
public void run(){
r=panel.r;
panel.loadspeed(-6.33f,-3.4f);
while(true){
if(stopcount==0){//button control variable
panel.xmoving();panel.ymoving();
panel.touchbond();
sleeping(T);}
panel.loade();
// panel.callnotify();
// panel.colorEmit(panel.comp2D);
panel.repaint();
}
}
#Override
public void start(){
}
int count=0,stopcount=0;
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==exit){
System.exit(0);
}
if(e.getSource()==FlyingBalls){
//panel=new thepane();
}
if(e.getSource()==resume){
stopcount=0;T=10;
panel.emit=false;
}
if(e.getSource()==slow){
if(count%2==0)
T=500;
else
T=10;
count++;
}
if(e.getSource()==stop){
stopcount++;
panel.emit=true;
}
}
}
So, lots of theory to cover.
Firstly...
Animation is not easy, good animation is hard.
Swing is single threaded and is not thread safe
This means that you should not perform any long running or blocking operations within the context of the Event Dispatching Thread.
It also means that you shouldn't modify the UI or anything the UI relies on from outside the context of the Event Dispatching Thread
More threads != more work
More threads doesn't always mean you're going to get more done. In fact, in this scenario, it could really cause a huge number of issues, as you need the ability to reason out the state at a single point in time (when painting)
Animation Theory
Okay, animation is simply the illusion of change, how you accomplish that will come down to the problem you trying to solve.
For me, the best animations are time based animations, not linear.
A linear animation keeps updating from its start state till it reaches its end state, in a constant progression. These don't tend to scale well and can suffer issues on low performant systems.
A time based animation is one where the amount of time is defined and then, based on a anchor time (ie start time) and the state of the animation is updated based on the amount of time which is passed. This is a really simple way to achieve "frame dropping". You'd also be very surprised to find that in general terms, time based animations tend to look better across more platforms.
A time based animation is also more capable of generating "easement" effects, but that's getting way deeper then we need to go right now.
Okay, but what's this got to do with your problem? Well, actually, quite a bit.
The first thing we need is some kind of "main-loop" from which all the animation can be driven. Typically, I'd look to a good animation library, but failing that, a simple Swing Timer will do the basic good really well.
It generates its ticks in the Event Dispatching Thread, which makes it very useful for our needs. See How to Use Swing Timers for more details
So, we start with something like...
private Timer timer;
//...
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Update the state
repaint();
}
});
//...
timer.start();
This gives us our "main loop", from which we can update the state as needed and then trigger a repaint of the component.
For the purpose of this demonstration, I'm going to devise a self-contained unit of "animation duration", used to track the amount of time which has passed since the animation was started, this is personal choice, but it would allow me to drive a number of animations and it contains the logic to a single unit of work.
public class AnimationDuration {
private Duration duration;
private Instant startedAt;
public AnimationDuration(Duration duration) {
this.duration = duration;
}
public Duration getDuration() {
return duration;
}
public void start() {
startedAt = Instant.now();
}
public void stop() {
startedAt = null;
}
public boolean isRunning() {
return startedAt != null;
}
public float getProgress() {
Duration runningTime = Duration.between(startedAt, Instant.now());
if (runningTime.compareTo(duration) > 0) {
runningTime = duration;
}
long total = duration.toMillis();
float progress = runningTime.toMillis() / (float) total;
return progress;
}
}
This basically allows to trigger the animation to start running (anchor point in time) and then get the progress of the animation at any point in time. This provides a normalised concept from 0-1, so if we want to make it longer or shorter, all we do is adjust the duration and everything else is taken care of.
For your specific problem, I'd consider some kind of "time line" or "key frames", which defines that certain actions should occur at certain points of time along the time line.
Now, the following is a really simple concept, but it gets the job.
public interface KeyFrame {
public float getProgress();
}
public class TimeLine<K extends KeyFrame> {
private List<K> keyFrames;
public TimeLine() {
keyFrames = new ArrayList<>(25);
}
// Returns the key frames between the current progression
public K getKeyFrameAt(float progress) {
for (int index = 0; index < keyFrames.size(); index++) {
K keyFrame = keyFrames.get(index);
if (progress >= keyFrame.getProgress()) {
if (index + 1 < keyFrames.size()) {
K nextFrame = keyFrames.get(index + 1);
// But only if your between each other
if (progress < nextFrame.getProgress()) {
return keyFrame;
}
} else {
// Nothing after me :D
return keyFrame;
}
}
}
return null;
}
public void add(K keyFrame) {
keyFrames.add(keyFrame);
Collections.sort(keyFrames, new Comparator<KeyFrame>() {
#Override
public int compare(KeyFrame lhs, KeyFrame rhs) {
if (lhs.getProgress() > rhs.getProgress()) {
return 1;
} else if (lhs.getProgress() < rhs.getProgress()) {
return -1;
}
return 0;
}
});
}
}
This allows you to define certain KeyFrames along the timeline, based on a normalised concept of time and then provides the ability to get the KeyFrame based on the current progression through animation.
There are much more complex solutions you might consider, which would generate self contained events based on time progressions automatically, but I prefer been able to driver the animation itself independently, makes these types of things more flexible - add a JSlider and you can manipulate the progression manually ;)
The next thing we need is something to carry the properties for the circle KeyFrame ...
public class CirclePropertiesKeyFrame implements KeyFrame {
private float progress;
private double radius;
private Color color;
public CirclePropertiesKeyFrame(float progress, double radius, Color color) {
this.progress = progress;
this.radius = radius;
this.color = color;
}
#Override
public float getProgress() {
return progress;
}
public Color getColor() {
return color;
}
public double getRadius() {
return radius;
}
#Override
public String toString() {
return "KeyFrame progress = " + getProgress() + "; raidus= " + radius + "; color = " + color;
}
}
Now, we need to put it together...
public class TestPane extends JPanel {
private AnimationDuration timelineDuration;
private TimeLine<CirclePropertiesKeyFrame> timeLine;
private Timer timer;
private CirclePropertiesKeyFrame circleProperties;
public TestPane() {
timelineDuration = new AnimationDuration(Duration.ofSeconds(10));
timeLine = new TimeLine<>();
timeLine.add(new CirclePropertiesKeyFrame(0, 5, Color.CYAN));
timeLine.add(new CirclePropertiesKeyFrame(0.2f, 10, Color.BLUE));
timeLine.add(new CirclePropertiesKeyFrame(0.4f, 15, Color.GREEN));
timeLine.add(new CirclePropertiesKeyFrame(0.6f, 20, Color.YELLOW));
timeLine.add(new CirclePropertiesKeyFrame(0.8f, 25, Color.MAGENTA));
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timelineDuration.isRunning()) {
float progress = timelineDuration.getProgress();
if (progress >= 1.0) {
timelineDuration.stop();
}
CirclePropertiesKeyFrame keyFrame = timeLine.getKeyFrameAt(progress);
circleProperties = keyFrame;
}
repaint();
}
});
}
#Override
public void addNotify() {
super.addNotify();
timelineDuration.start();
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
timer.stop();
timelineDuration.stop();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (circleProperties != null) {
double radius = circleProperties.radius;
double xPos = (getWidth() / 2) - radius;
double yPos = (getHeight() / 2) - radius;
g2d.setColor(circleProperties.color);
g2d.fill(new Ellipse2D.Double(xPos, yPos, radius * 2, radius * 2));
}
g2d.dispose();
}
}
And then we end up with something like...
Now, this is a 10 second sequence, so every 2 seconds it will update. Try changing the duration of the AnimationDuration and see what happens.
Note This is a non-repeating animation (it doesn't loop). You could make it loop, but the calculation to do so becomes more complicate, as you need to consider by how much you're over the expected Duration and then apply that to the next cycle, so it looks smooth
But what about movement?
Well, actually, pretty much already answered that question. You would also place the movement code inside the Timers ActionListener, right before the repaint request. In fact, I might be tempted to create some kind of class that could take the current KeyFrame information and combine it with the location properties, this would then be used the paintComponent method to draw the circle.
I want to blend the animation states ...
Well, that's a much more difficult question, especially when it comes to colors.
The basic theory is, you need the two key frames which set either side of the current progression. You would then apply a "blending" algorithm to calculate the amount of change to be applied between the two key frames.
Not impossible, just a step more difficult
I'm simulating a compass where I need to display current angular position upon reception of angular data from another source(via network). But somehow the circle group is getting shifted upon applying transformation to Text node based upon angular position. This is a sample code
MainApp.java
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.stage.Screen;
import javafx.stage.Stage;
public class MainApp extends Application {
static BorderPane borderPane;
static Group clock;
static Group angStatus;
public static Circle clockCircle;
public static Line angHand;
public static Text angText;
public static Rotate angRotate;
public static Label angLabel;
public static void main(String[] args) {
borderPane = new BorderPane();
clock = new Group();
angStatus = new Group();
clockCircle = new Circle();
clockCircle.centerXProperty().bind(borderPane.layoutXProperty());
clockCircle.centerYProperty().bind(borderPane.layoutYProperty());
clockCircle.setRadius(360);
clockCircle.setFill(Color.RED);
angHand = new Line();
angHand.startXProperty().bind(clockCircle.centerXProperty());
angHand.startYProperty().bind(clockCircle.centerYProperty());
angText = new Text();
angRotate = new Rotate();
angRotate.pivotXProperty().bind(angText.xProperty());
angRotate.pivotYProperty().bind(angText.yProperty());
angText.getTransforms().addAll(angRotate);
angLabel = new Label();
angLabel.layoutXProperty().bind(borderPane.layoutXProperty());
angLabel.layoutYProperty().bind(borderPane.layoutYProperty());
clock.getChildren().addAll(clockCircle, angHand, angText);
angStatus.getChildren().addAll(angLabel);
borderPane.setCenter(clock);
borderPane.setBottom(angStatus);
DataReceiver objDataReceiver = new DataReceiver();
Thread dataRecvThread = new Thread(objDataReceiver, "DATARECVR");
dataRecvThread.start();
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("CLOCK");
primaryStage.setMaximized(true);
primaryStage.setScene(new Scene(borderPane, Screen.getPrimary().getVisualBounds().getWidth(), Screen.getPrimary().getVisualBounds().getHeight()));
primaryStage.show();
}
}
DataReceiver.java
import javafx.application.Platform;
import java.time.Instant;
import static java.lang.Thread.sleep;
public class DataReceiver implements Runnable {
public static int deg;
DataReceiver() {
deg = 0;
}
public void run() {
while(true) {
long startTime = Instant.now().toEpochMilli();
deg++;
while(deg >= 360)
deg -= 360;
while(deg < 0)
deg += 360;
long endTime = Instant.now().toEpochMilli();
Platform.runLater(() -> {
MainApp.angHand.endXProperty().bind(MainApp.clockCircle.radiusProperty().multiply(Math.sin(Math.toRadians(deg))));
MainApp.angHand.endYProperty().bind(MainApp.clockCircle.radiusProperty().multiply(-Math.cos(Math.toRadians(deg))));
MainApp.angText.xProperty().bind(MainApp.angHand.endXProperty().add(10 * Math.sin(Math.toRadians(deg))));
MainApp.angText.yProperty().bind(MainApp.angHand.endYProperty().subtract(10 * Math.cos(Math.toRadians(deg))));
MainApp.angText.setText(String.valueOf(deg));
MainApp.angRotate.setAngle(deg);
MainApp.angLabel.setText("Angle: " + deg);
});
try {
sleep(1000 - (endTime - startTime));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
I'm using jdk-13.0.1 and javafx-sdk-11.0.2
The problem here is the fact that Group itself is non-resizeable, but changes it's own size to fit the contents. Modifying the transformations of the text results in the bounds of the Group changing horizontally and/or vertically which in turn makes the parent layout reposition the group to keep it centered. Depending on your needs you may want wrap the clock in a parent not doing this (Pane). If you want to keep the center of the circle centered, you'll probably need to implement your own layout.
Notes:
I don't recommend doing this using a Thread. Instead do this using AnimationTimer or similar utilities provided by JavaFX.
Bindings are only necessary for dynamic updates. If you just want to set values, it's the wrong choice.
There is the remainder operator (%) which could deal with the logic of one of the while loops. Furthermore the second loop is not actually needed, since the value of deg is never decreased below 0.
I wouldn't recommend putting the logic using threads in the main method. The way you implement the logic could result in an exception, if Platform.runLater is called before the toolkit has been started. Better initialize this kind of logic from Application.start.
static should be avoided, if possible, since it makes controlling the flow of data more complicated. And what if you want to display 2 clocks for different timezones? There's no way of reusing the class, if it relies on static data in the way your classes do.
A binding like the following makes no sense: borderPane is the root of the scene and therefore will keep the position (0,0); furthermore angLabel is a descendant of the borderPane. I recommend not wrapping the label in a group any use the static BorderPane.alignment property to tell borderPane how to position the node.
angLabel.layoutXProperty().bind(borderPane.layoutXProperty());
angLabel.layoutYProperty().bind(borderPane.layoutYProperty());
The following example makes Clock a Control with a property of type LocalTime and calculates the positions of the children itself:
public class Clock extends Control {
private final ObjectProperty<LocalTime> time = new SimpleObjectProperty<>(this, "time", LocalTime.MIDNIGHT) {
#Override
public void set(LocalTime newValue) {
// make sure the value is non-null
super.set(newValue == null ? LocalTime.MIDNIGHT : newValue);
}
};
public ObjectProperty<LocalTime> timeProperty() {
return time;
}
public LocalTime getTime() {
return time.get();
}
public void setTime(LocalTime value) {
time.set(value);
}
#Override
protected Skin<?> createDefaultSkin() {
return new ClockSkin(this);
}
}
public class ClockSkin extends SkinBase<Clock> {
private static final double SPACE = 20;
private static final double FACTOR = 360d / 60;
private final Circle face;
private final Line secondsHand;
private final Rotate rotate;
private final Text secondsText;
public ClockSkin(Clock control) {
super(control);
face = new Circle(360, Color.RED);
// line straight up from center to circle border
secondsHand = new Line();
secondsHand.endXProperty().bind(face.centerXProperty());
secondsHand.endYProperty().bind(face.centerYProperty().subtract(face.getRadius()));
secondsHand.startXProperty().bind(face.centerXProperty());
secondsHand.startYProperty().bind(face.centerYProperty());
secondsText = new Text();
rotate = new Rotate();
rotate.pivotXProperty().bind(face.centerXProperty());
rotate.pivotYProperty().bind(face.centerYProperty());
secondsHand.getTransforms().add(rotate);
secondsText.getTransforms().add(rotate);
registerChangeListener(control.timeProperty(), (observable) -> {
LocalTime value = (LocalTime) observable.getValue();
update(value);
control.requestLayout();
});
getChildren().addAll(face, secondsHand, secondsText);
update(control.getTime());
}
protected void update(LocalTime time) {
int seconds = time.getSecond();
secondsText.setText(Integer.toString(seconds));
rotate.setAngle(seconds * FACTOR);
}
#Override
protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
// center face
face.setCenterX(contentX + SPACE + contentWidth / 2);
face.setCenterY(contentY + SPACE + contentHeight / 2);
// position text
secondsText.setX(contentX + SPACE + (contentWidth - secondsText.prefWidth(-1)) / 2);
secondsText.setY(face.getCenterY() - face.getRadius() - SPACE / 2);
}
#Override
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset,
double leftInset) {
return 2 * (SPACE + face.getRadius()) + leftInset + rightInset;
}
#Override
protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset,
double leftInset) {
return 2 * (SPACE + face.getRadius()) + topInset + bottomInset;
}
#Override
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset,
double leftInset) {
return computeMinWidth(height, topInset, rightInset, bottomInset, leftInset);
}
#Override
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset,
double leftInset) {
return computeMinHeight(width, topInset, rightInset, bottomInset, leftInset);
}
#Override
public void dispose() {
getChildren().clear();
super.dispose();
}
}
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane borderPane = new BorderPane();
Clock clock = new Clock();
clock.setTime(LocalTime.now());
Label angLabel = new Label();
BorderPane.setAlignment(angLabel, Pos.TOP_LEFT);
borderPane.setCenter(clock);
borderPane.setBottom(angLabel);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
LocalTime time = LocalTime.now();
clock.setTime(time);
angLabel.setText(formatter.format(time));
}
};
timer.start();
primaryStage.setTitle("CLOCK");
primaryStage.setMaximized(true);
primaryStage.setScene(new Scene(borderPane));
primaryStage.show();
}
Note that here the separation between the logic for updating the visuals and updating the data is done much more cleanly than in your code. In general you want to prevent access of other classes to internal logic, since this prevents outside interference that could possibly break your control.
This question already has an answer here:
how to draw a straight line in javafx that updates itself when the user moves the mouse?
(1 answer)
Closed 5 years ago.
I want to draw a straight line that updates itself, kinda like what you've in Microsoft Paint. Currently I can draw it without the visual feedback, setting the starting x and y on the first click and when user clicks the mouse button again it sets the end x and y, and adds it to root children.
I found this answer how to draw a straight line in javafx that updates itself when the user moves the mouse?, which is basically what I would like to do and essentially solves my problem by using canvas. Other user's in that thread have suggested or implicated that it would be even easier to achieve without canvas, but have not provided examples.
Since I can't comment that question: Would someone show me an example of this without canvas? According user comments, it should be even easier, but I can't figure it out.
I hacked together this, which works but I feel like there is a better way to do this:
private void registerMouseEventHandlers() {
final Toggle toggle = new Toggle();
final CustomLineMouseEventHandler lineMouseEventHandler = new CustomLineMouseEventHandler();
this.scene.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
toggle.clickCount++;
if (toggle.clickedTwice()) {
lineMouseEventHandler.setEndXAndYAndAddToSceneGroup(event.getX(), event.getY());
}
else {
lineMouseEventHandler.setCustomLine(new CustomLine());
lineMouseEventHandler.setStartXAndY(event.getX(), event.getY());
}
}
});
}
public class CustomLineMouseEventHandler {
private CustomLine customLine;
private List<CustomLine> customLines = new ArrayList<CustomLine>();
public void setCustomLine(CustomLine customLine) {
this.customLine = customLine;
this.customLine.setVisible(true);
}
public void setStartXAndY(double x, double y) {
this.customLine.setStartX(x);
this.customLine.setStartY(y);
LineManipulator.getGroup().getChildren().add(this.customLine);
}
public void updateLine(double x, double y) {
if (this.customLine != null) {
this.customLine.setEndX(x);
this.customLine.setEndY(y);
}
}
public void setEndXAndYAndAddToSceneGroup(double x, double y) {
this.customLine.setEndX(x);
this.customLine.setEndY(y);
this.customLines.add(customLine);
LineManipulator.getGroup().getChildren().remove(LineManipulator.getGroup().getChildren().size() - 1);
LineManipulator.getGroup().getChildren().add(this.customLine);
this.customLine = null;
}
}
You can easily implement a click-drag-release user input style for this: add a new line to a pane when the mouse is pressed, and update its endpoint when the mouse is dragged:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class Draw extends Application {
private Line currentLine ;
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
pane.setOnMousePressed(e -> {
currentLine = new Line(e.getX(), e.getY(), e.getX(), e.getY());
pane.getChildren().add(currentLine);
});
pane.setOnMouseDragged(e -> {
currentLine.setEndX(e.getX());
currentLine.setEndY(e.getY());
});
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you prefer a click-move-click user experience to press-drag-release, replace the two event handlers with
pane.setOnMouseClicked(e -> {
if (currentLine == null) {
currentLine = new Line(e.getX(), e.getY(), e.getX(), e.getY());
pane.getChildren().add(currentLine);
} else {
currentLine = null ;
}
});
pane.setOnMouseMoved(e -> {
if (currentLine != null) {
currentLine.setEndX(e.getX());
currentLine.setEndY(e.getY());
}
});
I am fairly new to programming and have recently encountered a problem that I have not found a solution for online. I have created an ImageView in FXML and gave it an FXid of "gif1". I am using code from this stackoverflow post but I tried modifying it so that it would fit my needs. I put the entirety of the code in a separate java file that I called "Gif.java". Typically, "Gif.java" uses an hbox and other non applicable methods to set the ImageView. I removed those because I will be using an existing FXML document so there is no need to create a new one. In my code, I call on "Gif.java" and expect a returned ImageView. However, when I set my ImageView (gif1) to the returned ImageView, it doesn't update on the screen.
Here is the link to the gif post: How I can stop an animated GIF in JavaFX?
Here is my code that I use to update gif1:
#FXML
private void setUp(){
Gif newGif = new Gif("rightrock.gif"); // Creates object newGif and passes through path to gif
gif1 = newGif.returnImage2(); // Sets gif1 to returned ImageView
}
Here is the return code in Gif.java:
public class Gif{
private Animation ani;
private ImageView test;
public Gif(String image) {
// TODO: provide gif file, ie exchange banana.gif with your file
ani = new AnimatedGif(getClass().getResource(image).toExternalForm(), 1000);
ani.setCycleCount(1);
ani.play();
Button btPause = new Button( "Pause");
btPause.setOnAction( e -> ani.pause());
Button btResume = new Button( "Resume");
btResume.setOnAction( e -> ani.play());
}
public void returnImage(ImageView x){
test = x; // Sets ImageView test to parameter x
}
public ImageView returnImage2() {
return test; // Return instance field test
}
public static void main(String[] args) {
launch(args);
}
public class AnimatedGif extends Animation {
public AnimatedGif( String filename, double durationMs) {
GifDecoder d = new GifDecoder();
d.read( filename);
Image[] sequence = new Image[ d.getFrameCount()];
for( int i=0; i < d.getFrameCount(); i++) {
WritableImage wimg = null;
BufferedImage bimg = d.getFrame(i);
sequence[i] = SwingFXUtils.toFXImage( bimg, wimg);
}
super.init( sequence, durationMs);
}
}
public class Animation extends Transition {
private ImageView imageView;
private int count;
private int lastIndex;
private Image[] sequence;
private Animation() {
}
public Animation( Image[] sequence, double durationMs) {
init( sequence, durationMs);
}
private void init( Image[] sequence, double durationMs) {
this.imageView = new ImageView(sequence[0]);
this.sequence = sequence;
this.count = sequence.length;
setCycleCount(1);
setCycleDuration(Duration.millis(durationMs));
setInterpolator(Interpolator.LINEAR);
}
protected void interpolate(double k) {
final int index = Math.min((int) Math.floor(k * count), count - 1);
if (index != lastIndex) {
imageView.setImage(sequence[index]);
lastIndex = index;
}
}
public ImageView getView() {
returnImage(imageView); // This runs returnImage with the ImageView that I want to set
return imageView;
}
}
Any help would be greatly appreciated!
I'm experimenting with some Piccolo to create a zoomable interface.
I'm creating a rectangle on a canvas with some PText on it. Now when zooming, I want to change the text to something different.
I've done this in my initialize:
//
//specify the current Piccolo PCanvas
//
m_canvas = getCanvas();
m_canvas.removeInputEventListener(m_canvas.getPanEventHandler());
//m_canvas.addInputEventListener(new ClickAndDragHandler(m_canvas));
//
//add nodes to the collection -> adding to the collection = adding to the canvas
//
m_nodecollection = new NodeCollection(m_canvas);
RectangleNode node_links = new RectangleNode();
node_links.setBounds(10, 10, 500, 500);
m_nodecollection.addNode(node_links);
RectangleNode node_rechts = new RectangleNode();
node_rechts.setBounds(600,10,500,500);
m_nodecollection.addNode(node_rechts);
PImage node_arrowLeft = new PImage("left.gif");
node_arrowLeft.setBounds(600, 550, node_arrowLeft.getWidth(), node_arrowLeft.getHeight());
m_nodecollection.addNode(node_arrowLeft);
PImage node_arrowRight = new PImage("right.gif");
node_arrowRight.setBounds(680, 550, node_arrowRight.getWidth(), node_arrowRight.getHeight());
m_nodecollection.addNode(node_arrowRight);
m_nodecollection.connectNodesWithLine(node_rechts, node_arrowRight, true);
m_nodecollection.connectNodesWithLine(node_rechts, node_arrowLeft, true);
PText node_text = new PText("Zoomlevel Not Supported");
node_text.setBounds(180,150, node_text.getWidth(), node_text.getHeight());
m_nodecollection.connectNodesWithLine(node_links, node_text, true);
m_nodecollection.addNode(node_text);
node_links.addChild(node_text);
node_links.setCollection(m_nodecollection);
Created my own rectangle class with the whole nodecollection and PText as membervar.
public class RectangleNode extends PNode{
private Rectangle2D m_rectangle;
private NodeCollection collection;
private PText text;
public RectangleNode()
{
m_rectangle = new Rectangle2D.Double();
}
public Rectangle2D getRectangle()
{
if(m_rectangle == null)
m_rectangle = new Rectangle2D.Double();
return m_rectangle;
}
public boolean setBounds(double x, double y, double w, double h)
{
if(super.setBounds(x, y, w, h))
{
m_rectangle.setFrame(x, y, w, h);
return true;
}
return false;
}
public void setCollection(NodeCollection collection)
{
this.collection = collection;
}
public void setText(PText text)
{
this.text = text;
}
public void paint(PPaintContext paintcontext)
{
Graphics2D g2 = paintcontext.getGraphics();
//niet ingezoomd
if(paintcontext.getScale() <= 0.2)
g2.setPaint(Color.BLACK);
//half ingezoomd
if(paintcontext.getScale() > 0.2 && paintcontext.getScale() < 0.7)
{
g2.setPaint(Color.BLACK);
}
//volledig ingezoomd
if(paintcontext.getScale() > 0.7)
{
g2.setPaint(Color.RED);
g2.fill(getRectangle());
g2.setPaint(Color.BLACK);
g2.draw(getRectangle());
}
}
}
Now, I thought I could change the text like this: text.setText("Tester"); but It doesn't work, also when for example settext and then add the node to collection then it displays over the current text with a huge error...
Can someone help me please?
kind regards,
Consider posting the whole example as SSCCE, looks like some parts of the code are missing. It is not clear how you actually execute setText.
It may be easier to compose existing nodes and listen to events fired from camera. Consider the following example that draws a rectangle with some text which gets updated according to a zoom level:
import java.awt.Color;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.nodes.PText;
import edu.umd.cs.piccolo.util.PPaintContext;
import edu.umd.cs.piccolox.PFrame;
public class TestRectZoom extends PFrame {
public TestRectZoom() {
super("TestRectZoom", false, null);
}
public void initialize() {
getCanvas().setInteractingRenderQuality(
PPaintContext.HIGH_QUALITY_RENDERING);
getCanvas().setDefaultRenderQuality(
PPaintContext.HIGH_QUALITY_RENDERING);
getCanvas().setAnimatingRenderQuality(
PPaintContext.HIGH_QUALITY_RENDERING);
final PPath rect = PPath.createRectangle(100, 100, 200, 200);
rect.setPaint(Color.GREEN);
getCanvas().getLayer().addChild(rect);
final PText text = new PText(getZoomLevelString());
rect.addChild(text);
text.centerFullBoundsOnPoint(rect.getBounds().getCenterX(), rect
.getBounds().getCenterY());
getCanvas().getCamera().addPropertyChangeListener(
PCamera.PROPERTY_VIEW_TRANSFORM, new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent evt) {
text.setText(getZoomLevelString());
if (getCanvas().getCamera().getViewScale() > 0.9) {
rect.setPaint(Color.GREEN);
} else {
rect.setPaint(Color.RED);
}
}
});
}
private String getZoomLevelString() {
return "Zoom level:"
+ String.format("%.2f", getCanvas().getCamera().getViewScale());
}
public static void main(final String[] args) {
new TestRectZoom();
}
}
That's how the result looks like:
You are looking for semantic zooming available on the Piccolo Patterns page.
Pattern 17: Semantic Zooming
http://www.piccolo2d.org/learn/patterns.html
I solved the problem like Aqua suggested.