/**
* 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.settingsmenu;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.animation.FadeTransitionBuilder;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.ParallelTransition;
import javafx.animation.Timeline;
import javafx.animation.Transition;
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.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.ImageViewBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.util.Duration;
import com.mrlonee.radialfx.core.RadialMenuItem;
import com.mrlonee.radialfx.core.RadialMenuItemBuilder;
public class RadialSettingsMenu extends Group {
private final Group itemsContainer = new Group();
private final Color baseColor = Color.web("e0e0e0");
private final Color hoverColor = Color.web("30c0ff");
private final Color selectionColor = Color.BLACK;
private final Color valueColor = Color.web("30c0ff");
private final Color valueHoverColor = Color.web("30c0ff");
private final double menuSize = 45;
private final double innerRadius = 110;
private final double radius = 200;
private final List<RadialMenuItem> items = new ArrayList<RadialMenuItem>();
private final DoubleProperty initialAngle = new SimpleDoubleProperty(0);
private final RadialSettingsMenuCenter centerNode = new RadialSettingsMenuCenter();
private SelectionEventHandler selectionEventHandler = new SelectionEventHandler();
private RadialMenuItem selectedItem = null;
private final Map<RadialMenuItem, List<RadialMenuItem>> itemToValues = new HashMap<RadialMenuItem, List<RadialMenuItem>>();
private final Map<RadialMenuItem, Group> itemToGroupValue = new HashMap<RadialMenuItem, Group>();
private final Map<RadialMenuItem, ImageView> itemAndValueToIcon = new HashMap<RadialMenuItem, ImageView>();
private final Map<RadialMenuItem, ImageView> itemAndValueToWhiteIcon = new HashMap<RadialMenuItem, ImageView>();
private final Map<RadialMenuItem, RadialMenuItem> valueItemToItem = new HashMap<RadialMenuItem, RadialMenuItem>();
private final Group notSelectedItemEffect;
private Transition openAnim;
public class SelectionEventHandler implements EventHandler<MouseEvent> {
@Override
public void handle(final MouseEvent event) {
final RadialMenuItem newSelectedItem = (RadialMenuItem) event
.getSource();
if (selectedItem == newSelectedItem) {
closeValueSelection(newSelectedItem);
} else {
openValueSelection(newSelectedItem);
}
}
}
public RadialSettingsMenu() {
initialAngle.addListener(new ChangeListener<Number>() {
@Override
public void changed(
final ObservableValue<? extends Number> paramObservableValue,
final Number paramT1, final Number paramT2) {
RadialSettingsMenu.this.setInitialAngle(paramObservableValue
.getValue().doubleValue());
}
});
centerNode.visibleProperty().bind(visibleProperty());
getChildren().add(itemsContainer);
getChildren().add(centerNode);
addMenuItem("resources/icons/gemicon/PNG/32x32/row 1/9.png");
addMenuItem("resources/icons/gemicon/PNG/32x32/row 1/2.png");
addMenuItem("resources/icons/gemicon/PNG/32x32/row 1/3.png");
addMenuItem("resources/icons/gemicon/PNG/32x32/row 1/4.png");
addMenuItem("resources/icons/gemicon/PNG/32x32/row 1/5.png");
addMenuItem("resources/icons/gemicon/PNG/32x32/row 1/6.png");
addMenuItem("resources/icons/gemicon/PNG/32x32/row 1/7.png");
addMenuItem("resources/icons/gemicon/PNG/32x32/row 1/8.png");
final RadialMenuItem notSelected1 = createNotSelectedItemEffect();
final RadialMenuItem notSelected2 = createNotSelectedItemEffect();
notSelected2.setClockwise(false);
notSelectedItemEffect = new Group(notSelected1, notSelected2);
notSelectedItemEffect.setVisible(false);
notSelectedItemEffect.setOpacity(0);
itemsContainer.getChildren().add(notSelectedItemEffect);
computeItemsStartAngle();
setTranslateX(210);
setTranslateY(210);
}
private RadialMenuItem createNotSelectedItemEffect() {
final RadialMenuItem notSelectedItemEffect = RadialMenuItemBuilder
.create().length(180).backgroundFill(baseColor).startAngle(0)
.strokeFill(baseColor).backgroundMouseOnFill(baseColor)
.strokeMouseOnFill(baseColor).innerRadius(innerRadius)
.radius(radius).offset(0).clockwise(true).strokeVisible(true)
.backgroundVisible(true).build();
return notSelectedItemEffect;
}
private void addMenuItem(final String iconPath) {
final ImageView imageView = getImageView(iconPath);
final ImageView centerView = getImageView(iconPath.replace("32x32",
"64x64"));
final ImageView value1View = getImageView(iconPath.replace("row 1",
"row 2"));
final ImageView value2View = getImageView(iconPath.replace("row 1",
"row 3"));
final ImageView imageViewWhite = getImageView(iconPath.replace(".png",
"W.png"));
final RadialMenuItem item = newRadialMenuItem(imageView, imageViewWhite);
final RadialMenuItem value1Item = newValueRadialMenuItem(value1View);
final RadialMenuItem value2Item = newValueRadialMenuItem(value2View);
valueItemToItem.put(value1Item, item);
valueItemToItem.put(value2Item, item);
List<RadialMenuItem> values;
Group valueGroup;
if (Math.random() < 0.5) {
final ImageView value3View = getImageView(iconPath.replace("row 1",
"row 4"));
final RadialMenuItem value3Item = newValueRadialMenuItem(value3View);
valueItemToItem.put(value3Item, item);
values = Arrays.asList(value1Item, value2Item, value3Item);
valueGroup = new Group(value1Item, value2Item, value3Item);
} else {
values = Arrays.asList(value1Item, value2Item);
valueGroup = new Group(value1Item, value2Item);
}
itemToValues.put(item, values);
itemToGroupValue.put(item, valueGroup);
valueGroup.setVisible(false);
itemsContainer.getChildren().addAll(item, valueGroup);
item.addEventHandler(MouseEvent.MOUSE_CLICKED, selectionEventHandler);
centerNode.addCenterItem(item, centerView);
item.addEventHandler(MouseEvent.MOUSE_ENTERED,
new EventHandler<MouseEvent>() {
@Override
public void handle(final MouseEvent event) {
if (selectedItem == null) {
centerNode.displayCenter(event.getSource());
}
}
});
item.addEventHandler(MouseEvent.MOUSE_EXITED,
new EventHandler<MouseEvent>() {
@Override
public void handle(final MouseEvent event) {
if (selectedItem == null) {
centerNode.hideCenter(event.getSource());
}
}
});
item.addEventHandler(MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
@Override
public void handle(final MouseEvent event) {
// TODO Animate the little long click spoiler...
}
});
}
private RadialMenuItem newValueRadialMenuItem(final ImageView imageView) {
final RadialMenuItem item = RadialMenuItemBuilder.create()
.length(menuSize).backgroundFill(valueColor)
.strokeFill(valueColor).backgroundMouseOnFill(valueHoverColor)
.strokeMouseOnFill(valueHoverColor).innerRadius(innerRadius)
.radius(radius).offset(0).clockwise(true).graphic(imageView)
.backgroundVisible(true).strokeVisible(true).build();
item.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(final MouseEvent event) {
final RadialMenuItem valuItem = (RadialMenuItem) event
.getSource();
final RadialMenuItem item = valueItemToItem.get(valuItem);
RadialSettingsMenu.this.closeValueSelection(item);
}
});
itemAndValueToIcon.put(item, imageView);
return item;
}
private RadialMenuItem newRadialMenuItem(final ImageView imageView,
final ImageView imageViewWhite) {
final RadialMenuItem item = RadialMenuItemBuilder.create()
.backgroundFill(baseColor).strokeFill(baseColor)
.backgroundMouseOnFill(hoverColor)
.strokeMouseOnFill(hoverColor).radius(radius)
.innerRadius(innerRadius).length(menuSize).clockwise(true)
.backgroundVisible(true).strokeVisible(true).offset(0).build();
if (imageViewWhite != null) {
item.setGraphic(new Group(imageView, imageViewWhite));
imageViewWhite.setOpacity(0.0);
} else {
item.setGraphic(new Group(imageView));
}
items.add(item);
itemAndValueToIcon.put(item, imageView);
itemAndValueToWhiteIcon.put(item, imageViewWhite);
return item;
}
private void computeItemsStartAngle() {
double angleOffset = initialAngle.get();
for (final RadialMenuItem item : items) {
item.setStartAngle(angleOffset);
angleOffset = angleOffset + item.getLength();
}
}
private void setInitialAngle(final double angle) {
initialAngle.set(angle);
computeItemsStartAngle();
}
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;
}
private void openValueSelection(final RadialMenuItem newSelectedItem) {
selectedItem = newSelectedItem;
notSelectedItemEffect.toFront();
itemToGroupValue.get(selectedItem).setVisible(true);
itemToGroupValue.get(selectedItem).toFront();
selectedItem.toFront();
openAnim = createOpenAnimation(selectedItem);
openAnim.play();
}
private void closeValueSelection(final RadialMenuItem newSelectedItem) {
openAnim.setAutoReverse(true);
openAnim.setCycleCount(2);
openAnim.setOnFinished(new EventHandler<ActionEvent>() {
@Override
public void handle(final ActionEvent event) {
newSelectedItem.setBackgroundFill(baseColor);
newSelectedItem.setStrokeFill(baseColor);
newSelectedItem.setBackgroundMouseOnFill(hoverColor);
newSelectedItem.setStrokeMouseOnFill(hoverColor);
notSelectedItemEffect.setVisible(false);
itemToGroupValue.get(newSelectedItem).setVisible(false);
}
});
openAnim.playFrom(Duration.millis(400));
selectedItem = null;
}
private Transition createOpenAnimation(final RadialMenuItem newSelectedItem) {
// Children slide animation
final List<RadialMenuItem> children = itemToValues.get(newSelectedItem);
double startAngleEnd = 0;
final double startAngleBegin = newSelectedItem.getStartAngle();
final ParallelTransition transition = new ParallelTransition();
itemToGroupValue.get(newSelectedItem).setVisible(true);
int internalCounter = 1;
for (int i = 0; i < children.size(); i++) {
final RadialMenuItem it = children.get(i);
if (i % 2 == 0) {
startAngleEnd = startAngleBegin + internalCounter
* it.getLength();
} else {
startAngleEnd = startAngleBegin - internalCounter
* it.getLength();
internalCounter++;
}
final Animation itemTransition = new Timeline(new KeyFrame(
Duration.ZERO, new KeyValue(it.startAngleProperty(),
startAngleBegin)), new KeyFrame(
Duration.millis(400), new KeyValue(it.startAngleProperty(),
startAngleEnd)));
transition.getChildren().add(itemTransition);
final ImageView image = itemAndValueToIcon.get(it);
image.setOpacity(0.0);
final Timeline iconTransition = new Timeline(new KeyFrame(
Duration.millis(0),
new KeyValue(image.opacityProperty(), 0)), new KeyFrame(
Duration.millis(300), new KeyValue(image.opacityProperty(),
0)), new KeyFrame(Duration.millis(400),
new KeyValue(image.opacityProperty(), 1.0)));
transition.getChildren().add(iconTransition);
}
// Selected item background color change
final DoubleProperty backgroundColorAnimValue = new SimpleDoubleProperty();
final ChangeListener<? super Number> listener = new ChangeListener<Number>() {
@Override
public void changed(final ObservableValue<? extends Number> arg0,
final Number arg1, final Number arg2) {
final Color c = hoverColor.interpolate(selectionColor,
arg2.floatValue());
newSelectedItem.setBackgroundFill(c);
newSelectedItem.setStrokeFill(c);
newSelectedItem.setBackgroundMouseOnFill(c);
newSelectedItem.setStrokeMouseOnFill(c);
}
};
backgroundColorAnimValue.addListener(listener);
final Animation itemTransition = new Timeline(new KeyFrame(
Duration.ZERO, new KeyValue(backgroundColorAnimValue, 0)),
new KeyFrame(Duration.millis(300), new KeyValue(
backgroundColorAnimValue, 1.0)));
transition.getChildren().add(itemTransition);
// Selected item image icon color change
final FadeTransition selectedItemImageBlackFade = FadeTransitionBuilder
.create().node(itemAndValueToIcon.get(newSelectedItem))
.duration(Duration.millis(400)).fromValue(1.0).toValue(0.0)
.build();
final FadeTransition selectedItemImageWhiteFade = FadeTransitionBuilder
.create().node(itemAndValueToWhiteIcon.get(newSelectedItem))
.duration(Duration.millis(400)).fromValue(0).toValue(1.0)
.build();
transition.getChildren().addAll(selectedItemImageBlackFade,
selectedItemImageWhiteFade);
// Unselected items fading
final FadeTransition notSelectedTransition = FadeTransitionBuilder
.create().node(notSelectedItemEffect)
.duration(Duration.millis(200)).delay(Duration.millis(200))
.fromValue(0).toValue(0.8).build();
notSelectedItemEffect.setOpacity(0);
notSelectedItemEffect.setVisible(true);
transition.getChildren().add(notSelectedTransition);
return transition;
}
}