/* * Copyright 2008 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.lwuit.plaf; import com.sun.lwuit.Component; import com.sun.lwuit.Graphics; import com.sun.lwuit.Image; import com.sun.lwuit.Painter; import com.sun.lwuit.RGBImage; import com.sun.lwuit.geom.Rectangle; import java.lang.ref.WeakReference; /** * Base class that allows us to render a border for a component, a border is drawn before * the component and is drawn within the padding region of the component. It is the * responsibility of the component not to draw outside of the border line. * <p>This class can be extended to provide additional border types and custom made * border types. * <p>A border can optionally paint the background of the component, this depends on * the border type and is generally required for rounded borders that "know" the area * that should be filled. * * @author Shai Almog */ public class Border { private static Border defaultBorder = Border.createEtchedRaised(0x020202, 0xBBBBBB); private static final int TYPE_EMPTY = 0; private static final int TYPE_LINE = 1; private static final int TYPE_ROUNDED = 2; private static final int TYPE_ROUNDED_PRESSED = 3; private static final int TYPE_ETCHED_LOWERED = 4; private static final int TYPE_ETCHED_RAISED = 5; private static final int TYPE_BEVEL_RAISED = 6; private static final int TYPE_BEVEL_LOWERED = 7; private static final int TYPE_IMAGE = 8; // variables are package protected for the benefit of the resource editor! int type; Image[] images; /** * Indicates whether theme colors should be used or whether colors are specified * in the border */ boolean themeColors; int colorA; int colorB; int colorC; int colorD; int thickness = 0; int arcWidth; int arcHeight; boolean outline = true; Border pressedBorder; Border focusBorder; private static Border empty; /** * Prevents usage of new operator, use the factory methods in the class or subclass * to create new border types. */ protected Border() { } /** * Returns an empty border, this is mostly useful for overriding components that * have a border by default * * @return a border than draws nothing * @deprecated use createEmpty instead */ public static Border getEmpty() { if(empty == null) { empty = new Border(); } return empty; } /** * Creates an empty border, this is useful where we don't want a border for a * component but want a focus border etc... * * @return a border than draws nothing */ public static Border createEmpty() { return new Border(); } /** * The given images are tiled appropriately across the matching side of the border and placed * as expected in the four corners. The background image is optional and it will be tiled in * the background if necessary. * <p>By default this border does not override background unless a background image is specified * * @param top the image of the top line * @param bottom the image of the bottom line * @param left the image of the left line * @param right the image of the right line * @param topLeft the image of the top left corner * @param topRight the image of the top right corner * @param bottomLeft the image of the bottom left corner * @param bottomRight the image of the bottom right corner * @param background the image of the background (optional) * @return new border instance */ public static Border createImageBorder(Image top, Image bottom, Image left, Image right, Image topLeft, Image topRight, Image bottomLeft, Image bottomRight, Image background) { Border b = new Border(); b.type = TYPE_IMAGE; b.images = new Image[] {top, bottom, left, right, topLeft, topRight, bottomLeft, bottomRight, background}; return b; } /** * The given images are tiled appropriately across the matching side of the border, rotated and placed * as expected in the four corners. The background image is optional and it will be tiled in * the background if necessary. * <p>By default this border does not override background unless a background image is specified. * <p>Notice that this version of the method is potentially much more efficient since images * are rotated internally and this might save quite a bit of memory! * <p><b>The top and topLeft images must be square!</b> The width and height of these images * must be equal otherwise rotation won't work as you expect. * * @param top the image of the top line * @param topLeft the image of the top left corner * @param background the image of the background (optional) * @return new border instance */ public static Border createImageBorder(Image top, Image topLeft, Image background) { Border b = new Border(); b.type = TYPE_IMAGE; b.images = new Image[] {top, top.rotate(180), top.rotate(270), top.rotate(90), topLeft, topLeft.rotate(90), topLeft.rotate(270), topLeft.rotate(180), background}; return b; } /** * Creates a line border that uses the color of the component foreground for drawing * * @param thickness thickness of the boder in pixels * @return new border instance */ public static Border createLineBorder(int thickness) { Border b = new Border(); b.type = TYPE_LINE; b.themeColors = true; b.thickness = thickness; return b; } /** * Creates a line border that uses the given color for the component * * @param thickness thickness of the boder in pixels * @param color the color for the border * @return new border instance */ public static Border createLineBorder(int thickness, int color) { Border b = new Border(); b.type = TYPE_LINE; b.themeColors = false; b.thickness = thickness; b.colorA = color; return b; } /** * Creates a rounded corner border that uses the color of the component foreground for drawing. * Due to technical issues (lack of shaped clipping) performance and memory overhead of round * borders can be low if used with either a bgImage or translucency! * <p>This border overrides any painter used on the component and would ignor such a painter. * * @param arcWidth the horizontal diameter of the arc at the four corners. * @param arcHeight the vertical diameter of the arc at the four corners. * @return new border instance */ public static Border createRoundBorder(int arcWidth, int arcHeight) { Border b = new Border(); b.type = TYPE_ROUNDED; b.themeColors = true; b.arcHeight = arcHeight; b.arcWidth = arcWidth; return b; } /** * Creates a rounded corner border that uses the color of the component foreground for drawing. * Due to technical issues (lack of shaped clipping) performance and memory overhead of round * borders can be low if used with either a bgImage or translucency! * <p>This border overrides any painter used on the component and would ignor such a painter. * * @param arcWidth the horizontal diameter of the arc at the four corners. * @param arcHeight the vertical diameter of the arc at the four corners. * @param outline whether the round rect border outline should be drawn * @return new border instance */ public static Border createRoundBorder(int arcWidth, int arcHeight, boolean outline) { Border b = createRoundBorder(arcWidth, arcHeight); b.outline = outline; return b; } /** * Creates a rounded border that uses the given color for the component. * Due to technical issues (lack of shaped clipping) performance and memory overhead of round * borders can be low if used with either a bgImage or translucency! * <p>This border overrides any painter used on the component and would ignor such a painter. * * @param arcWidth the horizontal diameter of the arc at the four corners. * @param arcHeight the vertical diameter of the arc at the four corners. * @param color the color for the border * @return new border instance */ public static Border createRoundBorder(int arcWidth, int arcHeight, int color) { Border b = new Border(); b.type = TYPE_ROUNDED; b.themeColors = false; b.colorA = color; b.arcHeight = arcHeight; b.arcWidth = arcWidth; return b; } /** * Creates a rounded border that uses the given color for the component. * Due to technical issues (lack of shaped clipping) performance and memory overhead of round * borders can be low if used with either a bgImage or translucency! * <p>This border overrides any painter used on the component and would ignor such a painter. * * @param arcWidth the horizontal diameter of the arc at the four corners. * @param arcHeight the vertical diameter of the arc at the four corners. * @param color the color for the border * @param outline whether the round rect border outline should be drawn * @return new border instance */ public static Border createRoundBorder(int arcWidth, int arcHeight, int color, boolean outline) { Border b = createRoundBorder(arcWidth, arcHeight, color); b.outline = outline; return b; } /** * Creates a lowered etched border with default colors, highlight is derived * from the component and shadow is a plain dark color * * @return new border instance */ public static Border createEtchedLowered() { Border b = new Border(); b.type = TYPE_ETCHED_LOWERED; b.themeColors = true; return b; } /** * Creates a raised etched border with the given colors * * @param highlight color RGB value * @param shadow color RGB value * @return new border instance */ public static Border createEtchedLowered(int highlight, int shadow) { Border b = new Border(); b.type = TYPE_ETCHED_LOWERED; b.themeColors = false; b.colorA = shadow; b.colorB = highlight; return b; } /** * Creates a lowered etched border with default colors, highlight is derived * from the component and shadow is a plain dark color * * @return new border instance */ public static Border createEtchedRaised() { Border b = new Border(); b.type = TYPE_ETCHED_RAISED; b.themeColors = true; b.thickness = 2; return b; } /** * Creates a raised etched border with the given colors * * @param highlight color RGB value * @param shadow color RGB value * @return new border instance */ public static Border createEtchedRaised(int highlight, int shadow) { Border b = new Border(); b.type = TYPE_ETCHED_RAISED; b.themeColors = false; b.colorA = highlight; b.colorB = shadow; b.thickness = 2; return b; } /** * Returns true if installing this border will override the painting of the component background * * @return true if this border replaces the painter */ public boolean isBackgroundPainter() { return type == TYPE_ROUNDED || type == TYPE_ROUNDED_PRESSED || (type == TYPE_IMAGE); } /** * Creates a lowered bevel border with default colors, highlight is derived * from the component and shadow is a plain dark color * * @return new border instance */ public static Border createBevelLowered() { Border b = new Border(); b.type = TYPE_BEVEL_LOWERED; b.themeColors = true; b.thickness = 2; return b; } /** * Creates a raised bevel border with the given colors * * @param highlightOuter RGB of the outer edge of the highlight area * @param highlightInner RGB of the inner edge of the highlight area * @param shadowOuter RGB of the outer edge of the shadow area * @param shadowInner RGB of the inner edge of the shadow area * @return new border instance */ public static Border createBevelLowered(int highlightOuter, int highlightInner, int shadowOuter, int shadowInner) { Border b = new Border(); b.type = TYPE_BEVEL_LOWERED; b.themeColors = false; b.colorA = highlightOuter; b.colorB = highlightInner; b.colorC = shadowOuter; b.colorD = shadowInner; b.thickness = 2; return b; } /** * Creates a lowered bevel border with default colors, highlight is derived * from the component and shadow is a plain dark color * * @return new border instance */ public static Border createBevelRaised() { Border b = new Border(); b.type = TYPE_BEVEL_RAISED; b.themeColors = true; b.thickness = 2; return b; } /** * Creates a raised bevel border with the given colors * * @param highlightOuter RGB of the outer edge of the highlight area * @param highlightInner RGB of the inner edge of the highlight area * @param shadowOuter RGB of the outer edge of the shadow area * @param shadowInner RGB of the inner edge of the shadow area * @return new border instance */ public static Border createBevelRaised(int highlightOuter, int highlightInner, int shadowOuter, int shadowInner) { Border b = new Border(); b.type = TYPE_BEVEL_RAISED; b.themeColors = false; b.colorA = highlightOuter; b.colorB = highlightInner; b.colorC = shadowOuter; b.colorD = shadowInner; b.thickness = 2; return b; } /** * Allows us to define a border that will act as the pressed version of this border * * @param pressed a border that will be returned by the pressed version method */ public void setPressedInstance(Border pressed) { pressedBorder = pressed; } /** * Allows us to define a border that will act as the focused version of this border * * @param focused a border that will be returned by the focused version method * @deprecated use the getSelectedStyle() method in the component class */ public void setFocusedInstance(Border focused) { focusBorder = focused; } /** * Returns the focused version of the border if one is installed * * @return a focused version of this border if one exists * @deprecated use the getSelectedStyle() method in the component class */ public Border getFocusedInstance() { if(focusBorder != null) { return focusBorder; } return this; } /** * Returns the pressed version of the border if one is set by the user * * @return the pressed instance of this border */ public Border getPressedInstance() { if(pressedBorder != null) { return pressedBorder; } return this; } /** * When applied to buttons borders produce a version that reverses the effects * of the border providing a pressed feel * * @return a border that will make the button feel pressed */ public Border createPressedVersion() { if(pressedBorder != null) { return pressedBorder; } switch(type) { case TYPE_LINE: return createLineBorder(thickness + 1, colorA); case TYPE_ETCHED_LOWERED: { Border b = createEtchedRaised(colorA, colorB); b.themeColors = themeColors; return b; } case TYPE_ETCHED_RAISED: { Border b = createEtchedLowered(colorA, colorB); b.themeColors = themeColors; return b; } case TYPE_BEVEL_RAISED: { Border b = createBevelLowered(colorA, colorB, colorC, colorD); b.themeColors = themeColors; return b; } case TYPE_BEVEL_LOWERED: { Border b = createBevelRaised(colorA, colorB, colorC, colorD); b.themeColors = themeColors; return b; } case TYPE_ROUNDED: { Border b = createRoundBorder(arcWidth, arcHeight, colorA); b.themeColors = themeColors; b.type = TYPE_ROUNDED_PRESSED; return b; } case TYPE_ROUNDED_PRESSED: { Border b = createRoundBorder(arcWidth, arcHeight, colorA); b.themeColors = themeColors; return b; } } return this; } /** * Has effect when the border demands responsibility for background painting * normally the painter will perform this work but in this case the border might * do it instead. * * @param g graphics context to draw onto * @param c component whose border should be drawn */ public void paintBorderBackground(Graphics g, Component c) { int originalColor = g.getColor(); int x = c.getX(); int y = c.getY(); int width = c.getWidth(); int height = c.getHeight(); switch(type) { case TYPE_ROUNDED_PRESSED: x++; y++; width -= 2; height -= 2; case TYPE_ROUNDED: width--; height--; // rounded is also responsible for drawing the background Style s = c.getStyle(); if((s.getBgImage() != null && s.getBackgroundType() == Style.BACKGROUND_IMAGE_SCALED) || s.getBackgroundType() > 1) { WeakReference w = (WeakReference)s.roundRectCache; Image i = null; if(w != null) { i = (Image)w.get(); } if(i != null && i.getWidth() == width && i.getHeight() == height) { g.drawImage(i, x, y); } else { // we need to draw a background image! i = Image.createImage(width, height); Graphics imageG = i.getGraphics(); imageG.setColor(0); imageG.fillRoundRect(0, 0, width, height, arcWidth, arcHeight); int[] rgb = i.getRGBCached(); int transColor = rgb[0]; int[] imageRGB; if(s.getBackgroundType() == Style.BACKGROUND_IMAGE_SCALED) { imageRGB = s.getBgImage().scaled(width, height).getRGBCached(); } else { Image bgPaint = Image.createImage(width, height); Painter p = s.getBgPainter(); // might occur during temporary conditions in the theme switching if(p == null) { return; } p.paint(bgPaint.getGraphics(), new Rectangle(0, 0, width, height)); imageRGB = bgPaint.getRGB(); } for(int iter = 0 ; iter < rgb.length ; iter++) { if(rgb[iter] == transColor) { imageRGB[iter] = 0; } } i = Image.createImage(imageRGB, width, height); s.roundRectCache = new WeakReference(i); g.drawImage(i, x, y); } } else { int foreground = g.getColor(); g.setColor(s.getBgColor()); // Its opaque much easier job! if(s.getBgTransparency() == ((byte)0xff)) { g.fillRoundRect(x, y , width, height, arcWidth, arcHeight); } else { if(g.isAlphaSupported()) { int alpha = g.getAlpha(); g.setAlpha(s.getBgTransparency() & 0xff); g.fillRoundRect(x, y , width, height, arcWidth, arcHeight); g.setAlpha(alpha); } else { // if its transparent we don't need to do anything, if its // translucent... well.... if(s.getBgTransparency() != 0) { Image i = Image.createImage(width, height); int[] imageRgb; if(g.getColor() != 0xffffff) { Graphics imageG = i.getGraphics(); imageG.setColor(g.getColor()); imageG.fillRoundRect(0, 0 , width, height, arcWidth, arcHeight); imageRgb = i.getRGBCached(); } else { // background color is white we need to remove a different color // black is the only other "reliable" color on the device Graphics imageG = i.getGraphics(); imageG.setColor(0); imageG.fillRect(0, 0, width, height); imageG.setColor(g.getColor()); imageG.fillRoundRect(0, 0 , width, height, arcWidth, arcHeight); imageRgb = i.getRGBCached(); } int removeColor = imageRgb[0]; int size = width * height; int alphaInt = ((s.getBgTransparency() & 0xff) << 24) & 0xff000000; for(int iter = 0 ; iter < size ; iter++) { if(removeColor == imageRgb[iter]) { imageRgb[iter] = 0; continue; } if((imageRgb[iter] & 0xff000000) != 0) { imageRgb[iter] = (imageRgb[iter] & 0xffffff) | alphaInt; } } g.drawImage(new RGBImage(imageRgb, width, height), x, y); } } } g.setColor(foreground); } break; case TYPE_IMAGE: int clipX = g.getClipX(); int clipY = g.getClipY(); int clipWidth = g.getClipWidth(); int clipHeight = g.getClipHeight(); Image topLeft = images[4]; Image topRight = images[5]; Image bottomLeft = images[6]; Image center = images[8]; x += topLeft.getWidth(); y += topLeft.getHeight(); height -= (topLeft.getHeight() + bottomLeft.getHeight()); width -= (topLeft.getWidth() + topRight.getWidth()); g.clipRect(x, y, width, height); if(center != null){ int centerWidth = center.getWidth(); int centerHeight = center.getHeight(); for(int xCount = x ; xCount < x + width ; xCount += centerWidth) { for(int yCount = y ; yCount < y + height ; yCount += centerHeight) { g.drawImage(center, xCount, yCount); } } } Image top = images[0]; Image bottom = images[1]; Image left = images[2]; Image right = images[3]; Image bottomRight = images[7]; g.setClip(clipX, clipY, clipWidth, clipHeight); x = c.getX(); y = c.getY(); width = c.getWidth(); height = c.getHeight(); g.drawImage(topLeft, x, y); g.drawImage(bottomLeft, x, y + height - bottomLeft.getHeight()); g.drawImage(topRight, x + width - topRight.getWidth(), y); g.drawImage(bottomRight, x + width - bottomRight.getWidth(), y + height - bottomRight.getHeight()); g.setClip(clipX, clipY, clipWidth, clipHeight); drawImageBorderLine(g, topLeft, topRight, top, x, y, width); g.setClip(clipX, clipY, clipWidth, clipHeight); drawImageBorderLine(g, bottomLeft, bottomRight, bottom, x, y + height - bottom.getHeight(), width); g.setClip(clipX, clipY, clipWidth, clipHeight); drawImageBorderColumn(g, topLeft, bottomLeft, left, x, y, height); g.setClip(clipX, clipY, clipWidth, clipHeight); drawImageBorderColumn(g, topRight, bottomRight, right, x + width - right.getWidth(), y, height); g.setClip(clipX, clipY, clipWidth, clipHeight); break; } g.setColor(originalColor); } /** * Draws the border for the given component, this method is called before a call to * background painting is made. * * @param g graphics context to draw onto * @param c component whose border should be drawn */ public void paint(Graphics g, Component c) { int originalColor = g.getColor(); int x = c.getX(); int y = c.getY(); int width = c.getWidth(); int height = c.getHeight(); if(!themeColors) { g.setColor(colorA); } switch(type) { case TYPE_LINE: width--; height--; for(int iter = 0 ; iter < thickness ; iter++) { g.drawRect(x + iter, y + iter, width, height); width -= 2; height -= 2; } break; case TYPE_ROUNDED_PRESSED: x++; y++; width -= 2; height -= 2; case TYPE_ROUNDED: width--; height--; if(outline) { g.drawRoundRect(x, y , width, height, arcWidth, arcHeight); } break; case TYPE_ETCHED_LOWERED: case TYPE_ETCHED_RAISED: g.drawRect(x, y, width - 2, height - 2); if(themeColors) { if(type == TYPE_ETCHED_LOWERED) { g.lighterColor(40); } else { g.darkerColor(40); } } else { g.setColor(colorB); } g.drawLine(x + 1, y + height - 3, x + 1, y +1); g.drawLine(x + 1, y + 1, x + width - 3, y + 1); g.drawLine(x, y + height - 1, x + width - 1, y + height - 1); g.drawLine(x + width - 1, y + height - 1, x + width - 1, y); break; case TYPE_BEVEL_RAISED: if(themeColors) { g.setColor(getBackgroundColor(c)); g.lighterColor(50); } else { g.setColor(colorA); } g.drawLine(x, y, x, y + height - 2); g.drawLine(x + 1, y, x + width - 2, y); if(themeColors) { g.darkerColor(25); } else { g.setColor(colorB); } g.drawLine(x + 1, y + 1, x + 1, y + height - 3); g.drawLine(x + 2, y + 1, x + width - 3, y + 1); if(themeColors) { g.darkerColor(50); } else { g.setColor(colorC); } g.drawLine(x, y + height - 1, x + width - 1, y + height - 1); g.drawLine(x + width - 1, y, x + width - 1, y + height - 2); if(themeColors) { g.darkerColor(25); } else { g.setColor(colorD); } g.drawLine(x + 1, y + height - 2, x + width - 2, y + height - 2); g.drawLine(x + width - 2, y + 1, x + width - 2, y + height - 3); break; case TYPE_BEVEL_LOWERED: if(themeColors) { g.setColor(getBackgroundColor(c)); g.darkerColor(50); } else { g.setColor(colorD); } g.drawLine(x, y, x, y + height - 1); g.drawLine(x + 1, y, x + width - 1, y); if(themeColors) { g.lighterColor(25); } else { g.setColor(colorC); } g.drawLine(x + 1, y + 1, x + 1, y + height - 2); g.drawLine(x + 2, y + 1, x + width - 2, y + 1); if(themeColors) { g.lighterColor(50); } else { g.setColor(colorC); } g.drawLine(x + 1, y + height - 1, x + width - 1, y + height - 1); g.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 2); if(themeColors) { g.lighterColor(25); } else { g.setColor(colorA); } g.drawLine(x + 2, y + height - 2, x + width - 2, y + height - 2); g.drawLine(x + width - 2, y + 2, x + width - 2, y + height - 3); break; case TYPE_IMAGE: break; } g.setColor(originalColor); } private int getBackgroundColor(Component c) { return c.getStyle().getBgColor(); } private void drawImageBorderLine(Graphics g, Image left, Image right, Image center, int x, int y, int width) { int currentWidth = width - right.getWidth(); if(currentWidth > 0) { x += left.getWidth(); int destX = x + currentWidth; g.clipRect(x, y, currentWidth - left.getWidth(), center.getHeight()); int centerWidth = center.getWidth(); for(; x < destX ; x += centerWidth) { g.drawImage(center, x, y); } } } private void drawImageBorderColumn(Graphics g, Image top, Image bottom, Image center, int x, int y, int height) { int currentHeight = height - bottom.getHeight(); if(currentHeight > 0) { y += top.getHeight(); int destY = y + currentHeight; g.clipRect(x, y, center.getWidth(), currentHeight - top.getHeight()); int centerHeight = center.getHeight(); for(; y < destY ; y += centerHeight) { g.drawImage(center, x, y); } } } /** * Sets the default border to the given value * * @param border new border value */ public static void setDefaultBorder(Border border) { defaultBorder = border; } /** * Gets the default border to the given value * * @return the default border */ public static Border getDefaultBorder() { return defaultBorder; } /** * This methos returns how thick is the border in pixels * @return the Border thickness */ public int getThickness() { return thickness; } }