Redrawing stops on focus loss

I’m not sure where to look for a probably cause, so any tips on debugging this kind of problem appreciated.



The exact situation:

I’m running a jme3 application showing a rotating tree.

Whenever I alt-tab away from it, the rotation stops.

If I cover the window with something else and uncover it, the uncovered area is not redrawn (see screenshot).



The tree is game content, the top right was overlapped by the browser and didn’t get redrawn.



I’m running under Linux, using the XFCE window manager (i.e. Xubuntu).

I think I’m doing everything right (with thread issues and such), but of course I may have missed something - so the question is more which area to investigate first than anything else.

app.setPauseOnLostFocus(false);

2 Likes

Ah, that was quick and worked like a charm.

Well, almost - resizing the window changes the size by a pixel or two. I suspect that’s because the window gets recentered, or maybe it gets recreated; I’ll investigate a bit further unless somebody comes over with a full solution :slight_smile:

Darn.

Turned out that using restart() would recreate the application window, so the mouse would lose the border being dragged. Understandable.



Trouble is, without restart() the window will resize just fine but the LWJGL context won’t.

Is that a bug?



Even more trouble, I didn’t find a way to make the context resize inside the window. Nor a way to make the window replace the context with a properly-sized one. Not in Display or LinuxDisplay, anyway.

Did I overlook something, or is there really no way to do that?



Is this something that simply doesn’t work, and I should follow https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:swing_canvas for the windowed use case?

What about switching to fullscreen mode?



Sorry for that barrage of questions, just trying to keep all bases covered.

The main window isnt a swing window, you cannot resize it, it will always have the size of the gl context.

You need to recreate the context from the application with the new settings.

The cheap way would be “this setting requires a restart to be applied”.

The better is to recreate the context without stopping the application.

@normen

That can’t be right, there’s even an official API for making the main window resizable: Display.setResizable (true).



@Empire_Phoenix

Yes, recreating the context without stopping the application is exactly what I’m after.

Application.restart() does that. The snag here is that it recreates not just the context but the main window, too, which defeats dragging window borders with the mouse.

@toolforger said:
@normen
That can't be right, there's even an official API for making the main window resizable: Display.setResizable (true).

Yeah. Did you try it? It doesn't resize the content. What I said is true I'm afraid.

Yes.

With that, I can resize the window just fine with the mouse.

It’s just that enlarging the window does not enlarge the rendering area.

Shrinking the window does not shrink the rendering area, the rendered scene doesn’t get smaller. However, the output is clipped to window size.



You’re right that the render area can’t be resized (or at least it seems so), but what good is an API that allows resizing the window then?



The other question: Would it make sense to try the Swing display? It would be easy to put it into a JFrame (in fact I have seen code by others who do that).

Since nobody bothered to answer my last question: Yes, it works.

Details:



Resizing the JFrame will automatically resize the rendering area. This will scale the displayed scene, too (which is just what I wanted). Enforcing an aspect ratio or a minimum window size needs to be done at the Swing level.



The Swing window did not come with any noticeable different in frame rate. That’s not a big surprise, Swing’s performance problems are more-or-less gone since Java 5.



The basic trick is to override [Simple]Application’s start(), not call super.start(), and use this code somewhere in start():

[java]

JmeCanvasContext context =

(JmeCanvasContext) getContext();

canvas = context.getCanvas();

canvas.setSize(

getAppSettings().getWidth(),

getAppSettings().getHeight());

SwingUtilities.invokeLater(

new SwingApplicationStarter ());

[/java]

canvas can be embedded into any Swing or AWT component, just like a panel or button.



Details to consider:

  • In the traditional approach, you’d check for changed size inside update(), and put the new size into your Settings (and possibly save them). With Swing, it’s easier to attach a listener to the frame. Anything else related to window size or position is best dealt with in the listener, too (such as a policy on aspect ratio, minimum size, and such).
  • I don’t know the best way to switch to fullscreen mode with this. I’m pretty sure there’s a more elegant way, but it should always be possible to tear down the Swing setup and restart() the application traditionally and vice versa. The frame should be kept around and left open so it stays available if the user alt-tabs away from the application (a listener on the frame should then switch the application back to Swing mode).



    Here’s my code (without imports, and some comments added):

    [java]

    public class ClientApplicationSwing

    extends ClientApplication

    {

    // ClientApplication is my own variant of SimpleApplication.

    Canvas canvas; // Canvas is the standard AWT class.



    @Override

    public void start() {

    loadSettings (); // ClientApplication function to load my Settings object.

    setPauseOnLostFocus (false); // I like to keep it running.

    // Here we start doing things differently than traditionally.

    // If this were a descendant of SimpleApplication, you’d want

    // to setDragToRotate(true) here since this is windowed mode

    // where you don’t want to grab the mouse.

    createCanvas(); // Creates a canvas plus associated rendering machinery.

    // Now pull out the Canvas from that machinery:

    JmeCanvasContext context = (JmeCanvasContext) getContext();

    canvas = context.getCanvas();

    // We can now work with that canvas just like with any AWT/Swing Canvas.

    canvas.setSize(getAppSettings().getWidth(), getAppSettings().getHeight()); // SimpleApplication descendants would use settings instead of getAppSettings().

    SwingUtilities.invokeLater(new SwingApplicationStarter ());

    }



    protected class SwingApplicationStarter implements Runnable {

    @Override

    public void run() {

    // This is just some standard code to start a Swing application.

    // Except we insist on using “heavyweight” popup menus.

    // The lightweight ones assume that they do not cover anything

    // except other lightweight AWT components; lightweight popups

    // used to be an important optimization in the old 1.3 days but

    // became dubious in 1.4 and have become irrelevant.

    // Still, we need to set the default from lightweight to heavyweight

    // since our Canvas isn’t lightweight:

    JPopupMenu.setDefaultLightWeightPopupEnabled(false);

    // Nothing special anymore.

    JFrame frame = new JFrame (getAppSettings ().getTitle());

    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

    frame.addWindowListener (new SwingApplicationStopper ());

    frame.add(canvas);

    frame.pack();

    frame.setVisible(true);

    }

    }



    protected class SwingApplicationStopper extends WindowAdapter {

    @Override

    public void windowClosed(WindowEvent e) {

    // Make closing the window stop the render loop.

    // I don’t know why this is necessary; my guess is that the render

    // loop is marked as an interactive Java thread. The JVM will stop

    // when the last interactive thread dies, so we need to tell the

    // render loop to stop to close the program if the user closes the

    // main window.

    ClientApplicationSwing.this.stop ();

    }

    }



    }

    [/java]
1 Like