Maybe my case it's a simple confusion of ideas. How do draw a button like this using Shape?
I don't mind the rounded corners, heres my aproach for a round corner button.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.RoundRectangle2D;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.JComponent;
import javax.swing.plaf.basic.BasicButtonUI;
public class PlayButtonUI extends BasicButtonUI{
protected Shape shape;
#Override
protected void installDefaults(AbstractButton b) {
super.installDefaults(b);
b.setOpaque(false);//removes that annoying default background
}
#Override public void paint(Graphics g, JComponent c) {
Graphics2D g2 = (Graphics2D)g;
AbstractButton b = (AbstractButton) c;
ButtonModel model = b.getModel();
drawButtonShape(b);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);//smoth borders
if(model.isArmed()) {
g2.setColor(Color.RED);//color when button is pressed
}else{
g2.setColor(Color.GREEN);//default button color
}
g2.fill(shape);//aplying color
super.paint(g2, c);
}
private void drawButtonShape(JComponent c) {
//button shape is drawn here, 16 are the border radius
shape = new RoundRectangle2D.Float(0, 0, c.getWidth()-1, c.getHeight()-1,16, 16);
}
}
I don't really know how to draw anything at all, this class was a result from a chaotic example that i found somewhere, and then simplified by myself until it just worked, i left some comments for the important lines.
I've been looking for a while, and found this example in oracle docs.
https://docs.oracle.com/javase/tutorial/2d/geometry/arbitrary.html
I don't really know how to convert Graphics2D to Shape, please tell me if i'm taking the wrong way.
So, I've spent the better of the day banging my head against this problem, trying to do a whole bunch of trig magic ... I can't even do simple card tricks :P
Then I realised, there are other tricks I could do...
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicButtonUI;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
JButton btn = new JButton();
btn.setUI(new PlayButtonUI());
frame.add(btn);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class PlayButtonUI extends BasicButtonUI {
#Override
public Dimension getPreferredSize(JComponent c) {
return new Dimension(200, 200);
}
#Override
public void paint(Graphics g, JComponent c) {
Graphics2D g2 = (Graphics2D) g;
AbstractButton b = (AbstractButton) c;
ButtonModel model = b.getModel();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);//smoth borders
if (model.isArmed()) {
g2.setColor(Color.BLACK);//color when button is pressed
} else {
g2.setColor(Color.GRAY);//default button color
}
float thinkness = Math.min(c.getWidth(), c.getHeight()) * 0.1f;
Shape shape = shapeFor(c, thinkness);
g2.setStroke(new BasicStroke(thinkness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.fill(shape);//aplying color
g2.draw(shape);
super.paint(g2, c);
}
private Shape shapeFor(JComponent c, float thickness) {
GeneralPath gp = new GeneralPath();
double width = c.getWidth();
double height = c.getHeight();
double vPos = height / 2.0;
double hPos = width - thickness;
gp.moveTo(0.0 + thickness, 0.0 + thickness);
gp.lineTo(hPos, vPos);
gp.lineTo(0.0 + thickness, height - thickness);
gp.closePath();
return gp;
}
}
}
So, this is a slight "cheat". What this actually does is uses the properties of the Stroke to generate rounded edges, rather than trying to use curveTo or compound shapes
Have a look at Stroking and Filling Graphics Primitives for more details
Related
I am creating a retro arcade game in Java. The screen resolution for the game is 304 x 256, which I want to keep to preserve the retro characteristics of the game (visuals, animations, font blockiness, etc.).
But when I render this on a large desktop display, it is too small, as one would expect.
I'd like to be able to scale the window up say by a constant factor, without having to code the various paint(Graphics) methods to be knowledgeable about the fact that there's a scale-up. That is, I'd like the rendering code believe that the screen is 304 x 256. I also don't want to have to change my desktop resolution or go into full screen exclusive mode. Just want a big window with scaled up pixels, essentially.
I'd be looking for something along the following lines:
scale(myJFrame, 4);
and have all the contents automatically scale up.
UPDATE: Regarding input, my game happens to use keyboard input, so I don't myself need the inverse transform that trashgod describes. Still I can imagine that others would need that, so I think it's an appropriate suggestion.
One approach, suggested here, is to rely on drawImage() to scale an image of the content. Your game would render itself in the graphics context of a BufferedImage, rather than your implementation of paintComponent(). If the game includes mouse interaction, you'll have to scale the mouse coordinates as shown. In the variation below, I've given the CENTER panel a preferred size that is a multiple of SCALE = 8 and added the original as an icon in the WEST of a BorderLayout. As the default, CENTER, ignores a component's preferred size, you may want to add it to a (possibly nested) panel having FlowLayout. Resize the frame to see the effect.
f.setLayout(new FlowLayout());
f.add(new Grid(NAME));
//f.add(new JLabel(ICON), BorderLayout.WEST);
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
/**
* #see https://stackoverflow.com/a/44373975/230513
* #see http://stackoverflow.com/questions/2900801
*/
public class Grid extends JPanel implements MouseMotionListener {
private static final String NAME = "OptionPane.informationIcon";
private static final Icon ICON = UIManager.getIcon(NAME);
private static final int SCALE = 8;
private final BufferedImage image;
private int imgW, imgH, paneW, paneH;
public Grid(String name) {
super(true);
imgW = ICON.getIconWidth();
imgH = ICON.getIconHeight();
image = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) image.getGraphics();
ICON.paintIcon(null, g2d, 0, 0);
g2d.dispose();
this.addMouseMotionListener(this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(imgW * SCALE, imgH * SCALE);
}
#Override
protected void paintComponent(Graphics g) {
paneW = this.getWidth();
paneH = this.getHeight();
g.drawImage(image, 0, 0, paneW, paneH, null);
}
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
int x = p.x * imgW / paneW;
int y = p.y * imgH / paneH;
int c = image.getRGB(x, y);
this.setToolTipText(x + "," + y + ": "
+ String.format("%08X", c));
}
#Override
public void mouseDragged(MouseEvent e) {
}
private static void create() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new Grid(NAME));
f.add(new JLabel(ICON), BorderLayout.WEST);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
create();
}
});
}
}
One approach, suggested here, is to rely on the graphics context's scale() method and construct to an inverse transform to convert between mouse coordinates and image coordinates. In the example below, note how the original image is 256 x 256, while the displayed image is scaled by SCALE = 2.0. The mouse is hovering over the center the image; the tooltip shows an arbitrary point in the display and the center point (127, 127) in the original.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** #see https://stackoverflow.com/a/2244285/230513 */
public class InverseTransform {
private static final double SCALE = 2.0;
public static void main(String[] args) {
JFrame frame = new JFrame("Inverse Test");
BufferedImage image = getImage(256, 'F');
AffineTransform at = new AffineTransform();
at.scale(SCALE, SCALE);
frame.add(new ImageView(image, at));
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static BufferedImage getImage(int size, char c) {
final Font font = new Font("Serif", Font.BOLD, size);
BufferedImage bi = new BufferedImage(
size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.white);
g2d.fillRect(0, 0, size, size);
g2d.setPaint(Color.blue);
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
int x = (size - fm.charWidth(c)) / 2;
int y = fm.getAscent() + fm.getDescent() / 4;
g2d.drawString(String.valueOf(c), x, y);
g2d.setPaint(Color.black);
g2d.drawLine(0, y, size, y);
g2d.drawLine(x, 0, x, size);
g2d.fillOval(x - 3, y - 3, 6, 6);
g2d.drawRect(0, 0, size - 1, size - 1);
g2d.dispose();
return bi;
}
private static class ImageView extends JPanel {
private BufferedImage image;
private AffineTransform at;
private AffineTransform inverse;
private Graphics2D canvas;
private Point oldPt = new Point();
private Point newPt;
#Override
public Dimension getPreferredSize() {
return new Dimension( // arbitrary multiple of SCALE
(int)(image.getWidth() * SCALE * 1.25),
(int)(image.getHeight() * SCALE * 1.25));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
try {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
inverse = g2d.getTransform();
inverse.invert();
g2d.translate(this.getWidth() / 2, this.getHeight() / 2);
g2d.transform(at);
g2d.translate(-image.getWidth() / 2, -image.getHeight() / 2);
inverse.concatenate(g2d.getTransform());
g2d.drawImage(image, 0, 0, this);
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace(System.err);
}
}
ImageView(final BufferedImage image, final AffineTransform at) {
this.setBackground(Color.lightGray);
this.image = image;
this.at = at;
this.canvas = image.createGraphics();
this.canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
this.canvas.setColor(Color.BLACK);
this.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
Point m = e.getPoint();
Point i = e.getPoint();
try {
inverse.inverseTransform(m, i);
setToolTipText("<html>Mouse: " + m.x + "," + m.y
+ "<br>Inverse: " + i.x + "," + i.y + "</html>");
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
});
}
}
}
Thanks to trashgod for pointing me in the right direction with his two answers. I was able to combine elements of both answers to arrive at something that works for what I need to do.
So first, my goal was to scale up an entire UI rather than scaling up a single icon or other simple component. By "an entire UI" I specifically mean a JPanel containing multiple custom child components laid out using a BorderLayout. There are no JButtons or any other interactive Swing components, and no mouse input (it's all keyboard-based input), so really I just need to scale a 304 x 256 JPanel up by a factor of 3 or 4.
Here's what I did:
package bb.view;
import javax.swing.JComponent;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import static bb.BBConfig.SCREEN_WIDTH_PX; // 304
import static bb.BBConfig.SCREEN_HEIGHT_PX; // 256
public class Resizer extends JPanel {
private static final int K = 3;
private static final Dimension PREF_SIZE =
new Dimension(K * SCREEN_WIDTH_PX, K * SCREEN_HEIGHT_PX);
private static final AffineTransform SCALE_XFORM =
AffineTransform.getScaleInstance(K, K);
public Resizer(JComponent component) {
setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
add(component);
}
#Override
public Dimension getPreferredSize() {
return PREF_SIZE;
}
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setTransform(SCALE_XFORM);
super.paint(g2);
}
}
Some important elements of the solution:
Using a FlowLayout here shrinkwraps the child component, which is what I want. (Thanks trashgod for that.) That is, I don't want the child to expand to fill the Resizer preferred size, because that wrecks the child component's layout. Specifically it was creating this huge gap between the child component's CENTER and SOUTH regions.
I configured the FlowLayout with left alignment and hgap, vgap = 0. That way my scale transform would have the scaled up version anchored in the upper left corner too.
I used an AffineTransform to accomplish the scaling. (Again thanks trashgod.)
I used paint() instead of paintComponent() because the Resizer is simply a wrapper. I don't want to paint a border. I basically want to intercept the paint() call, inserting the scale transform and then letting the JPanel.paint() do whatever it would normally do.
I didn't end up needing to render anything in a separate BufferedImage.
The end result is that the UI is large, but the all the code other than this Resizer thinks the UI is 304 x 256.
I am writing an application which has layers (I used JLayeredPane) containing two principal layers (JPanels). I override the paintComponent method of the Panel at the bottom (call it Map) so it paints a Map, and the the paintComponent method of the one at the top (call it selectionPanel) so it paints a selection of an element.
Here's a summary of the structure:
layers -
|-selectionPanel(on top)
|-Map (at bottom)
I want the Map to stay static, ie, not to do any repaint (except the initial one) since it does not change.
The trouble is, whenever I call selectionPanel.repaint(), Map gets repainted as well! This is a definitely not efficient.
I think this is due to the eager painting behavior of JLayeredPane. Is there a way to disable this feature in JLayeredPane?
In case you're interested to see the above effect, I've modified this example:
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/** #see https://stackoverflow.com/q/9625495/230513 */
public class LayerDemo extends JFrame {
private static final Dimension d = new Dimension(320, 240);
public LayerDemo() {
JLayeredPane layers = new JLayeredPane();
layers.setPreferredSize(d);
layers.add(new LayerPanel(1 * d.height / 8), 100);
layers.add(new LayerPanel(2 * d.height / 8), 101);
layers.add(new LayerPanel(3 * d.height / 8), 102);
this.add(layers, BorderLayout.CENTER);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.pack();
this.setLocationByPlatform(true);
}
private static class LayerPanel extends JPanel {
private static final Random r = new Random();
private int n;
private Color color = new Color(r.nextInt());
public LayerPanel(int n) {
this.n = n;
this.setOpaque(false);
this.setBounds(n, n, d.width / 2, d.height / 2);
this.addMouseListener(new MouseHandler(this));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
color = new Color(r.nextInt());
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(color);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ((float) 20) / 100));
g2d.fillRoundRect(0, 0, getWidth(), getHeight(), 16, 16);
g2d.setColor(Color.black);
g2d.drawString(String.valueOf(n), 5, getHeight() - 5);
}
}
private static class MouseHandler extends MouseAdapter {
LayerPanel panel;
MouseHandler(LayerPanel panel) {
this.panel = panel;
}
#Override
public void mouseClicked(MouseEvent e) {
panel.repaint();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
(new LayerDemo()).setVisible(true);
}
});
}
}
Is there a way to disable this feature in JLayeredPane?
If is not a JLayeredPane feature. It is a Swing painting feature.
this.setOpaque(false);
When you make a component non-opaque, then Swing needs to repaint the parent component to make sure the background is painted properly.
In your case it looks like you are using transparency so you would definitely need the background to be repainted.
whenever I call selectionPanel.repaint(), Map gets repainted as well
If you are only painting a certain area of the child panel then you can use:
selectionPanel.repaint(Rectangle)
to minimize the area that is repainted.
color = new Color(r.nextInt());
Don't change the color in the paintComponent() method. This should be done in the MouseListener so it only affects the panel you click on. So even though the other panels will be repainted, their colors will not randomly change.
I have a graphics object and I want to color all the area except some rectangles. For e.g.
I want to color all area except these black areas. Can I do that? There can be many rectangles in the image.
I recommend you to fill all area with White color, and then draw Black rectangles on that, because it's simplier that draw figure with holes. For example like next:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class DrawExample extends JPanel{
List<Rectangle> rctangles = new ArrayList<>();
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DrawExample drawExample = new DrawExample();
drawExample.addRect(new Rectangle(20,20,25,25));
drawExample.addRect(new Rectangle(50,50,25,25));
frame.add(drawExample);
frame.setSize(200,200);
frame.setVisible(true);
}
private void addRect(Rectangle rectangle) {
rctangles.add(rectangle);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
for(Rectangle r : rctangles){
g.fillRect(r.x, r.y, r.width,r.height);
}
}
}
Write a program that fills the window with a larrge ellipse. The ellipse shoud touch the window boundaries, even if the window is resized.
I have the following code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import javax.swing.JComponent;
public class EllipseComponent extends JComponent {
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
Ellipse2D.Double ellipse = new Ellipse2D.Double(0,0,150,200);
g2.draw(ellipse);
g2.setColor(Color.red);
g2.fill(ellipse);
}
}
And the main class:
import javax.swing.JFrame;
public class EllipseViewer {
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setSize(150, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
EllipseComponent component = new EllipseComponent();
frame.add(component);
frame.setVisible(true);
}
}
in your EllipseComponent you do:
Ellipse2D.Double ellipse = new Ellipse2D.Double(0,0,getWidth(),getHeight());
I'd also recommend the changes given by Hovercraft Full Of Eels. In this simple case it might not be an issue but as the paintComponent method grows in complexity you realy want as little as possible to be computed in the paintComponent method.
Do not resize components within paintComponent. In fact, do not create objects or do any program logic within this method. The method needs to be lean, fast as possible, do drawing, and that's it. You must understand that you do not have complete control over when or even if this method is called, and you certainly don't want to add code to it unnecessarily that may slow it down.
You should create your ellipse in the class's constructor. To resize it according to the JComponent's size and on change of size, use a ComponentListener.:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import javax.swing.JComponent;
public class EllipseComponent extends JComponent {
Ellipse2D ellipse = null;
public EllipseComponent {
ellipse = new Ellipse2D.Double(0,0,150,200);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
// set the size of your ellipse here
// based on the component's width and height
}
});
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.draw(ellipse);
g2.setColor(Color.red);
g2.fill(ellipse);
}
}
Caveat: code not run nor tested
I want to draw the lines between 2 JScrollPanes (first scroll pane on the left side, second on the right). These JScrollPanes contain images. I want to draw lines between these 2 images (use some layers, use some trick etc.). I tried do it different ways, but i failed. Is it possible? (if not, i will have to make 2 images in one JScrollPane and it won't be nice).
EDIT
I want to draw between 2 images - throught components - get some points from images and draw lines between them. I apologize for poorly formulated question.
In order to accomplish this, I believe you'll need to make use of the Glass Pane. The Glass Pane sits on top of everything in the JRootPane and fills the entire view. This particular position allows two distinct capabilities:
Intercepting mouse and keyboard events
Drawing over the entire user interface
I believe your question is addressed by the second capability. The following is an example implementation, which you can later tailor to meet your own needs. Note that I've left out a lot of detail with regard to Glass Pane that you'll need to research on your own.
CODE
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class GlassPaneDemo {
private static BufferedImage bi;
public static void main(String[] args){
try {
loadImages();
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
createAndShowGUI();
}
});
} catch (IOException e) {
// handle exception
}
}
private static void loadImages() throws IOException{
bi = ImageIO.read(new File("src/resources/person.png"));
}
private static void createAndShowGUI(){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setGlassPane(new CustomGlassPane());
frame.getContentPane().add(getButtonPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.getGlassPane().setVisible(true);
frame.setVisible(true);
}
private static final JPanel getButtonPanel(){
#SuppressWarnings("serial")
final JPanel panel = new JPanel(){
#Override
protected void paintComponent(Graphics g){
Graphics gCopy = g.create();
gCopy.setColor(Color.BLUE.darker());
gCopy.fillRect(0, 0, getWidth(), getHeight());
gCopy.dispose();
}
};
final JLabel labelOne = new JLabel();
labelOne.setIcon(new ImageIcon(bi));
final JLabel labelTwo = new JLabel();
labelTwo.setIcon(new ImageIcon(bi));
panel.add(labelOne);
panel.add(labelTwo);
return panel;
}
#SuppressWarnings("serial")
private static class CustomGlassPane extends JComponent{
private Point p1;
private Point p2;
private boolean lineDrawn;
public CustomGlassPane(){
addMouseListener(new MouseAdapter(){
#Override
public void mouseClicked(MouseEvent e){
if(p1 == null || lineDrawn){
if(lineDrawn){
p1 = null;
p2 = null;
lineDrawn = false;
}
p1 = e.getPoint();
}else{
p2 = e.getPoint();
repaint(); // not optimal
lineDrawn = true;
}
}
});
// Block all other input events
addMouseMotionListener(new MouseMotionAdapter(){});
addKeyListener(new KeyAdapter(){});
addComponentListener(new ComponentAdapter(){
#Override
public void componentShown(ComponentEvent e){
requestFocusInWindow();
}
});
setFocusTraversalKeysEnabled(false);
}
#Override
protected void paintComponent(Graphics g){
if(p1 != null && p2 != null){
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.RED);
g2.drawLine((int)p1.getX(), (int)p1.getY(), (int)p2.getX(), (int)p2.getY());
g2.dispose();
}
}
}
}
OUTPUT
EXPLANATION
In this example, I clicked two arbitrary points within each JLabel, and then drew a connecting line.
This should be very possible. You will need to create a custom component that is aware of both vertical ScrollBars. It should add itself as an AdjustmentListener to each scroll bar in order to detect changes and repaint the lines between the two.
See:
addAdjustmentListener method in the API
You can use this
http://java-sl.com/connector.html
as an example of such code.