/* * 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 com.sun.javafx.css.converters.SizeConverter; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.beans.binding.Bindings; import javafx.css.CssMetaData; import javafx.css.SimpleStyleableDoubleProperty; import javafx.css.Styleable; import javafx.css.StyleableDoubleProperty; import javafx.scene.Parent; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Arc; import javafx.util.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * JFXSpinner is the material design implementation of a loading spinner. * * @author Bashir Elias & Shadi Shaheen * @version 1.0 * @since 2016-03-09 */ public class JFXSpinner extends StackPane { private Color greenColor; private Color redColor; private Color yellowColor; private Color blueColor; private Timeline timeline; private Arc arc; private boolean initialized; /** * creates a spinner node */ public JFXSpinner() { getStyleClass().add(DEFAULT_STYLE_CLASS); initialize(); } private void initialize() { blueColor = Color.valueOf("#4285f4"); redColor = Color.valueOf("#db4437"); yellowColor = Color.valueOf("#f4b400"); greenColor = Color.valueOf("#0F9D58"); arc = new Arc(0, 0, 12, 12, 0, 360); arc.setFill(Color.TRANSPARENT); arc.setStrokeWidth(3); arc.getStyleClass().addAll("arc"); arc.radiusXProperty().bindBidirectional(radius); arc.radiusYProperty().bindBidirectional(radius); getChildren().add(arc); this.minWidthProperty().bind(Bindings.createDoubleBinding(() -> { return getRadius() * 2 + arc.getStrokeWidth() + 5; }, radius, arc.strokeWidthProperty())); this.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> { return getRadius() * 2 + arc.getStrokeWidth() + 5; }, radius, arc.strokeWidthProperty())); this.minHeightProperty().bind(Bindings.createDoubleBinding(() -> { return getRadius() * 2 + arc.getStrokeWidth() + 5; }, radius, arc.strokeWidthProperty())); this.maxHeightProperty().bind(Bindings.createDoubleBinding(() -> { return getRadius() * 2 + arc.getStrokeWidth() + 5; }, radius, arc.strokeWidthProperty())); } private KeyFrame[] getKeyFrames(double angle, double duration, Color color) { KeyFrame[] frames = new KeyFrame[4]; frames[0] = new KeyFrame(Duration.seconds(duration), new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 45 + getStartingAngle(), Interpolator.LINEAR)); frames[1] = new KeyFrame(Duration.seconds(duration + 0.4), new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 90 + getStartingAngle(), Interpolator.LINEAR)); frames[2] = new KeyFrame(Duration.seconds(duration + 0.7), new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 135 + getStartingAngle(), Interpolator.LINEAR)); frames[3] = new KeyFrame(Duration.seconds(duration + 1.1), new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 435 + getStartingAngle(), Interpolator.LINEAR), new KeyValue(arc.strokeProperty(), color, Interpolator.EASE_BOTH)); return frames; } /** * {@inheritDoc} */ @Override protected void layoutChildren() { if (!initialized) { super.layoutChildren(); final Color initialColor = (Color) arc.getStroke(); if (initialColor == null) { arc.setStroke(blueColor); } KeyFrame[] blueFrame = getKeyFrames(0, 0, initialColor == null ? blueColor : initialColor); KeyFrame[] redFrame = getKeyFrames(450, 1.4, initialColor == null ? redColor : initialColor); KeyFrame[] yellowFrame = getKeyFrames(900, 2.8, initialColor == null ? yellowColor : initialColor); KeyFrame[] greenFrame = getKeyFrames(1350, 4.2, initialColor == null ? greenColor : initialColor); KeyFrame endingFrame = new KeyFrame(Duration.seconds(5.6), new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), 1845 + getStartingAngle(), Interpolator.LINEAR)); if (timeline != null) { timeline.stop(); } timeline = new Timeline(blueFrame[0], blueFrame[1], blueFrame[2], blueFrame[3], redFrame[0], redFrame[1], redFrame[2], redFrame[3], yellowFrame[0], yellowFrame[1], yellowFrame[2], yellowFrame[3], greenFrame[0], greenFrame[1], greenFrame[2], greenFrame[3], endingFrame); timeline.setCycleCount(Timeline.INDEFINITE); timeline.setRate(1); timeline.play(); initialized = true; } } /*************************************************************************** * * * Stylesheet Handling * * * **************************************************************************/ /** * Initialize the style class to 'jfx-spinner'. * <p> * This is the selector class from which CSS can be used to style * this control. */ private static final String DEFAULT_STYLE_CLASS = "jfx-spinner"; /** * specifies the radius of the spinner node */ private StyleableDoubleProperty radius = new SimpleStyleableDoubleProperty(StyleableProperties.RADIUS, JFXSpinner.this, "radius", 12.0); public final StyleableDoubleProperty radiusProperty() { return this.radius; } public final double getRadius() { return this.radiusProperty().get(); } public final void setRadius(final double radius) { this.radiusProperty().set(radius); } /** * specifies from which angle the spinner should start spinning */ private StyleableDoubleProperty startingAngle = new SimpleStyleableDoubleProperty(StyleableProperties.STARTING_ANGLE, JFXSpinner.this, "starting_angle", 360 - Math.random() * 720); public final StyleableDoubleProperty startingAngleProperty() { return this.startingAngle; } public final double getStartingAngle() { return this.startingAngleProperty().get(); } public final void setStartingAngle(final double startingAngle) { this.startingAngleProperty().set(startingAngle); } private static class StyleableProperties { private static final CssMetaData<JFXSpinner, Number> RADIUS = new CssMetaData<JFXSpinner, Number>("-jfx-radius", SizeConverter.getInstance(), 12) { @Override public boolean isSettable(JFXSpinner control) { return control.radius == null || !control.radius.isBound(); } @Override public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) { return control.radius; } }; private static final CssMetaData<JFXSpinner, Number> STARTING_ANGLE = new CssMetaData<JFXSpinner, Number>("-jfx-starting-angle", SizeConverter.getInstance(), 360 - Math.random() * 720) { @Override public boolean isSettable(JFXSpinner control) { return control.startingAngle == null || !control.startingAngle.isBound(); } @Override public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) { return control.startingAngle; } }; private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES; static { final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Parent.getClassCssMetaData()); Collections.addAll(styleables, RADIUS, STARTING_ANGLE ); CHILD_STYLEABLES = Collections.unmodifiableList(styleables); } } // inherit the styleable properties from parent private List<CssMetaData<? extends Styleable, ?>> STYLEABLES; @Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { if (STYLEABLES == null) { final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Parent.getClassCssMetaData()); styleables.addAll(getClassCssMetaData()); styleables.addAll(StackPane.getClassCssMetaData()); STYLEABLES = Collections.unmodifiableList(styleables); } return STYLEABLES; } public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { return StyleableProperties.CHILD_STYLEABLES; } }