package eu.hansolo.enzo.vumeter.skin;
import eu.hansolo.enzo.common.Section;
import eu.hansolo.enzo.vumeter.VuMeter;
import javafx.animation.AnimationTimer;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.WeakEventHandler;
import javafx.geometry.Orientation;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
/**
* User: hansolo
* Date: 04.11.13
* Time: 10:30
*/
public class VuMeterSkin extends SkinBase<VuMeter> implements Skin<VuMeter> {
private static final double PREFERRED_WIDTH = 24;
private static final double PREFERRED_HEIGHT = 24;
private static final double MINIMUM_WIDTH = 16;
private static final double MINIMUM_HEIGHT = 16;
private static final double MAXIMUM_WIDTH = 1024;
private static final double MAXIMUM_HEIGHT = 1024;
public static final long PEAK_TIMEOUT = 1_500_000_000l;
private static boolean active;
private double size;
private ObservableList<Region> leds;
private HBox hBox;
private VBox vBox;
private DoubleProperty stepSize;
private int peakLedIndex;
private long lastTimerCall;
private AnimationTimer timer;
// ******************** Constructors **************************************
public VuMeterSkin(final VuMeter CONTROL) {
super(CONTROL);
active = false;
lastTimerCall = 0l;
stepSize = new SimpleDoubleProperty((getSkinnable().getMaxValue() - getSkinnable().getMinValue()) / getSkinnable().getNoOfLeds());
timer = new AnimationTimer() {
@Override public void handle(final long NOW) {
if (NOW > lastTimerCall + PEAK_TIMEOUT) {
leds.get(peakLedIndex).getStyleClass().remove("led-peak");
peakLedIndex = Orientation.HORIZONTAL == getSkinnable().getOrientation() ? 0 : leds.size() - 1;
timer.stop();
}
}
};
init();
initGraphics();
registerListeners();
}
// ******************** Initialization ************************************
private void init() {
if (Double.compare(getSkinnable().getPrefWidth(), 0.0) <= 0 || Double.compare(getSkinnable().getPrefHeight(), 0.0) <= 0 ||
Double.compare(getSkinnable().getWidth(), 0.0) <= 0 || Double.compare(getSkinnable().getHeight(), 0.0) <= 0) {
if (Orientation.HORIZONTAL == getSkinnable().getOrientation()) {
getSkinnable().setPrefSize(getSkinnable().getNoOfLeds() * PREFERRED_WIDTH + (getSkinnable().getNoOfLeds() - 1) * getSkinnable().getLedSpacing(), PREFERRED_HEIGHT);
} else {
getSkinnable().setPrefSize(PREFERRED_WIDTH, getSkinnable().getNoOfLeds() * PREFERRED_HEIGHT + (getSkinnable().getNoOfLeds() - 1) * getSkinnable().getLedSpacing());
}
}
if (Double.compare(getSkinnable().getMinWidth(), 0.0) <= 0 || Double.compare(getSkinnable().getMinHeight(), 0.0) <= 0) {
getSkinnable().setMinSize(MINIMUM_WIDTH, MINIMUM_HEIGHT);
}
if (Double.compare(getSkinnable().getMaxWidth(), 0.0) <= 0 || Double.compare(getSkinnable().getMaxHeight(), 0.0) <= 0) {
getSkinnable().setMaxSize(MAXIMUM_WIDTH, MAXIMUM_WIDTH);
}
}
private void initGraphics() {
hBox = new HBox();
hBox.getStyleClass().setAll("vu-meter");
hBox.setSpacing(getSkinnable().getLedSpacing());
vBox = new VBox();
vBox.getStyleClass().setAll("vu-meter");
vBox.setSpacing(getSkinnable().getLedSpacing());
leds = FXCollections.observableArrayList();
for (int i = 0 ; i < getSkinnable().getNoOfLeds() ; i++) {
Region led = new Region();
led.setOnMouseClicked(new WeakEventHandler<>(event -> active = !active));
led.setOnMouseEntered(new WeakEventHandler<>(event -> handleMouseEvent(event)));
if (getSkinnable().getSections().isEmpty()) {
led.getStyleClass().setAll("led");
} else {
for (Section section : getSkinnable().getSections()) {
if (section.contains(i * stepSize.doubleValue())) {
led.getStyleClass().setAll("led", section.getStyleClass());
}
}
}
leds.add(led);
}
if (Orientation.HORIZONTAL == getSkinnable().getOrientation()) {
vBox.setManaged(false);
vBox.setVisible(false);
hBox.getChildren().setAll(leds);
peakLedIndex = 0;
} else {
hBox.setManaged(false);
hBox.setVisible(false);
FXCollections.reverse(leds);
vBox.getChildren().setAll(leds);
peakLedIndex = leds.size() - 1;
}
// Add all nodes
getChildren().setAll(hBox, vBox);
}
private void registerListeners() {
getSkinnable().widthProperty().addListener(observable -> handleControlPropertyChanged("RESIZE") );
getSkinnable().heightProperty().addListener(observable -> handleControlPropertyChanged("RESIZE") );
getSkinnable().valueProperty().addListener(observable -> handleControlPropertyChanged("VALUE"));
getSkinnable().orientationProperty().addListener((ov, oldOrientation, newOrientation) -> {
if (Orientation.HORIZONTAL == oldOrientation && Orientation.VERTICAL == newOrientation) {
FXCollections.reverse(leds);
} else if (Orientation.VERTICAL == oldOrientation && Orientation.HORIZONTAL == newOrientation) {
FXCollections.reverse(leds);
}
handleControlPropertyChanged("ORIENTATION");
});
getSkinnable().noOfLedsProperty().addListener(observable -> handleControlPropertyChanged("NO_OF_LEDS"));
getSkinnable().getSections().addListener((ListChangeListener<Section>) change -> handleControlPropertyChanged("SECTIONS"));
}
// ******************** Methods *******************************************
protected void handleControlPropertyChanged(final String PROPERTY) {
if ("RESIZE".equals(PROPERTY)) {
resize();
} else if ("STYLE".equals(PROPERTY)) {
resize();
} else if ("VALUE".equals(PROPERTY)) {
int currentLedPeakIndex;
if (Orientation.HORIZONTAL == getSkinnable().getOrientation()) {
// HORIZONTAL
currentLedPeakIndex = 0;
for (int i = 0 ; i < leds.size() ; i++) {
leds.get(i).getStyleClass().remove("led-on");
if (Double.compare(i * stepSize.doubleValue(), getSkinnable().getValue()) <= 0) {
leds.get(i).getStyleClass().add("led-on");
currentLedPeakIndex = i;
}
}
if (getSkinnable().isPeakValueVisible()) {
if (currentLedPeakIndex > peakLedIndex) {
timer.stop();
leds.get(peakLedIndex).getStyleClass().remove("led-peak");
leds.get(currentLedPeakIndex).getStyleClass().add("led-peak");
peakLedIndex = currentLedPeakIndex;
lastTimerCall = System.nanoTime();
timer.start();
}
}
} else {
// VERTICAL
currentLedPeakIndex = leds.size() - 1;
for (int i = 0 ; i < leds.size() ; i++) {
if (i != peakLedIndex) leds.get(i).getStyleClass().remove("led-on");
if (Double.compare((leds.size() - i - 1) * stepSize.doubleValue(), getSkinnable().getValue()) <= 0) {
leds.get(i).getStyleClass().add("led-on");
} else {
currentLedPeakIndex = i;
}
}
if (getSkinnable().isPeakValueVisible()) {
if (currentLedPeakIndex < peakLedIndex) {
timer.stop();
leds.get(peakLedIndex).getStyleClass().remove("led-peak");
leds.get(currentLedPeakIndex).getStyleClass().add("led-peak");
peakLedIndex = currentLedPeakIndex;
lastTimerCall = System.nanoTime();
timer.start();
}
}
}
} else if ("ORIENTATION".equals(PROPERTY)) {
hBox.getChildren().clear();
vBox.getChildren().clear();
if (Orientation.HORIZONTAL == getSkinnable().getOrientation()) {
vBox.setManaged(false);
vBox.setVisible(false);
hBox.getChildren().setAll(leds);
} else {
hBox.setManaged(false);
hBox.setVisible(false);
vBox.getChildren().setAll(leds);
}
} else if ("NO_OF_LEDS".equals(PROPERTY)) {
leds.clear();
hBox.getChildren().clear();
vBox.getChildren().clear();
for (int i = 0 ; i < getSkinnable().getNoOfLeds() ; i++) {
Region led = new Region();
led.setPrefSize(10, 20);
if (getSkinnable().getSections().isEmpty()) {
led.getStyleClass().setAll("led");
} else {
for (Section section : getSkinnable().getSections()) {
if (section.contains(i * stepSize.doubleValue())) {
led.getStyleClass().setAll("led", section.getStyleClass());
}
}
}
leds.add(led);
}
if (Orientation.HORIZONTAL == getSkinnable().getOrientation()) {
hBox.getChildren().setAll(leds);
} else {
vBox.getChildren().setAll(leds);
}
} else if ("SECTIONS".equals(PROPERTY)) {
for (int i = 0 ; i < getSkinnable().getNoOfLeds() ; i++) {
Region led = new Region();
led.setPrefSize(10, 20);
if (getSkinnable().getSections().isEmpty()) {
led.getStyleClass().setAll("led");
} else {
for (Section section : getSkinnable().getSections()) {
if (section.contains(i * stepSize.doubleValue())) {
led.getStyleClass().setAll("led", section.getStyleClass());
}
}
}
leds.add(led);
}
}
}
@Override protected double computeMinWidth(final double HEIGHT, double TOP_INSET, double RIGHT_INSET, double BOTTOM_INSET, double LEFT_INSET) {
return super.computeMinWidth(Math.max(MINIMUM_HEIGHT, HEIGHT - TOP_INSET - BOTTOM_INSET), TOP_INSET, RIGHT_INSET, BOTTOM_INSET, LEFT_INSET);
}
@Override protected double computeMinHeight(final double WIDTH, double TOP_INSET, double RIGHT_INSET, double BOTTOM_INSET, double LEFT_INSET) {
return super.computeMinHeight(Math.max(MINIMUM_WIDTH, WIDTH - LEFT_INSET - RIGHT_INSET), TOP_INSET, RIGHT_INSET, BOTTOM_INSET, LEFT_INSET);
}
@Override protected double computeMaxWidth(final double HEIGHT, double TOP_INSET, double RIGHT_INSET, double BOTTOM_INSET, double LEFT_INSET) {
return super.computeMaxWidth(Math.min(MAXIMUM_HEIGHT, HEIGHT - TOP_INSET - BOTTOM_INSET), TOP_INSET, RIGHT_INSET, BOTTOM_INSET, LEFT_INSET);
}
@Override protected double computeMaxHeight(final double WIDTH, double TOP_INSET, double RIGHT_INSET, double BOTTOM_INSET, double LEFT_INSET) {
return super.computeMaxHeight(Math.min(MAXIMUM_WIDTH, WIDTH - LEFT_INSET - RIGHT_INSET), TOP_INSET, RIGHT_INSET, BOTTOM_INSET, LEFT_INSET);
}
@Override protected double computePrefWidth(final double HEIGHT, double TOP_INSET, double RIGHT_INSET, double BOTTOM_INSET, double LEFT_INSET) {
double prefHeight = PREFERRED_HEIGHT;
if (HEIGHT != -1) {
prefHeight = Math.max(0, HEIGHT - TOP_INSET - BOTTOM_INSET);
}
return super.computePrefWidth(prefHeight, TOP_INSET, RIGHT_INSET, BOTTOM_INSET, LEFT_INSET);
}
@Override protected double computePrefHeight(final double WIDTH, double TOP_INSET, double RIGHT_INSET, double BOTTOM_INSET, double LEFT_INSET) {
double prefWidth = PREFERRED_WIDTH;
if (WIDTH != -1) {
prefWidth = Math.max(0, WIDTH - LEFT_INSET - RIGHT_INSET);
}
return super.computePrefHeight(prefWidth, TOP_INSET, RIGHT_INSET, BOTTOM_INSET, LEFT_INSET);
}
private void handleMouseEvent(final MouseEvent EVENT) {
if (active && MouseEvent.MOUSE_ENTERED == EVENT.getEventType()) {
final Region SRC = (Region) EVENT.getSource();
if (Orientation.HORIZONTAL == getSkinnable().getOrientation()) {
for (int i = 0 ; i < leds.size() ; i++) {
leds.get(i).getStyleClass().remove("led-on");
if (i <= leds.indexOf(SRC)) {
leds.get(i).getStyleClass().add("led-on");
}
}
} else {
for (int i = 0 ; i < leds.size() ; i++) {
leds.get(i).getStyleClass().remove("led-on");
if (i >= leds.indexOf(SRC)) {
leds.get(i).getStyleClass().add("led-on");
}
}
}
}
}
// ******************** Private Methods ***********************************
private void resize() {
size = getSkinnable().getWidth() < getSkinnable().getHeight() ? getSkinnable().getWidth() : getSkinnable().getHeight();
if (size > 0) {
for (Region led : leds) {
led.setPrefSize(size, size);
}
}
}
}