Can't toggle fullscreen/resizable window mode with Frame

I am attempting to do the follow:

  1. Allow my app to switch between full-screen & window modes
  2. When in window mode, allow the user to drag re-size the window, using a “java.awt.Frame”

I have a lot of code so I’m cutting it down to the problematic area.

This code meets my (1) goal, but makes no attempt at the (2)

[java]
public class CoreUI extends SimpleApplication {
Frame frame;

// other code

// This is called after this.settings.setFullscreen() has changed
// start is set true when this class is first created
private void applyGraphics(boolean start) {
	if( start ) {
		this.start();
	} else {
		this.restart();
	}
}

}
[/java]

This code meets my (2) goal, but will only work for its first execution - throwing various errors on additional calls. See my “TOFIX” comments

[java]
public class CoreUI extends SimpleApplication {
Frame frame;

// other code

private void applyGraphics(boolean start) {
	
	if( settings.isFullscreen() ) {
		if( frame != null ) {
			// occurs when switching from window to full screen
			frame.dispose(); // TOFIX: this code blocks indefinitely, waiting for the attached canvas to stop, calling .remove() or .removeAll() has the same effect
			frame = null;
		}
	} else {
		frame = new Frame(settings.getTitle());
		
		try {
			JmeCanvasContext c = (JmeCanvasContext) this.getContext();
			c.getCanvas().setSize(new Dimension(settings.getWidth(),settings.getHeight()));
			frame.add(c.getCanvas());
		} catch(Exception e) {
			// TOFIX: if this.start() had been called, the following error will occur:
			// java.lang.ClassCastException: com.jme3.system.lwjgl.LwjglDisplay cannot be cast to com.jme3.system.JmeCanvasContext
			// can this be safely recreated as a JmeCanvasContext?
			e.printStackTrace();
		}
		
		frame.pack();
		frame.setLocationRelativeTo(null);
		
		frame.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				stop();
			}
		});
		
		frame.addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				JmeCanvasContext c = (JmeCanvasContext) getContext();
				if( (c.getCanvas().getSize().getWidth() == settings.getWidth()) &&
					 c.getCanvas().getSize().getHeight() == settings.getHeight())
					return;
				
				settings.setWidth((int)c.getCanvas().getSize().getWidth());
				settings.setHeight((int)c.getCanvas().getSize().getHeight());
				
				saveGraphicsFile();
			}
		});

		frame.setVisible(true);
	}

	// NOTE: if "this.getContext()" was called, this.start() is unneeded and will throw an error
	if( start && settings.isFullscreen() ) {
		this.start();
	} else {
		this.restart();
	}
}

}
[/java]

Does anyone have an idea of how to get around these 2 issues?

Looks nice, but I think engine should be restarted when you resize it. Is that done in saveGraphicsFile() ?

The TOFIX in line 11 probably means you’re trying to do AWT/Swing calls in a non-AWT thread.
Use SwingUtilities.invokeLater to fix that, and AWT will call your dispose() after all other events have been handled. (You don’t care when exactly the frame will actually get disposed as long as it will eventually happen.)

Not sure about the second TOFIX, I’m using just a JFrame and never switch to fullscreen.
I’d probably start with entirely different operating modes and rebuild everything, then check what part of the machinery can be reused across the mode switch. Maybe I’d wait for the dispose() call to happen before activating fullscreen mode. (If the JVM shuts down: That’s because the last thread that’s marked as “daemon”, i.e. uninteresting, has finished. Can’t check right now but the LWJGL rendering thread should be non-daemon; make sure it’s running before dispose()ing the last window.)

not sure what you mean by “engine should be restarted”, did you mean line 60?
saveGraphicsFile() writes a file to remember width/height resolution, vsync, refresh rate, etc… You can ignore it.

I gave SwingUtilities.invokeLater a try,
with this the thread doesn’t freeze but the “frame.dispose()” in the invoke is simply never called…

I can get line 11 to execute correctly if I call “this.stop();”, but as far as I can tell this is was only meant to be called once, or am I wrong?
[java]
this.stop();
frame.dispose(); // this will work
this.restart(); // this will do nothing and the program will end, calling this.start() will say its already started
[/java]

If dispose() is never called, then something else disposes the window.
Did frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE) happen somewhere?
Or it might be that some framework code is doing the dispose(), then AWT decides that no message-receiving windows exist anymore and terminates its event loop.

You could set a breakpoint on JFrame.dispose (probably inherited from Window.dispose so you’d set the breakpoint there).
You’ll find the code that requested the dispose in the call stack.

I don’t have the JME sources right now, so I can’t look it up. As far as I remember, Application.stop closes everything down, and probably more than you want. I don’t know off the top of my head what exactly should be shut down.
However, single-stepping through the stop() call should give you a pretty good idea what subsystems are there, and it should give you an educated guess about what you actually want to shut down. Or at least a lead that you can verify using the docs.

The other advice would probably be “read the docs” :wink:
Switching between fullscreen and windowed mode isn’t such an outlandish use case, it should be covered.
Windowed mode may not be covered fully in the docs OTOH. It’s known to be buggy due to problems in the LWJGL implementation IIRC.

@reth said: not sure what you mean by "engine should be restarted", did you mean line 60? saveGraphicsFile() writes a file to remember width/height resolution, vsync, refresh rate, etc... You can ignore it.

No, I mean line 50 or so. When you resize the frame, you should restart the engine, as stated in the manual :
https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:intermediate:appsettings

Configure application settings in main(), before you call app.start() on the application object. If you change display settings during runtime, for eyample in simpleInitApp(), you must call app.restart() to make them take effect.