====== Zadanie dodatkowe - wątki i animacja w JavaFx ======
//Jest to zadanie dodatkowe za 10 punktów//
==== Opis ====
Celem jest realizacja przykładu z kulkami za pomocą biblioteki JavaFX.
1. Aplikacja ma składać się z dwóch komponentów
*Modelu zawierającego kulki
*Widoku - obiektu ''javafx.scene.layout.Pane'' wraz z elementami potomnymi
2. Model jest animowany przez wątek. Okresowo, np. co 10ms przesuwa kulki. Zapewniamy wzajemne wykluczanie za pomocą semafora lub umieszczając wszelkie operacje na kulkach w bloku synchronicznym ''synchronized(balls){}''
3. Widok jest animowany przez wbudowany watek JavaFX. Realizowane jest to za pomocą obiektu klasy ''AnimationTimer''. W metodzie, którą nazwiemy ''updateView()'':
* usunięte zostaną wszystkie węzły potomne widoku (czyli kulki z poprzedniej iteracji)
* zachowując wzajemne wykluczanie odczytane zostaną położenia i kolory kulek
* na ich podstawie zostaną utworzone węzły JavaFx w odpowiednim miejscu
==== Uwagi techniczne ====
*JavaFX nie jest częścią standardowej biblioteki. Musisz wygenerować projekt typu JavaFX korzystający z Mavena lub Gradle. Polecam Mavena.
*JavaFX może definiować układ komponentów w XML. Nie korzystamy z tego. Po wygenerowaniu wyrzuć plik *.fxml z zasobów oraz usuń ''HelloControler''
*Możesz zbudować układ GUI bezpośrednio w kodzie - wraz z menu, komponentem Toolbar, polem tekstowym pełniącym rolę komponentu statusbar, itp.
*Drzewo węzłów JavaFX maożna uaktualniać wyłącznie w wątku JavaFX. W przeciwnym przypadku generowane będą wyjątki.
*Aby uaktualnić pewne własności komponentów - wyłączane przyciski, uaktualnienie rozmiarów - musisz połączyć ze sobą własności ''property'', np.
child.widthProperty().bind(parent.widthProperty());
// gdzieś w kodzie
SimpleBooleanProperty isRunning = new SimpleBooleanProperty(false);
// gdzieś indziej
button3.disableProperty().bind(somewhereInTheCode.isRunning);
==== Przykład ====
{{ :pz1:rectangles.png?direct&400 |}}
=== Klasa pomocnicza do obliczania FPS ===
import java.util.ArrayDeque;
public class MovingAverage {
int windowSize;
ArrayDeque values;
public MovingAverage(int windowSize){
this.windowSize=windowSize;
values =new ArrayDeque<>(windowSize);
}
public void add(long value){
while(values.size()>=windowSize) values.poll();
values.offer(value);
}
public double getMean(){
if(values.isEmpty())return 0;
double mean = values.stream().mapToDouble(a->a).average().getAsDouble();
return mean;
}
}
=== Aplikacja z widokiem i wątkiem uaktualniającym widok ===
import java.util.Locale;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class RandomRectangles extends Application {
private final Random random = new Random();
static final int N = 100;
static final int SIZE=20;
Group group = new Group();
Pane root = new Pane();
MovingAverage mave = new MovingAverage(300);
void updateView(){
// usuń wszystkie węzły potomne grupy
group.getChildren().clear();
int w = (int)(root.getWidth()/SIZE);
int h = (int)(root.getHeight()/SIZE);
// wygeneruj nowe węzły na podstawie modelu
for(int i=0;i0)fps = 1/diff;
}
void addFPS(){
Text text = new Text();
text.setFont(new Font(40));
text.setFill(Color.WHITE);
text.setText(String.format(Locale.US,"%.2f FPS",fps));
text.setX(10);
text.setY(50);
group.getChildren().add(text);
}
@Override
public void handle(long now) {
if(lastPaint>0)mave.add(now-lastPaint);
lastPaint=now;
counter++;
updateView();
if(counter%50==0)updateFps();
addFPS();
}
}.start();
}
public static void main(String[] args) {
launch(args);
}
}