All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jfxtras.labs.internal.scene.control.skin.OdometerSkin Maven / Gradle / Ivy

There is a newer version: 9.0-r1
Show newest version
/*
 * Copyright (c) 2012, JFXtras
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions are met:
 *       * Redistributions of source code must retain the above copyright
 *         notice, this list of conditions and the following disclaimer.
 *       * Redistributions in binary form must reproduce the above copyright
 *         notice, this list of conditions and the following disclaimer in the
 *         documentation and/or other materials provided with the distribution.
 *       * Neither the name of the  nor the
 *         names of its contributors may be used to endorse or promote products
 *         derived from this software without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 *   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *   DISCLAIMED. IN NO EVENT SHALL  BE LIABLE FOR ANY
 *   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jfxtras.labs.internal.scene.control.skin;

import com.sun.javafx.scene.control.skin.SkinBase;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.VPos;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.FillRule;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.shape.StrokeType;
import javafx.scene.text.Font;
import javafx.scene.text.FontSmoothingType;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.util.Duration;
import jfxtras.labs.internal.scene.control.behavior.OdometerBehavior;
import jfxtras.labs.scene.control.gauge.Odometer;

import java.util.LinkedList;
import java.util.List;


/**
 * Created by
 * User: hansolo
 * Date: 16.03.12
 * Time: 15:24
 */
public class OdometerSkin extends SkinBase {
    private Odometer   control;
    private boolean    isDirty;
    private boolean    initialized;
    private Group      foreground;
    private List listOfDials;
    private Group      background;
    private Font       font;


    // ******************** Constructors **************************************
    public OdometerSkin(final Odometer CONTROL) {
        super(CONTROL, new OdometerBehavior(CONTROL));
        control     = CONTROL;
        initialized = false;
        isDirty     = false;
        foreground  = new Group();
        listOfDials = new LinkedList();
        background  = new Group();
        init();
    }

    private void init() {
        double prefWidth  = control.getPrefWidth();
        double prefHeight = control.getPrefHeight();
        if (prefWidth <= 0) {
            if (prefHeight <= 0) {
                prefHeight = 40;
            }
            prefWidth = 0.5925925925925926 * prefHeight * (control.getNoOfDigits() + control.getNoOfDecimals());
        }
        if (prefHeight <= 0) {
            if (prefWidth <= 0) {
                prefWidth = 0.5925925925925926 * 40 * (control.getNoOfDigits() + control.getNoOfDecimals());
            }
            prefHeight = (prefWidth / (control.getNoOfDigits() + control.getNoOfDecimals())) * 1.6875;
        }
        control.setPrefSize(prefWidth, prefHeight);

        // Register listeners
        registerChangeListener(control.rotationsProperty(), "ROTATION");
        registerChangeListener(control.rotationPresetProperty(), "ROTATION_PRESET");
        registerChangeListener(control.noOfDigitsProperty(), "NO_OF_DIGITS");
        registerChangeListener(control.noOfDecimalsProperty(), "NO_OF_DECIMALS");
        registerChangeListener(control.decimalColorProperty(), "DECIMAL_COLOR");
        registerChangeListener(control.colorProperty(), "COLOR");
        registerChangeListener(control.prefWidthProperty(), "PREF_WIDTH");
        registerChangeListener(control.prefHeightProperty(), "PREF_HEIGHT");

        initialized = true;
        repaint();
    }


    // ******************** Methods *******************************************
    @Override protected void handleControlPropertyChanged(final String PROPERTY) {
        super.handleControlPropertyChanged(PROPERTY);
        if ("ROTATION_PRESET".equals(PROPERTY)) {
            String rot = Integer.toString(control.getRotations());
            int noOfDials = control.getNoOfDigits() + control.getNoOfDecimals();
            if (rot.length() > noOfDials) {
                rot = rot.substring(rot.length() - noOfDials);
            } else if (rot.length() < noOfDials) {
                StringBuilder rotSB = new StringBuilder(noOfDials);
                for (int i = 0 ; i < (noOfDials - rot.length()) ; i++) {
                    rotSB.append("0");
                }
                rotSB.append(rot);
                rot = rotSB.toString();
            }
            int index = listOfDials.size() - 1;
            for (final char c : rot.toCharArray()) {
                listOfDials.get(index).setToNumber(Integer.parseInt(String.valueOf(c)));
                index--;
            }
        } else if ("ROTATION".equals(PROPERTY)) {
            for (int i = 1 ; i < (control.getNoOfDigits() + control.getNoOfDecimals() + 1) ; i++) {
                if (control.getRotations() == 0) {
                    listOfDials.get(i - 1).reset();
                } else {
                    listOfDials.get(i - 1).setNumber(control.getDialPosition(i));
                }
            }
        } else if ("NO_OF_DIGITS".equals(PROPERTY)) {
            control.setPrefSize(0.5925925925925926 * getPrefHeight() * (control.getNoOfDigits() + control.getNoOfDecimals()), getPrefHeight());
            repaint();
        } else if ("NO_OF_DECIMALS".equals(PROPERTY)) {
            control.setPrefSize(0.5925925925925926 * getPrefHeight() * (control.getNoOfDigits() + control.getNoOfDecimals()), getPrefHeight());
            repaint();
        } else if ("DECIMAL_COLOR".equals(PROPERTY)) {
            repaint();
        } else if ("COLOR".equals(PROPERTY)) {
            repaint();
        } else if ("PREF_WIDTH".equals(PROPERTY)) {
            double prefHeight = (control.getPrefWidth() / (control.getNoOfDigits() + control.getNoOfDecimals())) * 1.6875;
            if (Double.compare(control.getPrefHeight(), prefHeight) != 0) {
                control.setPrefHeight(prefHeight);
            }
            repaint();
        } else if ("PREF_HEIGHT".equals(PROPERTY)) {
            double prefWidth = control.getPrefHeight() * 0.5925925925925926 * (control.getNoOfDigits() + control.getNoOfDecimals());
            if (Double.compare(control.getPrefWidth(), prefWidth) != 0) {
                control.setPrefWidth(prefWidth);
            }
            repaint();
        }
    }

    public final void repaint() {
        isDirty = true;
        requestLayout();
    }

    @Override public void layoutChildren() {
        if (!isDirty) {
            return;
        }
        if (!initialized) {
            init();
        }
        if (control.getScene() != null) {
            font = Font.loadFont(getClass().getResourceAsStream("/jfxtras/labs/scene/control/gauge/droidsansmono.ttf"), (0.85 * control.getPrefHeight()));
            setClip(new Rectangle(0, 1, control.getPrefWidth(), control.getPrefHeight()));
            getChildren().clear();
            drawBackground();
            setupDials();
            drawForeground();
            getChildren().add(background);
            for (Dial dial : listOfDials) {
                getChildren().addAll(dial.getNextNumberGroup(), dial.getCurrentNumberGroup());
            }
            getChildren().add(foreground);
        }
        isDirty = false;

        super.layoutChildren();
    }

    @Override public final Odometer getSkinnable() {
        return control;
    }

    @Override public final void dispose() {
        control = null;
    }


    // ******************** Drawing related ***********************************
    public final void drawBackground() {
        final double WIDTH = control.getPrefWidth();
        final double HEIGHT = control.getPrefHeight();
        final double COLUMN_WIDTH  = control.getPrefHeight() * 0.5925925925925926;

        background.getChildren().clear();

        // background
        Rectangle digitBackground = new Rectangle(0, 0, WIDTH, HEIGHT);
        digitBackground.setFill(control.getColor());
        digitBackground.setStroke(null);
        background.getChildren().add(digitBackground);

        Rectangle decimalBackground = new Rectangle(0, 0, control.getNoOfDecimals() * COLUMN_WIDTH, HEIGHT);
        decimalBackground.setFill(control.getDecimalColor());
        decimalBackground.setStroke(null);
        decimalBackground.setTranslateX(WIDTH - decimalBackground.getLayoutBounds().getWidth());
        background.getChildren().add(decimalBackground);

        for (int i = 0 ; i < (control.getNoOfDigits()) ; i++) {
            Path path = new Path();
            path.setFillRule(FillRule.EVEN_ODD);
            path.getElements().add(new MoveTo(0, 0));
            path.getElements().add(new LineTo(0, HEIGHT));
            path.getElements().add(new MoveTo(COLUMN_WIDTH, 0));
            path.getElements().add(new LineTo(COLUMN_WIDTH, HEIGHT));
            path.setStrokeWidth(0.5);
            path.setStrokeType(StrokeType.CENTERED);
            path.setTranslateX(i * COLUMN_WIDTH);
            path.setStroke(Color.color(0.0, 0.0, 0.0, 0.8));
            background.getChildren().add(path);
        }
        Path path = new Path();
        path.setFillRule(FillRule.EVEN_ODD);
        path.getElements().add(new MoveTo(WIDTH, 0));
        path.getElements().add(new LineTo(WIDTH, HEIGHT));
        path.setStrokeWidth(0.5);
        path.setStrokeType(StrokeType.CENTERED);
        path.setStroke(Color.color(0.0, 0.0, 0.0, 0.8));
        background.getChildren().add(path);
        background.setCache(true);
    }

    public final void drawForeground() {
        final double WIDTH = control.getPrefWidth();
        final double HEIGHT = control.getPrefHeight();

        foreground.getChildren().clear();

        final Shape OVERLAY = new Rectangle(0, 0, WIDTH, HEIGHT);
        final LinearGradient OVERLAY_FILL  = new LinearGradient(0, 0,
                                                                0, HEIGHT,
                                                                false, CycleMethod.NO_CYCLE,
                                                                new Stop(0.0, Color.color(0, 0, 0, 1)),
                                                                new Stop(0.15, Color.color(0, 0, 0, 0.4)),
                                                                new Stop(0.33, Color.color(1, 1, 1, 0.45)),
                                                                new Stop(0.46, Color.color(1, 1, 1, 0.0)),
                                                                new Stop(0.85, Color.color(0, 0, 0, 0.4)),
                                                                new Stop(1.0, Color.color(0, 0, 0, 1)));
        OVERLAY.setFill(OVERLAY_FILL);
        OVERLAY.setStroke(null);

        foreground.getChildren().add(OVERLAY);
    }

    public final void setupDials() {
        final double COLUMN_WIDTH  = control.getPrefHeight() * 0.5925925925925926;
        final double WIDTH = control.getPrefWidth();
        final double HEIGHT = control.getPrefHeight();
        for (int i = 0 ; i < (control.getNoOfDigits() + control.getNoOfDecimals()) ; i++) {
            double offsetX = WIDTH / 2.0 - ((i + 1) * COLUMN_WIDTH) + COLUMN_WIDTH / 2;
            Color numberColor = i < control.getNoOfDecimals() ? control.getNumberDecimalColor() : control.getNumberColor();
            Dial dial = new Dial(numberColor, offsetX, HEIGHT);
            listOfDials.add(dial);
        }
    }

    private class Dial {
        private Group               nextNumberGroup;
        private Text                nextNumber;
        private Group               currentNumberGroup;
        private Text                currentNumber;
        private TranslateTransition next;
        private TranslateTransition current;
        private ParallelTransition  parallel;

        protected Dial(final Color NUMBER_COLOR, final double OFFSET_X, final double HEIGHT) {
            nextNumber         = new Text();
            nextNumberGroup    = createGroup(NUMBER_COLOR, nextNumber, 1, OFFSET_X, -HEIGHT);
            currentNumber      = new Text();
            currentNumberGroup = createGroup(NUMBER_COLOR, currentNumber, 0, OFFSET_X, 0);
            next               = new TranslateTransition(Duration.millis(control.getInterval()), nextNumberGroup);
            current            = new TranslateTransition(Duration.millis(control.getInterval()), currentNumberGroup);
            //parallel           = new ParallelTransition();
            next.setFromY(-control.getPrefHeight());
            next.setToY(0);
            next.setInterpolator(Interpolator.LINEAR);
            current.setFromY(0);
            current.setToY(control.getPrefHeight());
            current.setInterpolator(Interpolator.LINEAR);
            current.setDelay(Duration.ZERO);
            parallel = new ParallelTransition(next, current);
        }

        protected void setNumber(final int NUMBER) {
            if (parallel.getStatus() == Animation.Status.RUNNING) {
                parallel.stop();
                increaseNumbers();
            }
            if (!Integer.toString(NUMBER).equals(currentNumber.getText())) {
                parallel.play();

                parallel.setOnFinished(new EventHandler() {
                    @Override
                    public void handle(ActionEvent actionEvent) {
                    increaseNumbers();
                    }
                });
            }
        }

        protected void reset() {
            parallel.stop();
            nextNumberGroup.setTranslateY(-control.getPrefHeight());
            nextNumber.setText("1");
            currentNumberGroup.setTranslateY(0);
            currentNumber.setText("0");
        }

        protected void setToNumber(final int NUMBER) {
            int thisNumber = NUMBER < 0 ? 0 : (NUMBER > 9 ? 9 : NUMBER);
            int commingNumber = thisNumber + 1 > 9 ? 0 : thisNumber + 1;
            parallel.stop();
            nextNumberGroup.setTranslateY(-control.getPrefHeight());
            nextNumber.setText(Integer.toString(commingNumber));
            currentNumberGroup.setTranslateY(0);
            currentNumber.setText(Integer.toString(thisNumber));
        }

        protected Group getNextNumberGroup() {
            return nextNumberGroup;
        }

        protected Group getCurrentNumberGroup() {
            return currentNumberGroup;
        }

        private Group createGroup(final Color NUMBER_COLOR, Text text, final int NUMBER, final double OFFSET_X, final double OFFSET_Y) {
            Group group = new Group();
            text.setText(Integer.toString(NUMBER));
            text.setTextAlignment(TextAlignment.CENTER);
            text.setTextOrigin(VPos.CENTER);
            text.setFont(font);
            text.setFill(NUMBER_COLOR);
            text.setStroke(null);
            text.setFontSmoothingType(FontSmoothingType.LCD);
            group.getChildren().addAll(text);
            group.setTranslateX(OFFSET_X);
            group.setTranslateY(OFFSET_Y);
            return group;
        }

        private void increaseNumbers() {
            int number = Integer.parseInt(nextNumber.getText());
            nextNumberGroup.setTranslateY(-control.getPrefHeight());
            nextNumber.setText(number == 9 ? "0" : Integer.toString(number + 1));
            currentNumberGroup.setTranslateY(0);
            currentNumber.setText(Integer.toString(number));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy