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);
}
});
}
}