/*
* Copyright (c) 2013 by Gerrit Grunwald
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.hansolo.enzo.gauge.skin;
import eu.hansolo.enzo.common.Marker;
import eu.hansolo.enzo.common.Section;
import eu.hansolo.enzo.common.ValueEvent;
import eu.hansolo.enzo.gauge.Gauge;
import javafx.animation.FadeTransition;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.ParallelTransition;
import javafx.animation.PauseTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Point2D;
import javafx.geometry.VPos;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.effect.Blend;
import javafx.scene.effect.BlendMode;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.InnerShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TouchEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.FillRule;
import javafx.scene.shape.Path;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Created by
* User: hansolo
* Date: 01.04.13
* Time: 17:18
*/
public class GaugeSkin extends SkinBase<Gauge> implements Skin<Gauge> {
private static final double PREFERRED_WIDTH = 200;
private static final double PREFERRED_HEIGHT = 200;
private static final double MINIMUM_WIDTH = 50;
private static final double MINIMUM_HEIGHT = 50;
private static final double MAXIMUM_WIDTH = 1024;
private static final double MAXIMUM_HEIGHT = 1024;
private double size;
private double centerX;
private double centerY;
private Pane pane;
private Region background;
private Canvas ticksAndSectionsCanvas;
private GraphicsContext ticksAndSections;
private Region threshold;
private Rotate thresholdRotate;
private boolean thresholdExceeded;
private Region minMeasuredValue;
private Rotate minMeasuredValueRotate;
private Region maxMeasuredValue;
private Rotate maxMeasuredValueRotate;
private Region needle;
private Region needleHighlight;
private Rotate needleRotate;
private Region knob;
private Group shadowGroup;
private DropShadow dropShadow;
private Text title;
private Text unit;
private Text value;
private DropShadow valueBlendBottomShadow;
private InnerShadow valueBlendTopShadow;
private Blend valueBlend;
private Path histogram;
private double angleStep;
private Timeline timeline;
private double interactiveAngle;
private EventHandler<MouseEvent> mouseEventHandler;
private EventHandler<TouchEvent> touchEventHandler;
private List<Node> markersToRemove;
// ******************** Constructors **************************************
public GaugeSkin(Gauge gauge) {
super(gauge);
angleStep = gauge.getAngleRange() / (gauge.getMaxValue() - gauge.getMinValue());
timeline = new Timeline();
mouseEventHandler = mouseEvent -> handleMouseEvent(mouseEvent);
touchEventHandler = touchEvent -> handleTouchEvent(touchEvent);
markersToRemove = new ArrayList<>();
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 (getSkinnable().getPrefWidth() > 0 && getSkinnable().getPrefHeight() > 0) {
getSkinnable().setPrefSize(getSkinnable().getPrefWidth(), getSkinnable().getPrefHeight());
} else {
getSkinnable().setPrefSize(PREFERRED_WIDTH, PREFERRED_HEIGHT);
}
}
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_HEIGHT);
}
}
private void initGraphics() {
Font.loadFont(getClass().getResourceAsStream("/eu/hansolo/enzo/fonts/opensans-semibold.ttf"), (0.06 * PREFERRED_HEIGHT)); // "OpenSans"
valueBlendBottomShadow = new DropShadow();
valueBlendBottomShadow.setBlurType(BlurType.TWO_PASS_BOX);
valueBlendBottomShadow.setColor(Color.rgb(255, 255, 255, 0.5));
valueBlendBottomShadow.setOffsetX(0);
valueBlendBottomShadow.setOffsetY(0.005 * PREFERRED_WIDTH);
valueBlendBottomShadow.setRadius(0);
valueBlendTopShadow = new InnerShadow();
valueBlendTopShadow.setBlurType(BlurType.TWO_PASS_BOX);
valueBlendTopShadow.setColor(Color.rgb(0, 0, 0, 0.7));
valueBlendTopShadow.setOffsetX(0);
valueBlendTopShadow.setOffsetY(0.005 * PREFERRED_WIDTH);
valueBlendTopShadow.setRadius(0.005 * PREFERRED_WIDTH);
valueBlend = new Blend();
valueBlend.setMode(BlendMode.MULTIPLY);
valueBlend.setBottomInput(valueBlendBottomShadow);
valueBlend.setTopInput(valueBlendTopShadow);
background = new Region();
background.getStyleClass().setAll("background");
ticksAndSectionsCanvas = new Canvas(PREFERRED_WIDTH, PREFERRED_HEIGHT);
ticksAndSections = ticksAndSectionsCanvas.getGraphicsContext2D();
histogram = new Path();
histogram.setFillRule(FillRule.NON_ZERO);
histogram.getStyleClass().add("histogram-fill");
minMeasuredValue = new Region();
minMeasuredValue.getStyleClass().setAll("min-measured-value");
minMeasuredValueRotate = new Rotate(180 - getSkinnable().getStartAngle() - getSkinnable().getMinValue() * angleStep);
minMeasuredValue.getTransforms().setAll(minMeasuredValueRotate);
minMeasuredValue.setOpacity(getSkinnable().isMinMeasuredValueVisible() ? 1 : 0);
minMeasuredValue.setManaged(getSkinnable().isMinMeasuredValueVisible());
maxMeasuredValue = new Region();
maxMeasuredValue.getStyleClass().setAll("max-measured-value");
maxMeasuredValueRotate = new Rotate(180 - getSkinnable().getStartAngle() - getSkinnable().getMinValue() * angleStep);
maxMeasuredValue.getTransforms().setAll(maxMeasuredValueRotate);
maxMeasuredValue.setOpacity(getSkinnable().isMaxMeasuredValueVisible() ? 1 : 0);
maxMeasuredValue.setManaged(getSkinnable().isMaxMeasuredValueVisible());
threshold = new Region();
threshold.getStyleClass().setAll("threshold");
thresholdRotate = new Rotate(180 - getSkinnable().getStartAngle() - getSkinnable().getMinValue() * angleStep);
threshold.getTransforms().setAll(thresholdRotate);
threshold.setOpacity(getSkinnable().isThresholdVisible() ? 1 : 0);
threshold.setManaged(getSkinnable().isThresholdVisible());
thresholdExceeded = false;
needle = new Region();
needle.getStyleClass().setAll(Gauge.STYLE_CLASS_NEEDLE_STANDARD);
needleRotate = new Rotate(180 - getSkinnable().getStartAngle());
needleRotate.setAngle(needleRotate.getAngle() + (getSkinnable().getValue() - getSkinnable().getOldValue() - getSkinnable().getMinValue()) * angleStep);
needle.getTransforms().setAll(needleRotate);
needleHighlight = new Region();
needleHighlight.setMouseTransparent(true);
needleHighlight.getStyleClass().setAll("needle-highlight");
needleHighlight.getTransforms().setAll(needleRotate);
knob = new Region();
knob.setPickOnBounds(false);
knob.getStyleClass().setAll("knob");
dropShadow = new DropShadow();
dropShadow.setColor(Color.rgb(0, 0, 0, 0.25));
dropShadow.setBlurType(BlurType.TWO_PASS_BOX);
dropShadow.setRadius(0.015 * PREFERRED_WIDTH);
dropShadow.setOffsetY(0.015 * PREFERRED_WIDTH);
shadowGroup = new Group(needle, needleHighlight);//, knob);
shadowGroup.setEffect(getSkinnable().isDropShadowEnabled() ? dropShadow : null);
title = new Text(getSkinnable().getTitle());
title.setTextOrigin(VPos.CENTER);
title.getStyleClass().setAll("title");
unit = new Text(getSkinnable().getUnit());
unit.setMouseTransparent(true);
unit.setTextOrigin(VPos.CENTER);
unit.getStyleClass().setAll("unit");
value = new Text(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", getSkinnable().getValue()));
value.setMouseTransparent(true);
value.setTextOrigin(VPos.CENTER);
value.getStyleClass().setAll("value");
value.setEffect(getSkinnable().isPlainValue() ? null : valueBlend);
// Add all nodes
pane = new Pane();
pane.getChildren().setAll(background,
histogram,
ticksAndSectionsCanvas,
minMeasuredValue,
maxMeasuredValue,
threshold,
title,
shadowGroup,
knob,
unit,
value);
pane.getChildren().addAll(getSkinnable().getMarkers().keySet());
getChildren().setAll(pane);
}
private void registerListeners() {
getSkinnable().widthProperty().addListener(observable -> handleControlPropertyChanged("RESIZE"));
getSkinnable().heightProperty().addListener(observable -> handleControlPropertyChanged("RESIZE"));
getSkinnable().valueProperty().addListener(observable -> handleControlPropertyChanged("VALUE"));
getSkinnable().minValueProperty().addListener(observable -> handleControlPropertyChanged("RECALC"));
getSkinnable().maxValueProperty().addListener(observable -> handleControlPropertyChanged("RECALC"));
getSkinnable().minMeasuredValueProperty().addListener(observable -> handleControlPropertyChanged("MIN_MEASURED_VALUE"));
getSkinnable().minMeasuredValueVisibleProperty().addListener(observable -> handleControlPropertyChanged("MIN_MEASURED_VALUE_VISIBLE"));
getSkinnable().maxMeasuredValueProperty().addListener(observable -> handleControlPropertyChanged("MAX_MEASURED_VALUE"));
getSkinnable().maxMeasuredValueVisibleProperty().addListener(observable -> handleControlPropertyChanged("MAX_MEASURED_VALUE_VISIBLE"));
getSkinnable().tickLabelOrientationProperty().addListener(observable -> handleControlPropertyChanged("RESIZE"));
getSkinnable().needleTypeProperty().addListener(observable -> handleControlPropertyChanged("NEEDLE_TYPE"));
getSkinnable().needleColorProperty().addListener(observable -> handleControlPropertyChanged("NEEDLE_COLOR"));
getSkinnable().animatedProperty().addListener(observable -> handleControlPropertyChanged("ANIMATED"));
getSkinnable().thresholdProperty().addListener(observable -> handleControlPropertyChanged("THRESHOLD"));
getSkinnable().thresholdVisibleProperty().addListener(observable -> handleControlPropertyChanged("THRESHOLD_VISIBLE"));
getSkinnable().angleRangeProperty().addListener(observable -> handleControlPropertyChanged("ANGLE_RANGE"));
getSkinnable().numberFormatProperty().addListener(observable -> handleControlPropertyChanged("RECALC"));
getSkinnable().plainValueProperty().addListener(observable -> handleControlPropertyChanged("PLAIN_VALUE"));
getSkinnable().histogramEnabledProperty().addListener(observable -> handleControlPropertyChanged("HISTOGRAM"));
getSkinnable().dropShadowEnabledProperty().addListener(observable -> handleControlPropertyChanged("DROP_SHADOW"));
getSkinnable().interactiveProperty().addListener(observable -> handleControlPropertyChanged("INTERACTIVE"));
getSkinnable().getSections().addListener((ListChangeListener<Section>) change -> handleControlPropertyChanged("CANVAS_REFRESH"));
getSkinnable().getMarkers().addListener((MapChangeListener<Marker, Rotate>) change -> handleControlPropertyChanged("MARKER"));
needleRotate.angleProperty().addListener(observable -> handleControlPropertyChanged("ANGLE"));
knob.setOnMousePressed(event -> getSkinnable().setInteractive(!getSkinnable().isInteractive()));
minMeasuredValue.setOnMousePressed(mouseEventHandler);
minMeasuredValue.setOnMouseReleased(mouseEventHandler);
minMeasuredValue.setOnTouchPressed(touchEventHandler);
minMeasuredValue.setOnTouchReleased(touchEventHandler);
maxMeasuredValue.setOnMousePressed(mouseEventHandler);
maxMeasuredValue.setOnMouseReleased(mouseEventHandler);
maxMeasuredValue.setOnTouchPressed(touchEventHandler);
maxMeasuredValue.setOnTouchReleased(touchEventHandler);
threshold.setOnMousePressed(mouseEventHandler);
threshold.setOnMouseDragged(mouseEventHandler);
threshold.setOnMouseReleased(mouseEventHandler);
threshold.setOnTouchPressed(touchEventHandler);
threshold.setOnTouchMoved(touchEventHandler);
threshold.setOnTouchReleased(touchEventHandler);
for (Marker marker : getSkinnable().getMarkers().keySet()) {
marker.setOnMousePressed(mouseEventHandler);
marker.setOnMouseDragged(mouseEventHandler);
marker.setOnMouseReleased(mouseEventHandler);
marker.setOnTouchPressed(touchEventHandler);
marker.setOnTouchMoved(touchEventHandler);
marker.setOnTouchReleased(touchEventHandler);
}
}
// ******************** Methods *******************************************
protected void handleControlPropertyChanged(final String PROPERTY) {
if ("RESIZE".equals(PROPERTY)) {
resize();
} else if ("VALUE".equals(PROPERTY)) {
rotateNeedle();
} else if ("RECALC".equals(PROPERTY)) {
if (getSkinnable().getMinValue() < 0) {
angleStep = getSkinnable().getAngleRange() / (getSkinnable().getMaxValue() - getSkinnable().getMinValue());
needleRotate.setAngle(180 - getSkinnable().getStartAngle() - (getSkinnable().getMinValue()) * angleStep);
} else {
angleStep = getSkinnable().getAngleRange() / (getSkinnable().getMaxValue() + getSkinnable().getMinValue());
needleRotate.setAngle(180 - getSkinnable().getStartAngle() * angleStep);
}
resize();
} else if ("ANGLE".equals(PROPERTY)) {
if (getSkinnable().isInteractive()) return;
double currentValue = (needleRotate.getAngle() + getSkinnable().getStartAngle() - 180) / angleStep + getSkinnable().getMinValue();
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", currentValue));
value.setTranslateX((size - value.getLayoutBounds().getWidth()) * 0.5);
// Check threshold
if (thresholdExceeded) {
if (currentValue < getSkinnable().getThreshold()) {
getSkinnable().fireEvent(new ValueEvent(this, null, ValueEvent.VALUE_UNDERRUN));
thresholdExceeded = false;
}
} else {
if (currentValue > getSkinnable().getThreshold()) {
getSkinnable().fireEvent(new ValueEvent(this, null, ValueEvent.VALUE_EXCEEDED));
thresholdExceeded = true;
}
}
// Check each marker
for (Marker marker : getSkinnable().getMarkers().keySet()) {
if (marker.isExceeded()) {
if (currentValue < marker.getValue()) {
marker.fireMarkerEvent(new Marker.MarkerEvent(this, null, Marker.MarkerEvent.MARKER_UNDERRUN));
marker.setExceeded(false);
}
} else {
if (currentValue > marker.getValue()) {
marker.fireMarkerEvent(new Marker.MarkerEvent(this, null, Marker.MarkerEvent.MARKER_EXCEEDED));
marker.setExceeded(true);
}
}
}
// Check min- and maxMeasuredValue
if (currentValue < getSkinnable().getMinMeasuredValue()) {
getSkinnable().setMinMeasuredValue(currentValue);
minMeasuredValueRotate.setAngle(currentValue * angleStep - 180 - getSkinnable().getStartAngle() - getSkinnable().getMinValue() * angleStep);
}
if (currentValue > getSkinnable().getMaxMeasuredValue()) {
getSkinnable().setMaxMeasuredValue(currentValue);
maxMeasuredValueRotate.setAngle(currentValue * angleStep - 180 - getSkinnable().getStartAngle() - getSkinnable().getMinValue() * angleStep);
}
// Check sections
for (Section section : getSkinnable().getSections()) {
if (section.contains(currentValue)) {
section.fireSectionEvent(new Section.SectionEvent(section, null, Section.SectionEvent.ENTERING_SECTION));
break;
}
}
} else if ("PLAIN_VALUE".equals(PROPERTY)) {
value.setEffect(getSkinnable().isPlainValue() ? null : valueBlend);
} else if ("HISTOGRAM".equals(PROPERTY)) {
histogram.setVisible(getSkinnable().isHistogramEnabled());
histogram.setManaged(getSkinnable().isHistogramEnabled());
} else if ("DROP_SHADOW".equals(PROPERTY)) {
shadowGroup.setEffect(getSkinnable().isDropShadowEnabled() ? dropShadow : null);
} else if ("INTERACTIVE".equals(PROPERTY)) {
needle.setMouseTransparent(getSkinnable().isInteractive());
if (getSkinnable().isInteractive()) {
unit.setText("Interactive");
value.setText("");
resizeText();
shadowGroup.setEffect(null);
} else {
unit.setText(getSkinnable().getUnit());
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", (needleRotate.getAngle() + getSkinnable().getStartAngle() - 180) / angleStep));
resizeText();
shadowGroup.setEffect(dropShadow);
}
} else if ("CANVAS_REFRESH".equals(PROPERTY)) {
ticksAndSections.clearRect(0, 0, size, size);
drawSections(ticksAndSections);
drawTickMarks(ticksAndSections);
} else if ("THRESHOLD".equals(PROPERTY)) {
thresholdRotate.setAngle(getSkinnable().getThreshold() * angleStep - 180 - getSkinnable().getStartAngle());
} else if ("THRESHOLD_VISIBLE".equals(PROPERTY)) {
threshold.setOpacity(getSkinnable().isThresholdVisible() ? 1 : 0);
threshold.setManaged(getSkinnable().isThresholdVisible());
} else if ("MIN_MEASURED_VALUE_VISIBLE".equals(PROPERTY)) {
minMeasuredValue.setOpacity(getSkinnable().isMinMeasuredValueVisible() ? 1 : 0);
minMeasuredValue.setManaged(getSkinnable().isMinMeasuredValueVisible());
} else if ("MAX_MEASURED_VALUE_VISIBLE".equals(PROPERTY)) {
maxMeasuredValue.setOpacity(getSkinnable().isMaxMeasuredValueVisible() ? 1 : 0);
maxMeasuredValue.setManaged(getSkinnable().isMaxMeasuredValueVisible());
} else if ("MARKER".equals(PROPERTY)) {
checkForRemovedMarkers();
for (Marker marker : getSkinnable().getMarkers().keySet()) {
if (pane.getChildren().contains(marker)) continue;
pane.getChildren().add(marker);
// Add MouseEvent handler
marker.setOnMousePressed(mouseEventHandler);
marker.setOnMouseDragged(mouseEventHandler);
marker.setOnMouseReleased(mouseEventHandler);
// Add TouchEvent handler
marker.setOnTouchPressed(touchEventHandler);
marker.setOnTouchMoved(touchEventHandler);
marker.setOnTouchReleased(touchEventHandler);
}
drawMarkers();
}
}
@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 Methods ***********************************
private void checkForRemovedMarkers() {
markersToRemove.clear();
for (Node node : pane.getChildren()) {
if (node instanceof Marker) {
if (getSkinnable().getMarkers().keySet().contains(node)) continue;
node.setManaged(false);
node.removeEventHandler(MouseEvent.MOUSE_PRESSED, mouseEventHandler);
node.removeEventHandler(MouseEvent.MOUSE_DRAGGED, mouseEventHandler);
node.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseEventHandler);
node.removeEventHandler(TouchEvent.TOUCH_PRESSED, touchEventHandler);
node.removeEventHandler(TouchEvent.TOUCH_MOVED, touchEventHandler);
node.removeEventHandler(TouchEvent.TOUCH_RELEASED, touchEventHandler);
markersToRemove.add(node);
}
}
for (Node node : markersToRemove) pane.getChildren().remove(node);
}
private void handleMouseEvent(final MouseEvent MOUSE_EVENT) {
final Object SRC = MOUSE_EVENT.getSource();
final EventType TYPE = MOUSE_EVENT.getEventType();
if (getSkinnable().isInteractive() && SRC.equals(threshold)) {
if (MouseEvent.MOUSE_PRESSED == TYPE) {
unit.setText("Threshold");
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", getSkinnable().getThreshold()));
resizeText();
} else if (MouseEvent.MOUSE_DRAGGED == TYPE) {
touchRotate(MOUSE_EVENT.getSceneX() - getSkinnable().getLayoutX(), MOUSE_EVENT.getSceneY() - getSkinnable().getLayoutY(), thresholdRotate);
} else if (MouseEvent.MOUSE_RELEASED == TYPE) {
getSkinnable().setThreshold(Double.parseDouble(value.getText()));
fadeBackToInteractive();
}
} else if (getSkinnable().isInteractive() && SRC instanceof Marker) {
if (MouseEvent.MOUSE_PRESSED == TYPE) {
unit.setText(((Marker) SRC).getText());
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", ((Marker) SRC).getValue()));
resizeText();
} else if (MouseEvent.MOUSE_DRAGGED == TYPE) {
touchRotate(MOUSE_EVENT.getSceneX() - getSkinnable().getLayoutX(), MOUSE_EVENT.getSceneY() - getSkinnable().getLayoutY(), getSkinnable().getMarkers().get(SRC));
} else if (MouseEvent.MOUSE_RELEASED == TYPE) {
((Marker) SRC).setValue(Double.parseDouble(value.getText()));
fadeBackToInteractive();
}
} else if (getSkinnable().isInteractive() && SRC.equals(minMeasuredValue)) {
if (MouseEvent.MOUSE_PRESSED == TYPE) {
unit.setText("Min");
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", getSkinnable().getMinMeasuredValue()));
resizeText();
} else if (MouseEvent.MOUSE_RELEASED == TYPE) {
fadeBackToInteractive();
}
} else if (getSkinnable().isInteractive() && SRC.equals(maxMeasuredValue)) {
if (MouseEvent.MOUSE_PRESSED == TYPE) {
unit.setText("Max");
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", getSkinnable().getMaxMeasuredValue()));
resizeText();
} else if (MouseEvent.MOUSE_RELEASED == TYPE) {
fadeBackToInteractive();
}
}
}
private void handleTouchEvent(final TouchEvent TOUCH_EVENT) {
final Object SRC = TOUCH_EVENT.getSource();
final EventType TYPE = TOUCH_EVENT.getEventType();
if (SRC.equals(threshold)) {
if (TouchEvent.TOUCH_PRESSED == TYPE) {
unit.setText("Threshold");
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", getSkinnable().getThreshold()));
resizeText();
} else if (TouchEvent.TOUCH_MOVED == TYPE) {
touchRotate(TOUCH_EVENT.getTouchPoint().getSceneX() - getSkinnable().getLayoutX(), TOUCH_EVENT.getTouchPoint().getSceneY() - getSkinnable().getLayoutY(), thresholdRotate);
} else if (TouchEvent.TOUCH_RELEASED == TYPE) {
getSkinnable().setThreshold(Double.parseDouble(value.getText()));
fadeBackToInteractive();
}
} else if (SRC instanceof Marker) {
if (TouchEvent.TOUCH_PRESSED == TYPE) {
unit.setText(((Marker) SRC).getText());
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", ((Marker) SRC).getValue()));
resizeText();
} else if (TouchEvent.TOUCH_MOVED == TYPE) {
touchRotate(TOUCH_EVENT.getTouchPoint().getSceneX() - getSkinnable().getLayoutX(), TOUCH_EVENT.getTouchPoint().getSceneY() - getSkinnable().getLayoutY(), getSkinnable().getMarkers().get(SRC));
} else if (TouchEvent.TOUCH_RELEASED == TYPE) {
((Marker) SRC).setValue(Double.parseDouble(value.getText()));
fadeBackToInteractive();
}
} else if (SRC.equals(minMeasuredValue)) {
if (TouchEvent.TOUCH_PRESSED == TYPE) {
unit.setText("Min");
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", getSkinnable().getMinMeasuredValue()));
resizeText();
} else if (TouchEvent.TOUCH_RELEASED == TYPE) {
fadeBackToInteractive();
}
} else if (SRC.equals(maxMeasuredValue)) {
if (TouchEvent.TOUCH_PRESSED == TYPE) {
unit.setText("Max");
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", getSkinnable().getMaxMeasuredValue()));
resizeText();
} else if (TouchEvent.TOUCH_RELEASED == TYPE) {
fadeBackToInteractive();
}
}
}
private double getTheta(double x, double y) {
double deltaX = x - centerX;
double deltaY = y - centerY;
double radius = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY));
double nx = deltaX / radius;
double ny = deltaY / radius;
double theta = Math.atan2(ny, nx);
return Double.compare(theta, 0.0) >= 0 ? Math.toDegrees(theta) : Math.toDegrees((theta)) + 360.0;
}
private void touchRotate(final double X, final double Y, final Rotate ROTATE) {
double theta = getTheta(X, Y);
interactiveAngle = (theta + 90) % 360;
double newValue = Double.compare(interactiveAngle, 180) <= 0 ?
(interactiveAngle + 180.0 + getSkinnable().getStartAngle() - 360) / angleStep + getSkinnable().getMinValue():
(interactiveAngle - 180.0 + getSkinnable().getStartAngle() - 360) / angleStep + getSkinnable().getMinValue();
if (Double.compare(newValue, getSkinnable().getMinValue()) >= 0 && Double.compare(newValue, getSkinnable().getMaxValue()) <= 0) {
ROTATE.setAngle(interactiveAngle);
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", newValue));
resizeText();
}
}
private void fadeBackToInteractive() {
FadeTransition fadeUnitOut = new FadeTransition(Duration.millis(425), unit);
fadeUnitOut.setFromValue(1.0);
fadeUnitOut.setToValue(0.0);
FadeTransition fadeValueOut = new FadeTransition(Duration.millis(425), value);
fadeValueOut.setFromValue(1.0);
fadeValueOut.setToValue(0.0);
PauseTransition pause = new PauseTransition(Duration.millis(50));
FadeTransition fadeUnitIn = new FadeTransition(Duration.millis(425), unit);
fadeUnitIn.setFromValue(0.0);
fadeUnitIn.setToValue(1.0);
FadeTransition fadeValueIn = new FadeTransition(Duration.millis(425), value);
fadeValueIn.setFromValue(0.0);
fadeValueIn.setToValue(1.0);
ParallelTransition parallelIn = new ParallelTransition(fadeUnitIn, fadeValueIn);
ParallelTransition parallelOut = new ParallelTransition(fadeUnitOut, fadeValueOut);
parallelOut.setOnFinished(event -> {
unit.setText("Interactive");
value.setText("");
resizeText();
});
SequentialTransition sequence = new SequentialTransition(parallelOut, pause, parallelIn);
sequence.play();
}
private void rotateNeedle() {
double range = (getSkinnable().getMaxValue() - getSkinnable().getMinValue());
double angleRange = getSkinnable().getAngleRange();
angleStep = angleRange / range;
double targetAngle = needleRotate.getAngle() + (getSkinnable().getValue() - getSkinnable().getOldValue()) * angleStep;
if (getSkinnable().isAnimated()) {
timeline.stop();
//double animationDuration = (getSkinnable().getAnimationDuration() / (getSkinnable().getMaxValue() - getSkinnable().getMinValue())) * Math.abs(getSkinnable().getValue() - getSkinnable().getOldValue());
final KeyValue KEY_VALUE = new KeyValue(needleRotate.angleProperty(), targetAngle, Interpolator.SPLINE(0.5, 0.4, 0.4, 1.0));
final KeyFrame KEY_FRAME = new KeyFrame(Duration.millis(getSkinnable().getAnimationDuration()), KEY_VALUE);
timeline.getKeyFrames().setAll(KEY_FRAME);
timeline.play();
} else {
needleRotate.setAngle(targetAngle);
}
}
private void changeNeedle() {
switch(getSkinnable().getNeedleType()) {
default:
needle.getStyleClass().setAll(Gauge.STYLE_CLASS_NEEDLE_STANDARD);
}
}
private void drawTickMarks(final GraphicsContext CTX) {
if (getSkinnable().isHistogramEnabled()) {
double xy;
double wh;
double step = 0;
double OFFSET = 90 - getSkinnable().getStartAngle();
double ANGLE_EXTEND = (getSkinnable().getMaxValue()) * angleStep;
CTX.setStroke(Color.rgb(200, 200, 200));
CTX.setLineWidth(size * 0.001);
CTX.setLineCap(StrokeLineCap.BUTT);
for (int i = 0 ; i < 5 ; i++) {
xy = (size - (0.435 + step) * size) / 2;
wh = size * (0.435 + step);
CTX.strokeArc(xy, xy, wh, wh, -OFFSET, -ANGLE_EXTEND, ArcType.OPEN);
step += 0.075;
}
}
double sinValue;
double cosValue;
double startAngle = getSkinnable().getStartAngle();
double orthText = Gauge.TickLabelOrientation.ORTHOGONAL == getSkinnable().getTickLabelOrientation() ? 0.33 : 0.31;
Point2D center = new Point2D(size * 0.5, size * 0.5);
for (double angle = 0, counter = getSkinnable().getMinValue() ; Double.compare(counter, getSkinnable().getMaxValue()) <= 0 ; angle -= angleStep, counter++) {
sinValue = Math.sin(Math.toRadians(angle + startAngle));
cosValue = Math.cos(Math.toRadians(angle + startAngle));
Point2D innerMainPoint = new Point2D(center.getX() + size * 0.368 * sinValue, center.getY() + size * 0.368 * cosValue);
Point2D innerMediumPoint = new Point2D(center.getX() + size * 0.388 * sinValue, center.getY() + size * 0.388 * cosValue);
Point2D innerMinorPoint = new Point2D(center.getX() + size * 0.3975 * sinValue, center.getY() + size * 0.3975 * cosValue);
Point2D outerPoint = new Point2D(center.getX() + size * 0.432 * sinValue, center.getY() + size * 0.432 * cosValue);
Point2D textPoint = new Point2D(center.getX() + size * orthText * sinValue, center.getY() + size * orthText * cosValue);
CTX.setStroke(getSkinnable().getTickMarkFill());
if (counter % getSkinnable().getMajorTickSpace() == 0) {
// Draw major tickmark
CTX.setLineWidth(size * 0.0055);
CTX.strokeLine(innerMainPoint.getX(), innerMainPoint.getY(), outerPoint.getX(), outerPoint.getY());
// Draw text
CTX.save();
CTX.translate(textPoint.getX(), textPoint.getY());
switch(getSkinnable().getTickLabelOrientation()) {
case ORTHOGONAL:
if ((360 - startAngle - angle) % 360 > 90 && (360 - startAngle - angle) % 360 < 270) {
CTX.rotate((180 - startAngle - angle) % 360);
} else {
CTX.rotate((360 - startAngle - angle) % 360);
}
break;
case TANGENT:
if ((360 - startAngle - angle - 90) % 360 > 90 && (360 - startAngle - angle - 90) % 360 < 270) {
CTX.rotate((90 - startAngle - angle) % 360);
} else {
CTX.rotate((270 - startAngle - angle) % 360);
}
break;
case HORIZONTAL:
default:
break;
}
CTX.setFont(Font.font("Verdana", FontWeight.NORMAL, 0.045 * size));
CTX.setTextAlign(TextAlignment.CENTER);
CTX.setTextBaseline(VPos.CENTER);
CTX.setFill(getSkinnable().getTickLabelFill());
CTX.fillText(Integer.toString((int) counter), 0, 0);
CTX.restore();
} else if (getSkinnable().getMinorTickSpace() % 2 != 0 && counter % 5 == 0) {
CTX.setLineWidth(size * 0.0035);
CTX.strokeLine(innerMediumPoint.getX(), innerMediumPoint.getY(), outerPoint.getX(), outerPoint.getY());
} else if (counter % getSkinnable().getMinorTickSpace() == 0) {
CTX.setLineWidth(size * 0.00225);
CTX.strokeLine(innerMinorPoint.getX(), innerMinorPoint.getY(), outerPoint.getX(), outerPoint.getY());
}
}
}
private final void drawSections(final GraphicsContext CTX) {
final double xy = (size - 0.83 * size) / 2;
final double wh = size * 0.83;
final double MIN_VALUE = getSkinnable().getMinValue();
final double MAX_VALUE = getSkinnable().getMaxValue();
final double OFFSET = 90 - getSkinnable().getStartAngle();
for (int i = 0 ; i < getSkinnable().getSections().size() ; i++) {
final Section SECTION = getSkinnable().getSections().get(i);
if (SECTION.getStart() > MAX_VALUE || SECTION.getStop() < MIN_VALUE) continue;
final double SECTION_START_ANGLE;
if (SECTION.getStart() > MAX_VALUE || SECTION.getStop() < MIN_VALUE) continue;
if (SECTION.getStart() < MIN_VALUE && SECTION.getStop() < MAX_VALUE) {
SECTION_START_ANGLE = MIN_VALUE * angleStep;
} else {
SECTION_START_ANGLE = (SECTION.getStart() - MIN_VALUE) * angleStep;
}
final double SECTION_ANGLE_EXTEND;
if (SECTION.getStop() > MAX_VALUE) {
SECTION_ANGLE_EXTEND = MAX_VALUE * angleStep;
} else {
SECTION_ANGLE_EXTEND = (SECTION.getStop() - SECTION.getStart()) * angleStep;
}
CTX.save();
switch(i) {
case 0: CTX.setStroke(getSkinnable().getSectionFill0()); break;
case 1: CTX.setStroke(getSkinnable().getSectionFill1()); break;
case 2: CTX.setStroke(getSkinnable().getSectionFill2()); break;
case 3: CTX.setStroke(getSkinnable().getSectionFill3()); break;
case 4: CTX.setStroke(getSkinnable().getSectionFill4()); break;
case 5: CTX.setStroke(getSkinnable().getSectionFill5()); break;
case 6: CTX.setStroke(getSkinnable().getSectionFill6()); break;
case 7: CTX.setStroke(getSkinnable().getSectionFill7()); break;
case 8: CTX.setStroke(getSkinnable().getSectionFill8()); break;
case 9: CTX.setStroke(getSkinnable().getSectionFill9()); break;
}
CTX.setLineWidth(size * 0.037);
CTX.setLineCap(StrokeLineCap.BUTT);
CTX.strokeArc(xy, xy, wh, wh, -(OFFSET + SECTION_START_ANGLE), -SECTION_ANGLE_EXTEND, ArcType.OPEN);
CTX.restore();
}
}
private final void drawMarkers() {
for (Marker marker : getSkinnable().getMarkers().keySet()) {
marker.setPrefSize(0.0325 * size, 0.0325 * size);
marker.relocate((size - marker.getPrefWidth()) * 0.5, size * 0.04);
getSkinnable().getMarkers().get(marker).setPivotX(marker.getPrefWidth() * 0.5);
getSkinnable().getMarkers().get(marker).setPivotY(size * 0.46);
getSkinnable().getMarkers().get(marker).setAngle(marker.getValue() * angleStep - 180 - getSkinnable().getStartAngle() - getSkinnable().getMinValue() * angleStep);
}
}
private void resizeText() {
title.setFont(Font.font("Open Sans", FontWeight.NORMAL, size * 0.06));
title.setTranslateX((size - title.getLayoutBounds().getWidth()) * 0.5);
title.setTranslateY(size * 0.74);
unit.setFont(Font.font("Open Sans", FontWeight.NORMAL, size * 0.05));
unit.setTranslateX((size - unit.getLayoutBounds().getWidth()) * 0.5);
unit.setTranslateY(size * 0.4);
value.setFont(Font.font("Open Sans", FontWeight.BOLD, size * 0.1));
value.setTranslateX((size - value.getLayoutBounds().getWidth()) * 0.5);
value.setTranslateY(size * 0.5);
}
private void resize() {
size = getSkinnable().getWidth() < getSkinnable().getHeight() ? getSkinnable().getWidth() : getSkinnable().getHeight();
centerX = size * 0.5;
centerY = size * 0.5;
valueBlendBottomShadow.setOffsetY(0.005 * size);
valueBlendTopShadow.setOffsetY(0.005 * size);
valueBlendTopShadow.setRadius(0.005 * size);
dropShadow.setRadius(0.015 * size);
dropShadow.setOffsetY(0.015 * size);
background.setPrefSize(size, size);
ticksAndSectionsCanvas.setWidth(size);
ticksAndSectionsCanvas.setHeight(size);
ticksAndSections.clearRect(0, 0, size, size);
drawSections(ticksAndSections);
drawTickMarks(ticksAndSections);
ticksAndSectionsCanvas.setCache(true);
ticksAndSectionsCanvas.setCacheHint(CacheHint.QUALITY);
drawMarkers();
minMeasuredValue.setPrefSize(0.03 * size, 0.03 * size);
minMeasuredValue.relocate((size - minMeasuredValue.getPrefWidth()) * 0.5, size * 0.11);
minMeasuredValueRotate.setPivotX(minMeasuredValue.getPrefWidth() * 0.5);
minMeasuredValueRotate.setPivotY(size * 0.39);
minMeasuredValueRotate.setAngle(getSkinnable().getMinMeasuredValue() * angleStep - 180 - getSkinnable().getStartAngle() - getSkinnable().getMinValue() * angleStep);
maxMeasuredValue.setPrefSize(0.03 * size, 0.03 * size);
maxMeasuredValue.relocate((size - maxMeasuredValue.getPrefWidth()) * 0.5, size * 0.11);
maxMeasuredValueRotate.setPivotX(maxMeasuredValue.getPrefWidth() * 0.5);
maxMeasuredValueRotate.setPivotY(size * 0.39);
maxMeasuredValueRotate.setAngle(getSkinnable().getMaxMeasuredValue() * angleStep - 180 - getSkinnable().getStartAngle() - getSkinnable().getMinValue() * angleStep);
threshold.setPrefSize(0.03 * size, 0.0275 * size);
threshold.relocate((size - threshold.getPrefWidth()) * 0.5, size * 0.11);
thresholdRotate.setPivotX(threshold.getPrefWidth() * 0.5);
thresholdRotate.setPivotY(size * 0.39);
thresholdRotate.setAngle(getSkinnable().getThreshold() * angleStep - 180 - getSkinnable().getStartAngle() - getSkinnable().getMinValue() * angleStep);
value.setText(String.format(Locale.US, "%." + getSkinnable().getDecimals() + "f", (needleRotate.getAngle() + getSkinnable().getStartAngle() - 180) / angleStep));
switch (getSkinnable().getNeedleType()) {
default:
needle.setPrefSize(size * 0.04, size * 0.425);
}
needle.relocate((size - needle.getPrefWidth()) * 0.5, size * 0.5 - needle.getPrefHeight());
needleRotate.setPivotX(needle.getPrefWidth() * 0.5);
needleRotate.setPivotY(needle.getPrefHeight());
needleHighlight.setPrefSize(size * 0.04, size * 0.425);
needleHighlight.setTranslateX((size - needle.getPrefWidth()) * 0.5);
needleHighlight.setTranslateY(size * 0.5 - needle.getPrefHeight());
knob.setPrefSize(size * 0.35, size * 0.35);
knob.setTranslateX((size - knob.getPrefWidth()) * 0.5);
knob.setTranslateY((size - knob.getPrefHeight()) * 0.5);
resizeText();
}
}