/** * 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.core; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.EventHandler; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.ArcTo; import javafx.scene.shape.FillRule; import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; /** * Radial Menu Item that is the base item for radial menu. * * @author Mr LoNee - (Laurent NICOLAS) - www.mrlonee.com */ public class RadialMenuItem extends Group implements ChangeListener<Object> { /************************************************************************************* * Property part *************************************************************************************/ protected DoubleProperty startAngle = new SimpleDoubleProperty(0); protected DoubleProperty length = new SimpleDoubleProperty(45); protected DoubleProperty innerRadius = new SimpleDoubleProperty(50); protected DoubleProperty radius = new SimpleDoubleProperty(100); protected DoubleProperty offset = new SimpleDoubleProperty(5); protected ObjectProperty<Paint> backgroundMouseOnFill = new SimpleObjectProperty<Paint>( Color.LIGHTGRAY); protected ObjectProperty<Paint> backgroundFill = new SimpleObjectProperty<Paint>( Color.GRAY); protected BooleanProperty backgroundVisible = new SimpleBooleanProperty( true); protected BooleanProperty strokeVisible = new SimpleBooleanProperty(true); protected ObjectProperty<Paint> strokeFill = new SimpleObjectProperty<Paint>( Color.GRAY); protected ObjectProperty<Paint> strokeMouseOnFill = new SimpleObjectProperty<Paint>( Color.LIGHTGRAY); protected BooleanProperty clockwise = new SimpleBooleanProperty(false); protected ObjectProperty<Node> graphic = new SimpleObjectProperty<Node>(); /***************************************************************************** * Graphic Part *****************************************************************************/ protected MoveTo moveTo; protected ArcTo arcToInner; protected ArcTo arcTo; protected LineTo lineTo; protected LineTo lineTo2; protected Path path; protected Group graphicContainer = new Group(); protected String text; protected double innerStartX; protected double innerStartY; protected double innerEndX; protected double innerEndY; protected boolean innerSweep; protected double startX; protected double startY; protected double endX; protected double endY; protected boolean sweep; protected double graphicX; protected double graphicY; protected double translateX; protected double translateY; protected boolean mouseOn = false; public RadialMenuItem() { length.addListener(this); innerRadius.addListener(this); radius.addListener(this); offset.addListener(this); backgroundVisible.addListener(this); strokeVisible.addListener(this); clockwise.addListener(this); backgroundFill.addListener(this); strokeFill.addListener(this); backgroundMouseOnFill.addListener(this); strokeMouseOnFill.addListener(this); startAngle.addListener(this); graphic.addListener(this); path = new Path(); moveTo = new MoveTo(); arcToInner = new ArcTo(); arcTo = new ArcTo(); lineTo = new LineTo(); lineTo2 = new LineTo(); path.getElements().add(moveTo); path.getElements().add(arcToInner); path.getElements().add(lineTo); path.getElements().add(arcTo); path.getElements().add(lineTo2); getChildren().setAll(path, graphicContainer); setOnMouseEntered(new EventHandler<MouseEvent>() { @Override public void handle(final MouseEvent arg0) { mouseOn = true; RadialMenuItem.this.redraw(); } }); setOnMouseExited(new EventHandler<MouseEvent>() { @Override public void handle(final MouseEvent arg0) { mouseOn = false; RadialMenuItem.this.redraw(); } }); redraw(); } public DoubleProperty startAngleProperty() { return startAngle; } public void setStartAngle(final double angle) { startAngle.set(angle); } public double getStartAngle() { return startAngle.get(); } public DoubleProperty lengthProperty() { return length; } public void setLength(final double length) { this.length.set(length); } public double getLength() { return length.get(); } public DoubleProperty innerRadiusProperty() { return innerRadius; } public void setInnerRadius(final double radius) { innerRadius.set(radius); } public double getInnerRadius() { return innerRadius.get(); } public DoubleProperty radiusProperty() { return radius; } public void setRadius(final double radius) { this.radius.set(radius); } public double getRadius() { return radius.get(); } public DoubleProperty offsetProperty() { return offset; } public void setOffset(final double offset) { this.offset.set(offset); } public double getOffset() { return offset.get(); } public ObjectProperty<Paint> backgroundFillProperty() { return backgroundFill; } public void setBackgroundFill(final Paint fill) { backgroundFill.set(fill); } public Paint getBackgroundFill() { return backgroundFill.get(); } public ObjectProperty<Paint> strokeFillProperty() { return strokeFill; } public void setStrokeFill(final Paint fill) { strokeFill.set(fill); } public Paint getStrokeFill() { return strokeFill.get(); } public ObjectProperty<Paint> backgroundMouseOnFillProperty() { return backgroundMouseOnFill; } public void setBackgroundMouseOnFill(final Paint fill) { backgroundMouseOnFill.set(fill); } public Paint getBackgroundMouseOnFill() { return backgroundMouseOnFill.get(); } public ObjectProperty<Paint> strokeMouseOnFillProperty() { return strokeMouseOnFill; } public void setStrokeMouseOnFill(final Paint fill) { strokeMouseOnFill.set(fill); } public Paint getStrokeMouseOnFill() { return strokeMouseOnFill.get(); } public BooleanProperty clockwiseProperty() { return clockwise; } public void setClockwise(final boolean clockwise) { this.clockwise.set(clockwise); } public boolean isClockwise() { return clockwise.get(); } public BooleanProperty strokeVisibleProperty() { return strokeVisible; } public void setStrokeVisible(final boolean visible) { strokeVisible.set(visible); } public boolean isStrokeVisible() { return strokeVisible.get(); } public BooleanProperty backgroundVisibleProperty() { return backgroundVisible; } public void setBackgroundVisible(final boolean visible) { backgroundVisible.set(visible); } public boolean isBackgroundVisible() { return strokeVisible.get(); } public ObjectProperty<Node> graphicProperty() { return graphic; } public void setGraphic(final Node graphic) { this.graphic.set(graphic); } public Node getGraphic() { return graphic.get(); } protected void redraw() { if (graphic.get() != null) { graphicContainer.getChildren().setAll(graphic.get()); } else { graphicContainer.getChildren().clear(); } path.setFill(backgroundVisible.get() ? (mouseOn && backgroundMouseOnFill.get() != null ? backgroundMouseOnFill .get() : backgroundFill.get()) : null); path.setStroke(strokeVisible.get() ? (mouseOn && strokeMouseOnFill.get() != null ? strokeMouseOnFill.get() : strokeFill.get()) : null); path.setFillRule(FillRule.EVEN_ODD); computeCoordinates(); updateCoordinates(); } protected void updateCoordinates() { final double innerRadiusValue = innerRadius.get(); final double radiusValue = radius.get(); moveTo.setX(innerStartX + translateX); moveTo.setY(innerStartY + translateY); arcToInner.setX(innerEndX + translateX); arcToInner.setY(innerEndY + translateY); arcToInner.setSweepFlag(innerSweep); arcToInner.setRadiusX(innerRadiusValue); arcToInner.setRadiusY(innerRadiusValue); lineTo.setX(startX + translateX); lineTo.setY(startY + translateY); arcTo.setX(endX + translateX); arcTo.setY(endY + translateY); arcTo.setSweepFlag(sweep); arcTo.setRadiusX(radiusValue); arcTo.setRadiusY(radiusValue); lineTo2.setX(innerStartX + translateX); lineTo2.setY(innerStartY + translateY); if (graphic.get() != null) { graphic.get().setTranslateX(graphicX + translateX); graphic.get().setTranslateY(graphicY + translateY); } // this.translateXProperty().set(this.translateX); // this.translateYProperty().set(this.translateY); } protected void computeCoordinates() { final double innerRadiusValue = innerRadius.get(); final double startAngleValue = startAngle.get(); final double length = this.length.get(); final double graphicAngle = startAngleValue + (length / 2.0); final double radiusValue = radius.get(); final double graphicRadius = innerRadiusValue + (radiusValue - innerRadiusValue) / 2.0; final double offsetValue = offset.get(); if (!clockwise.get()) { innerStartX = innerRadiusValue * Math.cos(Math.toRadians(startAngleValue)); innerStartY = -innerRadiusValue * Math.sin(Math.toRadians(startAngleValue)); innerEndX = innerRadiusValue * Math.cos(Math.toRadians(startAngleValue + length)); innerEndY = -innerRadiusValue * Math.sin(Math.toRadians(startAngleValue + length)); innerSweep = false; startX = radiusValue * Math.cos(Math.toRadians(startAngleValue + length)); startY = -radiusValue * Math.sin(Math.toRadians(startAngleValue + length)); endX = radiusValue * Math.cos(Math.toRadians(startAngleValue)); endY = -radiusValue * Math.sin(Math.toRadians(startAngleValue)); sweep = true; if (graphic.get() != null) { graphicX = graphicRadius * Math.cos(Math.toRadians(graphicAngle)) - graphic.get().getBoundsInParent().getWidth() / 2.0; graphicY = -graphicRadius * Math.sin(Math.toRadians(graphicAngle)) - graphic.get().getBoundsInParent().getHeight() / 2.0; } translateX = offsetValue * Math.cos(Math.toRadians(startAngleValue + (length / 2.0))); translateY = -offsetValue * Math.sin(Math.toRadians(startAngleValue + (length / 2.0))); } else if (clockwise.get()) { innerStartX = innerRadiusValue * Math.cos(Math.toRadians(startAngleValue)); innerStartY = innerRadiusValue * Math.sin(Math.toRadians(startAngleValue)); innerEndX = innerRadiusValue * Math.cos(Math.toRadians(startAngleValue + length)); innerEndY = innerRadiusValue * Math.sin(Math.toRadians(startAngleValue + length)); innerSweep = true; startX = radiusValue * Math.cos(Math.toRadians(startAngleValue + length)); startY = radiusValue * Math.sin(Math.toRadians(startAngleValue + length)); endX = radiusValue * Math.cos(Math.toRadians(startAngleValue)); endY = radiusValue * Math.sin(Math.toRadians(startAngleValue)); sweep = false; if (graphic.get() != null) { graphicX = graphicRadius * Math.cos(Math.toRadians(graphicAngle)) - graphic.get().getBoundsInParent().getWidth() / 2.0; graphicY = graphicRadius * Math.sin(Math.toRadians(graphicAngle)) - graphic.get().getBoundsInParent().getHeight() / 2.0; } translateX = offsetValue * Math.cos(Math.toRadians(startAngleValue + (length / 2.0))); translateY = offsetValue * Math.sin(Math.toRadians(startAngleValue + (length / 2.0))); } } @Override public void changed( final ObservableValue<? extends Object> observableValue, final Object previousValue, final Object newValue) { redraw(); } }