MadProgrammer's solution was mostly correct. But I went a step further to address a few things.
I changed the JFrame to a JWindow and removed the call to setUndecorated. This kept functionality and removed the window from the taskbar.
Next and optionally, I used JNA to allow all inputs to pass through the window's components. My final solution is as follows, thanks for all of the help:
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.Timer;
import com.sun.jna.*;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinUser;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JWindow window = new JWindow();
window.setBackground(new Color(0, true));
window.add(new TestPane());
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
window.setAlwaysOnTop(true);
setTransparent(window);
}
});
}
public static class TestPane extends JPanel {
enum Direction {
UP, DOWN;
}
private Timer timer;
private Direction direction = Direction.UP;
private double yDelta = 2;
private double yPos = 380;
private Shape ball = new Ellipse2D.Double(0, 0, 20, 20);
public TestPane() {
setOpaque(false);
timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (direction == Direction.UP) {
yPos -= yDelta;
yDelta -= 0.006;
if (yDelta <= 0.0) {
yDelta = 0;
direction = Direction.DOWN;
}
if (yPos < 0) {
yPos = 0;
direction = Direction.DOWN;
}
} else if (direction == Direction.DOWN) {
yPos += yDelta;
yDelta += 0.01;
if (yDelta > 2.0) {
yDelta = 2.0;
}
if (yPos + ball.getBounds().height > getHeight()) {
yPos = getHeight() - ball.getBounds().height;
direction = Direction.UP;
yDelta = 2;
}
}
repaint();
}
});
}
@Override
public void addNotify() {
super.addNotify();
timer.start();
}
@Override
public void removeNotify() {
timer.stop();
super.removeNotify();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
RenderingHints hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
);
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.translate(190, yPos);
g2d.fill(ball);
g2d.dispose();
}
}
private static void setTransparent(Component w) {
WinDef.HWND hwnd = getHWnd(w);
int wl = User32.INSTANCE.GetWindowLong(hwnd, WinUser.GWL_EXSTYLE);
wl = wl | WinUser.WS_EX_LAYERED | WinUser.WS_EX_TRANSPARENT;
User32.INSTANCE.SetWindowLong(hwnd, WinUser.GWL_EXSTYLE, wl);
}
/**
* Get the window handle from the OS
*/
private static HWND getHWnd(Component w) {
HWND hwnd = new HWND();
hwnd.setPointer(Native.getComponentPointer(w));
return hwnd;
}
}