package utility.borders; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Composite; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.border.AbstractBorder; /** * This border has a smoothing effect on inside and outside of a component. * You can control the following values of the border, for example: * +Have the effect on both sides (inside and outside) or only on * a single side of a border. * +Inverse the smoothing effect towards the inside of the border (outside is default).
* +Show/hide the center line (inversed border looks nicer without the center line).
* +Round edges of your border. * Example of use:
* //create some component, e.g. a panel
* JPanel p = new JPanel();
* //set its preferred size, e.g. width & height are set to 200
* p.setPreferredSize(new Dimension(200, 200));
* //create an object of the smoothed border of red color,
* // effect thickness of 10, without center & inveresed
* SmoothedBorder sB = new SmoothedBorder(Color.RED, 10, false, true);
* //finally set it for your component
* p.setBorder(sB); *
* * @version 1.0.0 * @author Konrad Borowiecki */ public class SmoothedBorder extends AbstractBorder { private static final long serialVersionUID = 1L; /** Color to be used for this border. */ private Color color; /** The size of the one side of the effect. We always have one unmodified color * line in the middle of the border. */ private int effectThickness; /** True if central border line should be painted; false otherwise.*/ private boolean isWithCenter; /** True if the painting of the effect should be inverted. */ private boolean isInversed; /** Allows to control when the border is to be painted. */ private boolean isVisible = true; /** To control if the border should be smoothed from both sides inside and outside. */ private boolean isSingleSmooth = false; /** To control border rounding. */ private boolean isRounding = false; /** Angle width used for rounding effect.*/ private int angleW = 30; /** Angle height used for rounding effect.*/ private int angleH = 30; /** * Create a border with a smooth (vanishing) effect on both its ends (or inside if isInversed is true). * @param color color to be use for border painting * @param effectThickness the thickness of the effect which makes whole * (in case of a single smoothed border) or half of the border size (if not single smoothed) * @param isWithCenter should the central line be painted * @param isInversed should the effect be inversed (by default it is facing outside of a component) */ public SmoothedBorder(Color color, int effectThickness, boolean isWithCenter, boolean isInversed) { this.color = color; this.effectThickness = effectThickness; this.isWithCenter = isWithCenter; this.isInversed = isInversed; } /** * Create a border with a smooth (vanishing) effect on both its ends (or inside if isInversed is true). * Additionally lets to set if the effect should be only single. * @param color color to be use for border painting * @param effectThickness the thickness of the effect which makes whole * (in case of a single smoothed border) or half of the border size (if not single smoothed) * @param isWithCenter should the central line be painted * @param isInversed should the effect be inversed (by default it is facing outside of a component) * @param isSingleSmooth if true makes a single smoothing only from one side either outside * or inside of a component (if isInverse true). * Attention: Border size is calculated depending on the effectThickness and the value of * isSingleSmooth variables. It is equal twice the value of effectThickness, except when * isSingleSmooth is true, then it is equal to the value of effectThickness. */ public SmoothedBorder(Color color, int effectThickness, boolean isWithCenter, boolean isInversed, boolean isSingleSmooth) { this.color = color; this.effectThickness = effectThickness; this.isWithCenter = isWithCenter; this.isInversed = isInversed; this.isSingleSmooth = isSingleSmooth; } @Override public Insets getBorderInsets(Component c) { int insetThickness = getInsetThickness(); return new Insets(insetThickness, insetThickness, insetThickness, insetThickness); } @Override public Insets getBorderInsets(Component c, Insets insets) { int insetThickness = getInsetThickness(); insets.left = insets.top = insets.right = insets.bottom = insetThickness; return insets; } private int getInsetThickness() { int insetThickness = effectThickness * 2; if(isSingleSmooth) insetThickness = effectThickness; return insetThickness; } @Override public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) { if(isVisible == false) return; Graphics2D g2 = (Graphics2D) g; int rCX = x + effectThickness; int rCY = y + effectThickness; int rCW = w - 1 - effectThickness * 2; int rCH = h - 1 - effectThickness * 2; g2.setColor(color); if(isWithCenter) { Rectangle rC = new Rectangle(rCX, rCY, rCW, rCH); if(isRounding) g.drawRoundRect(rCX, rCY, rCW, rCH, angleW, angleH); else g2.draw(rC); } if(effectThickness == 0) return; Composite defaultComposite = g2.getComposite(); float step = 1.0f / (effectThickness + 1); for(int i = 0; i < effectThickness; i++) { float alpha = 1.0f - step * (i + 1); if(isInversed) alpha = step * (i + 1); // System.out.println("i="+i+";;alpha="+alpha); //effect outside the border Rectangle rOut = new Rectangle(rCX - (i + 1), rCY - (i + 1), rCW + (i + 1) * 2, rCH + (i + 1) * 2); g2.setComposite(makeComposite(alpha)); if(isRounding) g.drawRoundRect(rCX - (i + 1), rCY - (i + 1), rCW + (i + 1) * 2, rCH + (i + 1) * 2, angleW, angleH); else g2.draw(rOut); if(!isSingleSmooth) { //effect inside the border Rectangle rIn = new Rectangle(rCX + (i + 1), rCY + (i + 1), rCW - (i + 1) * 2, rCH - (i + 1) * 2); g2.setComposite(makeComposite(alpha)); if(isRounding) g.drawRoundRect(rCX + (i + 1), rCY + (i + 1), rCW - (i + 1) * 2, rCH - (i + 1) * 2, angleW, angleH); else g2.draw(rIn); } } g2.setComposite(defaultComposite); } /** * Creates alpha composite. For example, pass in 1.0f to have 100% opacity * pass in 0.25f to have 25% opacity. * @param alpha a float value between 0 and 1 both inclusive * @return alpha composite that has the specified opacity */ public static AlphaComposite makeComposite(float alpha) { int type = AlphaComposite.SRC_OVER; return (AlphaComposite.getInstance(type, alpha)); } @Override public boolean isBorderOpaque() { return false; } /** Allows to set if the border is to be painted or not. Default is true.*/ public void setVisible(boolean isVisible) { this.isVisible = isVisible; } /**Tells if the border is currently set to be painted or not.*/ public boolean isVisible() { return isVisible; } /** Sets the color used for the border painting.*/ public void setColor(Color color) { this.color = color; } /** Gets the color used for the border painting.*/ public Color getColor() { return color; } /** Sets thickness of the smoothing effect.*/ public void setEffectThickness(int effectThickness) { this.effectThickness = effectThickness; } /** Gets thickness of the smoothing effect.*/ public int getEffectThickness() { return effectThickness; } /**Allows to set that smoothing effect to be only done for a single side. Default is false. * Attention: Border size is calculated depending on the effectThickness and the value of * isSingleSmooth variables. It is equal twice the value of effectThickness, except when * isSingleSmooth is true, then it is equal to the value of effectThickness.*/ public void setSingleSmooth(boolean isSingleSmooth) { this.isSingleSmooth = isSingleSmooth; } /** Tells if smoothing effect is done one a single side of the border or not. */ public boolean isSingleSmooth(){ return isSingleSmooth; } /** Allows to set inverse effect on. Default is false.*/ public void setInversed(boolean isInversed) { this.isInversed = isInversed; } /** Tells is the effect is inversed. */ public boolean isInversed() { return isInversed; } /** Tells is the rounding effect is on.*/ public boolean isRounding() { return isRounding; } /** Allows to switch on the rounding effect. Default is false.*/ public void setRounding(boolean isRounding) { this.isRounding = isRounding; } /** Gets the width of angle used for rounding effect.*/ public int getAngleWidth() { return angleW; } /** Sets the width of angle used for rounding effect. Default is 30.*/ public void setAngleWidth(int angleWidth) { this.angleW = angleWidth; } /** Gets the height of angle used for rounding effect.*/ public int getAngleHeight() { return angleH; } /** Sets the height of angle used for rounding effect. Default is 30.*/ public void setAngleHeight(int angleHeight) { this.angleH = angleHeight; } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { final JPanel p = new JPanel(); p.setBackground(Color.WHITE); p.setPreferredSize(new Dimension(200, 200)); final SmoothedBorder sB = new SmoothedBorder(Color.RED, 10, false, true); p.setBorder(sB); JPanel contentPane = new JPanel(); contentPane.add(p); JButton inverseB = new JButton("Inverse"); inverseB.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { sB.setInversed(!sB.isInversed()); p.repaint(); } }); contentPane.add(inverseB); JButton singleDoubleSmoothingB = new JButton("Single <-> Double Smoothing"); singleDoubleSmoothingB.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { sB.setSingleSmooth(!sB.isSingleSmooth()); p.repaint(); } }); contentPane.add(singleDoubleSmoothingB); JFrame f = new JFrame(); f.setContentPane(contentPane); f.setSize(250, 400); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); } }); } }