/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.scene.paint;
import java.util.
List;
import com.sun.javafx.collections.
annotations.
ReturnsUnmodifiableCollection;
import com.sun.javafx.scene.paint.
GradientUtils;
import com.sun.javafx.tk.
Toolkit;
import javafx.beans.
NamedArg;
/**
* <p>The {@code LinearGradient} class fills a shape
* with a linear color gradient pattern. The user may specify two or
* more gradient colors, and this Paint will provide an interpolation
* between each color.</p>
*
* <p>
* The application provides an array of {@code Stop}s specifying how to distribute
* the colors along the gradient. The {@code Stop#offset} variable must be
* the range 0.0 to 1.0 and act like keyframes along the gradient.
* The offsets mark where the gradient should be exactly a particular color. </p>
*
* <p>If the proportional variable is set to true
* then the start and end points of the gradient
* should be specified relative to the unit square (0.0->1.0) and will
* be stretched across the shape. If the proportional variable is set
* to false, then the start and end points should be specified
* in the local coordinate system of the shape and the gradient will
* not be stretched at all.</p>
*
* <p>
* The two filled rectangles in the example below will render the same.
* The one on the left uses proportional coordinates to specify
* the end points of the gradient. The one on the right uses absolute
* coordinates. Both of them fill the specified rectangle with a
* horizontal gradient that varies from black to red</p>
*
<PRE>
// object bounding box relative (proportional = true)
Stop[] stops = new Stop[] { new Stop(0, Color.BLACK), new Stop(1, Color.RED)};
LinearGradient lg1 = new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE, stops);
Rectangle r1 = new Rectangle(0, 0, 100, 100);
r1.setFill(lg1);
// user space relative (proportional: = false)
LinearGradient lg2 = new LinearGradient(125, 0, 225, 0, false, CycleMethod.NO_CYCLE, stops);
Rectangle r2 = new Rectangle(125, 0, 100, 100);
r2.setFill(lg2);
</PRE>
* @since JavaFX 2.0
*/
public final class
LinearGradient extends
Paint {
private double
startX;
/**
* Defines the X coordinate of the gradient axis start point.
* If proportional is true (the default), this value specifies a
* point on a unit square that will be scaled to match the size of the
* the shape that the gradient fills.
(
* @defaultValue 0.0
*/
public final double
getStartX() {
return
startX;
}
private double
startY;
/**
* Defines the Y coordinate of the gradient axis start point.
* If proportional is true (the default), this value specifies a
* point on a unit square that will be scaled to match the size of the
* the shape that the gradient fills.
*
* @defaultValue 0.0
*/
public final double
getStartY() {
return
startY;
}
private double
endX;
/**
* Defines the X coordinate of the gradient axis end point.
* If proportional is true (the default), this value specifies a
* point on a unit square that will be scaled to match the size of the
* the shape that the gradient fills.
*
* @defaultValue 1.0
*/
public final double
getEndX() {
return
endX;
}
private double
endY;
/**
* Defines the Y coordinate of the gradient axis end point.
* If proportional is true (the default), this value specifies a
* point on a unit square that will be scaled to match the size of the
* the shape that the gradient fills.
*
* @defaultValue 1.0
*/
public final double
getEndY() {
return
endY;
}
private boolean
proportional;
/**
* Indicates whether start and end locations are proportional or absolute.
* If this flag is true, the two end points are defined in a coordinate
* space where coordinates in the range {@code [0..1]} are scaled to map
* onto the bounds of the shape that the gradient fills.
* If this flag is false, then the coordinates are specified in the local
* coordinate system of the node.
*
* @defaultValue true
*/
public final boolean
isProportional() {
return
proportional;
}
private
CycleMethod cycleMethod;
/**
* Defines which of the following cycle method is applied
* to the {@code LinearGradient}: {@code CycleMethod.NO_CYCLE},
* {@code CycleMethod.REFLECT}, or {@code CycleMethod.REPEAT}.
*
* @defaultValue NO_CYCLE
*/
public final
CycleMethod getCycleMethod() {
return
cycleMethod;
}
private
List<
Stop>
stops;
/**
* A sequence of 2 or more {@code Stop} values specifying how to distribute
* the colors along the gradient. These values must be in the range
* 0.0 to 1.0. They act like key frames along the gradient: they mark where
* the gradient should be exactly a particular color.
*
* <p>Each stop in the sequence must have an offset that is greater than the
* previous stop in the sequence.</p>
*
* <p>The list is unmodifiable and will throw
* {@code UnsupportedOperationException} on each modification attempt.</p>
*
* @defaultValue empty
*/
@
ReturnsUnmodifiableCollection
public final
List<
Stop>
getStops() {
return
stops;
}
/**
* @inheritDoc
* @since JavaFX 8.0
*/
@
Override public final boolean
isOpaque() {
return
opaque;
}
private final boolean
opaque;
/**
* A cached reference to the platform paint, no point recomputing twice
*/
private
Object platformPaint;
/**
* The cached hash code, used to improve performance in situations where
* we cache gradients, such as in the CSS routines.
*/
private int
hash;
/**
* Creates a new instance of LinearGradient.
* @param startX the X coordinate of the gradient axis start point
* @param startY the Y coordinate of the gradient axis start point
* @param endX the X coordinate of the gradient axis end point
* @param endY the Y coordinate of the gradient axis end point
* @param proportional whether the coordinates are proportional
* to the shape which this gradient fills
* @param cycleMethod cycle method applied to the gradient
* @param stops the gradient's color specification
*/
public
LinearGradient(
@
NamedArg("startX") double
startX,
@
NamedArg("startY") double
startY,
@
NamedArg(value="endX", defaultValue="1") double
endX,
@
NamedArg(value="endY", defaultValue="1") double
endY,
@
NamedArg(value="proportional", defaultValue="true") boolean
proportional,
@
NamedArg("cycleMethod")
CycleMethod cycleMethod,
@
NamedArg("stops")
Stop...
stops) {
this.
startX =
startX;
this.
startY =
startY;
this.
endX =
endX;
this.
endY =
endY;
this.
proportional =
proportional;
this.
cycleMethod = (
cycleMethod == null) ?
CycleMethod.
NO_CYCLE:
cycleMethod;
this.
stops =
Stop.
normalize(
stops);
this.
opaque =
determineOpacity();
}
/**
* Creates a new instance of LinearGradient.
* @param startX the X coordinate of the gradient axis start point
* @param startY the Y coordinate of the gradient axis start point
* @param endX the X coordinate of the gradient axis end point
* @param endY the Y coordinate of the gradient axis end point
* @param proportional whether the coordinates are proportional
* to the shape which this gradient fills
* @param cycleMethod cycle method applied to the gradient
* @param stops the gradient's color specification
*/
public
LinearGradient(
@
NamedArg("startX") double
startX,
@
NamedArg("startY") double
startY,
@
NamedArg(value="endX", defaultValue="1") double
endX,
@
NamedArg(value="endY", defaultValue="1") double
endY,
@
NamedArg(value="proportional", defaultValue="true") boolean
proportional,
@
NamedArg("cycleMethod")
CycleMethod cycleMethod,
@
NamedArg("stops")
List<
Stop>
stops) {
this.
startX =
startX;
this.
startY =
startY;
this.
endX =
endX;
this.
endY =
endY;
this.
proportional =
proportional;
this.
cycleMethod = (
cycleMethod == null) ?
CycleMethod.
NO_CYCLE:
cycleMethod;
this.
stops =
Stop.
normalize(
stops);
this.
opaque =
determineOpacity();
}
/**
* Iterate over all the stops. If any one of them has a transparent
* color, then we return false. If there are no stops, we return false.
* Otherwise, we return true. Note that this is called AFTER Stop.normalize,
* which ensures that we always have at least 2 stops.
*
* @return Whether this gradient is opaque
*/
private boolean
determineOpacity() {
final int
numStops = this.
stops.
size();
for (int
i = 0;
i <
numStops;
i++) {
if (!
stops.
get(
i).
getColor().
isOpaque()) {
return false;
}
}
return true;
}
@
Override
Object acc_getPlatformPaint() {
if (
platformPaint == null) {
platformPaint =
Toolkit.
getToolkit().
getPaint(this);
}
return
platformPaint;
}
/**
* Indicates whether some other object is "equal to" this one.
* @param obj the reference object with which to compare.
* @return {@code true} if this object is equal to the {@code obj} argument; {@code false} otherwise.
*/
@
Override public boolean
equals(
Object obj) {
if (
obj == null) return false;
if (
obj == this) return true;
if (
obj instanceof
LinearGradient) {
final
LinearGradient other = (
LinearGradient)
obj;
if ((
startX !=
other.
startX) ||
(
startY !=
other.
startY) ||
(
endX !=
other.
endX) ||
(
endY !=
other.
endY) ||
(
proportional !=
other.
proportional) ||
(
cycleMethod !=
other.
cycleMethod)) return false;
if (!
stops.
equals(
other.
stops)) return false;
return true;
} else return false;
}
/**
* Returns a hash code for this {@code LinearGradient} object.
* @return a hash code for this {@code LinearGradient} object.
*/
@
Override public int
hashCode() {
if (
hash == 0) {
long
bits = 17L;
bits = 37L *
bits +
Double.
doubleToLongBits(
startX);
bits = 37L *
bits +
Double.
doubleToLongBits(
startY);
bits = 37L *
bits +
Double.
doubleToLongBits(
endX);
bits = 37L *
bits +
Double.
doubleToLongBits(
endY);
bits = 37L *
bits + ((
proportional) ? 1231L : 1237L);
bits = 37L *
bits +
cycleMethod.
hashCode();
for (
Stop stop:
stops) {
bits = 37L *
bits +
stop.
hashCode();
}
hash = (int) (
bits ^ (
bits >> 32));
}
return
hash;
}
/**
* Returns a string representation of this {@code LinearGradient} object.
* @return a string representation of this {@code LinearGradient} object.
*/
@
Override public
String toString() {
final
StringBuilder s = new
StringBuilder("linear-gradient(from ")
.
append(
GradientUtils.
lengthToString(
startX,
proportional))
.
append(" ").
append(
GradientUtils.
lengthToString(
startY,
proportional))
.
append(" to ").
append(
GradientUtils.
lengthToString(
endX,
proportional))
.
append(" ").
append(
GradientUtils.
lengthToString(
endY,
proportional))
.
append(", ");
switch (
cycleMethod) {
case
REFLECT:
s.
append("reflect").
append(", ");
break;
case
REPEAT:
s.
append("repeat").
append(", ");
break;
}
for (
Stop stop :
stops) {
s.
append(
stop).
append(", ");
}
s.
delete(
s.
length() - 2,
s.
length());
s.
append(")");
return
s.
toString();
}
/**
* Creates a linear gradient value from a string representation.
* <p>The format of the string representation is based on
* JavaFX CSS specification for linear gradient which is
* <pre>
* linear-gradient( [ [from <point> to <point>| [ to <side-or-corner>], ]? [ [ repeat | reflect ], ]? <color-stop>[, <color-stop>]+)
* </pre>
* where
* <pre>
* <side-or-corner> = [left | right] || [top | bottom]
* <point> = [ [ <length> <length> ] | [ <percentage> | <percentage> ] ]
* <color-stop> = [ <color> [ <percentage> | <length>]? ]
* </pre>
* </p>
* <p> Currently length can be only specified in px, the specification of unit can be omited.
* Format of color representation is the one used in {@link Color#web(String color)}.
* The linear-gradient keyword can be omited.
* For additional information about the format of string representation, see the
* <a href="../doc-files/cssref.html">CSS Reference Guide</a>.
* </p>
*
* Examples:
* <pre><code>
* LinearGradient g
* = LinearGradient.valueOf("linear-gradient(from 0% 0% to 100% 100%, red 0% , blue 30%, black 100%)");
* LinearGradient g
* = LinearGradient.valueOf("from 0% 0% to 100% 100%, red 0% , blue 30%, black 100%");
* LinearGradient g
* = LinearGradient.valueOf("linear-gradient(from 0px 0px to 200px 0px, #00ff00 0%, 0xff0000 50%, 0x1122ff40 100%)");
* LinearGradient g
* = LinearGradient.valueOf("from 0px 0px to 200px 0px, #00ff00 0%, 0xff0000 50%, 0x1122ff40 100%");
* </code></pre>
*
* @param value the string to convert
* @throws NullPointerException if the {@code value} is {@code null}
* @throws IllegalArgumentException if the {@code value} cannot be parsed
* @return a {@code LinearGradient} object holding the value represented
* by the string argument.
* @since JavaFX 2.1
*/
public static
LinearGradient valueOf(
String value) {
if (
value == null) {
throw new
NullPointerException("gradient must be specified");
}
String start = "linear-gradient(";
String end = ")";
if (
value.
startsWith(
start)) {
if (!
value.
endsWith(
end)) {
throw new
IllegalArgumentException("Invalid gradient specification, "
+ "must end with \"" +
end + '"');
}
value =
value.
substring(
start.
length(),
value.
length() -
end.
length());
}
GradientUtils.
Parser parser = new
GradientUtils.
Parser(
value);
if (
parser.
getSize() < 2) {
throw new
IllegalArgumentException("Invalid gradient specification");
}
GradientUtils.
Point startX =
GradientUtils.
Point.
MIN;
GradientUtils.
Point startY =
GradientUtils.
Point.
MIN;
GradientUtils.
Point endX =
GradientUtils.
Point.
MIN;
GradientUtils.
Point endY =
GradientUtils.
Point.
MIN;
String[]
tokens =
parser.
splitCurrentToken();
if ("from".
equals(
tokens[0])) {
GradientUtils.
Parser.
checkNumberOfArguments(
tokens, 5);
startX =
parser.
parsePoint(
tokens[1]);
startY =
parser.
parsePoint(
tokens[2]);
if (!"to".
equals(
tokens[3])) {
throw new
IllegalArgumentException("Invalid gradient specification, \"to\" expected");
}
endX =
parser.
parsePoint(
tokens[4]);
endY =
parser.
parsePoint(
tokens[5]);
parser.
shift();
} else if ("to".
equals(
tokens[0])) {
int
horizontalSet = 0;
int
verticalSet = 0;
for (int
i = 1;
i < 3 &&
i <
tokens.length;
i++) {
if ("left".
equals(
tokens[
i])) {
startX =
GradientUtils.
Point.
MAX;
endX =
GradientUtils.
Point.
MIN;
horizontalSet++;
} else if ("right".
equals(
tokens[
i])) {
startX =
GradientUtils.
Point.
MIN;
endX =
GradientUtils.
Point.
MAX;
horizontalSet++;
} else if ("top".
equals(
tokens[
i])) {
startY =
GradientUtils.
Point.
MAX;
endY =
GradientUtils.
Point.
MIN;
verticalSet++;
} else if ("bottom".
equals(
tokens[
i])) {
startY =
GradientUtils.
Point.
MIN;
endY =
GradientUtils.
Point.
MAX;
verticalSet++;
} else {
throw new
IllegalArgumentException("Invalid gradient specification,"
+ " unknown value after 'to'");
}
}
if (
verticalSet > 1) {
throw new
IllegalArgumentException("Invalid gradient specification,"
+ " vertical direction set twice after 'to'");
}
if (
horizontalSet > 1) {
throw new
IllegalArgumentException("Invalid gradient specification,"
+ " horizontal direction set twice after 'to'");
}
parser.
shift();
} else {
// default is "to bottom"
startY =
GradientUtils.
Point.
MIN;
endY =
GradientUtils.
Point.
MAX;
}
// repeat/reflect
CycleMethod method =
CycleMethod.
NO_CYCLE;
String currentToken =
parser.
getCurrentToken();
if ("repeat".
equals(
currentToken)) {
method =
CycleMethod.
REPEAT;
parser.
shift();
} else if ("reflect".
equals(
currentToken)) {
method =
CycleMethod.
REFLECT;
parser.
shift();
}
double
dist = 0;
if (!
startX.
proportional) {
double
dx =
endX.
value -
startX.
value;
double
dy =
endY.
value -
startY.
value;
dist =
Math.
sqrt(
dx*
dx +
dy*
dy);
}
Stop[]
stops =
parser.
parseStops(
startX.
proportional,
dist);
return new
LinearGradient(
startX.
value,
startY.
value,
endX.
value,
endY.
value,
startX.
proportional,
method,
stops);
}
}