From 9d391c893ecad20826f570f6c406bef7e4eaeb19 Mon Sep 17 00:00:00 2001 From: UnlegitDqrk Date: Sat, 14 Feb 2026 16:36:02 +0100 Subject: [PATCH] Small changes --- .../oacswing/component/OACFrame.java | 271 ++++++++++-------- .../oacswing/component/OACTitleBar.java | 1 + .../component/design/DesignManager.java | 14 +- .../oacswing/test/CustomizedTests.java | 11 +- 4 files changed, 166 insertions(+), 131 deletions(-) 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:

+ * */ 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);