com.codename1.ui.plaf.CSSBorder Maven / Gradle / Ivy
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.codename1.ui.plaf;
import com.codename1.charts.util.ColorUtil;
import com.codename1.io.Log;
import com.codename1.io.Util;
import static com.codename1.ui.CN.convertToPixels;
import com.codename1.ui.*;
import static com.codename1.ui.Component.BOTTOM;
import static com.codename1.ui.Component.LEFT;
import static com.codename1.ui.Component.RIGHT;
import static com.codename1.ui.Component.TOP;
import com.codename1.ui.geom.GeneralPath;
import com.codename1.ui.geom.Rectangle2D;
import com.codename1.ui.util.Resources;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.codename1.compat.java.util.Objects;
/**
* A border that can be configured using a limited subset of CSS directives. This
* class is designed as a stop-gap to deal with common CSS style patterns that aren't
* well-covered by existing borders. As time goes on this class will be enhanced to
* support more CSS styles. At present, it is used by the CSS compiler for compound borders.
* E.g. If one side has a different border style, color, or thickness than other sides.
*
* The follow types of borders are well-supported with this class:
*
*
* - border-radius - can support different x and y radii for each corner.
* - border-width - can support different widths for each side.
* - border-color - can support different colors for each side
* - background-color
*
*
* This class also supports background images and gradients, but these are not well-tested
* and are not currently used by the CSS compiler.
*
* @since 7.0
* @author shannah
*/
public class CSSBorder extends Border {
/**
* Constant indicating no-repeat for background images.
*/
public static final byte REPEAT_NONE=0;
/**
* Constant indicating repeating on both x and y for background images.
*/
public static final byte REPEAT_BOTH=1;
/**
* Constant indicating repeat-x for background images.
*/
public static final byte REPEAT_X=2;
/**
* Constant indicating repeat-y for background images.
*/
public static final byte REPEAT_Y=3;
/**
* Constant indicating background-position top.
*/
public static final byte VPOSITION_TOP=0;
/**
* Constant indicating background-position bottom.
*/
public static final byte VPOSITION_BOTTOM=1;
/**
* Constant indicating background-position center.
*/
public static final byte VPOSITION_CENTER=2;
public static final byte VPOSITION_OTHER=99;
/**
* Constant indicating background-position left.
*/
public static final byte HPOSITION_LEFT=0;
/**
* Constant indicating background-position right.
*/
public static final byte HPOSITION_RIGHT=1;
/**
* Constant indicating background-position center (horizontal).
*/
public static final byte HPOSITION_CENTER=2;
public static final byte HPOSITION_OTHER=99;
public static final byte SIZE_AUTO=0;
public static final byte SIZE_CONTAIN=1;
public static final byte SIZE_COVER=2;
public static final byte SIZE_OTHER=99;
/**
* Constant for border-style none
*/
public static final byte STYLE_NONE=0;
/**
* Constant for border-style hidden
*/
public static final byte STYLE_HIDDEN=1;
/**
* Constant for border-style dotted
*/
public static final byte STYLE_DOTTED=2;
/**
* Constant for border-style dashed
*/
public static final byte STYLE_DASHED=3;
/**
* Constant for border-style solid
*/
public static final byte STYLE_SOLID=4;
private static interface Decorator {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue);
}
private static final Map decorators = new HashMap();
static {
decorators.put("background-color", new Decorator() {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue) {
return border.backgroundColor(cssPropertyValue);
}
});
decorators.put("background-image", new Decorator() {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue) {
return border.backgroundImage(cssPropertyValue);
}
});
decorators.put("background-position", new Decorator() {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue) {
return border.backgroundPosition(cssPropertyValue);
}
});
decorators.put("background-repeat", new Decorator() {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue) {
return border.backgroundRepeat(cssPropertyValue);
}
});
decorators.put("border-color", new Decorator() {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue) {
return border.borderColor(cssPropertyValue);
}
});
decorators.put("border-radius", new Decorator() {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue) {
return border.borderRadius(cssPropertyValue);
}
});
decorators.put("border-stroke", new Decorator() {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue) {
return border.borderStroke(cssPropertyValue);
}
});
decorators.put("border-style", new Decorator() {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue) {
return border.borderStyle(cssPropertyValue);
}
});
decorators.put("border-width", new Decorator() {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue) {
return border.borderWidth(cssPropertyValue);
}
});
decorators.put("border-image", new Decorator() {
public CSSBorder decorate(CSSBorder border, String cssProperty, String cssPropertyValue) {
return border.borderImage(cssPropertyValue);
}
});
}
private Color backgroundColor;
private BackgroundImage[] backgroundImages;
private BorderImage borderImage;
private BorderStroke[] stroke;
private BoxShadow boxShadow;
private BorderRadius borderRadius;
private Resources res;
/**
* Constant for unit px
*/
public static final byte UNIT_PIXELS=0;
/**
* Constant for unit mm
*/
public static final byte UNIT_MM=2;
/**
* Constant for unit %
*/
public static final byte UNIT_PERCENT=1;
/**
* Constant for unit em
*/
public static final byte UNIT_EM=4;
private void setAlpha(Graphics g, Color c) {
g.setAlpha(c == null ? 0 : c.alpha);
}
/**
* Creates a new empty CSS border.
*/
public CSSBorder() {
res = Resources.getGlobalResources();
}
/**
* Creates an empty border.
* @param res Theme resource file from which images can be referenced.
*/
public CSSBorder(Resources res) {
this.res = res;
}
/**
* Creates a new CSS border with the provided CSS styles. This currenlty only supports a subset of CSS. The following
* properties are currently supported:
*
*
*
* - background-color
* - background-image
* - background-position
* - background-repeat
* - border-color
* - border-radius
* - border-stroke
* - border-style
* - border-width
* - border-image
*
*
* @param css CSS to parse.
* @throws IllegalArgumentException If it fails to parse the style.
*/
public CSSBorder(String css) {
this(Resources.getGlobalResources(), css);
}
/**
* Creates a new CSS border with the provided CSS styles. This currenlty only supports a subset of CSS. The following
* properties are currently supported:
*
*
*
* - background-color
* - background-image
* - background-position
* - background-repeat
* - border-color
* - border-radius
* - border-stroke
* - border-style
* - border-width
* - border-image
*
*
* @param res Theme resource file from which images can be loaded.
* @param css CSS to parse.
* @throws IllegalArgumentException If it fails to parse the style.
*/
public CSSBorder(Resources res, String css) {
this.res = res;
String[] parts = Util.split(css, ";");
for (String part : parts) {
int colonPos = part.indexOf(":");
if (colonPos == -1) {
continue;
}
String key = part.substring(0, colonPos).trim().toLowerCase();
String value = part.substring(colonPos+1).trim();
Decorator decorator = decorators.get(key);
if (decorator == null) {
throw new IllegalArgumentException("Unsupported CSS property: "+key);
}
decorator.decorate(this, key, value);
}
}
@Override
public boolean equals(Object obj) {
return obj == this;
}
/**
* Converts this border to a CSS string.
* @return CSS string for this border.
*/
public String toCSSString() {
StringBuilder sb = new StringBuilder();
if (backgroundColor != null) {
sb.append("background-color:").append(backgroundColor.toCSSString()).append(";");
}
if (backgroundImages != null) {
sb.append("background-image:");
boolean first = true;
for (BackgroundImage img : backgroundImages) {
if (first) {
first = false;
} else {
sb.append(",");
}
sb.append(img.toCSSString());
}
sb.append(";");
sb.append("background-position:");
first = true;
for (BackgroundImage img : backgroundImages) {
if (first) {
first = false;
} else {
sb.append(",");
}
sb.append(img.getBackgroundPositionCSSString());
}
sb.append(";");
}
if (borderRadius != null) {
sb.append("border-radius:");
sb.append(borderRadius.toCSSString());
sb.append(";");
}
if (stroke != null) {
sb.append("border-width:");
sb.append(stroke[TOP].toBorderWidthCSSString()).append(" ")
.append(stroke[RIGHT].toBorderWidthCSSString()).append(" ")
.append(stroke[BOTTOM].toBorderWidthCSSString()).append(" ")
.append(stroke[LEFT].toBorderWidthCSSString());
sb.append(";");
sb.append("border-style:")
.append(stroke[TOP].toBorderStyleCSSString()).append(" ")
.append(stroke[RIGHT].toBorderStyleCSSString()).append(" ")
.append(stroke[BOTTOM].toBorderStyleCSSString()).append(" ")
.append(stroke[LEFT].toBorderStyleCSSString());
sb.append(";");
sb.append("border-color:")
.append(stroke[TOP].toBorderColorCSSString()).append(" ")
.append(stroke[RIGHT].toBorderColorCSSString()).append(" ")
.append(stroke[BOTTOM].toBorderColorCSSString()).append(" ")
.append(stroke[LEFT].toBorderColorCSSString());
sb.append(";");
}
if (boxShadow != null) {
sb.append("box-shadow:").append(boxShadow.toCSSString()).append(";");
}
if (borderImage != null) {
sb.append("border-image:").append(borderImage.toCSSString()).append(";");
}
return sb.toString();
}
private static class ScalarUnit {
float value;
byte type;
ScalarUnit copy() {
return new ScalarUnit(this);
}
ScalarUnit(String unit) {
if("0".equals(unit) || "0.0".equals(unit)) {
this.value = 0;
this.type = UNIT_PIXELS;
} else if (unit.endsWith("mm")) {
this.value = Float.parseFloat(unit.substring(0, unit.length()-2));
this.type = UNIT_MM;
} else if (unit.endsWith("px")) {
this.value = Integer.parseInt(unit.substring(0, unit.length()-2));
this.type = UNIT_PIXELS;
} else if (unit.endsWith("em")) {
this.value = Float.parseFloat(unit.substring(0, unit.length()-2));
this.type = UNIT_EM;
} else if (unit.endsWith("%")) {
this.value = Float.parseFloat(unit.substring(0, unit.length()-1));
this.type = UNIT_PERCENT;
} else if (unit.endsWith("pt")) {
this.value = Float.parseFloat(unit.substring(0, unit.length()-2)) / 72f * 25.4f;
this.type = UNIT_MM;
} else if (unit.endsWith("in")) {
this.value = Float.parseFloat(unit.substring(0, unit.length()-2)) * 25.4f;
this.type = UNIT_MM;
} else {
throw new IllegalArgumentException("Illegal unit "+unit);
}
}
ScalarUnit(ScalarUnit u) {
this.value = u.value;
this.type = u.type;
}
ScalarUnit(float value, byte type) {
this.value = value;
this.type = type;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ScalarUnit) {
ScalarUnit u = (ScalarUnit)obj;
return u.value == 0 && value == 0 || u.value == value && u.type == type;
}
return false;
}
static boolean validate(String val) {
val = val.trim();
int len = val.length();
if ("0".equals(val) || "0.0".equals(val)) {
return true;
}
if (val.endsWith("px") && isInt(val.substring(0, len-2))) {
return true;
}
if ((val.endsWith("em") || val.endsWith("mm") || val.endsWith("pt") || val.endsWith("in")) && isFloat(val.substring(0, len-2))) {
return true;
}
if (val.endsWith("%") && isFloat(val.substring(0, len-1))) {
return true;
}
return false;
}
private static boolean isInt(String val) {
try {
Integer.parseInt(val);
return true;
} catch (Throwable t) {
return false;
}
}
private static boolean isFloat(String val) {
try {
Float.parseFloat(val);
return true;
} catch (Throwable t) {
return false;
}
}
boolean isZero() {
return value == 0;
}
int px() {
switch (type) {
case UNIT_PIXELS:
return (int)value;
case UNIT_MM:
return convertToPixels(value);
default:
throw new IllegalArgumentException("Can't get px() units for type "+type+" without providing content rect");
}
}
float floatPx() {
switch (type) {
case UNIT_PIXELS:
return value;
case UNIT_MM:
return convertToPixels(value*1000f)/1000f;
default:
throw new IllegalArgumentException("Can't get px() units for type "+type+" without providing content rect");
}
}
float floatPx(Component c, Rectangle2D contentRect, boolean horizontal) {
switch (type) {
case UNIT_PIXELS:
return value;
case UNIT_MM:
return convertToPixels(value*1000f)/1000f;
case UNIT_PERCENT:
return (float)(horizontal ? contentRect.getWidth() * value/100.0 : contentRect.getHeight() * value/100.0);
case UNIT_EM:
Font f = c.getStyle().getFont();
return f != null ? c.getStyle().getFont().getPixelSize() : Font.getDefaultFont().getPixelSize();
default:
throw new IllegalArgumentException("Can't get px() units for type "+type+" without providing content rect");
}
}
private String toCSSString() {
StringBuilder sb = new StringBuilder();
if (Math.ceil(value) == Math.floor(value)) {
sb.append((int)value);
} else {
sb.append(value);
}
switch (type) {
case UNIT_PIXELS:
sb.append("px");
break;
case UNIT_MM:
sb.append("mm");
break;
case UNIT_PERCENT:
sb.append("%");
break;
case UNIT_EM:
sb.append("em");
break;
default:
throw new IllegalStateException("Unsupported unit type "+type);
}
return sb.toString();
}
}
private float floatPx(ScalarUnit u) {
return u == null ? 0 : u.floatPx();
}
private float floatPx(ScalarUnit u, Component c, Rectangle2D contentRect, boolean horizontal) {
return u == null ? 0 : u.floatPx(c, contentRect, horizontal);
}
private class BoxShadow {
ScalarUnit hOffset, vOffset, blurRadius, spread;
boolean inset;
Color color;
int spreadPx() {
return spread != null ? spread.px() : 0;
}
int blurPx() {
return blurRadius != null ? blurRadius.px() : 0;
}
int vOffsetPx() {
return vOffset != null ? vOffset.px() : 0;
}
int hOffsetPx() {
return hOffset != null ? hOffset.px() : 0;
}
void paint(Graphics g, Component c, Rectangle2D contentRect) {
int alpha = g.getAlpha();
int color = g.getColor();
boolean antialiased = g.isAntiAliased();
setColor(g, this.color);
GeneralPath p = GeneralPath.createFromPool();
try {
createShape(
p,
contentRect.getX(),
contentRect.getY(),
contentRect.getWidth(),
contentRect.getHeight(),
createArrow(c)
);
p.transform(Transform.makeTranslation(hOffset.floatPx(c, contentRect, true), vOffset.floatPx(c, contentRect, false)));
g.fillShape(p);
} finally {
GeneralPath.recycle(p);
g.setAlpha(alpha);
g.setColor(color);
g.setAntiAliased(antialiased);
}
}
private String toCSSString() {
throw new RuntimeException("Box-shadow not fully supported yet");
}
}
private static class Context {
Component component;
Rectangle2D contentRect;
Context(Component comp, Rectangle2D contentRect) {
component = comp;
this.contentRect = contentRect;
}
}
private static Context context;
private class BorderRadius {
private ScalarUnit topLeftX, topRightX, bottomLeftX, bottomRightX;
private ScalarUnit topLeftY, topRightY, bottomLeftY, bottomRightY;
ScalarUnit[] all() {
return new ScalarUnit[]{topLeftX, topLeftY, topRightX, topRightY, bottomRightX, bottomRightY, bottomLeftX, bottomLeftY};
}
ScalarUnit[] horizontal() {
return new ScalarUnit[]{topLeftX, topRightX, bottomRightX, bottomLeftX};
}
ScalarUnit[] vertical() {
return new ScalarUnit[]{topLeftY, topRightY, bottomRightY, bottomLeftY};
}
ScalarUnit[] topLeft() {
return new ScalarUnit[]{topLeftX, topLeftY};
}
ScalarUnit[] topRight() {
return new ScalarUnit[]{topRightX, topRightY};
}
ScalarUnit[] bottomRight() {
return new ScalarUnit[]{bottomRightX, bottomRightY};
}
ScalarUnit[] bottomLeft() {
return new ScalarUnit[]{bottomLeftX, bottomLeftY};
}
BorderRadius(String value) {
if (value.indexOf("/") > 0) {
String[] parts = Util.split(value, "/");
String[] hVals = Util.split(parts[0].trim(), " ");
String[] vVals = Util.split(parts[1].trim(), " ");
if (hVals.length == 1) {
topLeftX = new ScalarUnit(hVals[0]);
topRightX = new ScalarUnit(topLeftX);
bottomLeftX = new ScalarUnit(topLeftX);
bottomRightX = new ScalarUnit(topLeftX);
} else if (hVals.length == 2) {
topLeftX = new ScalarUnit(hVals[0]);
bottomRightX = new ScalarUnit(topLeftX);
topRightX = new ScalarUnit(hVals[1]);
bottomLeftX = new ScalarUnit(topRightX);
} else if (hVals.length == 3) {
topLeftX = new ScalarUnit(hVals[0]);
topRightX = new ScalarUnit(hVals[1]);
bottomLeftX = new ScalarUnit(topRightX);
bottomRightX = new ScalarUnit(hVals[2]);
} else if (hVals.length == 4) {
topLeftX = new ScalarUnit(hVals[0]);
topRightX = new ScalarUnit(hVals[1]);
bottomRightX = new ScalarUnit(hVals[2]);
bottomLeftX = new ScalarUnit(hVals[3]);
} else {
throw new IllegalArgumentException("Border radius should include 1, 2, 3, of 4 params only");
}
if (vVals.length == 1) {
topLeftY = new ScalarUnit(vVals[0]);
topRightY = new ScalarUnit(topLeftY);
bottomLeftY = new ScalarUnit(topLeftY);
bottomRightY = new ScalarUnit(topLeftY);
} else if (vVals.length == 2) {
topLeftY = new ScalarUnit(vVals[0]);
bottomRightY = new ScalarUnit(topLeftY);
topRightY = new ScalarUnit(hVals[1]);
bottomLeftY = new ScalarUnit(topRightY);
} else if (vVals.length == 3) {
topLeftY = new ScalarUnit(hVals[0]);
topRightY = new ScalarUnit(hVals[1]);
bottomLeftY = new ScalarUnit(topRightY);
bottomRightY = new ScalarUnit(hVals[2]);
} else if (vVals.length == 4) {
topLeftY = new ScalarUnit(vVals[0]);
topRightY = new ScalarUnit(vVals[1]);
bottomRightY = new ScalarUnit(vVals[2]);
bottomLeftY = new ScalarUnit(vVals[3]);
} else {
throw new IllegalArgumentException("Border radius should include 1, 2, 3, of 4 params only: "+Arrays.toString(vVals));
}
} else {
String[] vals = Util.split(value, " ");
switch (vals.length) {
case 1 :
topLeftX = new ScalarUnit(vals[0]);
topLeftY = topLeftX.copy();
topRightX = topLeftX.copy();
topRightY = topLeftX.copy();
bottomRightX = topLeftX.copy();
bottomRightY = topLeftX.copy();
bottomLeftX = topLeftX.copy();
bottomLeftY = topLeftX.copy();
break;
case 2:
topLeftX = new ScalarUnit(vals[0]);
topLeftY = topLeftX.copy();
bottomRightX = topLeftX.copy();
bottomRightY = topLeftX.copy();
topRightX = new ScalarUnit(vals[1]);
topRightY = topRightX.copy();
bottomLeftX = topRightX.copy();
bottomLeftY = topRightX.copy();
break;
case 3:
topLeftX = new ScalarUnit(vals[0]);
topLeftY = topLeftX.copy();
topRightX = new ScalarUnit(vals[1]);
topRightY = topRightX.copy();
bottomLeftX = topRightX.copy();
bottomLeftY = topRightX.copy();
bottomRightX = new ScalarUnit(vals[2]);
bottomRightY = bottomRightX.copy();
break;
case 4:
topLeftX = new ScalarUnit(vals[0]);
topLeftY = topLeftX.copy();
topRightX = new ScalarUnit(vals[1]);
topRightY = topRightX.copy();
bottomRightX = new ScalarUnit(vals[2]);
bottomRightY =bottomRightX.copy();
bottomLeftX = new ScalarUnit(vals[3]);
bottomLeftY = bottomLeftX.copy();
break;
default:
throw new IllegalArgumentException("Illegal input for border radius: "+value);
}
}
}
boolean hasNonZeroRadius() {
return topLeftX != null && !topLeftX.isZero() ||
topRightX != null && !topRightX.isZero() ||
bottomLeftX != null && !bottomLeftX.isZero() ||
bottomRightX != null && !bottomRightX.isZero() ||
topLeftY != null && !topLeftY.isZero() ||
topRightY != null && !topRightY.isZero() ||
bottomLeftY != null && !bottomLeftY.isZero() ||
bottomRightY != null && !bottomRightY.isZero();
}
float topLeftRadiusX() {
if (context != null) {
return floatPx(topLeftX, context.component, context.contentRect, true);
}
return floatPx(topLeftX);
}
float topLeftRadiusY() {
if (context != null) {
return floatPx(topLeftY, context.component, context.contentRect, false);
}
return floatPx(topLeftY);
}
float topRightRadiusX() {
if (context != null) {
return floatPx(topRightX, context.component, context.contentRect, true);
}
return floatPx(topRightX);
}
float topRightRadiusY() {
if (context != null) {
return floatPx(topRightY, context.component, context.contentRect, false);
}
return floatPx(topRightY);
}
float bottomLeftX() {
if (context != null) {
return floatPx(bottomLeftX, context.component, context.contentRect, true);
}
return floatPx(bottomLeftX);
}
float bottomLeftY() {
if (context != null) {
return floatPx(bottomLeftY, context.component, context.contentRect, false);
}
return floatPx(bottomLeftY);
}
float bottomRightX() {
if (context != null) {
return floatPx(bottomRightX, context.component, context.contentRect, true);
}
return floatPx(bottomRightX);
}
float bottomRightY() {
if (context != null) {
return floatPx(bottomRightY, context.component, context.contentRect, false);
}
return floatPx(bottomRightY);
}
private String toCSSString() {
StringBuilder sb = new StringBuilder();
sb.append(topLeftX.toCSSString()).append(" ")
.append(topRightX.toCSSString()).append(" ")
.append(bottomRightX.toCSSString()).append(" ")
.append(bottomLeftX.toCSSString()).append(" / ")
.append(topLeftY.toCSSString()).append(" ")
.append(topRightY.toCSSString()).append(" ")
.append(bottomRightY.toCSSString()).append(" ")
.append(bottomLeftY.toCSSString());
return sb.toString();
}
}
private static class Color {
int color;
int alpha;
static final int CACHE_SIZE=100;
static Map cache;
static Map cache() {
if (cache == null) {
cache = new HashMap();
}
return cache;
}
private String padLeft(String str, int len) {
while (str.length() < len) {
str = "0" + str;
}
return str;
}
public String toCSSString() {
StringBuilder sb = new StringBuilder();
sb.append("#");
sb.append(padLeft(Integer.toHexString(color), 6));
sb.append(padLeft(Integer.toHexString(alpha), 2));
return sb.toString();
}
static Color parse(String value) {
value = value.trim();
if (!cache().containsKey(value)) {
if (cache.size() > CACHE_SIZE) {
cache.clear();
}
cache.put(value, new Color(value));
}
return cache.get(value);
}
static boolean validate(String value) {
return value.startsWith("#") || value.startsWith("rgb(") || value.startsWith("rbga(") || "transparent".equals(value);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Color) {
Color c = (Color)obj;
return c.alpha == alpha && c.color == color;
}
return false;
}
Color(String value) {
if (value.startsWith("#")) {
if (value.length() == 9) {
alpha = Integer.parseInt(value.substring(7, 9), 16);
color = Integer.parseInt(value.substring(1, 7), 16);
} else if (value.length() == 7) {
alpha = 0xff;
color = Integer.parseInt(value.substring(1, 7), 16);
} else if (value.length() == 5) {
String rStr = value.substring(1,2);
String gStr = value.substring(2,3);
String bStr = value.substring(3,4);
String aStr = value.substring(4,5);
alpha = Integer.parseInt(aStr+aStr, 16);
color = 0xffffff & ColorUtil.rgb(
Integer.parseInt(rStr+rStr, 16),
Integer.parseInt(gStr+gStr, 16),
Integer.parseInt(bStr+bStr, 16)
);
} else if (value.length() == 4) {
alpha = 0xff;
String rStr = value.substring(1,2);
String gStr = value.substring(2,3);
String bStr = value.substring(3,4);
color = 0xffffff & ColorUtil.rgb(
Integer.parseInt(rStr+rStr, 16),
Integer.parseInt(gStr+gStr, 16),
Integer.parseInt(bStr+bStr, 16)
);
} else {
throw new IllegalArgumentException("Illegal color value "+value);
}
} else if (value.startsWith("rgb(")) {
throw new IllegalArgumentException("rgb() color values not supported yet: "+value);
} else if ("transparent".equals(value)) {
alpha = 0;
color = 0x0;
} else {
throw new IllegalArgumentException("Unsuppored color value: "+value);
}
}
boolean isTransparent() {
return alpha == 0;
}
}
private static boolean isTransparent(Color color) {
return color == null || color.isTransparent();
}
private class ColorStop {
Color color;
int position;
public String toCSSString() {
StringBuilder sb = new StringBuilder();
sb.append(color.toCSSString());
if (position > 0) {
sb.append(" ").append(position).append("%");
}
return sb.toString();
}
}
private class LinearGradient {
float angle;
ColorStop[] colors;
double directionRadian() {
return angle * Math.PI/180.0;
}
private String toCSSString() {
StringBuilder sb = new StringBuilder();
sb.append("linear-gradient(");
sb.append(angle).append("deg");
sb.append(",");
boolean first = true;
for (ColorStop cs : colors) {
if (first) {
first = false;
} else {
sb.append(",");
}
sb.append(cs.toCSSString());
}
sb.append(")");
return sb.toString();
}
}
private class RadialGradient {
byte shape;
byte size;
float xPos, yPos;
ColorStop[] colors;
private String toCSSString() {
throw new RuntimeException("RadialGradlient toCSSString() not implemented yet");
}
}
private static Map styleMap;
private static Map styleMap() {
if (styleMap == null) {
styleMap = new HashMap();
styleMap.put("none", STYLE_NONE);
styleMap.put("hidden", STYLE_HIDDEN);
styleMap.put("dotted", STYLE_DOTTED);
styleMap.put("dashed", STYLE_DASHED);
styleMap.put("solid", STYLE_SOLID);
}
return styleMap;
}
private static byte getBorderStyle(String style) {
style = style.trim();
styleMap();
Byte b = styleMap.get(style);
if (b == null) {
throw new IllegalArgumentException("Unsupported border style "+style);
}
return b;
}
private static boolean validateBorderStyle(String style) {
return styleMap().containsKey(style);
}
private static class BorderStroke {
byte type;
ScalarUnit thickness;
Color color;
public String toBorderWidthCSSString() {
return thickness.toCSSString();
}
public String toBorderColorCSSString() {
return color.toCSSString();
}
public String toBorderStyleCSSString() {
switch (type) {
case STYLE_SOLID:
return "solid";
case STYLE_DOTTED:
return "dotted";
case STYLE_DASHED:
return "dashed";
case STYLE_HIDDEN:
return "hidden";
case STYLE_NONE:
return "none";
}
return "none";
}
BorderStroke(String value) {
String[] parts = Util.split(value, " ");
if (parts.length == 3) {
thickness = parseThickness(parts[0]);
type = getBorderStyle(parts[1]);
color = Color.parse(parts[2]);
} else if (parts.length == 2) {
int index = 0;
if (validateThickness(parts[index])) {
thickness = parseThickness(parts[index]);
index++;
} else {
thickness = parseThickness("medium");
}
if (validateBorderStyle(parts[index])) {
type = getBorderStyle(parts[index]);
index++;
} else {
type = STYLE_NONE;
}
if (index < 2) {
color = Color.parse(parts[index]);
index++;
} else {
color = Color.parse("transparent");
}
if (index < 2) {
throw new IllegalArgumentException("Illegal border stroke parameter "+value);
}
} else if (parts.length == 1) {
boolean used = false;
if (validateThickness(value)) {
thickness = parseThickness(value);
used = true;
} else {
thickness = parseThickness("medium");
}
if (!used && validateBorderStyle(value)) {
type = getBorderStyle(value);
used = true;
} else {
type = STYLE_NONE;
}
if (!used && Color.validate(value)) {
color = Color.parse(value);
used = true;
} else {
color = Color.parse("transparent");
}
if (!used) {
throw new IllegalArgumentException("Illegal border stroke parameter "+value);
}
} else {
throw new IllegalArgumentException("Illegal border stroke parameter "+value);
}
}
BorderStroke(BorderStroke stroke) {
this.type = stroke.type;
this.thickness = stroke.thickness.copy();
this.color = stroke.color;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BorderStroke) {
BorderStroke s = (BorderStroke)obj;
return s.type == type && s.thickness.equals(thickness) && s.color.equals(color);
}
return false;
}
boolean isVisible() {
return color != null && !isTransparent(color) && type != STYLE_NONE && type != STYLE_HIDDEN;
}
static boolean validateThickness(String val) {
return ScalarUnit.validate(val) || "thin".equals(val) || "medium".equals(val) || "thick".equals(val);
}
static ScalarUnit parseThickness(String val) {
val = val.trim();
if (ScalarUnit.validate(val)) {
return new ScalarUnit(val);
} else if ("thin".equals(val)) {
return new ScalarUnit("1px");
} else if ("medium".equals(val)) {
return new ScalarUnit("0.75mm");
} else if ("thick".equals(val)) {
return new ScalarUnit("1.4mm");
}
throw new IllegalArgumentException("Illegal thickness value "+val);
}
Stroke getStroke(Component c, Rectangle2D contentRect, boolean horizontal) {
return new Stroke(thickness.floatPx(c, contentRect, horizontal), Stroke.CAP_BUTT, Stroke.JOIN_MITER, 100f);
}
}
private class BorderImage {
Image image;
double[] slices;
Border internal;
String imageName;
BorderImage(String imageName, double... slces) {
this.imageName = imageName;
slices = new double[4];
if (slces.length == 4) {
System.arraycopy(slces, 0, slices, 0, 4);
} else if (slces.length == 3) {
slices[0] = slces[0];
slices[1] = slices[3] = slces[1];
slices[2] = slces[2];
} else if (slces.length == 2) {
slices[0] = slices[2] = slces[0];
slices[1] = slices[3] = slces[1];
} else if (slces.length == 1) {
slices[0] = slices[1] = slices[2] = slices[3] = slces[0];
} else {
throw new IllegalArgumentException("Slices expected to be length 1 to 4, but found size "+slces.length+": "+Arrays.toString(slces));
}
}
BorderImage(Image img, double... slces) {
image = img;
slices = new double[4];
if (slces.length == 4) {
System.arraycopy(slces, 0, slices, 0, 4);
} else if (slces.length == 3) {
slices[0] = slces[0];
slices[1] = slices[3] = slces[1];
slices[2] = slces[2];
} else if (slces.length == 2) {
slices[0] = slices[2] = slces[0];
slices[1] = slices[3] = slces[1];
} else if (slces.length == 1) {
slices[0] = slices[1] = slices[2] = slices[3] = slces[0];
} else {
throw new IllegalArgumentException("Slices expected to be length 1 to 4, but found size "+slces.length+": "+Arrays.toString(slces));
}
internal = Border.createImageSplicedBorder(img, slices[0], slices[1], slices[2], slices[3]);
}
void paint(Graphics g, Component c, Rectangle2D contentRect) {
internal().paint(g, (int)contentRect.getX(), (int)contentRect.getY(), (int)contentRect.getWidth(), (int)contentRect.getHeight(), c);
}
Image image() {
if (image == null) {
image = res.getImage(imageName);
if (image == null) {
try {
image = EncodedImage.create("/"+imageName);
} catch (IOException ex) {
Log.p("Failed to load image named "+imageName+" for CSSBorder");
throw new IllegalStateException("Failed to load image "+imageName);
}
}
}
return image;
}
Border internal() {
if (internal == null) {
internal = Border.createImageSplicedBorder(image(), slices[0], slices[1], slices[2], slices[3]);
}
return internal;
}
String toCSSString() {
String imgName = imageName;
if (imgName == null && image != null) {
imgName = image.getImageName();
}
return Util.encodeUrl(imgName)+" "+slices[0]+" "+slices[1]+" "+slices[2]+" "+slices[3];
}
}
private class BackgroundImage {
LinearGradient linearGradient;
RadialGradient radialGradient;
byte verticalPositionType, horizontalPositionType, verticalSizeType, horizontalSizeType;
ScalarUnit verticalPosition;
ScalarUnit horizontalPosition;
ScalarUnit verticalSize;
ScalarUnit horizontalSize;
Image image;
byte repeat;
BackgroundImage() {
repeat = REPEAT_NONE;
verticalPositionType = VPOSITION_TOP;
horizontalPositionType = HPOSITION_LEFT;
}
BackgroundImage(Image image) {
this.image = image;
repeat = REPEAT_NONE;
verticalPositionType = VPOSITION_TOP;
horizontalPositionType = HPOSITION_LEFT;
}
public String toCSSString() {
if (linearGradient != null) {
return linearGradient.toCSSString();
}
if (radialGradient != null) {
return radialGradient.toCSSString();
}
if (image != null && image.getImageName() != null) {
return "url(\"" + image.getImageName()+"\"";
}
return "none";
}
private void setPosition(String pos) {
}
private String getBackgroundPositionCSSString() {
StringBuilder sb = new StringBuilder();
switch (verticalPositionType) {
case VPOSITION_TOP:
sb.append("top").append(" ");
break;
case VPOSITION_BOTTOM:
sb.append("bottom").append(" ");
break;
case VPOSITION_CENTER:
sb.append("center").append(" ");
break;
}
if (verticalPosition != null) {
sb.append(verticalPosition.toCSSString()).append(" ");
}
switch (horizontalPositionType) {
case HPOSITION_LEFT:
sb.append("left").append(" ");
break;
case HPOSITION_RIGHT:
sb.append("right").append(" ");
break;
case HPOSITION_CENTER:
sb.append("center").append(" ");
break;
}
if (horizontalPosition != null) {
sb.append(horizontalPosition.toCSSString()).append(" ");
}
return sb.toString().trim();
}
private Rectangle2D getTargetRect(Component c, Rectangle2D out, Rectangle2D contentRect) {
if (image != null) {
double w = image.getWidth();
double h = image.getHeight();
switch (verticalSizeType) {
case SIZE_CONTAIN:
if (w > contentRect.getWidth()) {
h = h * contentRect.getWidth()/w;
w = contentRect.getWidth();
}
if (h > contentRect.getHeight()) {
w = contentRect.getHeight()/h;
h = contentRect.getHeight();
}
break;
case SIZE_COVER:
double aspect = w/h;
w = image.getWidth();
h = w/aspect;
if (h < image.getHeight()) {
h = image.getHeight();
w = h * aspect;
}
break;
case SIZE_OTHER:
w = floatPx(horizontalSize, c, contentRect, true);
h = floatPx(verticalSize, c, contentRect, false);
break;
}
double x = contentRect.getX();
double y = contentRect.getY();
switch (verticalPositionType) {
case VPOSITION_BOTTOM:
y = contentRect.getY() + contentRect.getHeight()-h;
break;
case VPOSITION_CENTER:
y = contentRect.getY() + contentRect.getHeight()/2 - h/2;
break;
case VPOSITION_OTHER:
y = contentRect.getY() + floatPx(verticalPosition, c, contentRect, false);
break;
}
switch (horizontalPositionType) {
case HPOSITION_RIGHT:
x = contentRect.getX() + contentRect.getWidth() - w;
break;
case HPOSITION_CENTER:
x = contentRect.getX() + contentRect.getWidth()/2 - w/2;
break;
case HPOSITION_OTHER:
x = contentRect.getX() + floatPx(horizontalPosition, c, contentRect, true);
break;
}
out.setBounds(x, y, w, h);
return out;
} else {
out.setBounds(contentRect.getX(), contentRect.getY(), contentRect.getWidth(), contentRect.getHeight());
return out;
}
}
void paint(Graphics g, Component c, Rectangle2D contentRect) {
// Note: This assumes that a shape clip has already happened f
if (image != null) {
switch (repeat) {
case REPEAT_NONE: {
Rectangle2D targetRect = getTargetRect(c, new Rectangle2D(), contentRect);
g.drawImage(image, (int)targetRect.getX(), (int)targetRect.getY(), (int)targetRect.getWidth(), (int)targetRect.getHeight());
break;
}
case REPEAT_X: {
Rectangle2D targetRect = getTargetRect(c, new Rectangle2D(), contentRect);
Image scaled = image.scaled((int)targetRect.getWidth(), (int)targetRect.getHeight());
double offX = targetRect.getX() - contentRect.getX();
while (offX > 0) {
offX -= targetRect.getWidth();
}
// offX should be non-positive
g.tileImage(scaled, (int)(contentRect.getX() + offX), (int)targetRect.getY(), (int)(contentRect.getWidth() - offX), (int)targetRect.getHeight());
break;
}
case REPEAT_Y: {
Rectangle2D targetRect = getTargetRect(c, new Rectangle2D(), contentRect);
Image scaled = image.scaled((int)targetRect.getWidth(), (int)targetRect.getHeight());
double offY = targetRect.getY() - contentRect.getY();
while (offY > 0) {
offY -= targetRect.getHeight();
}
// offX should be non-positive
g.tileImage(scaled, (int)targetRect.getX(), (int)(contentRect.getY() + offY), (int)targetRect.getWidth(), (int)(contentRect.getHeight()-offY));
break;
}
case REPEAT_BOTH: {
Rectangle2D targetRect = getTargetRect(c, new Rectangle2D(), contentRect);
Image scaled = image.scaled((int)targetRect.getWidth(), (int)targetRect.getHeight());
double offY = targetRect.getY() - contentRect.getY();
while (offY > 0) {
offY -= targetRect.getHeight();
}
double offX = targetRect.getX() - contentRect.getX();
while (offX > 0) {
offX -= targetRect.getWidth();
}
// offX should be non-positive
g.tileImage(scaled, (int)(contentRect.getX() + offX), (int)(contentRect.getY() + offY), (int)(contentRect.getWidth() - offX), (int)(contentRect.getHeight()-offY));
break;
}
}
} else {
if (linearGradient != null) {
ColorStop prevColor = null;
// Figure out the width that will be required
double contentWidth = contentRect.getWidth() * Math.cos(linearGradient.directionRadian()) + contentRect.getHeight() * Math.sin(linearGradient.directionRadian());
double contentHeight = contentRect.getHeight() + Math.cos(linearGradient.directionRadian()) + contentRect.getWidth() * Math.sin(linearGradient.directionRadian());
double contentX = contentRect.getX() + contentRect.getWidth()/2 - contentWidth/2;
double contentY = contentRect.getY() + contentRect.getHeight()/2 - contentHeight/2;
double x = contentX;
Transform existingT = null;
if (linearGradient.directionRadian() != 0) {
existingT = Transform.makeIdentity();
g.getTransform(existingT);
Transform newT = existingT.copy();
newT.rotate((float)linearGradient.directionRadian(), (float)(contentX + contentWidth/2), (int)(contentY + contentHeight/2));
g.setTransform(newT);
}
for (ColorStop colorStop : linearGradient.colors) {
if (prevColor == null) {
prevColor = colorStop;
continue;
}
int alpha = g.getAlpha();
setAlpha(g, colorStop.color);
double nextX = x + contentWidth * colorStop.position /100.0;
g.fillLinearGradient(prevColor.color.color, colorStop.color.color, (int)x, (int)contentY, (int)(nextX-x), (int)contentHeight, true);
g.setAlpha(alpha);
x = nextX;
}
if (existingT != null) {
g.setTransform(existingT);
}
}
if (radialGradient != null) {
}
}
}
}
private boolean hasBorderRadius() {
return borderRadius != null && borderRadius.hasNonZeroRadius();
}
/**
* Since borders are drawn inside the bounds of components - this differs from HTML. We
* need to be able to find the inner content bounds of the component so that we have room to
* draw shadows, etc..
* @param outerWidth
* @param outerHeight
* @param rect Out param
*/
private void calculateContentRect(int outerWidth, int outerHeight, Rectangle2D rect) {
int paddingLeft = 0;
int paddingRight = 0;
int paddingBottom = 0;
int paddingTop = 0;
if (stroke != null) {
if (stroke[TOP] != null) {
paddingTop += Math.ceil(stroke[TOP].thickness.floatPx()/2);
}
if (stroke[LEFT] != null) {
paddingLeft += Math.ceil(stroke[LEFT].thickness.floatPx()/2);
}
if (stroke[RIGHT] != null) {
paddingRight += Math.ceil(stroke[RIGHT].thickness.floatPx()/2);
}
if (stroke[BOTTOM] != null) {
paddingBottom += Math.ceil(stroke[BOTTOM].thickness.floatPx()/2);
}
}
if (boxShadow != null && !boxShadow.inset) {
paddingTop += -boxShadow.vOffsetPx() + boxShadow.blurPx() + boxShadow.spreadPx();
paddingBottom += boxShadow.vOffsetPx() + boxShadow.blurPx() + boxShadow.spreadPx();
paddingLeft += -boxShadow.hOffsetPx() + boxShadow.blurPx() + boxShadow.spreadPx();
paddingRight += boxShadow.hOffsetPx() + boxShadow.blurPx() + boxShadow.spreadPx();
}
rect.setX(paddingLeft);
rect.setY(paddingTop);
rect.setWidth(outerWidth-paddingLeft-paddingRight);
rect.setHeight(outerHeight-paddingTop-paddingBottom);
}
private GeneralPath createShape(
GeneralPath out,
double x,
double y,
double width,
double height,
Arrow arrow
) {
double tx = x;
double ty = y;
x = y = 0;
if(arrow != null) {
int arrowHeightPixels = CN.convertToPixels((float)arrow.size);
switch(arrow.direction) {
case CN.TOP:
y = arrowHeightPixels;
// intentional fall through to the next statement...
case CN.BOTTOM:
height -= arrowHeightPixels;
break;
case CN.LEFT:
x = arrowHeightPixels;
// intentional fall through to the next statement...
case CN.RIGHT:
width -= arrowHeightPixels;
break;
}
}
if (hasBorderRadius()) {
out.reset();
out.moveTo(x + borderRadius.topLeftRadiusX(), y);
out.lineTo(x + width - borderRadius.topRightRadiusX(), y);
out.quadTo(x + width, y, x + width, y + borderRadius.topRightRadiusY());
out.lineTo(x + width, y + height - borderRadius.bottomRightY());
out.quadTo(x + width, y + height, x + width-borderRadius.bottomRightX(), y + height);
out.lineTo(x + borderRadius.bottomLeftX(), y + height);
out.quadTo(x, y + height, x, y + height - borderRadius.bottomLeftY());
out.lineTo(x, y + borderRadius.topLeftRadiusY());
out.quadTo(x, y, x + borderRadius.topLeftRadiusX(), y);
out.closePath();
} else {
out.reset();
out.moveTo(x,y);
out.lineTo(x + width, y);
out.lineTo(x + width, y + height);
out.lineTo(x, y + height);
out.closePath();
}
if (arrow != null) {
if (arrow.direction == CN.LEFT) {
int arrowHeightPixels = CN.convertToPixels((float)arrow.size);
int actualArrowPosition = (int)
Math.min(y + height,
Math.max(arrow.position, y + borderRadius.topLeftRadiusY()));
out.moveTo(x, actualArrowPosition);
out.lineTo(x - arrowHeightPixels, actualArrowPosition + arrowHeightPixels / 2f);
out.lineTo(x, actualArrowPosition + arrowHeightPixels);
out.closePath();
}
if (arrow.direction == CN.RIGHT) {
int arrowHeightPixels = CN.convertToPixels((float)arrow.size);
int actualArrowPosition = (int)
Math.min(y + height,
Math.max(arrow.position, y + borderRadius.topRightRadiusY()));
out.moveTo(x + width, actualArrowPosition);
out.lineTo(x + width + arrowHeightPixels, actualArrowPosition + arrowHeightPixels / 2f);
out.lineTo(x + width, actualArrowPosition + arrowHeightPixels);
out.closePath();
}
if (arrow.direction == CN.BOTTOM) {
int arrowHeightPixels = CN.convertToPixels((float)arrow.size);
int actualArrowPosition = (int)
Math.min(x + width,
Math.max(arrow.position, x + borderRadius.topLeftRadiusX()));
out.moveTo(actualArrowPosition, y + height);
out.lineTo(actualArrowPosition + arrowHeightPixels / 2f, y + height + arrowHeightPixels);
out.lineTo( actualArrowPosition + arrowHeightPixels, y + height);
out.closePath();
}
if (arrow.direction == CN.TOP) {
int arrowHeightPixels = CN.convertToPixels((float)arrow.size);
int actualArrowPosition = (int)
Math.min(x + width,
Math.max(arrow.position, x + borderRadius.topLeftRadiusX()));
out.moveTo(actualArrowPosition, y);
out.lineTo(actualArrowPosition + arrowHeightPixels / 2f, y - arrowHeightPixels);
out.lineTo(actualArrowPosition + arrowHeightPixels, y);
out.closePath();
}
}
out.transform(Transform.makeTranslation((float)tx, (float)ty));
return out;
}
private Rectangle2D contentRect;
private boolean hasBackgroundImages() {
return backgroundImages != null && backgroundImages.length > 0;
}
private void setColor(Graphics g, Color c) {
if (c != null) {
g.setAlpha(c.alpha);
g.setColor(c.color);
}
}
boolean allSidesHaveSameStroke() {
if (stroke == null) return true;
return stroke[TOP].equals(stroke[BOTTOM]) && stroke[LEFT].equals(stroke[RIGHT]) && stroke[TOP].equals(stroke[LEFT]);
}
/**
* {@inheritDoc }
*/
@Override
public boolean isBackgroundPainter() {
return true;
}
/**
* {@inheritDoc }
*/
@Override
public void paintBorderBackground(Graphics g, Component c) {
if (borderImage != null) {
// A border image overrides everything!
borderImage.internal().paintBorderBackground(g, c);
return;
}
int alpha = g.getAlpha();
int color = g.getColor();
boolean antialias = g.isAntiAliased();
g.setAntiAliased(true);
Style s = c.getStyle();
try {
if (contentRect == null) contentRect = new Rectangle2D();
calculateContentRect(c.getWidth(), c.getHeight(), contentRect);
contentRect.setX(contentRect.getX() + c.getX());
contentRect.setY(contentRect.getY() + c.getY());
context = new Context(c, contentRect);
String borderPathKey = "$$CSSBorderPath";
GeneralPath p = (GeneralPath) c.getClientProperty(borderPathKey);
if (p == null) {
p = new GeneralPath();
c.putClientProperty(borderPathKey, p);
}
createShape(
p,
contentRect.getX(),
contentRect.getY(),
contentRect.getWidth(),
contentRect.getHeight(),
createArrow(c)
);
if (boxShadow != null) {
boxShadow.paint(g, c, contentRect);
}
if (s.getBgTransparency() != 0) {
g.setColor(s.getBgColor());
int tp = s.getBgTransparency() & 0xff;
int al = (int)Math.round(alpha * tp/255.0);
g.setAlpha(al);
g.fillShape(p);
g.setColor(color);
g.setAlpha(alpha);
}
if (hasBackgroundImages()) {
int[] oldClip = g.getClip();
g.setClip(p);
g.clipRect(oldClip[0], oldClip[1], oldClip[2], oldClip[3]);
for (BackgroundImage img : backgroundImages) {
img.paint(g, c, contentRect);
}
g.setClip(oldClip);
}
if (stroke != null) {
if (allSidesHaveSameStroke() && stroke[TOP].isVisible()) {
setColor(g,stroke[TOP].color);
g.drawShape(p, stroke[TOP].getStroke(c, contentRect, true));
} else {
p.reset();
double x = contentRect.getX();
double y = contentRect.getY();
double w = contentRect.getWidth();
double h = contentRect.getHeight();
if (hasBorderRadius()) {
if (stroke[TOP].isVisible()) {
p.moveTo(x, y + borderRadius.topLeftRadiusY());
//p.arcTo(contentRect.getX() + borderRadius.topLeftRadiusX(), contentRect.getY() + borderRadius.topLeftRadiusY(), contentRect.getX() + borderRadius.topLeftRadiusX(), contentRect.getY());
p.quadTo(x, y, x + borderRadius.topLeftRadiusX(), y);
p.lineTo(x + w - borderRadius.topRightRadiusX(), y);
//p.arcTo(contentRect.getX() + contentRect.getWidth() - borderRadius.topRightRadiusX(), contentRect.getY() + borderRadius.topRightRadiusY(), contentRect.getX() + contentRect.getWidth(), contentRect.getY() + borderRadius.topRightRadiusY());
p.quadTo(x + w, y, x+w, y + borderRadius.topRightRadiusY());
setColor(g, stroke[TOP].color);
g.drawShape(p, stroke[TOP].getStroke(c, contentRect, true));
}
if (stroke[BOTTOM].isVisible()) {
p.reset();
p.moveTo(x, y + h - borderRadius.bottomLeftY());
//p.arcTo(contentRect.getX() + borderRadius.bottomLeftX(), contentRect.getY() + contentRect.getHeight() - borderRadius.bottomLeftY(), contentRect.getX() + borderRadius.bottomLeftX(), contentRect.getY() + contentRect.getHeight());
p.quadTo(x, y+h, x+borderRadius.bottomLeftX(), y+h);
p.lineTo(x + w - borderRadius.bottomRightX(), y + h);
//p.arcTo(contentRect.getX() + contentRect.getWidth() - borderRadius.bottomRightX(), contentRect.getY() + contentRect.getHeight() - borderRadius.bottomRightY(), contentRect.getX() + contentRect.getWidth(), contentRect.getY() + contentRect.getHeight() - borderRadius.bottomRightY());
p.quadTo(x+w, y+h, x+w, y+h-borderRadius.bottomLeftY());
setColor(g, stroke[BOTTOM].color);
g.drawShape(p, stroke[BOTTOM].getStroke(c, contentRect, true));
}
if (stroke[LEFT].isVisible()) {
p.reset();
p.moveTo(x, y + borderRadius.topLeftRadiusY());
p.lineTo(x, y + h - borderRadius.bottomLeftY());
setColor(g, stroke[LEFT].color);
g.drawShape(p, stroke[LEFT].getStroke(c, contentRect, false));
}
if (stroke[RIGHT].isVisible()) {
p.reset();
p.moveTo(x + w, y + borderRadius.topRightRadiusY());
p.lineTo(x + w, y + h - borderRadius.bottomRightY());
setColor(g, stroke[RIGHT].color);
g.drawShape(p, stroke[RIGHT].getStroke(c, contentRect, false));
}
} else {
if (stroke[TOP].isVisible()) {
p.reset();
p.moveTo(x, y);
p.lineTo(x + w, y);
setColor(g, stroke[TOP].color);
Stroke st = stroke[TOP].getStroke(c, contentRect, true);
g.drawShape(p, st);
}
if (stroke[BOTTOM].isVisible()) {
p.reset();
p.moveTo(x, y + h);
p.lineTo(x + w,y + h);
setColor(g, stroke[BOTTOM].color);
g.drawShape(p, stroke[BOTTOM].getStroke(c, contentRect, true));
}
if (stroke[LEFT].isVisible()) {
p.reset();
p.moveTo(x, y);
p.lineTo(x, y + h);
setColor(g, stroke[LEFT].color);
g.drawShape(p, stroke[LEFT].getStroke(c, contentRect, false));
}
if (stroke[RIGHT].isVisible()) {
p.reset();
p.moveTo(x + w, y);
p.lineTo(x + w, y + h);
setColor(g, stroke[RIGHT].color);
g.drawShape(p, stroke[RIGHT].getStroke(c, contentRect, false));
}
}
}
}
} finally {
g.setAlpha(alpha);
g.setColor(color);
g.setAntiAliased(antialias);
}
}
/**
* {@inheritDoc }
*/
@Override
public int getMinimumHeight() {
if (borderImage != null) {
return borderImage.internal().getMinimumHeight();
}
return super.getMinimumHeight(); //To change body of generated methods, choose Tools | Templates.
}
/**
* {@inheritDoc }
*/
@Override
public int getMinimumWidth() {
if (borderImage != null) {
return borderImage.internal().getMinimumWidth();
}
return super.getMinimumWidth();
}
/**
* Creates a 9-piece image border.
*
* Insets are all given in a (u,v) coordinate space where (0,0) is the top-left corner of the image, and (1.0, 1.0) is the bottom-right corner of the image.
* If a border image is set for the CSS border, it will override all other border types, and will result in only the 9-piece
* border being rendered.
*
* @param borderImage The border image.
* @param slicePoints The slice points. Accepts 1 - 4 values:
*
* - 1 value = all sides
* - 2 values = vertical horizontal
* - 3 values = top horizontal bottom
* - 4 values = top right bottom left
*
* @return Self for chaining.
* @since 7.0
* @see #borderImageWithName(java.lang.String, double...)
*/
public CSSBorder borderImage(Image borderImage, double... slicePoints) {
this.borderImage = new BorderImage(borderImage, slicePoints);
return this;
}
/**
* Adds a 9-piece image border using the provided image name, which should exist in the
* theme resource file.
* Insets are all given in a (u,v) coordinate space where (0,0) is the top-left corner of the image, and (1.0, 1.0) is the bottom-right corner of the image.
* If a border image is set for the CSS border, it will override all other border types, and will result in only the 9-piece
* border being rendered.
* @param borderImageName The image name.
* @param slicePoints The slice points. Accepts 1 - 4 values:
*
* - 1 value = all sides
* - 2 values = vertical horizontal
* - 3 values = top horizontal bottom
* - 4 values = top right bottom left
*
* @return Self for chaining.
* @since 7.0
* @see #borderImage(com.codename1.ui.Image, double...)
*/
public CSSBorder borderImageWithName(String borderImageName, double... slicePoints) {
this.borderImage = new BorderImage(borderImageName, slicePoints);
return this;
}
private CSSBorder borderImage(String cssProperty) {
String[] parts = Util.split(cssProperty, " ");
parts[0] = Util.decode(parts[0], "UTF-8", false);
int len = parts.length;
double[] splices = new double[len-1];
for (int i=1; i imgs = new ArrayList();
for (String part : parts) {
part = part.trim();
if (part.indexOf("url(") == 0) {
part = part.substring(4, part.length()-1);
}
if (part.charAt(0) == '"' || part.charAt(0) == '"') {
part = part.substring(1, part.length()-1);
}
if (part.indexOf("/") != -1) {
part = part.substring(part.lastIndexOf("/")+1);
}
Image im = res.getImage(part);
if (im == null) {
try {
im = EncodedImage.create("/"+part);
im.setImageName(part);
} catch (IOException ex) {
Log.e(ex);
throw new IllegalArgumentException("Failed to parse image: "+part);
}
}
imgs.add(im);
}
return backgroundImage(imgs.toArray(new Image[imgs.size()]));
}
/**
* Sets the background image of the border.
* @param images Images to use as background images.
* @return Self for chaining.
*/
public CSSBorder backgroundImage(Image... images) {
int len = images.length;
if (backgroundImages == null) {
backgroundImages = new BackgroundImage[len];
} else {
if (backgroundImages.length < len) {
BackgroundImage[] tmp = new BackgroundImage[len];
System.arraycopy(backgroundImages, 0, tmp, 0, backgroundImages.length);
backgroundImages = tmp;
}
}
for (int i=0; i= trackY + getTrackComponent().getHeight()) {
// we are below the component
direction = CN.TOP;
position = (trackX + getTrackComponent().getWidth() / 2) - cabsX - arrowWH / 2;
} else {
if(trackComponentSide == CN.BOTTOM || cabsY + c.getHeight() <= trackY) {
// we are above the component
direction = CN.BOTTOM;
position = (trackX + getTrackComponent().getWidth() / 2) - cabsX - arrowWH / 2;
} else {
if(cabsX >= trackX + getTrackComponent().getWidth()) {
// we are to the right of the component
direction = CN.LEFT;
position = (trackY + getTrackComponent().getHeight() / 2) - cabsY - arrowWH / 2;
} else {
if(cabsX + c.getWidth() <= trackX) {
// we are to the left of the component
direction = CN.RIGHT;
position = (trackY + getTrackComponent().getHeight() / 2) - cabsY - arrowWH / 2;
}
}
}
}
} else if (trackComponentSide >= 0) {
switch (trackComponentSide) {
case CN.TOP:
direction = CN.TOP;
position = 0;
if (trackComponentHorizontalPosition >= 0) {
position = (int)(c.getWidth() * trackComponentHorizontalPosition);
} break;
case CN.BOTTOM:
direction = CN.BOTTOM;
position = 0;
if (trackComponentHorizontalPosition >= 0) {
position = (int)(c.getWidth() * trackComponentHorizontalPosition);
} break;
case CN.LEFT:
direction = CN.LEFT;
position = 0;
if (trackComponentVerticalPosition >= 0) {
position = (int)(c.getHeight() * trackComponentVerticalPosition);
} break;
case CN.RIGHT:
direction = CN.RIGHT;
position = 0;
if (trackComponentVerticalPosition >= 0) {
position = (int)(c.getHeight() * trackComponentVerticalPosition);
} break;
default:
break;
}
}
}
}
}