160eb2bd39
Reviewed-by: erikj
639 lines
30 KiB
Java
639 lines
30 KiB
Java
/*
|
|
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code 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 General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
package build.tools.generatenimbus;
|
|
|
|
import java.awt.geom.Point2D;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
|
|
/**
|
|
* PainterGenerator - Class for generating Painter class java source from a Canvas
|
|
*
|
|
* Following in the general theory that is used to generate a Painter file.
|
|
*
|
|
* Each Painter file represents a Region. So there is one painter file per region. In
|
|
* skin.laf we support Icon subregions, which are really just hacked versions of the
|
|
* parent region.
|
|
*
|
|
* In order to generate the most compact and efficient bytecode possible for the
|
|
* Painters, we actually perform the generation sequence in two steps. The first
|
|
* step is the analysis phase, where we walk through the SynthModel for the region
|
|
* and discover commonality among the different states in the region. For example,
|
|
* do they have common paths? Do they have common colors? Gradients? Is the painting
|
|
* code for the different states identical other than for colors?
|
|
*
|
|
* We gather this information up. On the second pass, we use this data to determine the
|
|
* methods that need to be generated, and the class variables that need to be generated.
|
|
* We try to keep the actual bytecode count as small as possible so that we may reduce
|
|
* the overall size of the look and feel significantly.
|
|
*
|
|
* @author Richard Bair
|
|
* @author Jasper Potts
|
|
*/
|
|
public class PainterGenerator {
|
|
|
|
private static final boolean debug = false;
|
|
|
|
//a handful of counters, incremented whenever the associated object type is encountered.
|
|
//These counters form the basis of the field and method suffixes.
|
|
//These are all 1 based, because I felt like it :-)
|
|
private int colorCounter = 1;
|
|
private int gradientCounter = 1;
|
|
private int radialCounter = 1;
|
|
private int pathCounter = 1;
|
|
private int rectCounter = 1;
|
|
private int roundRectCounter = 1;
|
|
private int ellipseCounter = 1;
|
|
|
|
private int stateTypeCounter = 1;
|
|
|
|
//during the first pass, we will construct these maps
|
|
private Map<String, String> colors = new HashMap<String, String>();
|
|
/**
|
|
* Code=>method name.
|
|
*/
|
|
private Map<String, String> methods = new HashMap<String, String>();
|
|
|
|
//these variables hold the generated code
|
|
/**
|
|
* The source code in this variable will be used to define the various state types
|
|
*/
|
|
private StringBuilder stateTypeCode = new StringBuilder();
|
|
/**
|
|
* The source code in this variable will be used to define the switch statement for painting
|
|
*/
|
|
private StringBuilder switchCode = new StringBuilder();
|
|
/**
|
|
* The source code in this variable will be used to define the methods for painting each state
|
|
*/
|
|
private StringBuilder paintingCode = new StringBuilder();
|
|
/**
|
|
* The source code in this variable will be used to add getExtendedCacheKeys
|
|
* implementation if needed.
|
|
*/
|
|
private StringBuilder getExtendedCacheKeysCode = new StringBuilder();
|
|
/**
|
|
* The source code in this variable will be used to define the methods for decoding gradients
|
|
* and shapes.
|
|
*/
|
|
private StringBuilder gradientsCode = new StringBuilder();
|
|
private StringBuilder colorCode = new StringBuilder();
|
|
private StringBuilder shapesCode = new StringBuilder();
|
|
/**
|
|
* Map of component colors keyed by state constant name
|
|
*/
|
|
private Map<String, List<ComponentColor>> componentColorsMap =
|
|
new LinkedHashMap<String, List<ComponentColor>>();
|
|
/**
|
|
* For the current state the list of all component colors used by this
|
|
* painter, the index in this list is also the index in the runtime array
|
|
* of defaults and keys.
|
|
*/
|
|
private List<ComponentColor> componentColors = null;
|
|
|
|
PainterGenerator(UIRegion r) {
|
|
generate(r);
|
|
}
|
|
|
|
private void generate(UIRegion r) {
|
|
for (UIState state : r.getBackgroundStates()) {
|
|
Canvas canvas = state.getCanvas();
|
|
String type = (r instanceof UIIconRegion ? r.getKey() : "Background");
|
|
generate(state, canvas, type);
|
|
}
|
|
for (UIState state : r.getForegroundStates()) {
|
|
Canvas canvas = state.getCanvas();
|
|
generate(state, canvas, "Foreground");
|
|
}
|
|
for (UIState state : r.getBorderStates()) {
|
|
Canvas canvas = state.getCanvas();
|
|
generate(state, canvas, "Border");
|
|
}
|
|
//now check for any uiIconRegions, since these are collapsed together.
|
|
for (UIRegion sub : r.getSubRegions()) {
|
|
if (sub instanceof UIIconRegion) {
|
|
generate(sub);
|
|
}
|
|
}
|
|
//generate all the code for component colors
|
|
if (!componentColorsMap.isEmpty()) {
|
|
getExtendedCacheKeysCode
|
|
.append(" protected Object[] getExtendedCacheKeys(JComponent c) {\n")
|
|
.append(" Object[] extendedCacheKeys = null;\n")
|
|
.append(" switch(state) {\n");
|
|
for (Map.Entry<String, List<ComponentColor>> entry : componentColorsMap.entrySet()) {
|
|
getExtendedCacheKeysCode
|
|
.append(" case ")
|
|
.append(entry.getKey()).append(":\n")
|
|
.append(" extendedCacheKeys = new Object[] {\n");
|
|
for (int i=0; i<entry.getValue().size(); i++) {
|
|
ComponentColor cc = entry.getValue().get(i);
|
|
cc.write(getExtendedCacheKeysCode);
|
|
if (i + 1 < entry.getValue().size()) {
|
|
getExtendedCacheKeysCode.append("),\n");
|
|
} else {
|
|
getExtendedCacheKeysCode.append(")");
|
|
}
|
|
}
|
|
getExtendedCacheKeysCode.append("};\n")
|
|
.append(" break;\n");
|
|
}
|
|
getExtendedCacheKeysCode
|
|
.append(" }\n")
|
|
.append(" return extendedCacheKeys;\n")
|
|
.append(" }");
|
|
}
|
|
}
|
|
|
|
//type is background, foreground, border, upArrowIcon, etc.
|
|
private void generate(UIState state, Canvas canvas, String type) {
|
|
String states = state.getStateKeys();
|
|
String stateType = Utils.statesToConstantName(type + "_" + states);
|
|
String paintMethodName = "paint" + type + Utils.statesToClassName(states);
|
|
//create new array for component colors for this state
|
|
componentColors = new ArrayList<ComponentColor>();
|
|
|
|
stateTypeCode.append(" static final int ").append(stateType).append(" = ").append(stateTypeCounter++).append(";\n");
|
|
|
|
if (canvas.isBlank()) {
|
|
return;
|
|
}
|
|
|
|
switchCode.append(" case ").append(stateType).append(": ").append(paintMethodName).append("(g); break;\n");
|
|
paintingCode.append(" private void ").append(paintMethodName).append("(Graphics2D g) {\n");
|
|
|
|
//start by setting up common info needed to encode the control points
|
|
Insets in = canvas.getStretchingInsets();
|
|
float a = in.left;
|
|
float b = canvas.getSize().width - in.right;
|
|
float c = in.top;
|
|
float d = canvas.getSize().height - in.bottom;
|
|
float width = canvas.getSize().width;
|
|
float height = canvas.getSize().height;
|
|
float cw = b - a;
|
|
float ch = d - c;
|
|
|
|
Layer[] layers = canvas.getLayers().toArray(new Layer[0]);
|
|
for (int index=layers.length-1; index >= 0; index--) {
|
|
Layer layer = layers[index];
|
|
|
|
//shapes must be painted in reverse order
|
|
List<Shape> shapes = layer.getShapes();
|
|
for (int i=shapes.size()-1; i>=0; i--) {
|
|
Shape shape = shapes.get(i);
|
|
Paint paint = shape.getPaint();
|
|
|
|
/*
|
|
We attempt to write the minimal number of bytecodes as possible when
|
|
generating code. Due to the inherit complexities in determining what
|
|
is extraneous, we use the following system:
|
|
|
|
We first generate the code for the shape. Then, we check to see if
|
|
this shape has already been generated. If so, then we defer to an
|
|
existing method. If not, then we will create a new methods, stick
|
|
the code in it, and refer to that method.
|
|
*/
|
|
|
|
String shapeMethodName = null; // will contain the name of the method which creates the shape
|
|
String shapeVariable = null; // will be one of rect, roundRect, ellipse, or path.
|
|
String shapeMethodBody = null;
|
|
|
|
if (shape instanceof Rectangle) {
|
|
Rectangle rshape = (Rectangle) shape;
|
|
float x1 = encode((float)rshape.getX1(), a, b, width);
|
|
float y1 = encode((float)rshape.getY1(), c, d, height);
|
|
float x2 = encode((float)rshape.getX2(), a, b, width);
|
|
float y2 = encode((float)rshape.getY2(), c, d, height);
|
|
if (rshape.isRounded()) {
|
|
//it is a rounded rectangle
|
|
float rounding = (float)rshape.getRounding();
|
|
|
|
shapeMethodBody =
|
|
" roundRect.setRoundRect(" +
|
|
writeDecodeX(x1) + ", //x\n" +
|
|
" " + writeDecodeY(y1) + ", //y\n" +
|
|
" " + writeDecodeX(x2) + " - " + writeDecodeX(x1) + ", //width\n" +
|
|
" " + writeDecodeY(y2) + " - " + writeDecodeY(y1) + ", //height\n" +
|
|
" " + rounding + "f, " + rounding + "f); //rounding";
|
|
shapeVariable = "roundRect";
|
|
} else {
|
|
shapeMethodBody =
|
|
" rect.setRect(" +
|
|
writeDecodeX(x1) + ", //x\n" +
|
|
" " + writeDecodeY(y1) + ", //y\n" +
|
|
" " + writeDecodeX(x2) + " - " + writeDecodeX(x1) + ", //width\n" +
|
|
" " + writeDecodeY(y2) + " - " + writeDecodeY(y1) + "); //height";
|
|
shapeVariable = "rect";
|
|
}
|
|
} else if (shape instanceof Ellipse) {
|
|
Ellipse eshape = (Ellipse) shape;
|
|
float x1 = encode((float)eshape.getX1(), a, b, width);
|
|
float y1 = encode((float)eshape.getY1(), c, d, height);
|
|
float x2 = encode((float)eshape.getX2(), a, b, width);
|
|
float y2 = encode((float)eshape.getY2(), c, d, height);
|
|
shapeMethodBody =
|
|
" ellipse.setFrame(" +
|
|
writeDecodeX(x1) + ", //x\n" +
|
|
" " + writeDecodeY(y1) + ", //y\n" +
|
|
" " + writeDecodeX(x2) + " - " + writeDecodeX(x1) + ", //width\n" +
|
|
" " + writeDecodeY(y2) + " - " + writeDecodeY(y1) + "); //height";
|
|
shapeVariable = "ellipse";
|
|
} else if (shape instanceof Path) {
|
|
Path pshape = (Path) shape;
|
|
List<Point> controlPoints = pshape.getControlPoints();
|
|
Point first, last;
|
|
first = last = controlPoints.get(0);
|
|
StringBuilder buffer = new StringBuilder();
|
|
buffer.append(" path.reset();\n");
|
|
buffer.append(" path.moveTo(" + writeDecodeX(encode((float)first.getX(), a, b, width)) + ", " + writeDecodeY(encode((float)first.getY(), c, d, height)) + ");\n");
|
|
for (int j=1; j<controlPoints.size(); j++) {
|
|
Point cp = controlPoints.get(j);
|
|
if (last.isP2Sharp() && cp.isP1Sharp()) {
|
|
float x = encode((float)cp.getX(), a, b, width);
|
|
float y = encode((float)cp.getY(), c, d, height);
|
|
buffer.append(" path.lineTo(" + writeDecodeX(x) + ", " + writeDecodeY(y) + ");\n");
|
|
} else {
|
|
float x1 = encode((float)last.getX(), a, b, width);
|
|
float y1 = encode((float)last.getY(), c, d, height);
|
|
float x2 = encode((float)cp.getX(), a, b, width);
|
|
float y2 = encode((float)cp.getY(), c, d, height);
|
|
buffer.append(
|
|
" path.curveTo(" + writeDecodeBezierX(x1, last.getX(), last.getCp2X()) + ", "
|
|
+ writeDecodeBezierY(y1, last.getY(), last.getCp2Y()) + ", "
|
|
+ writeDecodeBezierX(x2, cp.getX(), cp.getCp1X()) + ", "
|
|
+ writeDecodeBezierY(y2, cp.getY(), cp.getCp1Y()) + ", "
|
|
+ writeDecodeX(x2) + ", " + writeDecodeY(y2) + ");\n");
|
|
}
|
|
last = cp;
|
|
}
|
|
if (last.isP2Sharp() && first.isP1Sharp()) {
|
|
float x = encode((float)first.getX(), a, b, width);
|
|
float y = encode((float)first.getY(), c, d, height);
|
|
buffer.append(" path.lineTo(" + writeDecodeX(x) + ", " + writeDecodeY(y) + ");\n");
|
|
} else {
|
|
float x1 = encode((float)last.getX(), a, b, width);
|
|
float y1 = encode((float)last.getY(), c, d, height);
|
|
float x2 = encode((float)first.getX(), a, b, width);
|
|
float y2 = encode((float)first.getY(), c, d, height);
|
|
buffer.append(
|
|
" path.curveTo(" + writeDecodeBezierX(x1, last.getX(), last.getCp2X()) + ", "
|
|
+ writeDecodeBezierY(y1, last.getY(), last.getCp2Y()) + ", "
|
|
+ writeDecodeBezierX(x2, first.getX(), first.getCp1X()) + ", "
|
|
+ writeDecodeBezierY(y2, first.getY(), first.getCp1Y()) + ", "
|
|
+ writeDecodeX(x2) + ", " + writeDecodeY(y2) + ");\n");
|
|
}
|
|
buffer.append(" path.closePath();");
|
|
shapeMethodBody = buffer.toString();
|
|
shapeVariable = "path";
|
|
} else {
|
|
throw new RuntimeException("Cannot happen unless a new Shape has been defined");
|
|
}
|
|
|
|
//now that we have the shape defined in shapeMethodBody, and a shapeVariable name,
|
|
//look to see if such a body has been previously defined.
|
|
shapeMethodName = methods.get(shapeMethodBody);
|
|
String returnType = null;
|
|
if (shapeMethodName == null) {
|
|
if ("rect".equals(shapeVariable)) {
|
|
shapeMethodName = "decodeRect" + rectCounter++;
|
|
returnType = "Rectangle2D";
|
|
} else if ("roundRect".equals(shapeVariable)) {
|
|
shapeMethodName = "decodeRoundRect" + roundRectCounter++;
|
|
returnType = "RoundRectangle2D";
|
|
} else if ("ellipse".equals(shapeVariable)) {
|
|
shapeMethodName = "decodeEllipse" + ellipseCounter++;
|
|
returnType = "Ellipse2D";
|
|
} else {
|
|
shapeMethodName = "decodePath" + pathCounter++;
|
|
returnType = "Path2D";
|
|
}
|
|
methods.put(shapeMethodBody, shapeMethodName);
|
|
|
|
//since the method wasn't previously defined, time to define it
|
|
shapesCode.append(" private ").append(returnType).append(" ").append(shapeMethodName).append("() {\n");
|
|
shapesCode.append(shapeMethodBody);
|
|
shapesCode.append("\n");
|
|
shapesCode.append(" return " + shapeVariable + ";\n");
|
|
shapesCode.append(" }\n\n");
|
|
}
|
|
|
|
//now that the method has been defined, I can go on and decode the
|
|
//paint. After the paint is decoded, I can write the g.fill() method call,
|
|
//using the result of the shapeMethodName. Yay!
|
|
|
|
// if (shapeVariable != null) {
|
|
//first, calculate the bounds of the shape being painted and store in variables
|
|
paintingCode.append(" ").append(shapeVariable).append(" = ").append(shapeMethodName).append("();\n");
|
|
|
|
if (paint instanceof Matte) {
|
|
String colorVariable = encodeMatte((Matte)paint);
|
|
paintingCode.append(" g.setPaint(").append(colorVariable).append(");\n");
|
|
} else if (paint instanceof Gradient) {
|
|
String gradientMethodName = encodeGradient(shape, (Gradient)paint);
|
|
paintingCode.append(" g.setPaint(").append(gradientMethodName).append("(").append(shapeVariable).append("));\n");
|
|
} else if (paint instanceof RadialGradient) {
|
|
String radialMethodName = encodeRadial(shape, (RadialGradient)paint);
|
|
paintingCode.append(" g.setPaint(").append(radialMethodName).append("(").append(shapeVariable).append("));\n");
|
|
}
|
|
paintingCode.append(" g.fill(").append(shapeVariable).append(");\n");
|
|
}
|
|
}
|
|
|
|
paintingCode.append("\n }\n\n");
|
|
|
|
//collect component colors
|
|
if (!componentColors.isEmpty()) {
|
|
componentColorsMap.put(stateType, componentColors);
|
|
componentColors = null;
|
|
}
|
|
}
|
|
|
|
private float encode(float x, float a, float b, float w) {
|
|
float r = 0;
|
|
if (x < a) {
|
|
r = (x / a);
|
|
} else if (x > b) {
|
|
r = 2 + ((x - b) / (w - b));
|
|
} else if (x == a && x == b) {
|
|
return 1.5f;
|
|
} else {
|
|
r = 1 + ((x - a) / (b - a));
|
|
}
|
|
|
|
if (Float.isNaN(r)) {
|
|
if (debug) {
|
|
System.err.println("[Error] Encountered NaN: encode(" + x + ", " + a + ", " + b + ", " + w + ")");
|
|
}
|
|
return 0;
|
|
} else if (Float.isInfinite(r)) {
|
|
if (debug) {
|
|
System.err.println("[Error] Encountered Infinity: encode(" + x + ", " + a + ", " + b + ", " + w + ")");
|
|
}
|
|
return 0;
|
|
} else if (r < 0) {
|
|
if (debug) {
|
|
System.err.println("[Error] encoded value was less than 0: encode(" + x + ", " + a + ", " + b + ", " + w + ")");
|
|
}
|
|
return 0;
|
|
} else if (r > 3) {
|
|
if (debug) {
|
|
System.err.println("[Error] encoded value was greater than 3: encode(" + x + ", " + a + ", " + b + ", " + w + ")");
|
|
}
|
|
return 3;
|
|
} else {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
private String writeDecodeX(float encodedX) {
|
|
return "decodeX(" + encodedX + "f)";
|
|
}
|
|
|
|
private String writeDecodeY(float encodedY) {
|
|
return "decodeY(" + encodedY + "f)";
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param ex encoded x value
|
|
* @param x unencoded x value
|
|
* @param cpx unencoded cpx value
|
|
* @return
|
|
*/
|
|
private static String writeDecodeBezierX(double ex, double x, double cpx) {
|
|
return "decodeAnchorX(" + ex + "f, " + (cpx - x) + "f)";
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param ey encoded y value
|
|
* @param y unencoded y value
|
|
* @param cpy unencoded cpy value
|
|
* @return
|
|
*/
|
|
private static String writeDecodeBezierY(double ey, double y, double cpy) {
|
|
return "decodeAnchorY(" + ey + "f, " + (cpy - y) + "f)";
|
|
}
|
|
|
|
private String encodeMatte(Matte m) {
|
|
String declaration = m.getDeclaration();
|
|
String variableName = colors.get(declaration);
|
|
if (variableName == null) {
|
|
variableName = "color" + colorCounter++;
|
|
colors.put(declaration, variableName);
|
|
colorCode.append(String.format(" private Color %s = %s;\n",
|
|
variableName, declaration));
|
|
}
|
|
// handle component colors
|
|
if (m.getComponentPropertyName() != null) {
|
|
ComponentColor cc = m.createComponentColor(variableName);
|
|
int index = componentColors.indexOf(cc);
|
|
if (index == -1) {
|
|
index = componentColors.size();
|
|
componentColors.add(cc);
|
|
}
|
|
return "(Color)componentColors[" + index + "]";
|
|
} else {
|
|
return variableName;
|
|
}
|
|
}
|
|
|
|
private String encodeGradient(Shape ps, Gradient g) {
|
|
StringBuilder b = new StringBuilder();
|
|
float x1 = (float)ps.getPaintX1();
|
|
float y1 = (float)ps.getPaintY1();
|
|
float x2 = (float)ps.getPaintX2();
|
|
float y2 = (float)ps.getPaintY2();
|
|
b.append(" return decodeGradient((");
|
|
b.append(x1);
|
|
b.append("f * w) + x, (");
|
|
b.append(y1);
|
|
b.append("f * h) + y, (");
|
|
b.append(x2);
|
|
b.append("f * w) + x, (");
|
|
b.append(y2);
|
|
b.append("f * h) + y,\n");
|
|
encodeGradientColorsAndFractions(g,b);
|
|
b.append(");");
|
|
|
|
String methodBody = b.toString();
|
|
String methodName = methods.get(methodBody);
|
|
if (methodName == null) {
|
|
methodName = "decodeGradient" + gradientCounter++;
|
|
gradientsCode.append(" private Paint ").append(methodName).append("(Shape s) {\n");
|
|
gradientsCode.append(" Rectangle2D bounds = s.getBounds2D();\n");
|
|
gradientsCode.append(" float x = (float)bounds.getX();\n");
|
|
gradientsCode.append(" float y = (float)bounds.getY();\n");
|
|
gradientsCode.append(" float w = (float)bounds.getWidth();\n");
|
|
gradientsCode.append(" float h = (float)bounds.getHeight();\n");
|
|
gradientsCode.append(methodBody);
|
|
gradientsCode.append("\n }\n\n");
|
|
methods.put(methodBody, methodName);
|
|
}
|
|
return methodName;
|
|
}
|
|
|
|
/**
|
|
* Takes a abstract gradient and creates the code for the fractions float
|
|
* array and the colors array that can be used in the constructors of linear
|
|
* and radial gradients.
|
|
*
|
|
* @param g The abstract gradient to get stops from
|
|
* @param b Append code string of the form "new float[]{...},
|
|
* new Color[]{...}" to this StringBuilder
|
|
*/
|
|
private void encodeGradientColorsAndFractions(AbstractGradient g,
|
|
StringBuilder b) {
|
|
List<GradientStop> stops = g.getStops();
|
|
// there are stops.size() number of main stops. Between each is a
|
|
// fractional stop. Thus, there are: stops.size() + stops.size() - 1
|
|
// number of fractions and colors.
|
|
float[] fractions = new float[stops.size() + stops.size() - 1];
|
|
String[] colors = new String[fractions.length];
|
|
//for each stop, create the stop and it's associated fraction
|
|
int index = 0; // the index into fractions and colors
|
|
for (int i = 0; i < stops.size(); i++) {
|
|
GradientStop s = stops.get(i);
|
|
//copy over the stop's data
|
|
colors[index] = encodeMatte(s.getColor());
|
|
fractions[index] = s.getPosition();
|
|
|
|
//If this isn't the last stop, then add in the fraction
|
|
if (index < fractions.length - 1) {
|
|
float f1 = s.getPosition();
|
|
float f2 = stops.get(i + 1).getPosition();
|
|
index++;
|
|
fractions[index] = f1 + (f2 - f1) * s.getMidpoint();
|
|
colors[index] = "decodeColor("+
|
|
colors[index - 1]+","+
|
|
encodeMatte(stops.get(i + 1).getColor())+",0.5f)";
|
|
}
|
|
index++;
|
|
}
|
|
// Check boundary conditions
|
|
for (int i = 1; i < fractions.length; i++) {
|
|
//to avoid an error with LinearGradientPaint where two fractions
|
|
//are identical, bump up the fraction value by a minuscule amount
|
|
//if it is identical to the previous one
|
|
//NOTE: The <= is critical because the previous value may already
|
|
//have been bumped up
|
|
if (fractions[i] <= fractions[i - 1]) {
|
|
fractions[i] = fractions[i - 1] + .000001f;
|
|
}
|
|
}
|
|
//another boundary condition where multiple stops are all at the end. The
|
|
//previous loop bumped all but one of these past 1.0, which is bad.
|
|
//so remove any fractions (and their colors!) that are beyond 1.0
|
|
int outOfBoundsIndex = -1;
|
|
for (int i = 0; i < fractions.length; i++) {
|
|
if (fractions[i] > 1) {
|
|
outOfBoundsIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (outOfBoundsIndex >= 0) {
|
|
float[] f = fractions;
|
|
String[] c = colors;
|
|
fractions = new float[outOfBoundsIndex];
|
|
colors = new String[outOfBoundsIndex];
|
|
System.arraycopy(f, 0, fractions, 0, outOfBoundsIndex);
|
|
System.arraycopy(c, 0, colors, 0, outOfBoundsIndex);
|
|
}
|
|
// build string
|
|
b.append(" new float[] { ");
|
|
for (int i = 0; i < fractions.length; i++) {
|
|
if (i>0)b.append(',');
|
|
b.append(fractions[i]);
|
|
b.append('f');
|
|
}
|
|
b.append(" },\n new Color[] { ");
|
|
for (int i = 0; i < colors.length; i++) {
|
|
if (i>0) b.append(",\n ");
|
|
b.append(colors[i]);
|
|
}
|
|
b.append("}");
|
|
}
|
|
|
|
private String encodeRadial(Shape ps, RadialGradient g) {
|
|
float centerX1 = (float)ps.getPaintX1();
|
|
float centerY1 = (float)ps.getPaintY1();
|
|
float x2 = (float)ps.getPaintX2();
|
|
float y2 = (float)ps.getPaintY2();
|
|
float radius = (float)Point2D.distance(centerX1, centerY1, x2, y2);
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
b.append(" return decodeRadialGradient((");
|
|
b.append(centerX1);
|
|
b.append("f * w) + x, (");
|
|
b.append(centerY1);
|
|
b.append("f * h) + y, ");
|
|
b.append(radius);
|
|
b.append("f,\n");
|
|
encodeGradientColorsAndFractions(g,b);
|
|
b.append(");");
|
|
|
|
String methodBody = b.toString();
|
|
String methodName = methods.get(methodBody);
|
|
if (methodName == null) {
|
|
methodName = "decodeRadial" + radialCounter++;
|
|
gradientsCode.append(" private Paint ").append(methodName).append("(Shape s) {\n");
|
|
gradientsCode.append(" Rectangle2D bounds = s.getBounds2D();\n");
|
|
gradientsCode.append(" float x = (float)bounds.getX();\n");
|
|
gradientsCode.append(" float y = (float)bounds.getY();\n");
|
|
gradientsCode.append(" float w = (float)bounds.getWidth();\n");
|
|
gradientsCode.append(" float h = (float)bounds.getHeight();\n");
|
|
gradientsCode.append(methodBody);
|
|
gradientsCode.append("\n }\n\n");
|
|
methods.put(methodBody, methodName);
|
|
}
|
|
return methodName;
|
|
}
|
|
|
|
//note that this method is not thread-safe. In fact, none of this class is.
|
|
public static void writePainter(UIRegion r, String painterName) {
|
|
//Need only write out the stuff for this region, don't need to worry about subregions
|
|
//since this method will be called for each of those (and they go in their own file, anyway).
|
|
//The only subregion that we compound into this is the one for icons.
|
|
PainterGenerator gen = new PainterGenerator(r);
|
|
System.out.println("Generating source file: " + painterName + ".java");
|
|
|
|
Map<String, String> variables = Generator.getVariables();
|
|
variables.put("PAINTER_NAME", painterName);
|
|
variables.put("STATIC_DECL", gen.stateTypeCode.toString());
|
|
variables.put("COLORS_DECL", gen.colorCode.toString());
|
|
variables.put("DO_PAINT_SWITCH_BODY", gen.switchCode.toString());
|
|
variables.put("PAINTING_DECL", gen.paintingCode.toString());
|
|
variables.put("GET_EXTENDED_CACHE_KEYS", gen.getExtendedCacheKeysCode.toString());
|
|
variables.put("SHAPES_DECL", gen.shapesCode.toString());
|
|
variables.put("GRADIENTS_DECL", gen.gradientsCode.toString());
|
|
|
|
Generator.writeSrcFile("PainterImpl", variables, painterName);
|
|
}
|
|
}
|