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

com.jfoenix.controls.JFXNodesList 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.controls;

import javafx.animation.Animation.Status;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import javafx.util.Duration;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

/**
 * list of nodes that are toggled On/Off by clicking on the 1st node
 *
 * @author Shadi Shaheen
 * @version 1.0
 * @since 2016-03-09
 */
public class JFXNodesList extends VBox {

    private final HashMap>> animationsMap = new HashMap<>();
    private boolean expanded = false;
    private final Timeline animateTimeline = new Timeline();

    /**
     * Creates empty nodes list.
     */
    public JFXNodesList() {
        this.setPickOnBounds(false);
        this.getStyleClass().add("jfx-nodes-list");
    }

    /**
     * Adds node to list.
     * Note: this method must be called instead of getChildren().add().
     *
     * @param node {@link Region} to add
     */
    public void addAnimatedNode(Region node) {
        addAnimatedNode(node, null);
    }

    /**
     * add node to list with a specified callback that is triggered after the node animation is finished.
     * Note: this method must be called instead of getChildren().add().
     *
     * @param node {@link Region} to add
     */
    public void addAnimatedNode(Region node, Callback> animationCallBack) {
        // create container for the node if it's a sub nodes list
        if (node instanceof JFXNodesList) {
            StackPane container = new StackPane(node);
            container.setPickOnBounds(false);
            addAnimatedNode(container, animationCallBack);
            return;
        }

        // init node property
        node.setVisible(false);
        node.minWidthProperty().bind(node.prefWidthProperty());
        node.minHeightProperty().bind(node.prefHeightProperty());
        if (this.getChildren().size() > 0) {
            initNode(node);
        } else {
            if (node instanceof Button) {
                ((Button) node).setOnAction((action) -> this.animateList());
            } else {
                node.setOnMouseClicked((click) -> this.animateList());
            }
            node.getStyleClass().add("trigger-node");
        }

        // init the list height and width
        if (this.getChildren().size() == 0) {
            node.setVisible(true);
            this.minHeightProperty().bind(node.prefHeightProperty());
            this.maxHeightProperty().bind(node.prefHeightProperty());
            this.minWidthProperty().bind(node.prefWidthProperty());
            this.maxWidthProperty().bind(node.prefWidthProperty());
        }

        // add the node and its listeners
        this.getChildren().add(node);
        this.rotateProperty()
            .addListener((o, oldVal, newVal) -> node.setRotate(newVal.doubleValue() % 180 == 0 ? newVal.doubleValue() : -newVal
                .doubleValue()));
        if (animationCallBack == null && this.getChildren().size() != 1) {
            animationCallBack = (expanded) -> initDefaultAnimation(node, expanded);
        } else if (animationCallBack == null && this.getChildren().size() == 1) {
            animationCallBack = (expanded) -> new ArrayList<>();
        }
        animationsMap.put(node, animationCallBack);
    }

    /**
     * Animates the list to show/hide the nodes.
     */
    public void animateList() {
        expanded = !expanded;

        if (animateTimeline.getStatus() == Status.RUNNING) {
            animateTimeline.stop();
        }

        animateTimeline.getKeyFrames().clear();
        double duration = 120 / (double) this.getChildren().size();

        // show child nodes
        if (expanded) {
            this.getChildren().forEach(child -> child.setVisible(true));
        }

        // add child nodes animation
        for (int i = 1; i < this.getChildren().size(); i++) {
            Node child = this.getChildren().get(i);
            Collection keyValues = animationsMap.get(child).call(expanded);
            animateTimeline.getKeyFrames()
                .add(new KeyFrame(Duration.millis(i * duration),
                    keyValues.toArray(new KeyValue[keyValues.size()])));
        }
        // add 1st element animation
        Collection keyValues = animationsMap.get(this.getChildren().get(0)).call(expanded);
        animateTimeline.getKeyFrames()
            .add(new KeyFrame(Duration.millis(160), keyValues.toArray(new KeyValue[keyValues.size()])));

        // hide child nodes to allow mouse events on the nodes behind them
        if (!expanded) {
            animateTimeline.setOnFinished((finish) -> {
                for (int i = 1; i < this.getChildren().size(); i++) {
                    this.getChildren().get(i).setVisible(false);
                }
            });
        } else {
            animateTimeline.setOnFinished(null);
        }

        animateTimeline.play();
    }

    protected void initNode(Node node) {
        node.setScaleX(0);
        node.setScaleY(0);
        node.getStyleClass().add("sub-node");
    }

    // init default animation keyvalues
    private ArrayList initDefaultAnimation(Region region, boolean expanded) {
        ArrayList defaultAnimationValues = new ArrayList<>();
        defaultAnimationValues.add(new KeyValue(region.scaleXProperty(), expanded ? 1 : 0, Interpolator.EASE_BOTH));
        defaultAnimationValues.add(new KeyValue(region.scaleYProperty(), expanded ? 1 : 0, Interpolator.EASE_BOTH));
        return defaultAnimationValues;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy