diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACFrame.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACFrame.java
index 9e28f06..c97da88 100644
--- a/src/main/java/org/openautonomousconnection/oacswing/component/OACFrame.java
+++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACFrame.java
@@ -1,7 +1,5 @@
package org.openautonomousconnection.oacswing.component;
-import lombok.Getter;
-import lombok.NonNull;
import org.openautonomousconnection.oacswing.component.design.DesignManager;
import javax.swing.*;
@@ -13,51 +11,42 @@ import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
/**
- * OAC Swing frame with a custom title bar.
+ * Custom undecorated frame with rounded visuals without using Window#setShape.
+ *
+ *
Windows note:
+ *
+ * - Fully transparent pixels (alpha=0) are click-through on per-pixel transparent windows.
+ * - Therefore this implementation ensures alpha is NEVER 0 anywhere in the window.
+ *
*/
public class OACFrame extends JFrame {
- private static final int RESIZE_MARGIN = 8;
+
+ private static final int RESIZE_MARGIN = 2;
private static final int TITLE_BAR_HEIGHT = 42;
+ private static final int CORNER_ARC = 30;
+
private Point dragStart;
private Rectangle startBounds;
private int resizeCursor = Cursor.DEFAULT_CURSOR;
- @Getter
+
private OACTitleBar titleBar;
- /**
- * Creates a new frame.
- */
+ private final RoundedRootPanel roundedRoot = new RoundedRootPanel(CORNER_ARC);
+
public OACFrame() {
- super();
init();
}
- /**
- * Creates a new frame with the given graphics configuration.
- *
- * @param gc graphics configuration
- */
public OACFrame(GraphicsConfiguration gc) {
super(gc);
init();
}
- /**
- * Creates a new frame with the given title.
- *
- * @param title frame title
- */
public OACFrame(String title) {
super(title);
init();
}
- /**
- * Creates a new frame with the given title and graphics configuration.
- *
- * @param title frame title
- * @param gc graphics configuration
- */
public OACFrame(String title, GraphicsConfiguration gc) {
super(title, gc);
init();
@@ -71,12 +60,15 @@ public class OACFrame extends JFrame {
setMinimumSize(new Dimension(900, 600));
setLocationByPlatform(true);
- OACPanel content = new OACPanel(new BorderLayout());
- setContentPane(content);
+ // CRITICAL: Do NOT use alpha=0. Alpha=0 pixels are click-through on Windows.
+ setBackground(new Color(0, 0, 0, 1));
+
+ roundedRoot.setLayout(new BorderLayout());
+ setContentPane(roundedRoot);
titleBar = new OACTitleBar(this);
- OACPanel titleRoot = new OACPanel(new BorderLayout());
+ final OACPanel titleRoot = new OACPanel(new BorderLayout());
titleRoot.setOpaque(false);
titleRoot.add(titleBar, BorderLayout.CENTER);
@@ -87,15 +79,13 @@ public class OACFrame extends JFrame {
@Override
public void componentResized(ComponentEvent e) {
titleRoot.setBounds(0, 0, getWidth(), TITLE_BAR_HEIGHT);
-
- setShape(new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 30, 30));
+ roundedRoot.repaint();
}
@Override
public void componentShown(ComponentEvent e) {
titleRoot.setBounds(0, 0, getWidth(), TITLE_BAR_HEIGHT);
-
- setShape(new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 30, 30));
+ roundedRoot.repaint();
}
});
@@ -103,15 +93,56 @@ public class OACFrame extends JFrame {
SwingUtilities.invokeLater(() -> {
titleRoot.setBounds(0, 0, getWidth(), TITLE_BAR_HEIGHT);
- setShape(new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 30, 30));
+ roundedRoot.repaint();
});
- MouseAdapter adapter = new MouseAdapter() {
+ installResizeHandling();
+ DesignManager.apply(this);
+ }
+
+ @Override
+ protected OACRootPane createRootPane() {
+ OACRootPane rp = new OACRootPane();
+ rp.setOpaque(false);
+ return rp;
+ }
+
+ @Override
+ public OACRootPane getRootPane() {
+ return (OACRootPane) rootPane;
+ }
+
+ @Override
+ public OACLayeredPane getLayeredPane() {
+ return (OACLayeredPane) super.getLayeredPane();
+ }
+
+ @Override
+ public void setLayeredPane(JLayeredPane layeredPane) {
+ if (layeredPane instanceof OACLayeredPane) {
+ super.setLayeredPane(layeredPane);
+ }
+ }
+
+ public void setLayeredPane(OACLayeredPane layeredPane) {
+ setLayeredPane((JLayeredPane) layeredPane);
+ }
+
+ /**
+ * Returns the title bar.
+ *
+ * @return title bar
+ */
+ public OACTitleBar getTitleBar() {
+ return titleBar;
+ }
+
+ private void installResizeHandling() {
+ MouseAdapter adapter = new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
resizeCursor = getResizeCursor(e);
-
setCursor(Cursor.getPredefinedCursor(resizeCursor));
}
@@ -132,20 +163,15 @@ public class OACFrame extends JFrame {
Rectangle newBounds = new Rectangle(startBounds);
switch (resizeCursor) {
- case Cursor.E_RESIZE_CURSOR -> newBounds.width += dx;
- case Cursor.S_RESIZE_CURSOR -> newBounds.height += dy;
+ case Cursor.SW_RESIZE_CURSOR -> {
+ newBounds.x += dx;
+ newBounds.width -= dx;
+ newBounds.height += dy;
+ }
case Cursor.SE_RESIZE_CURSOR -> {
newBounds.width += dx;
newBounds.height += dy;
}
- case Cursor.W_RESIZE_CURSOR -> {
- newBounds.x += dx;
- newBounds.width -= dx;
- }
- case Cursor.N_RESIZE_CURSOR -> {
- newBounds.y += dy;
- newBounds.height -= dy;
- }
case Cursor.NW_RESIZE_CURSOR -> {
newBounds.x += dx;
newBounds.y += dy;
@@ -157,100 +183,29 @@ public class OACFrame extends JFrame {
newBounds.width += dx;
newBounds.height -= dy;
}
- case Cursor.SW_RESIZE_CURSOR -> {
+ case Cursor.N_RESIZE_CURSOR -> {
+ newBounds.y += dy;
+ newBounds.height -= dy;
+ }
+ case Cursor.S_RESIZE_CURSOR -> newBounds.height += dy;
+ case Cursor.W_RESIZE_CURSOR -> {
newBounds.x += dx;
newBounds.width -= dx;
- newBounds.height += dy;
}
+ case Cursor.E_RESIZE_CURSOR -> newBounds.width += dx;
}
setBounds(newBounds);
-
- e.getComponent().dispatchEvent(new ComponentEvent(e.getComponent(), ComponentEvent.COMPONENT_RESIZED));
+ e.getComponent().dispatchEvent(
+ new ComponentEvent(e.getComponent(), ComponentEvent.COMPONENT_RESIZED)
+ );
}
};
addMouseListener(adapter);
addMouseMotionListener(adapter);
-
- DesignManager.apply(this);
}
- /**
- * Adds a component into the content area (center) by default.
- *
- * @param comp component
- */
- @Override
- public Component add(Component comp) {
- initIfOACComponent(comp);
- return super.add(comp);
- }
-
- @Override
- public Component add(Component comp, int index) {
- initIfOACComponent(comp);
- return super.add(comp, index);
- }
-
- @Override
- public void add(@NonNull Component comp, Object constraints) {
- initIfOACComponent(comp);
- super.add(comp, constraints);
- }
-
- @Override
- public Component add(String name, Component comp) {
- initIfOACComponent(comp);
- return super.add(name, comp);
- }
-
- @Override
- public void add(Component comp, Object constraints, int index) {
- initIfOACComponent(comp);
- super.add(comp, constraints, index);
- }
-
- private void initIfOACComponent(Component comp) {
- if (comp instanceof OACComponent component) {
- component.initDesign();
- }
- }
-
- @Override
- protected OACRootPane createRootPane() {
- OACRootPane rp = new OACRootPane();
- rp.setOpaque(true);
- return rp;
- }
-
- @Override
- public OACRootPane getRootPane() {
- return (OACRootPane) this.rootPane;
- }
-
- @Override
- public OACLayeredPane getLayeredPane() {
- return (OACLayeredPane) super.getLayeredPane();
- }
-
- @Override
- public void setLayeredPane(JLayeredPane layeredPane) {
- if (layeredPane instanceof OACLayeredPane)
- super.setLayeredPane(layeredPane);
- }
-
- public void setLayeredPane(OACLayeredPane layeredPane) {
- setLayeredPane((JLayeredPane) layeredPane);
- }
-
- /**
- * Get the resize cursor for the given edge / corner of this frame.
- * Required, since undecorated Frames cannot be resized by default
- *
- * @param e event passed by mouse adapter
- * @return id of detected resize cursor
- */
private int getResizeCursor(MouseEvent e) {
int x = e.getX();
int y = e.getY();
@@ -273,4 +228,68 @@ public class OACFrame extends JFrame {
return Cursor.DEFAULT_CURSOR;
}
+
+ /**
+ * Root panel that paints a near-transparent full-window layer (alpha=1)
+ * to prevent click-through, then paints the rounded background and clips children.
+ */
+ private static final class RoundedRootPanel extends OACPanel {
+
+ private final int arc;
+
+ private RoundedRootPanel(int arc) {
+ super(new BorderLayout());
+ this.arc = Math.max(0, arc);
+ setOpaque(false);
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2 = (Graphics2D) g.create();
+ try {
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ int w = getWidth();
+ int h = getHeight();
+ if (w <= 0 || h <= 0) return;
+
+ // 1) Hit-test filler: alpha MUST be > 0 everywhere, otherwise clicks go through.
+ g2.setComposite(AlphaComposite.Src);
+ g2.setColor(new Color(0, 0, 0, 1));
+ g2.fillRect(0, 0, w, h);
+
+ // 2) Rounded background.
+ Shape rr = new RoundRectangle2D.Double(0, 0, w, h, arc, arc);
+
+ Color bg = getBackground();
+ if (bg == null) bg = new Color(0, 0, 0);
+ // Force opaque fill for the visible area.
+ Color opaqueBg = new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 255);
+
+ g2.setColor(opaqueBg);
+ g2.fill(rr);
+ } finally {
+ g2.dispose();
+ }
+ }
+
+ @Override
+ protected void paintChildren(Graphics g) {
+ Graphics2D g2 = (Graphics2D) g.create();
+ try {
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ int w = getWidth();
+ int h = getHeight();
+ if (w <= 0 || h <= 0) return;
+
+ Shape clip = new RoundRectangle2D.Double(0, 0, w, h, arc, arc);
+ g2.clip(clip);
+
+ super.paintChildren(g2);
+ } finally {
+ g2.dispose();
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACTitleBar.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACTitleBar.java
index f56a696..ea95e62 100644
--- a/src/main/java/org/openautonomousconnection/oacswing/component/OACTitleBar.java
+++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACTitleBar.java
@@ -109,6 +109,7 @@ public class OACTitleBar extends OACPanel {
b.setBorderPainted(false);
b.setContentAreaFilled(true);
b.setOpaque(true);
+ b.setBorder(null);
b.setPreferredSize(new Dimension(42, 28));
return b;
diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java b/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java
index b09e526..b335364 100644
--- a/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java
+++ b/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java
@@ -21,9 +21,19 @@ public class DesignManager {
private static DesignManager instance;
static {
- Design.DARK.getElements().put(OACButton.class, new DesignFlags(OACColor.DARK_BUTTON, OACColor.DARK_TEXT, OACColor.DARK_BUTTON_HOVER, OACColor.DARK_BUTTON_HOVER, OACColor.DARK_INACTIVE_BUTTON, true));
+ Design.DARK.getElements().put(OACButton.class, new DesignFlags(
+ OACColor.DARK_INPUT_BUTTON, // background
+ OACColor.DARK_TEXT, // foreground
+ OACColor.DARK_INPUT_BUTTON_HOVER,
+ OACColor.DARK_INPUT_BUTTON_HOVER,
+ OACColor.DARK_INACTIVE_BUTTON,
+ true
+ ));
+
Design.DARK.getElements().put(OACCheckBox.class, new DesignFlags(OACColor.DARK_INPUT_FIELD));
Design.DARK.getElements().put(OACCheckBoxMenuItem.class, new DesignFlags(OACColor.DARK_ITEM));
+ Design.DARK.getElements().put(OACTextField.class, new DesignFlags(OACColor.DARK_ITEM, OACColor.DARK_TEXT));
+ Design.DARK.getElements().put(OACTextArea.class, new DesignFlags(OACColor.DARK_ITEM, OACColor.DARK_TEXT));
Design.DARK.getElements().put(OACColorChooser.class, new DesignFlags(OACColor.DARK_SECTION));
Design.DARK.getElements().put(OACComboBox.class, new DesignFlags(OACColor.DARK_INPUT_FIELD));
Design.DARK.getElements().put(OACFrame.class, new DesignFlags(OACColor.DARK_BACKGROUND));
@@ -41,7 +51,7 @@ public class DesignManager {
Design.DARK.getElements().put(OACRadioButton.class, new DesignFlags(OACColor.DARK_BUTTON));
Design.DARK.getElements().put(OACTitleBar.class, new DesignFlags(OACColor.DARK_SECTION));
- Design.DARK.border = new RoundedBorder(OACColor.DARK_BORDERS.getColor(), 8, 1);
+ Design.DARK.border =new RoundedBorder(OACColor.DARK_BORDERS.getColor(), 18, 1);
DesignManager.getInstance().registerDesign(Design.DARK);
}
diff --git a/src/test/java/org/openautonomousconnection/oacswing/test/CustomizedTests.java b/src/test/java/org/openautonomousconnection/oacswing/test/CustomizedTests.java
index 7f3e087..11b88b4 100644
--- a/src/test/java/org/openautonomousconnection/oacswing/test/CustomizedTests.java
+++ b/src/test/java/org/openautonomousconnection/oacswing/test/CustomizedTests.java
@@ -7,6 +7,8 @@ package org.openautonomousconnection.oacswing.test;
import org.junit.jupiter.api.Test;
import org.openautonomousconnection.oacswing.component.OACButton;
import org.openautonomousconnection.oacswing.component.OACFrame;
+import org.openautonomousconnection.oacswing.component.OACPanel;
+import org.openautonomousconnection.oacswing.component.OACTextField;
import org.openautonomousconnection.oacswing.component.design.Design;
import org.openautonomousconnection.oacswing.component.design.DesignManager;
@@ -18,12 +20,15 @@ public class CustomizedTests {
DesignManager designManager = DesignManager.getInstance();
DesignManager.setGlobalDesign(Design.DARK);
-
+ OACPanel navBar = new OACPanel(new BorderLayout(8, 0));
OACFrame frame = TestUtils.mockOacFrame();
+ OACTextField textField = new OACTextField();
- frame.setLayout(new FlowLayout());
+ textField.setText("Hello");
+ textField.setToolTipText("test");
- frame.add(new OACButton());
+ navBar.add(textField, BorderLayout.CENTER);
+ frame.getContentPane().add(navBar, BorderLayout.NORTH);
frame.setVisible(true);