javaswingjbuttonjtogglebutton

Java Swing macOS buttons are grey instead of blue when selected


I have a bug in my Swing program where the background of JButton and JToggleButton components is grey instead of blue, when the component is selected. Here is a screenshot of the issue on a JToggleButton, but the same issue occurs when pressing down on a JButton.

Demo of the issue on a JToggleButton

I have tried manually setting the background with

button.setBackground(Color.BLUE);
button.setOpaque(true);

but that just paints a small border around the button:

first fix try

Also added button.setBorderPainted(false); which removes the border but also removes the macOS button look entirely + gives the text a darker background color:

try 2, I hate my life

It looks like the border (AquaButtonBorder) blocks the background color from changing or something? Because simply setting button.setBackground(Color.BLUE); does absolutely nothing.

The look and feel is set to UIManager.getSystemLookAndFeelClassName(), but also tried UIManager.getCrossPlatformLookAndFeelClassName() which was unsuccessful.

Java specifications: running on Java 11, but the same issue persists in Java 17

System specifications: M1 MacBook Pro, macOS version 12.4


Solution

  • Sooo, I spent WAY to much time digging into the source code for the "Aqua" look and feel, and I found that the AquaButtonUI has a "button type" client property which is used to (amongst a couple of other things) determine the border type.

    Sooo, I pulled all the internal "types" and did a quick test...

    import java.awt.BorderLayout;
    import java.awt.EventQueue;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JToggleButton;
    import javax.swing.border.EmptyBorder;
    
    public class Main {
        public static void main(String[] args) {
            System.out.println(System.getProperty("user.dir"));
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame frame = new JFrame();
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
            private String[] keys = new String[]{
                "toolbar",
                "icon",
                "text",
                "toggle",
                "combobox",
                "comboboxInternal",
                "comboboxEndCap",
                "square",
                "gradient",
                "bevel",
                "textured",
                "roundRect",
                "recessed",
                "well",
                "help",
                "round",
                "texturedRound",
                "segmented-first",
                "segmented-middle",
                "segmented-last",
                "segmented-only",
                "segmentedRoundRect-first",
                "segmentedRoundRect-middle",
                "segmentedRoundRect-last",
                "segmentedRoundRect-only",
                "segmentedTexturedRounded-first",
                "segmentedTexturedRounded-middle",
                "segmentedTexturedRounded-last",
                "segmentedTexturedRounded-only",
                "segmentedTextured-first",
                "segmentedTextured-middle",
                "segmentedTextured-last",
                "segmentedTextured-only",
                "segmentedCapsule-first",
                "segmentedCapsule-middle",
                "segmentedCapsule-last",
                "segmentedCapsule-only",
                "segmentedGradient-first",
                "segmentedGradient-middle",
                "segmentedGradient-last",
                "segmentedGradient-only",
                "disclosure",
                "scrollColumnSizer"
            };
    
            public TestPane() {
                setBorder(new EmptyBorder(32, 32, 32, 32));
                setLayout(new BorderLayout());
    
                JPanel contentPane = new JPanel(new GridBagLayout());
    
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridwidth = GridBagConstraints.REMAINDER;
    
                for (String key : keys) {
                    JToggleButton btn = new JToggleButton(key);
                    btn.putClientProperty("JButton.buttonType", key);
                    contentPane.add(btn, gbc);
                }
                add(new JScrollPane(contentPane));
            }
        }
    }
    

    Generally, I found that bevel and segmented-only will achieve your desired result (although segment-first/middle/last looks interesting)

    enter image description here