Package eu.hansolo.enzo.gauge.skin

Source Code of eu.hansolo.enzo.gauge.skin.GaugeSkin

/*
* 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();
    }
}
TOP

Related Classes of eu.hansolo.enzo.gauge.skin.GaugeSkin

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.