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

com.jfoenix.skins.JFXCustomColorPicker Maven / Gradle / Ivy

There is a newer version: 9.0.10
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 com.jfoenix.skins;

import com.jfoenix.effects.JFXDepthManager;
import com.jfoenix.transitions.CachedTransition;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;

import java.util.ArrayList;

import static javafx.animation.Interpolator.EASE_BOTH;


/**
 * @author Shadi Shaheen
 */
class JFXCustomColorPicker extends Pane {

    ObjectProperty selectedPath = new SimpleObjectProperty<>();
    private MoveTo startPoint;
    private CubicCurveTo curve0To;
    private CubicCurveTo outerCircleCurveTo;
    private CubicCurveTo curve1To;
    private CubicCurveTo innerCircleCurveTo;
    private ArrayList curves = new ArrayList<>();

    private double distance = 200;
    private double centerX = distance;
    private double centerY = distance;
    private double radius = 110;

    private int shapesNumber = 13;
    private ArrayList shapes = new ArrayList<>();
    private CachedTransition showAnimation;
    private JFXColorPickerUI hslColorPicker;

    public JFXCustomColorPicker() {
        this.setPickOnBounds(false);
        this.setMinSize(distance * 2, distance * 2);

        final DoubleProperty rotationAngle = new SimpleDoubleProperty(2.1);

        // draw recent colors shape using cubic curves
        init(rotationAngle, centerX + 53, centerY + 162);

        hslColorPicker = new JFXColorPickerUI((int) distance);
        hslColorPicker.setLayoutX(centerX - distance / 2);
        hslColorPicker.setLayoutY(centerY - distance / 2);
        this.getChildren().add(hslColorPicker);

        // add recent colors shapes
        final int shapesStartIndex = this.getChildren().size();
        final int shapesEndIndex = shapesStartIndex + shapesNumber;
        for (int i = 0; i < shapesNumber; i++) {
            final double angle = 2 * i * Math.PI / shapesNumber;
            final RecentColorPath path = new RecentColorPath(startPoint,
                curve0To,
                outerCircleCurveTo,
                curve1To,
                innerCircleCurveTo);
            shapes.add(path);
            path.setPickOnBounds(false);
            final Rotate rotate = new Rotate(Math.toDegrees(angle), centerX, centerY);
            path.getTransforms().add(rotate);
            this.getChildren().add(shapesStartIndex, path);
            path.setFill(Color.valueOf(getDefaultColor(i)));
            path.addEventHandler(MouseEvent.MOUSE_CLICKED, (event) -> selectedPath.set(path));
        }

        // add selection listeners
        selectedPath.addListener((o, oldVal, newVal) -> {
            if (oldVal != null) {
                hslColorPicker.removeColorSelectionNode(oldVal);
                oldVal.playTransition(-1);
            }
            // re-arrange children
            while (this.getChildren().indexOf(newVal) != shapesEndIndex - 1) {
                final Node temp = this.getChildren().get(shapesEndIndex - 1);
                this.getChildren().remove(shapesEndIndex - 1);
                this.getChildren().add(shapesStartIndex, temp);
            }
            // update path fill according to the color picker
            newVal.setStroke(Color.rgb(255, 255, 255, 0.87));
            newVal.playTransition(1);
            hslColorPicker.moveToColor((Color) newVal.getFill());
            hslColorPicker.addColorSelectionNode(newVal);
        });
        // init selection
        selectedPath.set((RecentColorPath) this.getChildren().get(shapesStartIndex));
    }


    public int getShapesNumber() {
        return shapesNumber;
    }

    public int getSelectedIndex() {
        if (selectedPath.get() != null) {
            return shapes.indexOf(selectedPath.get());
        }
        return -1;
    }

    public void setColor(final Color color) {
        shapes.get(getSelectedIndex()).setFill(color);
        hslColorPicker.moveToColor(color);
    }

    public Color getColor(final int index) {
        if (index >= 0 && index < shapes.size()) {
            return (Color) shapes.get(index).getFill();
        } else {
            return Color.WHITE;
        }
    }


    public void preAnimate() {
        final CubicCurve firstCurve = curves.get(0);
        final double x = firstCurve.getStartX();
        final double y = firstCurve.getStartY();
        firstCurve.setStartX(centerX);
        firstCurve.setStartY(centerY);

        final CubicCurve secondCurve = curves.get(1);
        final double x1 = secondCurve.getStartX();
        final double y1 = secondCurve.getStartY();
        secondCurve.setStartX(centerX);
        secondCurve.setStartY(centerY);

        final double cx1 = firstCurve.getControlX1();
        final double cy1 = firstCurve.getControlY1();
        firstCurve.setControlX1(centerX + radius);
        firstCurve.setControlY1(centerY + radius / 2);

        final KeyFrame keyFrame = new KeyFrame(Duration.millis(1000),
            new KeyValue(firstCurve.startXProperty(), x, EASE_BOTH),
            new KeyValue(firstCurve.startYProperty(), y, EASE_BOTH),
            new KeyValue(secondCurve.startXProperty(), x1, EASE_BOTH),
            new KeyValue(secondCurve.startYProperty(), y1, EASE_BOTH),
            new KeyValue(firstCurve.controlX1Property(), cx1, EASE_BOTH),
            new KeyValue(firstCurve.controlY1Property(), cy1, EASE_BOTH)
        );
        final Timeline timeline = new Timeline(keyFrame);
        showAnimation = new CachedTransition(this, timeline) {
            {
                setCycleDuration(Duration.millis(240));
                setDelay(Duration.millis(0));
            }
        };
    }

    public void animate() {
        showAnimation.play();
    }

    private void init(final DoubleProperty rotationAngle, final double initControlX1, final double initControlY1) {

        final Circle innerCircle = new Circle(centerX, centerY, radius, Color.TRANSPARENT);
        final Circle outerCircle = new Circle(centerX, centerY, radius * 2, Color.web("blue", 0.5));

        // Create a composite shape of 4 cubic curves
        // create 2 cubic curves of the shape
        createQuadraticCurve(rotationAngle, initControlX1, initControlY1);

        // inner circle curve
        final CubicCurve innerCircleCurve = new CubicCurve();
        innerCircleCurve.startXProperty().bind(curves.get(0).startXProperty());
        innerCircleCurve.startYProperty().bind(curves.get(0).startYProperty());
        innerCircleCurve.endXProperty().bind(curves.get(1).startXProperty());
        innerCircleCurve.endYProperty().bind(curves.get(1).startYProperty());
        curves.get(0).startXProperty().addListener((o, oldVal, newVal) -> {
            final Point2D controlPoint = makeControlPoint(newVal.doubleValue(),
                curves.get(0).getStartY(),
                innerCircle,
                shapesNumber,
                -1);
            innerCircleCurve.setControlX1(controlPoint.getX());
            innerCircleCurve.setControlY1(controlPoint.getY());
        });
        curves.get(0).startYProperty().addListener((o, oldVal, newVal) -> {
            final Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(),
                newVal.doubleValue(),
                innerCircle,
                shapesNumber,
                -1);
            innerCircleCurve.setControlX1(controlPoint.getX());
            innerCircleCurve.setControlY1(controlPoint.getY());
        });
        curves.get(1).startXProperty().addListener((o, oldVal, newVal) -> {
            final Point2D controlPoint = makeControlPoint(newVal.doubleValue(),
                curves.get(1).getStartY(),
                innerCircle,
                shapesNumber,
                1);
            innerCircleCurve.setControlX2(controlPoint.getX());
            innerCircleCurve.setControlY2(controlPoint.getY());
        });
        curves.get(1).startYProperty().addListener((o, oldVal, newVal) -> {
            final Point2D controlPoint = makeControlPoint(curves.get(1).getStartX(),
                newVal.doubleValue(),
                innerCircle,
                shapesNumber,
                1);
            innerCircleCurve.setControlX2(controlPoint.getX());
            innerCircleCurve.setControlY2(controlPoint.getY());
        });
        Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(),
            curves.get(0).getStartY(),
            innerCircle,
            shapesNumber,
            -1);
        innerCircleCurve.setControlX1(controlPoint.getX());
        innerCircleCurve.setControlY1(controlPoint.getY());
        controlPoint = makeControlPoint(curves.get(1).getStartX(),
            curves.get(1).getStartY(),
            innerCircle,
            shapesNumber,
            1);
        innerCircleCurve.setControlX2(controlPoint.getX());
        innerCircleCurve.setControlY2(controlPoint.getY());

        // outer circle curve
        final CubicCurve outerCircleCurve = new CubicCurve();
        outerCircleCurve.startXProperty().bind(curves.get(0).endXProperty());
        outerCircleCurve.startYProperty().bind(curves.get(0).endYProperty());
        outerCircleCurve.endXProperty().bind(curves.get(1).endXProperty());
        outerCircleCurve.endYProperty().bind(curves.get(1).endYProperty());
        controlPoint = makeControlPoint(curves.get(0).getEndX(),
            curves.get(0).getEndY(),
            outerCircle,
            shapesNumber,
            -1);
        outerCircleCurve.setControlX1(controlPoint.getX());
        outerCircleCurve.setControlY1(controlPoint.getY());
        controlPoint = makeControlPoint(curves.get(1).getEndX(), curves.get(1).getEndY(), outerCircle, shapesNumber, 1);
        outerCircleCurve.setControlX2(controlPoint.getX());
        outerCircleCurve.setControlY2(controlPoint.getY());

        startPoint = new MoveTo();
        startPoint.xProperty().bind(curves.get(0).startXProperty());
        startPoint.yProperty().bind(curves.get(0).startYProperty());

        curve0To = new CubicCurveTo();
        curve0To.controlX1Property().bind(curves.get(0).controlX1Property());
        curve0To.controlY1Property().bind(curves.get(0).controlY1Property());
        curve0To.controlX2Property().bind(curves.get(0).controlX2Property());
        curve0To.controlY2Property().bind(curves.get(0).controlY2Property());
        curve0To.xProperty().bind(curves.get(0).endXProperty());
        curve0To.yProperty().bind(curves.get(0).endYProperty());

        outerCircleCurveTo = new CubicCurveTo();
        outerCircleCurveTo.controlX1Property().bind(outerCircleCurve.controlX1Property());
        outerCircleCurveTo.controlY1Property().bind(outerCircleCurve.controlY1Property());
        outerCircleCurveTo.controlX2Property().bind(outerCircleCurve.controlX2Property());
        outerCircleCurveTo.controlY2Property().bind(outerCircleCurve.controlY2Property());
        outerCircleCurveTo.xProperty().bind(outerCircleCurve.endXProperty());
        outerCircleCurveTo.yProperty().bind(outerCircleCurve.endYProperty());

        curve1To = new CubicCurveTo();
        curve1To.controlX1Property().bind(curves.get(1).controlX2Property());
        curve1To.controlY1Property().bind(curves.get(1).controlY2Property());
        curve1To.controlX2Property().bind(curves.get(1).controlX1Property());
        curve1To.controlY2Property().bind(curves.get(1).controlY1Property());
        curve1To.xProperty().bind(curves.get(1).startXProperty());
        curve1To.yProperty().bind(curves.get(1).startYProperty());

        innerCircleCurveTo = new CubicCurveTo();
        innerCircleCurveTo.controlX1Property().bind(innerCircleCurve.controlX2Property());
        innerCircleCurveTo.controlY1Property().bind(innerCircleCurve.controlY2Property());
        innerCircleCurveTo.controlX2Property().bind(innerCircleCurve.controlX1Property());
        innerCircleCurveTo.controlY2Property().bind(innerCircleCurve.controlY1Property());
        innerCircleCurveTo.xProperty().bind(innerCircleCurve.startXProperty());
        innerCircleCurveTo.yProperty().bind(innerCircleCurve.startYProperty());
    }


    private void createQuadraticCurve(final DoubleProperty rotationAngle, final double initControlX1, final double initControlY1) {
        for (int i = 0; i < 2; i++) {

            double angle = 2 * i * Math.PI / shapesNumber;
            double xOffset = radius * Math.cos(angle);
            double yOffset = radius * Math.sin(angle);
            final double startx = centerX + xOffset;
            final double starty = centerY + yOffset;

            final double diffStartCenterX = startx - centerX;
            final double diffStartCenterY = starty - centerY;
            final double sinRotAngle = Math.sin(rotationAngle.get());
            final double cosRotAngle = Math.cos(rotationAngle.get());
            final double startXR = cosRotAngle * diffStartCenterX - sinRotAngle * diffStartCenterY + centerX;
            final double startYR = sinRotAngle * diffStartCenterX + cosRotAngle * diffStartCenterY + centerY;

            angle = 2 * i * Math.PI / shapesNumber;
            xOffset = distance * Math.cos(angle);
            yOffset = distance * Math.sin(angle);

            final double endx = centerX + xOffset;
            final double endy = centerY + yOffset;

            final CubicCurve curvedLine = new CubicCurve();
            curvedLine.setStartX(startXR);
            curvedLine.setStartY(startYR);
            curvedLine.setControlX1(startXR);
            curvedLine.setControlY1(startYR);
            curvedLine.setControlX2(endx);
            curvedLine.setControlY2(endy);
            curvedLine.setEndX(endx);
            curvedLine.setEndY(endy);
            curvedLine.setStroke(Color.FORESTGREEN);
            curvedLine.setStrokeWidth(1);
            curvedLine.setStrokeLineCap(StrokeLineCap.ROUND);
            curvedLine.setFill(Color.TRANSPARENT);
            curvedLine.setMouseTransparent(true);
            rotationAngle.addListener((o, oldVal, newVal) -> {
                final double newstartXR = ((cosRotAngle * diffStartCenterX) - (sinRotAngle * diffStartCenterY)) + centerX;
                final double newstartYR = (sinRotAngle * diffStartCenterX) + (cosRotAngle * diffStartCenterY) + centerY;
                curvedLine.setStartX(newstartXR);
                curvedLine.setStartY(newstartYR);
            });

            curves.add(curvedLine);

            if (i == 0) {
                curvedLine.setControlX1(initControlX1);
                curvedLine.setControlY1(initControlY1);
            } else {
                final CubicCurve firstCurve = curves.get(0);
                final double curveTheta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber;

                curvedLine.controlX1Property().bind(Bindings.createDoubleBinding(() -> {
                    final double a = firstCurve.getControlX1() - centerX;
                    final double b = Math.sin(curveTheta) * (firstCurve.getControlY1() - centerY);
                    return ((Math.cos(curveTheta) * a) - b) + centerX;
                }, firstCurve.controlX1Property(), firstCurve.controlY1Property()));

                curvedLine.controlY1Property().bind(Bindings.createDoubleBinding(() -> {
                    final double a = Math.sin(curveTheta) * (firstCurve.getControlX1() - centerX);
                    final double b = Math.cos(curveTheta) * (firstCurve.getControlY1() - centerY);
                    return a + b + centerY;
                }, firstCurve.controlX1Property(), firstCurve.controlY1Property()));


                curvedLine.controlX2Property().bind(Bindings.createDoubleBinding(() -> {
                    final double a = firstCurve.getControlX2() - centerX;
                    final double b = firstCurve.getControlY2() - centerY;
                    return ((Math.cos(curveTheta) * a) - (Math.sin(curveTheta) * b)) + centerX;
                }, firstCurve.controlX2Property(), firstCurve.controlY2Property()));

                curvedLine.controlY2Property().bind(Bindings.createDoubleBinding(() -> {
                    final double a = Math.sin(curveTheta) * (firstCurve.getControlX2() - centerX);
                    final double b = Math.cos(curveTheta) * (firstCurve.getControlY2() - centerY);
                    return a + b + centerY;
                }, firstCurve.controlX2Property(), firstCurve.controlY2Property()));
            }
        }
    }

    private String getDefaultColor(final int i) {
        String color = "#FFFFFF";
        switch (i) {
            case 0:
                color = "#8F3F7E";
                break;
            case 1:
                color = "#B5305F";
                break;
            case 2:
                color = "#CE584A";
                break;
            case 3:
                color = "#DB8D5C";
                break;
            case 4:
                color = "#DA854E";
                break;
            case 5:
                color = "#E9AB44";
                break;
            case 6:
                color = "#FEE435";
                break;
            case 7:
                color = "#99C286";
                break;
            case 8:
                color = "#01A05E";
                break;
            case 9:
                color = "#4A8895";
                break;
            case 10:
                color = "#16669B";
                break;
            case 11:
                color = "#2F65A5";
                break;
            case 12:
                color = "#4E6A9C";
                break;
            default:
                break;
        }
        return color;
    }

    private Point2D rotate(final Point2D a, final Point2D center, final double angle) {
        final double resultX = center.getX() + (a.getX() - center.getX()) * Math.cos(angle) - (a.getY() - center.getY()) * Math
            .sin(angle);
        final double resultY = center.getY() + (a.getX() - center.getX()) * Math.sin(angle) + (a.getY() - center.getY()) * Math
            .cos(angle);
        return new Point2D(resultX, resultY);
    }

    private Point2D makeControlPoint(final double endX, final double endY, final Circle circle, final int numSegments, int direction) {
        final double controlPointDistance = (4.0 / 3.0) * Math.tan(Math.PI / (2 * numSegments)) * circle.getRadius();
        final Point2D center = new Point2D(circle.getCenterX(), circle.getCenterY());
        final Point2D end = new Point2D(endX, endY);
        Point2D perp = rotate(center, end, direction * Math.PI / 2.);
        Point2D diff = perp.subtract(end);
        diff = diff.normalize();
        diff = scale(diff, controlPointDistance);
        return end.add(diff);
    }

    private Point2D scale(final Point2D a, final double scale) {
        return new Point2D(a.getX() * scale, a.getY() * scale);
    }

    final class RecentColorPath extends Path {
        PathClickTransition transition;

        RecentColorPath(final PathElement... elements) {
            super(elements);
            this.setStrokeLineCap(StrokeLineCap.ROUND);
            this.setStrokeWidth(0);
            this.setStrokeType(StrokeType.CENTERED);
            this.setCache(true);
            JFXDepthManager.setDepth(this, 2);
            this.transition = new PathClickTransition(this);
        }

        void playTransition(final double rate) {
            transition.setRate(rate);
            transition.play();
        }
    }

    private final class PathClickTransition extends CachedTransition {
        PathClickTransition(final Path path) {
            super(JFXCustomColorPicker.this, new Timeline(
                    new KeyFrame(Duration.ZERO,
                        new KeyValue(((DropShadow) path.getEffect()).radiusProperty(),
                            JFXDepthManager.getShadowAt(2).radiusProperty().get(),
                            EASE_BOTH),
                        new KeyValue(((DropShadow) path.getEffect()).spreadProperty(),
                            JFXDepthManager.getShadowAt(2).spreadProperty().get(),
                            EASE_BOTH),
                        new KeyValue(((DropShadow) path.getEffect()).offsetXProperty(),
                            JFXDepthManager.getShadowAt(2).offsetXProperty().get(),
                            EASE_BOTH),
                        new KeyValue(((DropShadow) path.getEffect()).offsetYProperty(),
                            JFXDepthManager.getShadowAt(2).offsetYProperty().get(),
                            EASE_BOTH),
                        new KeyValue(path.strokeWidthProperty(), 0, EASE_BOTH)
                    ),
                    new KeyFrame(Duration.millis(1000),
                        new KeyValue(((DropShadow) path.getEffect()).radiusProperty(),
                            JFXDepthManager.getShadowAt(5).radiusProperty().get(),
                            EASE_BOTH),
                        new KeyValue(((DropShadow) path.getEffect()).spreadProperty(),
                            JFXDepthManager.getShadowAt(5).spreadProperty().get(),
                            EASE_BOTH),
                        new KeyValue(((DropShadow) path.getEffect()).offsetXProperty(),
                            JFXDepthManager.getShadowAt(5).offsetXProperty().get(),
                            EASE_BOTH),
                        new KeyValue(((DropShadow) path.getEffect()).offsetYProperty(),
                            JFXDepthManager.getShadowAt(5).offsetYProperty().get(),
                            EASE_BOTH),
                        new KeyValue(path.strokeWidthProperty(), 2, EASE_BOTH)
                    )
                )
            );
            // reduce the number to increase the shifting , increase number to reduce shifting
            setCycleDuration(Duration.millis(120));
            setDelay(Duration.seconds(0));
            setAutoReverse(false);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy