/** * Copyright 2013 (C) Mr LoNee - (Laurent NICOLAS) - www.mrlonee.com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see <http://www.gnu.org/licenses/>. */ package com.mrlonee.radialfx.globalmenu; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import javafx.animation.Animation.Status; import javafx.animation.FadeTransition; import javafx.animation.FadeTransitionBuilder; import javafx.animation.ParallelTransition; import javafx.animation.Transition; import javafx.animation.TranslateTransition; import javafx.animation.TranslateTransitionBuilder; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.image.ImageViewBuilder; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.paint.CycleMethod; import javafx.scene.paint.RadialGradient; import javafx.scene.paint.Stop; import javafx.scene.shape.Rectangle; import javafx.util.Duration; import com.mrlonee.radialfx.core.RadialMenuItem; import com.mrlonee.radialfx.core.RadialMenuItemBuilder; public class RadialGlobalMenu extends Group { private static final Color BACK_GRADIENT_COLOR = Color.web("000000a0"); private final Color BACK_SELECT_GRADIENT_COLOR = Color.web("00fffb97"); private final DoubleProperty length = new SimpleDoubleProperty(45); private final DoubleProperty innerRadius = new SimpleDoubleProperty(150); private final DoubleProperty radius = new SimpleDoubleProperty(300); private final DoubleProperty offset = new SimpleDoubleProperty(6); private final DoubleProperty widthProp = new SimpleDoubleProperty(); private final DoubleProperty heightProp = new SimpleDoubleProperty(); private final List<RadialMenuItem> items = new ArrayList<RadialMenuItem>(); private final double initialAngle = -90 + 45.0 / 2.0; private Group itemsContainer = new Group(); private Group backContainer; private ParallelTransition pTrans; public RadialGlobalMenu() { backContainer = new Group(); final ChangeListener<Number> sizeChangeListener = new ChangeListener<Number>() { @Override public void changed(final ObservableValue<? extends Number> arg0, final Number arg1, final Number arg2) { RadialGlobalMenu.this.computeBack(); } }; widthProp.addListener(sizeChangeListener); heightProp.addListener(sizeChangeListener); computeItemsStartAngle(); getChildren().setAll(backContainer, itemsContainer); } private void computeBack() { final Rectangle rect = new Rectangle(); rect.setWidth(widthProp.get()); rect.setHeight(heightProp.get()); rect.setTranslateX(widthProp.get() / -2.0); rect.setTranslateY(heightProp.get() / -2.0); final RadialGradient radialGradient = new RadialGradient(0, 0, widthProp.get() / 2.0, heightProp.get() / 2.0, widthProp.get() / 2.0, false, CycleMethod.NO_CYCLE, new Stop(0, Color.TRANSPARENT), new Stop(1, BACK_GRADIENT_COLOR)); rect.setFill(radialGradient); backContainer.getChildren().setAll(rect); } public void addMenuItem(final String iconPath, final EventHandler<MouseEvent> eventHandler) { final RadialGradient backGradient = new RadialGradient(0, 0, 0, 0, radius.get(), false, CycleMethod.NO_CYCLE, new Stop(0, BACK_GRADIENT_COLOR), new Stop(1, Color.TRANSPARENT)); final RadialGradient backSelectGradient = new RadialGradient(0, 0, 0, 0, radius.get(), false, CycleMethod.NO_CYCLE, new Stop(0, BACK_SELECT_GRADIENT_COLOR), new Stop(1, Color.TRANSPARENT)); final RadialMenuItem item = RadialMenuItemBuilder.create() .length(length).graphic(new Group(getImageView(iconPath))) .backgroundFill(backGradient) .backgroundMouseOnFill(backSelectGradient) .innerRadius(innerRadius).radius(radius).offset(offset) .clockwise(true).backgroundVisible(true).strokeVisible(false) .build(); item.setOnMouseClicked(eventHandler); items.add(item); itemsContainer.getChildren().addAll(item); } private void computeItemsStartAngle() { double angleOffset = initialAngle; int i = 1; for (final RadialMenuItem item : items) { item.setStartAngle(angleOffset); angleOffset = angleOffset + item.getLength(); if (i % 3 == 0) { angleOffset += item.getLength(); } i++; } } private ImageView getImageView(final String path) { ImageView imageView = null; try { imageView = ImageViewBuilder.create() .image(new Image(new FileInputStream(path))).build(); } catch (final FileNotFoundException e) { e.printStackTrace(); } assert (imageView != null); return imageView; } public void transitionVisible(final boolean visible) { double currentAnimX = -1; double currentAnimOpacity = -1; if (pTrans != null && pTrans.getStatus() == Status.RUNNING) { pTrans.stop(); currentAnimX = items.get(0).getTranslateX(); currentAnimOpacity = backContainer.getOpacity(); } if (visible) { pTrans = new ParallelTransition(); int i = 0; final double startX = currentAnimX != -1 ? currentAnimX : widthProp .get() / 2.0; for (final RadialMenuItem item : items) { TranslateTransition transition; if (i < 3) { item.setTranslateX(startX); transition = TranslateTransitionBuilder.create().node(item) .fromX(startX).toX(0) .duration(Duration.millis(200)).build(); } else { item.setTranslateX(-currentAnimX); transition = TranslateTransitionBuilder.create().node(item) .fromX(-startX).toX(0) .duration(Duration.millis(200)).build(); } i++; pTrans.getChildren().add(transition); } final double startOpacity = currentAnimOpacity != -1 ? currentAnimOpacity : 0.0; pTrans.getChildren().add( createOpacityTransition(backContainer, startOpacity, 1.0)); setVisible(true); pTrans.play(); } else { final ParallelTransition pTrans = new ParallelTransition(); int i = 0; final double startX = currentAnimX != -1 ? currentAnimX : 0; for (final RadialMenuItem item : items) { TranslateTransition transition; if (i < 3) { transition = TranslateTransitionBuilder.create().node(item) .fromX(startX).toX(widthProp.get() / 2.0) .duration(Duration.millis(200)).build(); } else { transition = TranslateTransitionBuilder.create().node(item) .fromX(-startX).toX(-widthProp.get() / 2.0) .duration(Duration.millis(200)).build(); } i++; pTrans.getChildren().add(transition); } final double startOpacity = currentAnimOpacity != -1 ? currentAnimOpacity : 1.0; pTrans.getChildren().add( createOpacityTransition(backContainer, startOpacity, 0)); pTrans.setOnFinished(new EventHandler<ActionEvent>() { @Override public void handle(final ActionEvent arg0) { setVisible(false); } }); pTrans.play(); } } private Transition createOpacityTransition(final Node node, final double from, final double to) { final FadeTransition fadeIn = FadeTransitionBuilder.create().node(node) .fromValue(from).toValue(to).duration(Duration.millis(200)) .build(); backContainer.setOpacity(from); return fadeIn; } public void setHeight(final double value){ heightProp.set(value); } public double getHeight(){ return heightProp.get(); } public DoubleProperty heightProperty() { return heightProp; } public void setInnerRadius(final double value){ innerRadius.set(value); } public double getInnerRadius(){ return innerRadius.get(); } public DoubleProperty innerRadiusProperty(){ return innerRadius; } public void setRadius(final double value){ radius.set(value); } public double getRadius(){ return radius.get(); } public DoubleProperty radiusProperty(){ return radius; } public void setOffset(final double value){ offset.set(value); } public double getOffset(){ return offset.get(); } public DoubleProperty offsetProperty(){ return offset; } public void setWidth(final double value){ widthProp.set(value); } public double getWidth(){ return widthProp.get(); } public DoubleProperty widthProperty() { return widthProp; } }