javaswingawttransparencymixing

Swing layering - transparent component ignores underlying AWT element


First, to get it out of the way, I absolutely NEED to use heavyweight AWT component with a swing application. I need features from both of them.

The task is simple - render a heavyweight AWT Canvas (or any other element), render OpenGL scene directly onto it, then display Swing buttons above it for the UI.

My problem is that it works half-way. I don't seem to have problems with Z-ordering. I am using jLayeredPanes for it, and I can move Canvas between layers and it actually works, popping on top or below other elements.

The problems are with transparency. The thing is, Swing elements have Opaque parameter, and when it's set to false (non-opaque) - it should basically be transparent and you should see the next element below it. In my case, however, the AWT Canvas gets ignored, and you instead only see the next underlying SWING element.

Here are a couple of screenshots. They are taken from a standalone test project of mine. The canvas is stretched to the size of the frame, and in the upper left there is a JLayeredPane dummy element that is a simplified version of the menu.

On the first screenshot, the JLayeredPane's Opaque setting is set to true, and you can see that it's background property is set to Blue color.

On the second screenshot, everything is exactly the same but Opaque is set to false. Instead of displaying whatever is on the Canvas - what gets drawn in empty grey jFrame background.

Lastly, on the third screenshot I have put Canvas into a jPanel instead of leaving it on its own. As you can see, the Panel's orange color is seen through the transparent jLayeredPane, but the Canvas is yet again hidden.

paneMenu Opaque = true paneMenu.setOpaque(false) paneMenu.setOpaque(false) + Canvas inside jPanel

Here's the code for the Frame layout. I would not post my rendering/context code right now

frame = new JFrame("AWT test");
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.setLayout(new BorderLayout());
    frame.setPreferredSize(new Dimension(width, height));

    canvas = new Canvas();
    canvas.setSize(width,height);

    //this part exists only in the third example
    JPanel p = new JPanel();
    p.setSize(width,height);
    p.setBackground(Color.orange);
    p.add(canvas);
    // third example end

    JLayeredPane pane = new JLayeredPane();
    JLayeredPane paneMenu = new JLayeredPane();
    JButton button = new JButton();
    button.setSize(20,20);
    paneMenu.setSize(200,200);
    paneMenu.add(button, new Integer(1));
    paneMenu.setBackground(Color.BLUE);
    paneMenu.setOpaque(false);           //True for the first example

    pane.add(p, new Integer(1));    // canvas for the first two examples
    pane.add(paneMenu, new Integer(2));

    pane.setOpaque(false);

    frame.add(pane);
    frame.pack();
    frame.setVisible(true);
    frame.transferFocus();

Could anyone please explain me what is going on and how to do what I need to do. I will repeat again - I have to use heavyweight component as render target. I am aware of solutions like JOGL's GLPanel which is a lightweight Swing-compatible component. But I tried that method and the performance is really slow, because instead of directly rendering onto it as a context target - it reads FrameBuffer from memory, flips it, and then paints it as BufferedImage. This path is not fitting for the limited resources of an embedded system that I'll be running on.

c0der said: Please post minimal reproducible example Errrrm.... Didn't I? Here, you can have it in complete java class form if you want, but I literally changed some variables for constants.

import javax.swing.*;
import java.awt.*;

public class Main
{
    public static void main(String[] args)
    {
        JFrame frame = new JFrame("AWT test");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.setPreferredSize(new Dimension(500, 500));

        Canvas canvas = new Canvas();
        canvas.setSize(500,500);
        canvas.setBackground(Color.RED);

        //this part exists only in the third example
        JPanel p = new JPanel();
        p.setSize(500,500);
        p.setBackground(Color.orange);
        p.add(canvas);
        // third example end

        JLayeredPane pane = new JLayeredPane();
        JLayeredPane paneMenu = new JLayeredPane();
        JButton button = new JButton();
        button.setSize(20,20);
        paneMenu.setSize(200,200);
        paneMenu.add(button, new Integer(1));
        paneMenu.setBackground(Color.BLUE);
        paneMenu.setOpaque(false);           //True for the first example

        pane.add(p, new Integer(1));    // canvas for the first two examples
        pane.add(paneMenu, new Integer(2));

        pane.setOpaque(false);

        frame.add(pane);
        frame.pack();
        frame.setVisible(true);
        frame.transferFocus();
    }
}

A little update:

I initially suspected that because Swing elements delegate all their drawing to the underlying heavyweight element (In my case JFrame), then what happens is that the frame generates a single frameBuffer for itself and then displays on top of Canvas. Canvas itself is not handled in this generation and thus the frame "covers" over the canvas.

That doesn't seem to be the case. I tried making the JFrame undecorated, all panels non-opaque, and display the picture. The result - canvas is still "cut", and through the hole you can see the underlying IDE menu. This makes me think that somewhere during Drawing, the Canvas itself detects that it is obscured by another element, and that it doesn't need to draw that area. So it "optimizes" itself and doesn't update these pixels.

Maybe I'm wrong. But here's another screenshot. This is the same example as before, but I took out 3d rendering and simply trying to display Canvas with background set to Red.

enter image description here


Solution

  • Once again, going to reply to my own question.

    It turned out that I need to do

    setComponentMixingCutoutShape(paneMenu, new Rectangle());

    for the menu pane that lies underneath the button. That essentially tells java not to cut out the element from the heavyweight underlying component.