/* * 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.imgsplitflap; import javafx.animation.AnimationTimer; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.geometry.VPos; import javafx.scene.CacheHint; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.Pane; import javafx.scene.layout.Region; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.TextAlignment; import javafx.scene.transform.Rotate; import javafx.util.Duration; import java.util.ArrayList; import java.util.Arrays; /** * Created by * User: hansolo * Date: 18.04.13 * Time: 07:46 */ public class SplitFlap extends Region { public static final String[] TIME_0_TO_5 = {"1", "2", "3", "4", "5", "0"}; public static final String[] TIME_0_TO_9 = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}; public static final String[] NUMERIC = {" ", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}; public static final String[] ALPHANUMERIC = {" ", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}; public static final String[] ALPHA = {" ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}; public static final String[] EXTENDED = {" ", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "-", "/", ":", ",", "", ";", "@", "#", "+", "?", "!", "%", "$", "=", "<", ">"}; private static final Image BACKGROUND = new Image(SplitFlap.class.getResource("background.png").toExternalForm()); private static final Image FLAP = new Image(SplitFlap.class.getResource("flap.png").toExternalForm()); private static final double DEFAULT_WIDTH = 234; private static final double DEFAULT_HEIGHT = 402; private static final double MINIMUM_WIDTH = 5; private static final double MINIMUM_HEIGHT = 5; private static final double MAXIMUM_WIDTH = 1024; private static final double MAXIMUM_HEIGHT = 1024; private static final double MIN_FLIP_TIME = 16_666_666.6666667; // 60 fps private String[] selection; private ArrayList<String> selectedSet; private int currentSelectionIndex; private int nextSelectionIndex; private int previousSelectionIndex; private String _text; private StringProperty text; private Color _textColor; private ObjectProperty<Color> textColor; private double width; private double height; private Pane pane; private ImageView background; private ImageView flap; private Font font; private Canvas upperBackgroundText; private GraphicsContext ctxUpperBackgroundText; private Canvas lowerBackgroundText; private GraphicsContext ctxLowerBackgroundText; private Canvas flapTextFront; private GraphicsContext ctxTextFront; private Canvas flapTextBack; private GraphicsContext ctxTextBack; private Rotate rotateFlap; private Duration flipTime; private boolean flipping; private double angleStep; private double currentAngle; private AnimationTimer timer; // ******************** Constructors ************************************** public SplitFlap() { this(EXTENDED, " "); } public SplitFlap(final String[] SELECTION, final String TEXT) { selection = SELECTION; selectedSet = new ArrayList<>(64); selectedSet.addAll(Arrays.asList(selection)); _text = TEXT; _textColor = Color.WHITE; currentSelectionIndex = 0; nextSelectionIndex = 1; previousSelectionIndex = selectedSet.size() - 1; pane = new Pane(); rotateFlap = new Rotate(); rotateFlap.setAxis(Rotate.X_AXIS); rotateFlap.setAngle(0); flipTime = Duration.millis(100); flipping = false; angleStep = 180.0 / ((flipTime.toMillis() * 1_000_000) / (MIN_FLIP_TIME)); timer = new AnimationTimer() { @Override public void handle(long now) { flip(angleStep); } }; init(); initGraphics(); registerListeners(); } // ******************** Initialization ************************************ private void init() { if (Double.compare(getPrefWidth(), 0.0) <= 0 || Double.compare(getPrefHeight(), 0.0) <= 0 || Double.compare(getWidth(), 0.0) <= 0 || Double.compare(getHeight(), 0.0) <= 0) { if (getPrefWidth() > 0 && getPrefHeight() > 0) { setPrefSize(getPrefWidth(), getPrefHeight()); } else { setPrefSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); } } if (Double.compare(getMinWidth(), 0.0) <= 0 || Double.compare(getMinHeight(), 0.0) <= 0) { setMinSize(MINIMUM_WIDTH, MINIMUM_HEIGHT); } if (Double.compare(getMaxWidth(), 0.0) <= 0 || Double.compare(getMaxHeight(), 0.0) <= 0) { setMaxSize(MAXIMUM_WIDTH, MAXIMUM_HEIGHT); } } private void initGraphics() { background = new ImageView(BACKGROUND); background.setFitWidth(DEFAULT_WIDTH); background.setFitHeight(DEFAULT_HEIGHT); background.setPreserveRatio(true); background.setSmooth(true); background.setCache(true); flap = new ImageView(FLAP); flap.setFitWidth(DEFAULT_WIDTH * 0.8461538462); flap.setFitHeight(DEFAULT_HEIGHT * 0.407960199); flap.setPreserveRatio(true); flap.setSmooth(true); flap.setTranslateX(DEFAULT_HEIGHT * 0.0447761194); flap.setTranslateY(DEFAULT_HEIGHT * 0.0447761194); flap.setCache(true); flap.setCacheHint(CacheHint.ROTATE); flap.getTransforms().add(rotateFlap); rotateFlap.setPivotY(DEFAULT_HEIGHT * 0.460199005); flap.setCache(true); flap.setCacheHint(CacheHint.SPEED); upperBackgroundText = new Canvas(); ctxUpperBackgroundText = upperBackgroundText.getGraphicsContext2D(); ctxUpperBackgroundText.setTextBaseline(VPos.CENTER); ctxUpperBackgroundText.setTextAlign(TextAlignment.CENTER); lowerBackgroundText = new Canvas(); ctxLowerBackgroundText = lowerBackgroundText.getGraphicsContext2D(); ctxLowerBackgroundText.setTextBaseline(VPos.CENTER); ctxLowerBackgroundText.setTextAlign(TextAlignment.CENTER); flapTextFront = new Canvas(); flapTextFront.getTransforms().add(rotateFlap); ctxTextFront = flapTextFront.getGraphicsContext2D(); ctxTextFront.setTextBaseline(VPos.CENTER); ctxTextFront.setTextAlign(TextAlignment.CENTER); flapTextBack = new Canvas(); flapTextBack.getTransforms().add(rotateFlap); flapTextBack.setOpacity(0); ctxTextBack = flapTextBack.getGraphicsContext2D(); ctxTextBack.setTextBaseline(VPos.CENTER); ctxTextBack.setTextAlign(TextAlignment.CENTER); pane.getChildren().setAll(background, upperBackgroundText, lowerBackgroundText, flap, flapTextFront, flapTextBack); getChildren().setAll(pane); resize(); } private void registerListeners() { widthProperty().addListener(observable -> resize() ); heightProperty().addListener(observable -> resize() ); } // ******************** Methods ******************************************* public final String[] getSelection() { return selection; } public final void setSelection(final String[] SELECTION) { selection = SELECTION; selectedSet.clear(); selectedSet.addAll(Arrays.asList(selection)); } public double getFlipTime() { return flipTime.toMillis(); } public void setFlipTime(final double FLIP_TIME) { flipTime = Duration.millis(FLIP_TIME); angleStep = 180.0 / ((flipTime.toMillis() * 1_000_000) / (MIN_FLIP_TIME)); } public final String getText() { return null == text ? _text : text.get(); } public final void setText(final char CHAR) { setText(Character.toString(CHAR)); } public final void setText(final String TEXT) { if (TEXT.equals(getText())) return; if(!TEXT.isEmpty() && selectedSet.contains(TEXT)) { if (null == text) { _text = TEXT; } else { text.set(TEXT); } flipping = true; timer.start(); } else { if (null == text) { _text = selectedSet.get(0); } else { text.set(selectedSet.get(0)); } currentSelectionIndex = 0; nextSelectionIndex = currentSelectionIndex + 1 > selectedSet.size() ? 0 : currentSelectionIndex + 1; } } public final StringProperty textProperty() { if (null == text) { text = new SimpleStringProperty(this, "text", _text); } return text; } public final String getNextText() { return selectedSet.get(nextSelectionIndex); } public final String getPreviousText() { return selectedSet.get(previousSelectionIndex); } public final Color getTextColor() { return null == textColor ? _textColor : textColor.get(); } public final void setTextColor(final Color TEXT_COLOR) { if (null == textColor) { _textColor = TEXT_COLOR; } else { textColor.set(TEXT_COLOR); } } public final ObjectProperty<Color> textColorProperty() { if (null == textColor) { textColor = new SimpleObjectProperty<>(this, "textColor", _textColor); } return textColor; } @Override protected double computePrefWidth(final double PREF_HEIGHT) { double prefHeight = DEFAULT_HEIGHT; if (PREF_HEIGHT != -1) { prefHeight = Math.max(0, PREF_HEIGHT - getInsets().getTop() - getInsets().getBottom()); } return super.computePrefWidth(prefHeight); } @Override protected double computePrefHeight(final double PREF_WIDTH) { double prefWidth = DEFAULT_WIDTH; if (PREF_WIDTH != -1) { prefWidth = Math.max(0, PREF_WIDTH - getInsets().getLeft() - getInsets().getRight()); } return super.computePrefWidth(prefWidth); } @Override protected double computeMinWidth(final double MIN_HEIGHT) { return super.computeMinWidth(Math.max(MINIMUM_HEIGHT, MIN_HEIGHT - getInsets().getTop() - getInsets().getBottom())); } @Override protected double computeMinHeight(final double MIN_WIDTH) { return super.computeMinHeight(Math.max(MINIMUM_WIDTH, MIN_WIDTH - getInsets().getLeft() - getInsets().getRight())); } @Override protected double computeMaxWidth(final double MAX_HEIGHT) { return super.computeMaxWidth(Math.min(MAXIMUM_HEIGHT, MAX_HEIGHT - getInsets().getTop() - getInsets().getBottom())); } @Override protected double computeMaxHeight(final double MAX_WIDTH) { return super.computeMaxHeight(Math.min(MAXIMUM_WIDTH, MAX_WIDTH - getInsets().getLeft() - getInsets().getRight())); } private void flip(final double ANGLE_STEP) { currentAngle += ANGLE_STEP; if (Double.compare(currentAngle, 180) >= 0) { currentAngle = 0; flapTextBack.setOpacity(0); flapTextFront.setOpacity(1); currentSelectionIndex++; if (currentSelectionIndex >= selectedSet.size()) { currentSelectionIndex = 0; } nextSelectionIndex = currentSelectionIndex + 1; if (nextSelectionIndex >= selectedSet.size()) { nextSelectionIndex = 0; } if (selectedSet.get(currentSelectionIndex).equals(getText())) { timer.stop(); flipping = false; rotateFlap.setAngle(currentAngle); } refreshTextCtx(); } if (currentAngle > 90) { flapTextFront.setOpacity(0); flapTextBack.setOpacity(1); } if (flipping) { rotateFlap.setAngle(currentAngle); } } private void refreshTextCtx() { //double flapX = flap.getTranslateX(); //double flapY = flap.getTranslateY(); double flapWidth = flap.getLayoutBounds().getWidth(); //flapTextFront.getWidth(); double flapHeight = flap.getLayoutBounds().getHeight(); //flapTextFront.getHeight(); // set the text on the upper background ctxUpperBackgroundText.clearRect(0, 0, flapWidth, flapHeight); ctxUpperBackgroundText.setFill(getTextColor()); ctxUpperBackgroundText.fillText(selectedSet.get(nextSelectionIndex), width * 0.44, height * 0.43); // set the text on the lower background ctxLowerBackgroundText.clearRect(0, 0, flapWidth, flapHeight); ctxLowerBackgroundText.setFill(getTextColor()); ctxLowerBackgroundText.fillText(selectedSet.get(currentSelectionIndex), width * 0.44, 0); // set the text on the flap front ctxTextFront.clearRect(0, 0, flapWidth, flapHeight); ctxTextFront.setFill(getTextColor()); ctxTextFront.fillText(selectedSet.get(currentSelectionIndex), width * 0.44, height * 0.43); // set the text on the flap back ctxTextBack.clearRect(0, 0, flapWidth, flapHeight); ctxTextBack.setFill(getTextColor()); ctxTextBack.save(); ctxTextBack.scale(1,-1); ctxTextBack.fillText(selectedSet.get(nextSelectionIndex), width * 0.44, -height * 0.405); ctxTextBack.restore(); } // ******************** Resizing ****************************************** private void resize() { width = getWidth(); height = getHeight(); if (width > 0 && height > 0) { background.setCache(false); flap.setCache(false); background.setFitWidth(width); background.setFitHeight(height); flap.setFitWidth(width * 0.8461538462); flap.setFitHeight(height * 0.407960199); if (width < height) { flap.setTranslateX(width * 0.0769230769); flap.setTranslateY(width * 0.0769230769); rotateFlap.setPivotY(width * 0.715); } else { flap.setTranslateX(height * 0.0447761194); flap.setTranslateY(height * 0.0447761194); rotateFlap.setPivotY(height * 0.460199005); } background.setCache(true); flap.setCache(true); flap.setCacheHint(CacheHint.ROTATE); font = Font.font("Droid Sans Mono", background.getLayoutBounds().getHeight() * 0.7); upperBackgroundText.setWidth(flap.getLayoutBounds().getWidth()); upperBackgroundText.setHeight(flap.getLayoutBounds().getHeight()); upperBackgroundText.setTranslateX(flap.getTranslateX()); upperBackgroundText.setTranslateY(flap.getTranslateY()); lowerBackgroundText.setWidth(flap.getLayoutBounds().getWidth()); lowerBackgroundText.setHeight(flap.getLayoutBounds().getHeight()); lowerBackgroundText.setTranslateX(flap.getTranslateX()); lowerBackgroundText.setTranslateY(0.4701492537 * background.getLayoutBounds().getHeight()); flapTextFront.setWidth(flap.getLayoutBounds().getWidth()); flapTextFront.setHeight(flap.getLayoutBounds().getHeight()); flapTextFront.setTranslateX(flap.getTranslateX()); flapTextFront.setTranslateY(flap.getTranslateY()); flapTextBack.setWidth(flap.getLayoutBounds().getWidth()); flapTextBack.setHeight(flap.getLayoutBounds().getHeight()); flapTextBack.setTranslateX(flap.getTranslateX()); flapTextBack.setTranslateY(flap.getTranslateY()); ctxUpperBackgroundText.setFont(font); ctxLowerBackgroundText.setFont(font); ctxTextFront.setFont(font); ctxTextBack.setFont(font); refreshTextCtx(); } } }