I'm newbie in the swing and have a question how better to draw this shape:
I thought in two ways
to draw regular rectangle and to write custom border to it?
to draw regular rectangle + compound border(which contains 2 or 3 borders). But here i do not succeed to draw border inside the shape, is is possible at all? Something like this :
figure.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBor‌​der(outside top,left,bottom, right, Color.WHITE), createMatteBorder(inside top,left,bottom, right, Color.WHITE)), where the inside border is small rectangle, and outside is big rectangle - not sure if it is possible???
Please advise and an examples will be highly appreciated!
Take a look at the Java 2D API. It helps you to draw complex shapes.
E.g.
class IrregularShape extends JComponent {
private int strokeWidth;
IrregularShape(int strokeWidth){
this.strokeWidth = strokeWidth;
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D newGraphics = (Graphics2D) g.create();
Insets borderInsets = new Insets(0, 0, 0, 0);
Border border = getBorder();
if (border != null) {
borderInsets = border.getBorderInsets(this);
}
BasicStroke basicStroke = new BasicStroke(strokeWidth);
newGraphics.setStroke(basicStroke);
int x = getX() + borderInsets.left + strokeWidth;
int y = getY() + borderInsets.top + strokeWidth;
int width = getWidth() - x - borderInsets.right - strokeWidth;
int height = getHeight() - y - borderInsets.bottom - strokeWidth;
Double outterRactangleDouble = new Rectangle2D.Double(x, y, width, height);
Area outterRectangle = new Area(outterRactangleDouble);
Area innerRectangle = new Area(outterRactangleDouble);
AffineTransform affineTransform = new AffineTransform();
affineTransform.scale(0.5, 0.5);
affineTransform.translate(x + width * 0.10, y + height * 1.2);
innerRectangle.transform(affineTransform);
outterRectangle.subtract(innerRectangle);
newGraphics.draw(outterRectangle);
}
}
public class MainFrame {
public static void main(String[] args) {
JFrame frame = new JFrame("Irregular Shape");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
contentPane.add(new IrregularShape(3));
frame.setSize(640, 150);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Result
and it's also resizeable
you can use the polygon class (java.awt.Polygon)
int xs = new int[]{1,2,3...7}; //your x-coordinates
int ys = new int[]{1,2,3...7}; //your y-coordinates
Shape irr = new Polygon(xs, ys, xs.length);
if you want to use certain borders you can use Graphics2D
public void paintComponent(Graphics gr){
Graphics2D g2d = (Graphics2D)gr;
GradientPaint redToWhite = new GradientPaint(0,0,color.RED,100, 0,color.WHITE);
g2d.setPaint(redtowhite)
g2d.fill(irr); //fill special color
Stroke customBorder = getCustomBorder();
g2d.setStroke(customBorder);
g2d.draw(irr); //draw 'special' borders
}
have a look at stroke and fill
note that Polygon implements the contains(double x, double y)method which lets you detect if you're inside or not
You could use a Area for example...
public class TestPane extends JPanel {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Area area = new Area(new Rectangle(10, 10, getWidth() - 20, getHeight() - 20));
area.subtract(new Area(new Rectangle(20, getHeight() / 2, getWidth() / 2, getHeight() - 10)));
g2d.draw(area);
g2d.dispose();
}
}
You define a custom shape...
public class TestPane extends JPanel {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Path2D path = new Path2D.Float();
path.moveTo(10, 10);
path.lineTo(getWidth() - 20, 10);
path.lineTo(getWidth() - 20, getHeight() - 20);
path.lineTo(getWidth() / 2, getHeight() - 20);
path.lineTo(getWidth() / 2, getHeight() / 2);
path.lineTo(20, getHeight() / 2);
path.lineTo(20, getHeight() - 20);
path.lineTo(10, getHeight() - 20);
path.closePath();
g2d.draw(path);
g2d.dispose();
}
}
Actually writing a custom border would be very, very difficult, because of the irregular style of shape, where would the components actually be contained?
It might be possible to create two or more borders, which could then be laid out so that the appeared as one
See Working with Geometry for more details
Updated with Border example...
Getting a Border to actually work is far more difficult, as the expectation is that the internal area of the border will be rectangular.
Based on the complex shape you've provided, one solution would be to actually create two borders, a left and right borer, which take care of generating a "safe" area for components to be laid out within, for example:
public class LeftBorder implements Border {
private int offset;
public LeftBorder(int offset) {
this.offset = offset;
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Path2D path = new Path2D.Float();
int xOffset = x + offset;
int yOffset = y + offset;
width -= offset;
height -= offset * 2;
float gap = width * 0.1f;
path.moveTo(xOffset, yOffset);
path.lineTo(xOffset + width, yOffset);
path.moveTo(xOffset, yOffset);
path.lineTo(xOffset, yOffset + height);
path.lineTo(xOffset + gap, yOffset + height);
path.lineTo(xOffset + gap, yOffset + (height - (height / 2)));
path.lineTo(xOffset + width, yOffset + (height - (height / 2)));
((Graphics2D)g).draw(path);
}
#Override
public Insets getBorderInsets(Component c) {
int height = c.getHeight();
height -= (height / 2);
System.out.println(height);
return new Insets(offset + 4, offset + 4, height + 4, 0);
}
#Override
public boolean isBorderOpaque() {
return false;
}
}
public class RightBorder implements Border {
private int offset;
public RightBorder(int offset) {
this.offset = offset;
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Path2D path = new Path2D.Float();
int xOffset = x;
int yOffset = y + offset;
width -= offset;
height -= offset * 2;
path.moveTo(xOffset, yOffset);
path.lineTo(xOffset + width, yOffset);
path.lineTo(xOffset + width, yOffset + height);
path.lineTo(xOffset, yOffset + height);
path.lineTo(xOffset, yOffset + (height - (height / 2)));
((Graphics2D)g).draw(path);
}
#Override
public Insets getBorderInsets(Component c) {
return new Insets(offset + 4, 0, offset + 4, offset + 4);
}
#Override
public boolean isBorderOpaque() {
return false;
}
}
This would then require you to provide at least two panels of equal height, for example:
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
public class Main {
public static void main(String args[]) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
frame.add(new LeftPane());
frame.add(new RightPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RightPane extends JPanel {
public RightPane() {
setBorder(new RightBorder(10));
setLayout(new GridBagLayout());
add(new JLabel("Righty"));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class LeftPane extends JPanel {
public LeftPane() {
setBorder(new LeftBorder(10));
setLayout(new GridBagLayout());
add(new JLabel("Lefty"));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
This will also be relient on the layout manager been able to layout the two components next to each other
In addition to my first answer https://stackoverflow.com/a/34287251/974186
You can also implement it as a Border.
class IrregularBorder implements Border {
private int thickness;
public IrregularBorder(int thickness) {
this.thickness = thickness;
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width,
int height) {
Graphics2D graphics2d = (Graphics2D) g;
BasicStroke basicStroke = new BasicStroke(thickness);
graphics2d.setStroke(basicStroke);
int halfThickness = thickness / 2;
Double outterRactangleDouble = new Rectangle2D.Double(
x + halfThickness, y + halfThickness, width - thickness,
height - thickness);
Area outterRectangle = new Area(outterRactangleDouble);
Area innerRectangle = computeInnerRect(x, y, width, height,
outterRactangleDouble);
outterRectangle.subtract(innerRectangle);
graphics2d.draw(outterRectangle);
}
private Area computeInnerRect(int x, int y, int width, int height,
Double outterRactangleDouble) {
Area innerRectangle = new Area(outterRactangleDouble);
AffineTransform affineTransform = new AffineTransform();
affineTransform.scale(0.5, 0.5);
affineTransform.translate(x + width * 0.10, y + height * 1.2);
innerRectangle.transform(affineTransform);
return innerRectangle;
}
#Override
public Insets getBorderInsets(Component c) {
int left = (int) (thickness + (c.getWidth() * 0.6));
return new Insets(thickness, left, thickness, thickness);
}
#Override
public boolean isBorderOpaque() {
return true;
}
}
and use it as usual
public class MainFrame {
public static void main(String[] args) {
JFrame frame = new JFrame("Irregular Shape");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
contentPane.add(mainPanel);
JPanel irregularShapeBorderedPanel = new JPanel(new BorderLayout());
irregularShapeBorderedPanel.add(new JButton("Button"),
BorderLayout.CENTER);
irregularShapeBorderedPanel.setBorder(new IrregularBorder(2));
mainPanel.add(irregularShapeBorderedPanel);
frame.setSize(640, 150);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Related
The following screenshot shows a test of TextBubbleBorder1. I would like to make the corners of the component that are outside the rectangle to be entirely transparent & show whatever component is beneath it. I found a way to restrict the BG color of a label to 'inside the border' by setting a Clip (representing the area outside the rounded corners) on the Graphics2D instance and calling clearRect(). That can be seen in Label 1.
However you can see the downside of this approach when there is a red BG (or any non-standard color) on the parent panel. The corners default to the default panel color (easiest to see in Panel 2).
Ultimately I would like this to work for a non-standard color in the parent container, but it was partly inspired by What do I need to do to replicate this component with gradient paint?
Does anybody know a way to get those corners transparent?
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
public class BorderTest {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
JPanel gui = new JPanel(new GridLayout(1,0,5,5));
gui.setBorder(new EmptyBorder(10,10,10,10));
gui.setBackground(Color.RED);
AbstractBorder brdr = new TextBubbleBorder(Color.BLACK,2,16,0);
JLabel l1 = new JLabel("Label 1");
l1.setBorder(brdr);
gui.add(l1);
JLabel l2 = new JLabel("Label 2");
l2.setBorder(brdr);
l2.setBackground(Color.YELLOW);
l2.setOpaque(true);
gui.add(l2);
JPanel p1 = new JPanel();
p1.add(new JLabel("Panel 1"));
p1.setBorder(brdr);
p1.setOpaque(false);
gui.add(p1);
JPanel p2 = new JPanel();
p2.add(new JLabel("Panel 2"));
p2.setBorder(brdr);
gui.add(p2);
JOptionPane.showMessageDialog(null, gui);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
class TextBubbleBorder extends AbstractBorder {
private Color color;
private int thickness = 4;
private int radii = 8;
private int pointerSize = 7;
private Insets insets = null;
private BasicStroke stroke = null;
private int strokePad;
private int pointerPad = 4;
RenderingHints hints;
TextBubbleBorder(
Color color) {
new TextBubbleBorder(color, 4, 8, 7);
}
TextBubbleBorder(
Color color, int thickness, int radii, int pointerSize) {
this.thickness = thickness;
this.radii = radii;
this.pointerSize = pointerSize;
this.color = color;
stroke = new BasicStroke(thickness);
strokePad = thickness / 2;
hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int pad = radii + strokePad;
int bottomPad = pad + pointerSize + strokePad;
insets = new Insets(pad, pad, bottomPad, pad);
}
#Override
public Insets getBorderInsets(Component c) {
return insets;
}
#Override
public Insets getBorderInsets(Component c, Insets insets) {
return getBorderInsets(c);
}
#Override
public void paintBorder(
Component c,
Graphics g,
int x, int y,
int width, int height) {
Graphics2D g2 = (Graphics2D) g;
int bottomLineY = height - thickness - pointerSize;
RoundRectangle2D.Double bubble = new RoundRectangle2D.Double(
0 + strokePad,
0 + strokePad,
width - thickness,
bottomLineY,
radii,
radii);
Polygon pointer = new Polygon();
// left point
pointer.addPoint(
strokePad + radii + pointerPad,
bottomLineY);
// right point
pointer.addPoint(
strokePad + radii + pointerPad + pointerSize,
bottomLineY);
// bottom point
pointer.addPoint(
strokePad + radii + pointerPad + (pointerSize / 2),
height - strokePad);
Area area = new Area(bubble);
area.add(new Area(pointer));
g2.setRenderingHints(hints);
Area spareSpace = new Area(new Rectangle(0, 0, width, height));
spareSpace.subtract(area);
g2.setClip(spareSpace);
g2.clearRect(0, 0, width, height);
g2.setClip(null);
g2.setColor(color);
g2.setStroke(stroke);
g2.draw(area);
}
}
While the TextBubbleBorder was devised for Internal padding for JTextArea with background Image (& ended up using a JLabel since the text area was a mess for the reasons mentioned above), by specifying a pointerSize of 0 we end up with a 'rounded rectangle' instead.
N.B. There is a clipping bug in this code, which is fixed in the accepted answer to paintComponent() is drawing on other components. This should only be considered as a solution if the 'clipping bug fix' is incorporated.
// Paint the BG color of the parent, everywhere outside the clip
// of the text bubble.
See this point in the code for the source that shows correctly as:
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
public class BorderTest {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
JPanel gui = new JPanel(new GridLayout(2,0,5,5));
gui.setBorder(new EmptyBorder(10,10,10,10));
gui.setBackground(Color.RED);
AbstractBorder brdrLeft = new TextBubbleBorder(Color.BLACK,2,16,16);
AbstractBorder brdrRight = new TextBubbleBorder(Color.BLACK,2,16,16,false);
JLabel l1 = new JLabel("Label 1");
l1.setBorder(brdrRight);
gui.add(l1);
JLabel l2 = new JLabel("Label 2");
l2.setBorder(brdrLeft);
l2.setBackground(Color.YELLOW);
l2.setOpaque(true);
gui.add(l2);
JPanel p1 = new JPanel();
p1.add(new JLabel("Panel 1"));
p1.setBorder(brdrRight);
p1.setOpaque(false);
gui.add(p1);
JPanel p2 = new JPanel();
p2.add(new JLabel("Panel 2"));
p2.setBorder(brdrLeft);
gui.add(p2);
JOptionPane.showMessageDialog(null, gui);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
class TextBubbleBorder extends AbstractBorder {
private Color color;
private int thickness = 4;
private int radii = 8;
private int pointerSize = 7;
private Insets insets = null;
private BasicStroke stroke = null;
private int strokePad;
private int pointerPad = 4;
private boolean left = true;
RenderingHints hints;
TextBubbleBorder(
Color color) {
this(color, 4, 8, 7);
}
TextBubbleBorder(
Color color, int thickness, int radii, int pointerSize) {
this.thickness = thickness;
this.radii = radii;
this.pointerSize = pointerSize;
this.color = color;
stroke = new BasicStroke(thickness);
strokePad = thickness / 2;
hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int pad = radii + strokePad;
int bottomPad = pad + pointerSize + strokePad;
insets = new Insets(pad, pad, bottomPad, pad);
}
TextBubbleBorder(
Color color, int thickness, int radii, int pointerSize, boolean left) {
this(color, thickness, radii, pointerSize);
this.left = left;
}
#Override
public Insets getBorderInsets(Component c) {
return insets;
}
#Override
public Insets getBorderInsets(Component c, Insets insets) {
return getBorderInsets(c);
}
#Override
public void paintBorder(
Component c,
Graphics g,
int x, int y,
int width, int height) {
Graphics2D g2 = (Graphics2D) g;
int bottomLineY = height - thickness - pointerSize;
RoundRectangle2D.Double bubble = new RoundRectangle2D.Double(
0 + strokePad,
0 + strokePad,
width - thickness,
bottomLineY,
radii,
radii);
Polygon pointer = new Polygon();
if (left) {
// left point
pointer.addPoint(
strokePad + radii + pointerPad,
bottomLineY);
// right point
pointer.addPoint(
strokePad + radii + pointerPad + pointerSize,
bottomLineY);
// bottom point
pointer.addPoint(
strokePad + radii + pointerPad + (pointerSize / 2),
height - strokePad);
} else {
// left point
pointer.addPoint(
width - (strokePad + radii + pointerPad),
bottomLineY);
// right point
pointer.addPoint(
width - (strokePad + radii + pointerPad + pointerSize),
bottomLineY);
// bottom point
pointer.addPoint(
width - (strokePad + radii + pointerPad + (pointerSize / 2)),
height - strokePad);
}
Area area = new Area(bubble);
area.add(new Area(pointer));
g2.setRenderingHints(hints);
// Paint the BG color of the parent, everywhere outside the clip
// of the text bubble.
Component parent = c.getParent();
if (parent!=null) {
Color bg = parent.getBackground();
Rectangle rect = new Rectangle(0,0,width, height);
Area borderRegion = new Area(rect);
borderRegion.subtract(area);
g2.setClip(borderRegion);
g2.setColor(bg);
g2.fillRect(0, 0, width, height);
g2.setClip(null);
}
g2.setColor(color);
g2.setStroke(stroke);
g2.draw(area);
}
}
Try this:
JPanel p = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension arcs = new Dimension(15,15); //Border corners arcs {width,height}, change this to whatever you want
int width = getWidth();
int height = getHeight();
Graphics2D graphics = (Graphics2D) g;
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//Draws the rounded panel with borders.
graphics.setColor(getBackground());
graphics.fillRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height);//paint background
graphics.setColor(getForeground());
graphics.drawRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height);//paint border
}
};
With my test:
JFrame f = new JFrame();
f.setLayout(null);
f.setDefaultCloseOperation(3);
f.setSize(500, 500);
JPanel p = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension arcs = new Dimension(15,15);
int width = getWidth();
int height = getHeight();
Graphics2D graphics = (Graphics2D) g;
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//Draws the rounded opaque panel with borders.
graphics.setColor(getBackground());
graphics.fillRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height);//paint background
graphics.setColor(getForeground());
graphics.drawRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height);//paint border
}
};
p.setBounds(10,10,100,30);
p.setOpaque(false);
f.getContentPane().setBackground(Color.red);
f.add(p);
f.show();
the result is:
Thanks #BackSlash, nice and simple. I expanded upon this so it's more reusable. This also allows setting a background color in the constructor. And I show how you can make a circular panel for fun.
import java.awt.*;
import javax.swing.*;
public class RoundedPanelExample extends JFrame
{
public RoundedPanelExample()
{
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("Rounded Panel Example");
setResizable(true);
setDefaultLookAndFeelDecorated(true);
setSize(500, 500);
Container pane = getContentPane();
pane.setLayout(null);
pane.setBackground(Color.LIGHT_GRAY);
JPanel p1 = new RoundedPanel(10, Color.CYAN);
p1.setBounds(10,10,100,60);
p1.setOpaque(false);
pane.add(p1);
JPanel p2 = new RoundedPanel(15, Color.RED);
p2.setBounds(150,10,50,50);
p2.setOpaque(false);
pane.add(p2);
JPanel p3 = new RoundedPanel(30);
p3.setBounds(230,10,100,150);
p3.setOpaque(false);
pane.add(p3);
JPanel p4 = new RoundedPanel(20);
p4.setBounds(10,200,100,100);
p4.setBackground(Color.GREEN);
p4.setOpaque(false);
pane.add(p4);
JPanel p5 = new RoundedPanel(200);
p5.setBounds(150,200,200,200);
p5.setBackground(Color.BLUE);
p5.setOpaque(false);
pane.add(p5);
}
public static void main(String[] args)
{
RoundedPanelExample gui = new RoundedPanelExample();
gui.setVisible(true);
}
class RoundedPanel extends JPanel
{
private Color backgroundColor;
private int cornerRadius = 15;
public RoundedPanel(LayoutManager layout, int radius) {
super(layout);
cornerRadius = radius;
}
public RoundedPanel(LayoutManager layout, int radius, Color bgColor) {
super(layout);
cornerRadius = radius;
backgroundColor = bgColor;
}
public RoundedPanel(int radius) {
super();
cornerRadius = radius;
}
public RoundedPanel(int radius, Color bgColor) {
super();
cornerRadius = radius;
backgroundColor = bgColor;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension arcs = new Dimension(cornerRadius, cornerRadius);
int width = getWidth();
int height = getHeight();
Graphics2D graphics = (Graphics2D) g;
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//Draws the rounded panel with borders.
if (backgroundColor != null) {
graphics.setColor(backgroundColor);
} else {
graphics.setColor(getBackground());
}
graphics.fillRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height); //paint background
graphics.setColor(getForeground());
graphics.drawRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height); //paint border
}
}
}
Possible cheaper alternative
public class RoundedLabel extends JLabel {
private final Rectangle rv = new Rectangle();
#Override
public void updateUI() {
super.updateUI();
setBorder(new EmptyBorder(1, 3, 1, 3));
}
#Override
protected void paintComponent(Graphics g) {
getBounds(rv);
var g2 = (Graphics2D) g;
g2.setColor(getBackground());
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.fillRoundRect(rv.x, rv.y, rv.width, rv.height, 8, 8);
super.paintComponent(g);
}
}
sorry if this has already been asked at some point. I'm trying to implement a JSlider in Java that will grow and shrink an icon in a GUI. So far I have developed the GUI and implemented the slider etc., but the program returns "AWT-EventQueue-0" NullPointerExceptions when I try to move the slider (which call paints the object again. Below I've copied the code for the GUI and for the CarIcon class used therein. Thanks for your help! Sorry if I have made any obvious mistakes here, I am new to GUI coding.
Here's the Slider GUI:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.Timer;
public SliderGui(){
}
public void initGUI() {
JFrame frame = new JFrame("Slider");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setPreferredSize(new Dimension(F_WIDTH, F_HEIGHT));
frame.setLayout(new BorderLayout());
CarIcon initI = new CarIcon(DEFAULT_SIZE);
this.imagePan = new JLabel(initI);
frame.add(imagePan, BorderLayout.CENTER);
this.setSlider(frame);
}
public void setSlider(JFrame frame) {
JSlider sizeSlider = new JSlider(JSlider.VERTICAL, MIN_SIZE,
MAX_SIZE, DEFAULT_SIZE);
sizeSlider.setMajorTickSpacing(10);
sizeSlider.setMinorTickSpacing(5);
sizeSlider.setPaintTicks(true);
sizeSlider.setPaintLabels(true);
sizeSlider.addChangeListener((ChangeEvent e) -> {
JSlider source = (JSlider)e.getSource();
if (!source.getValueIsAdjusting()) {
int level = (int)source.getValue();
this.icon.setWidth(level);
this.icon.paintIcon(imagePan, this.g, x, y);
}
});
frame.add(sizeSlider, BorderLayout.WEST);
}
public static void main(String[] args) {
SliderGui test = new SliderGui();
test.initGUI();
}
public CarIcon icon;
public Graphics2D g;
private JLabel imagePan;
static final int x = 350;
static final int y = 350;
static final int F_WIDTH = 700;
static final int F_HEIGHT = 700;
static final int MAX_SIZE = 200;
static final int MIN_SIZE = 5;
static final int DEFAULT_SIZE = 75;
}
And here's the CarIcon class:
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class CarIcon implements Icon
{
public CarIcon(int aWidth)
{
width = aWidth;
}
public void setWidth(int aWidth) {
this.width = aWidth;
}
public int getIconWidth()
{
return width;
}
public int getIconHeight()
{
return width / 2;
}
public void paintIcon(Component c, Graphics g, int x, int y)
{
Graphics2D g2 = (Graphics2D) g;
Rectangle2D.Double body
= new Rectangle2D.Double(x, y + width / 6,
width - 1, width / 6);
Ellipse2D.Double frontTire
= new Ellipse2D.Double(x + width / 6, y + width / 3,
width / 6, width / 6);
Ellipse2D.Double rearTire
= new Ellipse2D.Double(x + width * 2 / 3, y + width / 3,
width / 6, width / 6);
// The bottom of the front windshield
Point2D.Double r1
= new Point2D.Double(x + width / 6, y + width / 6);
// The front of the roof
Point2D.Double r2
= new Point2D.Double(x + width / 3, y);
// The rear of the roof
Point2D.Double r3
= new Point2D.Double(x + width * 2 / 3, y);
// The bottom of the rear windshield
Point2D.Double r4
= new Point2D.Double(x + width * 5 / 6, y + width / 6);
Line2D.Double frontWindshield
= new Line2D.Double(r1, r2);
Line2D.Double roofTop
= new Line2D.Double(r2, r3);
Line2D.Double rearWindshield
= new Line2D.Double(r3, r4);
g2.fill(frontTire);
g2.fill(rearTire);
g2.setColor(Color.red);
g2.fill(body);
g2.draw(frontWindshield);
g2.draw(roofTop);
g2.draw(rearWindshield);
}
private int width;
}
Problems:
Don't give your class Graphics or Graphics2D fields, as doing this is almost guaranteed to give your gui faulty graphics due to an unstable Graphics object or have it throw a NullPointerException for trying to use a null Graphics object.
You shouldn't be trying to draw the Icon directly by calling paintIcon(...). Let Java itself do this. Instead simply call repaint() on the JLabel that holds the icon after changing your icon's width -- that's it!
This is how I tested it:
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
#SuppressWarnings("serial")
public class ResizeIcon extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final int MAX_ICON_WIDTH = 400;
private int iconWidth = MAX_ICON_WIDTH / 2;
private CarIcon carIcon = new CarIcon(iconWidth);
private JLabel carLabel = new JLabel(carIcon);
private JSlider slider = new JSlider(0, MAX_ICON_WIDTH, iconWidth);
public ResizeIcon() {
slider.setMajorTickSpacing(50);
slider.setMinorTickSpacing(10);
slider.setPaintLabels(true);
slider.setPaintTicks(true);
slider.setPaintTrack(true);
slider.setSnapToTicks(true);
slider.addChangeListener(new SliderListener());
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
setLayout(new BorderLayout());
add(slider, BorderLayout.PAGE_START);
add(carLabel, BorderLayout.CENTER);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class SliderListener implements ChangeListener {
#Override
public void stateChanged(ChangeEvent e) {
int value = slider.getValue();
carIcon.setWidth(value);
carLabel.repaint();
}
}
private static void createAndShowGui() {
ResizeIcon mainPanel = new ResizeIcon();
JFrame frame = new JFrame("Resize Icon");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class CarIcon implements Icon {
public CarIcon(int aWidth) {
width = aWidth;
}
public void setWidth(int aWidth) {
this.width = aWidth;
}
public int getIconWidth() {
return width;
}
public int getIconHeight() {
return width / 2;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics2D g2 = (Graphics2D) g;
Rectangle2D.Double body = new Rectangle2D.Double(x, y + width / 6, width - 1, width / 6);
Ellipse2D.Double frontTire = new Ellipse2D.Double(x + width / 6, y + width / 3, width / 6,
width / 6);
Ellipse2D.Double rearTire = new Ellipse2D.Double(x + width * 2 / 3, y + width / 3,
width / 6, width / 6);
// The bottom of the front windshield
Point2D.Double r1 = new Point2D.Double(x + width / 6, y + width / 6);
// The front of the roof
Point2D.Double r2 = new Point2D.Double(x + width / 3, y);
// The rear of the roof
Point2D.Double r3 = new Point2D.Double(x + width * 2 / 3, y);
// The bottom of the rear windshield
Point2D.Double r4 = new Point2D.Double(x + width * 5 / 6, y + width / 6);
Line2D.Double frontWindshield = new Line2D.Double(r1, r2);
Line2D.Double roofTop = new Line2D.Double(r2, r3);
Line2D.Double rearWindshield = new Line2D.Double(r3, r4);
g2.fill(frontTire);
g2.fill(rearTire);
g2.setColor(Color.red);
g2.fill(body);
g2.draw(frontWindshield);
g2.draw(roofTop);
g2.draw(rearWindshield);
}
private int width;
}
I have a simple animation code that creates a meter type rectangle effect. I would like to create it so that it fills a panel completely. I am so close, but it extends past the frame on the bottom. What am I missing?
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Pong");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(1,2));
frame.setSize(500, 500);
//MouseTest test = new MouseTest();
Test test2 = new Test(frame.getWidth(), frame.getHeight(), frame.getHeight(), 50);
//frame.add(test);
frame.add(test2);
frame.setVisible(true);
}
}
public class Test extends JPanel implements ActionListener, MouseListener{
int y = 0, width, height, dy=0, maxHeight;
int BOTTOM;
Timer timer;
public Test(int width, int height, int BOTTOM, int SPEED){
setBackground(Color.BLUE);
this.width = width;
maxHeight = height;
this.BOTTOM = BOTTOM;
addMouseListener(this);
timer = new Timer(1000/SPEED, this);
timer.start();
}
public void move(){
if(y>=maxHeight){
dy = -1;
}
if(y<=0)
{
dy = 1;
}
y+=dy;
height+=dy;
//System.out.println(y);
repaint();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
if(y<=maxHeight/2) {
g.setColor(Color.green);
g.fillRect(0, BOTTOM - y, width, height);
}
if(y>=maxHeight/2 && y<(maxHeight/10)*9){
g.setColor(Color.green);
g.fillRect(0, BOTTOM-(maxHeight/2), width, maxHeight/2);
g.setColor(Color.green);
g.fillRect(0, BOTTOM - y, width, height-(maxHeight/2));
}
if(y>=(maxHeight/10)*9){
g.setColor(Color.green);
g.fillRect(0, BOTTOM-(maxHeight/2), width, maxHeight/2);
g.setColor(Color.green);
g.fillRect(0, BOTTOM-(9*(maxHeight/10)), width, (4*maxHeight)/10);
g.setColor(Color.green);
g.fillRect(0, BOTTOM - y, width, height-(9*(maxHeight)/10));
}
for(int i = 1; i<6; i++)
g.fillRect(0, BOTTOM - (i*(maxHeight/5)), width, 10);
System.out.println(y);
}
Your panel takes up less room than your JFrame, because your frame has a title bar and window borders. You can just query in your paint method for the correct width and height used by the 'JPanel' with your JPanel's getWidth() and getHeight() (in other words, your JFrame's width and height is not the same as your JPanel's width and height, even though the JPanel takes up all the space within the frame visually between the borders).
Alternatively, if you did have logic where this was not possible, you could query the size of the insets of the frame, and subtract the left and right inset values from the width, and pass that as the width, and do the same for the height with the top and bottom inset values.
Override getPreferredSize of your JPanel, this will helps the layout manager determine the best way to layout your component
Use JFrame#pack to wrap the frame borders around the view
Use getWidth and getHeight to get the actual size the panel. You should do this whenever you need to know these values, don't store them for long periods, as the values can change
You should also have a read through Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
This is basic example based on you code...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test1 {
public static void main(String[] args) {
new Test1();
}
public Test1() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int yPos;
private int dy = 1;
private int bottom = 50;
public TestPane() {
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
move();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
public void move() {
if (yPos >= getHeight()) {
dy = -1;
}
if (yPos <= 0) {
dy = 1;
}
yPos += dy;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int maxHeight = getHeight();
int width = getWidth();
int height = maxHeight;
if (yPos <= maxHeight / 2) {
g.setColor(Color.green);
g.fillRect(0, bottom - yPos, width, height);
}
if (yPos >= maxHeight / 2 && yPos < (maxHeight / 10) * 9) {
g.setColor(Color.green);
g.fillRect(0, bottom - (maxHeight / 2), width, maxHeight / 2);
g.setColor(Color.green);
g.fillRect(0, bottom - yPos, width, height - (maxHeight / 2));
}
if (yPos >= (maxHeight / 10) * 9) {
g.setColor(Color.green);
g.fillRect(0, bottom - (maxHeight / 2), width, maxHeight / 2);
g.setColor(Color.green);
g.fillRect(0, bottom - (9 * (maxHeight / 10)), width, (4 * maxHeight) / 10);
g.setColor(Color.green);
g.fillRect(0, bottom - yPos, width, height - (9 * (maxHeight) / 10));
}
for (int i = 1; i < 6; i++) {
g.fillRect(0, bottom - (i * (maxHeight / 5)), width, 10);
}
}
}
}
Another way to go is to use pack(). It sets the size of the frame to be the size of the frame's contents. In your code it would look like this:
frame.add(test2);
frame.pack();
If you do it this way, you don't need the frame.setSize() call. You can add the panel either with .add() as you've done or with:
frame.setContentPane(test2);
So I combined both your comments and got it to work correctly. Here is the code that works.
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Pong");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(1,2));
Insets inset = frame.getInsets();
Test test2 = new Test(200-(inset.right + inset.left), 200 -(inset.top + inset.bottom), 200, 50);
frame.setContentPane(test2);
frame.pack();
frame.setVisible(true);
}
}
public class Test extends JPanel implements ActionListener, MouseListener{
int y = 0, width, height, dy=0, maxHeight;
int BOTTOM;
Timer timer;
Dimension size;
Insets inset;
public Test(int width, int height, int BOTTOM, int SPEED){
size = new Dimension(width, height);
setBackground(Color.BLUE);
setPreferredSize(size);
inset = this.getInsets();
this.width = width-(inset.right+inset.left);
maxHeight = height;
this.BOTTOM = BOTTOM - (inset.bottom+inset.top);
addMouseListener(this);
timer = new Timer(1000/SPEED, this);
timer.start();
}
public void move(){
if(y>=maxHeight){
dy = -1;
}
if(y<=0)
{
dy = 1;
}
y+=dy;
height+=dy;
//System.out.println(y);
repaint();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
if(y<=maxHeight/2) {
g.setColor(Color.green);
g.fillRect(0, BOTTOM - y, width, height);
}
if(y>=maxHeight/2 && y<(maxHeight/10)*9){
g.setColor(Color.green);
g.fillRect(0, BOTTOM - (maxHeight / 2), width, maxHeight / 2);
g.setColor(Color.green);
g.fillRect(0, BOTTOM - y, width, height-(maxHeight/2));
}
if(y>=(maxHeight/10)*9){
g.setColor(Color.green);
g.fillRect(0, BOTTOM-(maxHeight/2), width, maxHeight/2);
g.setColor(Color.green);
g.fillRect(0, BOTTOM-(9*(maxHeight/10)), width, (4*maxHeight)/10);
g.setColor(Color.green);
g.fillRect(0, BOTTOM - y, width, height-(9*(maxHeight)/10));
}
g.setColor(Color.white);
for(int i = 1; i<6; i++)
g.fillRect(0, BOTTOM - (i*(maxHeight/5)), width, 5);
}
Ok here is the thing I am making a function grapher and I want when you move your mouse over a line it will show the coordinates. So I set up just a basic drawstring on the panel to show the mouse coords but I can not get to repaint well normally. It seems to be painting just fine but it is not clearing the contents before painting here is the code I have thus far.
public class Grapher extends JPanel implements MouseMotionListener{
private final int BORDER_GAP = 15;
private final int MAX_SCORE = 20;
private final int PREF_WIDTH = 800;
private final int PREF_HEIGHT = 650;
private final int GRAPH_POINT_WIDTH = 12;
private static final int GRAPH_INTERVAL = 15;
private static Point mse;
private List<Point> values;
public Grapher(List<Point> values) {
setMse(new Point(0,0));
this.values = values;
addMouseMotionListener(this);
}
public void paintComponent(Graphics g){
super.paintComponents(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
float xScale = ((float) getWidth() - 2 * BORDER_GAP) / (values.size()-1);
float yScale = ((float) getHeight() - 2 * BORDER_GAP) / (MAX_SCORE-1);
g2d.drawString(xScale+"", 50, 50);
g2d.drawString(yScale+"", 50, 70);
g2d.drawString(getWidth()/2+"", 50, 90);
g2d.drawString(mse.x +" " + mse.y, 50, 30);
//create axis
g2d.setColor(new Color(0x7e7e7e));
//x line
g2d.drawLine(BORDER_GAP, (getHeight() - BORDER_GAP)/2, getWidth() - BORDER_GAP, (getHeight() - BORDER_GAP)/2);
//y line
g2d.drawLine(getWidth()/2, getHeight() - BORDER_GAP, getWidth()/2, BORDER_GAP);
}
public Dimension getPreferredSize(){
return new Dimension(PREF_WIDTH, PREF_HEIGHT);
}
private static void createAndShowGui(){
List<Point> values = new ArrayList<Point>();
int maxDataPoints = 20;
for (int i = -GRAPH_INTERVAL; i <= GRAPH_INTERVAL; i++) {
String val = "";
try {
val = EquationSolver.solveEquation(variableReplace('x', i));
} catch (Exception e) {
val ="0";
e.printStackTrace();
}
System.out.println(i + "= " + val);
values.add(new Point(i, Integer.parseInt(val)));
}
Grapher panel = new Grapher(values);
JFrame frame = new JFrame("Grapher");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();
//frame.setLocationByPlatform(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run(){
createAndShowGui();
}
});
}
#Override
public void mouseMoved(MouseEvent e) {
setMse(new Point(e.getX(), e.getY()));
removeAll();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
setMse(new Point(e.getX(), e.getY()));
//repaint();
}
Ok here is some images as to what the code is doing
the top numbers are the mouse position.
A fresh start to program: http://snag.gy/BFrUj.jpg.
After i move the mouse around a little: http://snag.gy/lNqie.jpg
Thanks for any help.
I would suggest the major problem you are having is related to g.dispose()
The graphics context is shared, meaning that if you dispose of the graphics context, nothing else can paint to it. Unless you create the context, you should never dispose it
Your other problem is, you are not actually calling super.paintComponent, which is responsible for preparing the graphics context for painting (by clearing the are to be painted), but instead, you are calling super.paintComponents(g) ... not the s at the end...
For some reason when I draw straight onto the panel it does that overlaying effect but if i add a white rectangle as a background that solves the problem.
...
float xScale = ((float) getWidth() - 2 * BORDER_GAP) / (values.size()-1);
float yScale = ((float) getHeight() - 2 * BORDER_GAP) / (MAX_SCORE-1);
g2d.setColor(Color.white);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.setColor(new Color(0x7e7e7e));
g2d.drawString(xScale+"", 50, 50);
g2d.drawString(yScale+"", 50, 70);
g2d.drawString(getWidth()/2+"", 50, 90);
g2d.drawString(mse.x +" " + mse.y, 50, 30);
...
That appears to be a fix for the problem. If anyone can explain why drawing straight to the panel overlays or has a better solution please do answer.
I have a JPanel element and I would like added a drop shadow to it, how can I add a nice faded drop shadow to the element? Do I need to use external libraries or is there something that is built in that I can use?
Example:
So I looked into swingx which extends JPanel and was able to achieve the results I was looking for with the following code:
public class Canvas extends JXPanel{
public Canvas(){
DropShadowBorder shadow = new DropShadowBorder();
shadow.setShadowColor(Color.BLACK);
shadow.setShowLeftShadow(true);
shadow.setShowRightShadow(true);
shadow.setShowBottomShadow(true);
shadow.setShowTopShadow(true);
this.setBorder(shadow);
}
}
And the result:
This is a complete HACK
This will require you to have a copy of JH-Labs Filters for the blur implementation
This is an expensive operation as it uses a blur operation, the reason I use it is that will take into account the the shape of the component it is shadowing.
The main problem you have is that borders aren't them selves, transparent, there's no way to really have an opaque component and a transparent border. Hench the hack
public class TestDropShadowBorder {
public static void main(String[] args) {
new TestDropShadowBorder();
}
public TestDropShadowBorder() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setBackground(Color.RED);
setBorder(new EmptyBorder(20, 20, 20, 20));
setLayout(new BorderLayout());
JPanel drop = new JPanel();
drop.setOpaque(false);
DropShadowBorder border = new DropShadowBorder();
border.setFillContentArea(true);
drop.setBorder(new CompoundBorder(border, new LineBorder(Color.BLACK)));
add(drop);
}
}
public static class DropShadowBorder implements Border {
protected static final int SHADOW_SIZE = 4;
protected static final Map<Component, DropShadowBorder.CachedBorder> BORDER_CACHE = new WeakHashMap<Component, CachedBorder>(5);
private boolean fillContentArea;
private int shadowSize;
private float shadowOpacity;
private Color shadowColor;
public DropShadowBorder() {
this(SHADOW_SIZE, Color.BLACK, 0.5f, true);
}
public DropShadowBorder(boolean paintContentArea) {
this(SHADOW_SIZE, Color.BLACK, 0.5f, paintContentArea);
}
public DropShadowBorder(int shadowSize) {
this(shadowSize, Color.BLACK, 0.5f, true);
}
public DropShadowBorder(Color shadowColor) {
this(SHADOW_SIZE, shadowColor, 0.5f, true);
}
public DropShadowBorder(int shadowSize, Color showColor) {
this(shadowSize, showColor, 0.5f, true);
}
public DropShadowBorder(int shadowSize, float opacity) {
this(shadowSize, Color.BLACK, opacity, true);
}
public DropShadowBorder(Color shadowColor, float opacity) {
this(SHADOW_SIZE, shadowColor, opacity, true);
}
public DropShadowBorder(int shadowSize, Color shadowColor, float opacity) {
this(shadowSize, shadowColor, opacity, true);
}
public DropShadowBorder(int shadowSize, boolean paintContentArea) {
this(shadowSize, Color.BLACK, 0.5f, paintContentArea);
}
public DropShadowBorder(Color shadowColor, boolean paintContentArea) {
this(SHADOW_SIZE, shadowColor, 0.5f, paintContentArea);
}
public DropShadowBorder(int shadowSize, Color showColor, boolean paintContentArea) {
this(shadowSize, showColor, 0.5f, paintContentArea);
}
public DropShadowBorder(int shadowSize, float opacity, boolean paintContentArea) {
this(shadowSize, Color.BLACK, opacity, paintContentArea);
}
public DropShadowBorder(Color shadowColor, float opacity, boolean paintContentArea) {
this(SHADOW_SIZE, shadowColor, opacity, paintContentArea);
}
public DropShadowBorder(int shadowSize, Color showColor, float opacity, boolean paintContentArea) {
setShadowSize(shadowSize);
setShadowColor(showColor);
setShadowOpacity(opacity);
setFillContentArea(paintContentArea);
}
public void setShadowColor(Color shadowColor) {
this.shadowColor = shadowColor;
}
public void setShadowOpacity(float shadowOpacity) {
this.shadowOpacity = shadowOpacity;
}
public Color getShadowColor() {
return shadowColor;
}
public float getShadowOpacity() {
return shadowOpacity;
}
public void setShadowSize(int size) {
shadowSize = size;
}
public int getShadowSize() {
return shadowSize;
}
public static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
}
public static BufferedImage createCompatibleImage(int width, int height) {
return createCompatibleImage(width, height, Transparency.TRANSLUCENT);
}
public static BufferedImage createCompatibleImage(int width, int height, int transparency) {
BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency);
image.coerceData(true);
return image;
}
public static BufferedImage generateShadow(BufferedImage imgSource, int size, Color color, float alpha) {
int imgWidth = imgSource.getWidth() + (size * 2);
int imgHeight = imgSource.getHeight() + (size * 2);
BufferedImage imgMask = createCompatibleImage(imgWidth, imgHeight);
Graphics2D g2 = imgMask.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
int x = Math.round((imgWidth - imgSource.getWidth()) / 2f);
int y = Math.round((imgHeight - imgSource.getHeight()) / 2f);
g2.drawImage(imgSource, x, y, null);
g2.dispose();
// ---- Blur here ---
BufferedImage imgGlow = generateBlur(imgMask, size, color, alpha);
//
// BufferedImage imgGlow = ImageUtilities.createCompatibleImage(imgWidth, imgHeight);
// g2 = imgGlow.createGraphics();
//
// g2.drawImage(imgMask, 0, 0, null);
// g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha));
// g2.setColor(color);
//
// g2.fillRect(x, y, imgSource.getWidth(), imgSource.getHeight());
// g2.dispose();
//
// imgGlow = filter.filter(imgGlow, null);
// ---- Blur here ----
// imgGlow = ImageUtilities.applyMask(imgGlow, imgMask, AlphaComposite.DST_OUT);
return imgGlow;
}
public static BufferedImage generateBlur(BufferedImage imgSource, int size, Color color, float alpha) {
GaussianFilter filter = new GaussianFilter(size);
int imgWidth = imgSource.getWidth();
int imgHeight = imgSource.getHeight();
BufferedImage imgBlur = createCompatibleImage(imgWidth, imgHeight);
Graphics2D g2d = imgBlur.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.drawImage(imgSource, 0, 0, null);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha));
g2d.setColor(color);
g2d.fillRect(0, 0, imgSource.getWidth(), imgSource.getHeight());
g2d.dispose();
imgBlur = filter.filter(imgBlur, null);
return imgBlur;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
/*
* Because of the amount of time it can take to render the drop shadow,
* we cache the results in a static cache, based on the component
* and the components size.
*
* This allows the shadows to repainted quickly so long as the component
* hasn't changed in size.
*/
BufferedImage dropShadow = null;
DropShadowBorder.CachedBorder cached = BORDER_CACHE.get(c);
if (cached != null) {
dropShadow = cached.getImage(c);
}
if (dropShadow == null) {
int shadowSize = getShadowSize();
float opacity = getShadowOpacity();
Color color = getShadowColor();
// Create a blank canvas, from which the actually border can be generated
// from...
// The ahadow routine can actually generate a non-rectangular border, but
// because we don't have a suitable template to run from, we need to
// set this up our selves...
// It would be nice to be able to user the component itself, but this will
// have to
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.fillRect(0, 0, width - (shadowSize * 2), height - (shadowSize * 2));
g2d.dispose();
// Generate the shadow
BufferedImage shadow = generateShadow(img, shadowSize, getShadowColor(), getShadowOpacity());
// We need to produce a clipping result, cause the border is painted ontop
// of the base component...
BufferedImage clipedShadow = createCompatibleImage(width, height, Transparency.TRANSLUCENT);
g2d = clipedShadow.createGraphics();
Shape clip = g2d.getClip();
// First we create a "area" filling the avaliable space...
Area area = new Area(new Rectangle(width, height));
// Then we subtract the space left over for the component
area.subtract(new Area(new Rectangle(width - (shadowSize * 2), height - (shadowSize * 2))));
// And then apply the clip
g2d.setClip(area);
// Then draw the shadow image
// g2d.drawImage(shadow, -(shadowSize / 2), -(shadowSize / 2), c);
g2d.drawImage(shadow, 0, 0, c);
g2d.setClip(clip);
if (!c.isOpaque() && isFillContentArea()) {
area = new Area(new Rectangle(width - (shadowSize * 2), height - (shadowSize * 2)));
g2d.setColor(c.getBackground());
g2d.fill(area);
}
// g2d.setColor(Color.RED);
// g2d.drawRect(x, y, width - 1, height - 1);
//
// g2d.setColor(Color.GREEN);
// g2d.drawRect(x, y, width - (shadowSize * 2), height - (shadowSize * 2));
g2d.dispose();
dropShadow = clipedShadow;
BORDER_CACHE.put(c, new CachedBorder(dropShadow, c.getSize()));
}
g.drawImage(dropShadow, x, y, c);
// if (!c.isOpaque() && isFillContentArea()) {
//
// Graphics2D g2d = (Graphics2D) g;
//
// Area area = new Area(new Rectangle(width - (shadowSize * 2), height - (shadowSize * 2)));
// g2d.setColor(c.getBackground());
// g2d.fill(area);
//
// }
// g.setColor(Color.MAGENTA);
// g.drawRect(x + 1, y + 1, width - (shadowSize * 2) - 1, height - (shadowSize * 2) - 1);
}
public Insets getBorderInsets(Component cmpnt) {
return new Insets(0, 0, getShadowSize() * 2, getShadowSize() * 2);
}
public boolean isBorderOpaque() {
return false;
}
/**
* Returns if the content area should be painted by this border when the
* parent component is opaque...
*
* The problem is, the paintComponent method will paint the WHOLE component
* background, including the border area. This is a reasonable assumption to
* make, but it makes the shadow border really show up when the parent
* component is a different color.
*
* This allows the border to take control of that fact.
*
* When using it, you will need to try and make this the first border to get
* painted though :P
*
* #return
*/
public boolean isFillContentArea() {
return fillContentArea;
}
public void setFillContentArea(boolean fill) {
fillContentArea = fill;
}
protected class CachedBorder {
private BufferedImage image;
private Dimension size;
public CachedBorder(BufferedImage border, Dimension size) {
this.image = border;
this.size = size;
}
public BufferedImage getImage(Component comp) {
BufferedImage dropShadow = null;
if (comp.getSize().equals(size)) {
dropShadow = image;
}
return dropShadow;
}
}
}
}
UPDATED with additional Example
The drop shadow border has limitations, it can't take into consideration the shape of the component, as the time the border is painted, the component hasn't begin, so we have no reference point.
In order to be able to generate a drop shadow which takes into consideration the shape of the component, we need to create a custom component and inject our border directly into the paint process.
public class TestDropShadowBorder {
public static void main(String[] args) {
new TestDropShadowBorder();
}
public TestDropShadowBorder() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setBackground(Color.RED);
setBorder(new EmptyBorder(20, 20, 20, 20));
setLayout(new BorderLayout());
add(new RoundedPane());
}
}
public class RoundedPane extends JPanel {
private int shadowSize = 5;
public RoundedPane() {
// This is very important, as part of the panel is going to be transparent
setOpaque(false);
}
#Override
public Insets getInsets() {
return new Insets(0, 0, 10, 10);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
int width = getWidth() - 1;
int height = getHeight() - 1;
Graphics2D g2d = (Graphics2D) g.create();
applyQualityProperties(g2d);
Insets insets = getInsets();
Rectangle bounds = getBounds();
bounds.x = insets.left;
bounds.y = insets.top;
bounds.width = width - (insets.left + insets.right);
bounds.height = height - (insets.top + insets.bottom);
RoundRectangle2D shape = new RoundRectangle2D.Float(bounds.x, bounds.y, bounds.width, bounds.height, 20, 20);
/**
* * THIS SHOULD BE CAHCED AND ONLY UPDATED WHEN THE SIZE OF THE
* COMPONENT CHANGES **
*/
BufferedImage img = createCompatibleImage(bounds.width, bounds.height);
Graphics2D tg2d = img.createGraphics();
applyQualityProperties(g2d);
tg2d.setColor(Color.BLACK);
tg2d.translate(-bounds.x, -bounds.y);
tg2d.fill(shape);
tg2d.dispose();
BufferedImage shadow = generateShadow(img, shadowSize, Color.BLACK, 0.5f);
g2d.drawImage(shadow, shadowSize, shadowSize, this);
g2d.setColor(getBackground());
g2d.fill(shape);
/**
* THIS ONE OF THE ONLY OCCASIONS THAT I WOULDN'T CALL
* super.paintComponent *
*/
getUI().paint(g2d, this);
g2d.setColor(Color.GRAY);
g2d.draw(shape);
g2d.dispose();
}
}
public static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
}
public static BufferedImage createCompatibleImage(int width, int height) {
return createCompatibleImage(width, height, Transparency.TRANSLUCENT);
}
public static void applyQualityProperties(Graphics2D g2) {
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
}
public static BufferedImage createCompatibleImage(int width, int height, int transparency) {
BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency);
image.coerceData(true);
return image;
}
public static BufferedImage generateShadow(BufferedImage imgSource, int size, Color color, float alpha) {
int imgWidth = imgSource.getWidth() + (size * 2);
int imgHeight = imgSource.getHeight() + (size * 2);
BufferedImage imgMask = createCompatibleImage(imgWidth, imgHeight);
Graphics2D g2 = imgMask.createGraphics();
applyQualityProperties(g2);
int x = Math.round((imgWidth - imgSource.getWidth()) / 2f);
int y = Math.round((imgHeight - imgSource.getHeight()) / 2f);
// g2.drawImage(imgSource, x, y, null);
g2.drawImage(imgSource, 0, 0, null);
g2.dispose();
// ---- Blur here ---
BufferedImage imgShadow = generateBlur(imgMask, size, color, alpha);
return imgShadow;
}
public static BufferedImage generateBlur(BufferedImage imgSource, int size, Color color, float alpha) {
GaussianFilter filter = new GaussianFilter(size);
int imgWidth = imgSource.getWidth();
int imgHeight = imgSource.getHeight();
BufferedImage imgBlur = createCompatibleImage(imgWidth, imgHeight);
Graphics2D g2d = imgBlur.createGraphics();
applyQualityProperties(g2d);
g2d.drawImage(imgSource, 0, 0, null);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha));
g2d.setColor(color);
g2d.fillRect(0, 0, imgSource.getWidth(), imgSource.getHeight());
g2d.dispose();
imgBlur = filter.filter(imgBlur, null);
return imgBlur;
}
}
Simple drop shadow you can work off. You can see implemented on these JPanels.
public class DropShadowPanel extends JPanel {
private static final long serialVersionUID = 1L;
public int pixels;
public DropShadowPanel(int pix) {
this.pixels = pix;
Border border = BorderFactory.createEmptyBorder(pixels, pixels, pixels, pixels);
this.setBorder(BorderFactory.createCompoundBorder(this.getBorder(), border));
this.setLayout(new BorderLayout());
}
#Override
protected void paintComponent(Graphics g) {
int shade = 0;
int topOpacity = 80;
for (int i = 0; i < pixels; i++) {
g.setColor(new Color(shade, shade, shade, ((topOpacity / pixels) * i)));
g.drawRect(i, i, this.getWidth() - ((i * 2) + 1), this.getHeight() - ((i * 2) + 1));
}
}
}
Do you mean something like this:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ShadowTest {
private JFrame frame;
public ShadowTest() {
initComponents();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ShadowTest();
}
});
}
private void initComponents() {
frame = new JFrame();
frame.setTitle("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//app exited when frame closes
frame.setResizable(false);
frame.setLayout(new GridBagLayout());
GridBagConstraints gc = new GridBagConstraints();
gc.fill = GridBagConstraints.HORIZONTAL;
gc.insets = new Insets(10, 10, 10, 10);
frame.add(new RoundedPanel(), gc);
//pack frame (size components to preferred size)
frame.pack();
frame.setVisible(true);//make frame visible
}
}
class RoundedPanel extends JPanel {
/**
* Stroke size. it is recommended to set it to 1 for better view
*/
protected int strokeSize = 1;
/**
* Color of shadow
*/
protected Color shadowColor = Color.black;
/**
* Sets if it drops shadow
*/
protected boolean shady = true;
/**
* Sets if it has an High Quality view
*/
protected boolean highQuality = true;
/**
* Double values for Horizontal and Vertical radius of corner arcs
*/
protected Dimension arcs = new Dimension(0, 0);
//protected Dimension arcs = new Dimension(20, 20);//creates curved borders and panel
/**
* Distance between shadow border and opaque panel border
*/
protected int shadowGap = 10;
/**
* The offset of shadow.
*/
protected int shadowOffset = 4;
/**
* The transparency value of shadow. ( 0 - 255)
*/
protected int shadowAlpha = 150;
int width = 300, height = 300;
public RoundedPanel() {
super();
setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Color shadowColorA = new Color(shadowColor.getRed(),
shadowColor.getGreen(), shadowColor.getBlue(), shadowAlpha);
Graphics2D graphics = (Graphics2D) g;
//Sets antialiasing if HQ.
if (highQuality) {
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
//Draws shadow borders if any.
if (shady) {
graphics.setColor(shadowColorA);
graphics.fillRoundRect(
shadowOffset,// X position
shadowOffset,// Y position
width - strokeSize - shadowOffset, // width
height - strokeSize - shadowOffset, // height
arcs.width, arcs.height);// arc Dimension
} else {
shadowGap = 1;
}
//Draws the rounded opaque panel with borders.
graphics.setColor(getBackground());
graphics.fillRoundRect(0, 0, width - shadowGap,
height - shadowGap, arcs.width, arcs.height);
graphics.setColor(getForeground());
graphics.setStroke(new BasicStroke(strokeSize));
graphics.drawRoundRect(0, 0, width - shadowGap,
height - shadowGap, arcs.width, arcs.height);
//Sets strokes to default, is better.
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
}
Reference:
Rounded Border JPanel