[FIXED] LWJGL in a canvas - Captures keyboard input even when unfocused on JRE7

Started by terryhau, October 06, 2011, 14:59:18

Previous topic - Next topic

spasi

Could you please create an issue on github and attach a code sample that reproduces the problem?

basil


tvdberg

I've created the issue with attached source on github:

https://github.com/LWJGL/lwjgl/issues/20

I never worked before with github so i'm hoping everything is fine.

spasi


JeroenWarmerdam

We're still seeing the later focus issue, even with the latest nightly build.

spasi

I think I might have a working fix for AWT's focus behavior change in JDK 7. Please try the next build (#54).

JeroenWarmerdam

That's great news. Any ETA on the build? I'm looking forward to it,

spasi


tvdberg

I've just tested build #54 and it looks very promising. There are no focus related issues any more for as far as I can see.

JeroenWarmerdam

The build solves all issues when you're using the default Look and Feels. I've found strange behavior still occurs when using the Substance Look and Feel. This only occurs when running the applet in the browser and not when running from an AppletViewer context.

Here's a test case to reproduce:

1. Load the applet
2. Select 'Focus' button in the debug internal frame
3. Select the game frame by clicking on the title bar (NOT the canvas)
4. Attempt to open the File menu.
5. Observe how the File menu stays collapsed.

package test;

import java.awt.Canvas;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JDesktopPane;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.metal.MetalLookAndFeel;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.pushingpixels.substance.api.skin.SubstanceBusinessBlackSteelLookAndFeel;

public class CanvasFocusTest extends JApplet {

    /**
     * 
     */
    private static final long serialVersionUID = 2853176700479564421L;
    private JMenuBar menuBar;
    private Canvas canvas;
    private Thread gameThread;

    private boolean running;

    private JInternalFrame createDebugFrame() {

        JInternalFrame internalFrame = new JInternalFrame("Debug Frame");
        internalFrame.setLayout(new FlowLayout(FlowLayout.CENTER));
        internalFrame.setSize(400, 100);
        internalFrame.setVisible(true);

        JButton focus = new JButton("Focus");
        focus.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                canvas.setFocusable(true);
            }
        });
        internalFrame.add(focus);

        JButton unfocus = new JButton("Unfocus");
        unfocus.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                canvas.setFocusable(false);
            }
        });

        internalFrame.add(unfocus);

        JButton metal = new JButton("Metal");
        metal.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                switchMetal();
            }
        });

        internalFrame.add(metal);

        JButton substance = new JButton("Substance");
        substance.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                switchSubstance();
            }
        });

        internalFrame.add(substance);
        return internalFrame;
    }

    private JInternalFrame createGameFrame() {
        JInternalFrame canvasFrame = new JInternalFrame("Game Frame");
        canvasFrame.setSize(800, 600);
        canvasFrame.setLocation(80, 80);
        canvasFrame.setVisible(true);

        canvas = new Canvas() {
            @Override
            public final void addNotify() {
                super.addNotify();
                startLWJGL();
            }

            @Override
            public final void removeNotify() {
                stopLWJGL();
                super.removeNotify();
            }
        };

        canvasFrame.add(canvas);
        canvas.setSize(canvasFrame.getContentPane().getSize());
        canvas.setFocusable(true);
        canvas.requestFocus();
        canvas.setIgnoreRepaint(true);

        Thread thread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    while (!Mouse.isCreated()) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    while (Mouse.next()) {
                        int wheelD = Mouse.getDWheel();
                        if (wheelD != 0) {
                            System.out.println("" + wheelD);
                        }
                    }
                }
            }
        };
        thread.start();

        return canvasFrame;
    }

    protected void gameLoop() {
        while (running) {
            Display.sync(60);
            Display.update();
        }

        Display.destroy();
    }

    @Override
    public void init() {

    }

    protected void initGL() {
    }

    private void loadSwing() {

        switchSubstance();

        menuBar = new JMenuBar();
        JMenu file = new JMenu("File");
        file.add(new JMenuItem("Item 1"));
        file.add(new JMenuItem("Item 2"));
        file.add(new JMenuItem("Item 3"));
        menuBar.add(file);

        setJMenuBar(menuBar);

        JDesktopPane desktop = new JDesktopPane();
        this.add(desktop);

        JInternalFrame canvasFrame = createGameFrame();
        desktop.add(canvasFrame);

        JInternalFrame debugFrame = createDebugFrame();
        desktop.add(debugFrame);

        this.setSize(1024, 768);

    }

    @Override
    public void start() {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                loadSwing();
            }
        });

    }

    public void startLWJGL() {
        gameThread = new Thread() {

            @Override
            public void run() {
                running = true;
                try {
                    Display.setParent(canvas);
                    Display.create();
                    initGL();
                } catch (LWJGLException e) {
                    e.printStackTrace();
                    return;
                }
                gameLoop();
            }
        };
        gameThread.start();
    }

    private void stopLWJGL() {
        Display.destroy();
    }

    protected void switchMetal() {
        try {
            UIManager.setLookAndFeel(new MetalLookAndFeel());
            UIManager.getLookAndFeelDefaults().put("ClassLoader", getClass().getClassLoader());
            SwingUtilities.updateComponentTreeUI(this);
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }

    protected void switchSubstance() {
        try {
            UIManager.setLookAndFeel(new SubstanceBusinessBlackSteelLookAndFeel());
            UIManager.getLookAndFeelDefaults().put("ClassLoader", getClass().getClassLoader());
            SwingUtilities.updateComponentTreeUI(this);
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }

}


The Substance L&F jars can be found here: http://grepcode.com/snapshot/repo1.maven.org/maven2/com.github.insubstantial/substance/7.2.1/

spasi

You need to make sure you're satisfying AWT's requirements for heavyweight-lightweight component mixing. Details here.

I wasn't able to test exactly what you described, but I managed to reproduced the issue using your code in a JFrame instead of an applet (without Substance too). AWT's mixing doesn't seem to work well when the JInternalFrame z-indices change. The fix was to add a ComponentListener and an InternalFrameListener to the game frame and do this on each event:

SwingUtilities.getWindowAncestor(canvasFrame).validate();


Another issue I noticed is that clicking on the game canvas does not active the JInternalFrame, or bring it to the front. This happens only on Windows, on Linux it behaves correctly. I'll try to implement a fix for that and let you know.

spasi

The internal frame activation should be fixed now, please try the next nightly build.

JeroenWarmerdam

It only partially fixed the issue. I've created a video that shows the issue and also deployed an applet where the issue can be reproduced:

Video: http://www.tygron.com/bug/lwjgl_video.html
Applet: http://www.tygron.com/bug/index.html

spasi

I decompiled the applet and I don't see the validate() calls I suggested you should use. I'm afraid that's the only way to make it work properly. Also, please update to the latest nightly (#63), it has another fix that may affect your test case.

I also don't see the point of the Focus/Unfocus buttons, what are you trying to achieve exactly?

JeroenWarmerdam

I thought I was using the right build. I'll check to be sure and implement the fix you're suggesting. The focus/unfocus are show that the menu behaves differently is the canvas is set focusable or not.