Willicious
Willicious

Reputation: 73

How to implement Full Screen mode for a Windowed Java program

Expectation: It'll be easy to implement a Full Screen mode for my Java app (a fork of an existing app, for which the code is extensive and can be very difficult to work with). All I need to do is remove the border and menu bar, set the frame to the size of the screen, ensure it's "always on top". A few lines of code, 1 hour tops, little bit of testing.

Step 1: Removing the menu bar.

This was relatively easy, it's now a separate option with which users can choose to show/hide the menu bar if they wish. Done.

Step 2: Removing the border.

And this is as far as I can get. Set up the Full Screen option and call this method after exiting the config menu (making use of setUndecorated(true)):

void toggleFullScreen() {
    boolean shouldDisplayFullScreen = GameController.isOptionEnabled(GameController.Options.FULL_SCREEN);
    GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();

if (shouldDisplayFullScreen) {
    Core.saveProgramProps(); // Store current window size and position
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    setExtendedState(JFrame.NORMAL); // Reset state before changing size
    setUndecorated(true)            
    setJMenuBar(null); // Hide the menu bar            
    setSize(screenSize.width, screenSize.height);
    setLocation(0, 0);
    setAlwaysOnTop(true);
    setResizable(false);
} else {
    // Restore windowed mode - TODO: This currently fires every time the Options menu is opened and closed; we need to see if fullscreen has actually changed
    setAlwaysOnTop(false);
    applyPropsWindowSize();
    applyPropsWindowPosition();
    toggleMenuBarVisibility(); // Restore the menu bar depending on props
    setResizable(true);
    setUndecorated(false);
}

validate();

}

We get this error:

    public void setUndecorated(boolean undecorated) {
    /* Make sure we don't run in the middle of peer creation.*/
    synchronized (getTreeLock()) {
        if (isDisplayable()) {
            throw new IllegalComponentStateException("The frame is displayable.");

Erm...OK? Well, surely the frame needs to be displayable in order to... be displayed! Apparently not, so I get to Google searching and eventually find this thread. With a bit of manoeuvering, I add the example code to the main class, to be run as soon as the app starts and as default behaviour (purely for testing purposes at this point, eventually we want full control over the Full Screen mode):

public MainFrame() {
    GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
    device = env.getDefaultScreenDevice();
    isFullScreen = device.isFullScreenSupported();

    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setUndecorated(isFullScreen);
    setResizable(false);
    
    if (isFullScreen) {
        device.setFullScreenWindow(this);
    } else {
        // Windowed mode behavior
        setExtendedState(JFrame.MAXIMIZED_BOTH);
    }

    // Apply other initializations after fullscreen/window setup
    try {
        String currentFolderStr = URLDecoder.decode(getClass().getProtectionDomain().getCodeSource().getLocation().getFile(), "UTF-8");
        System.out.println("Current directory: " + currentFolderStr);
        boolean successful = Core.init(currentFolderStr); // initialize Core object
        if (!successful) {
            System.exit(0);
        }
    } catch (LException ex) {
        JOptionPane.showMessageDialog(null, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
        System.exit(1);
    } catch (Throwable ex) {
        ToolBox.showException(ex);
        System.exit(1);
    }

    initComponents();
    
    if (!isFullScreen)
        setMinimumSize(getSize());
    
    RepeatingReleasedEventsFixer.install();
    
    setVisible(true);
}

And, we're finally getting somewhere. The window can now be displayed undecorated. But, I get a ton of errors (not worth printing them all here), the window doesn't span the width of the screen, and disappears behind other active windows whenever a sub-window is opened. Not ideal, but at least we know the app can support being borderless, etc.

However, the only reason this worked at all is because it's all getting set up before the window is displayed. Once it's displayed, the original "toggle" method still generates the same "frame is displayable" error when we attempt to switch between the two modes.

Obviously, this would all need to be refactored into a single method and be callable whether or not the window is displayed, as the point is to be able to switch between FullScreen and Windowed whilst the app is open.

Any clues?

Upvotes: 4

Views: 66

Answers (1)

VGR
VGR

Reputation: 44404

I find it easier to make two JFrames: one for normal windowed mode, and one for fullscreen mode. I then add my window contents to the contentPane of one JFrame when switching modes. Since a component can have only one parent at any given time, adding it to a different JFrame automatically removes it from the other JFrame.

In this demo, pressing ‘F’ switches between fullscreen and windowed modes:

import java.time.LocalTime;

import java.io.Serial;

import java.awt.EventQueue;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Font;
import java.awt.Component;
import java.awt.Window;

import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.AbstractAction;
import javax.swing.Timer;

public class FullScreenDemo {
    private final JFrame normalWindow;

    private final JFrame fullScreenWindow;

    private final JComponent contents;

    public FullScreenDemo() {
        contents = new JPanel() {
            @Serial
            private static final long serialVersionUID = 1;

            @Override
            public Dimension getPreferredSize() {
                return new Dimension(800, 600);
            }

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);

                String s = String.format("%tT", LocalTime.now());
                Font font = g.getFont();
                font = font.deriveFont(getHeight() / 4f);
                g.setFont(font);

                int width = (int) Math.ceil(
                    g.getFontMetrics().getStringBounds(s, g).getWidth());

                int x = (getWidth() - width) / 2;
                int y = getHeight() / 2;

                g.drawString(s, x, y);
            }
        };

        contents.setFocusable(true);

        String fullscreenID = "toggle-fullscreen";
        contents.getActionMap().put(fullscreenID,
            new AbstractAction() {
                @Serial
                private static final long serialVersionUID = 1;

                @Override
                public void actionPerformed(ActionEvent event) {
                    toggleFullScreen();
                }
            });
        contents.getInputMap().put(KeyStroke.getKeyStroke("F"), fullscreenID);

        normalWindow = new JFrame("Full-Screen Demo");
        fullScreenWindow = new JFrame("Full-Screen Demo");
        fullScreenWindow.setUndecorated(true);

        WindowAdapter closeListener = new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                quit();
            }
        };

        normalWindow.addWindowListener(closeListener);
        fullScreenWindow.addWindowListener(closeListener);

        normalWindow.getContentPane().add(contents);
        normalWindow.pack();
        normalWindow.setLocationByPlatform(true);

        Timer timer = new Timer(1000, e -> contents.repaint());
        timer.start();
    }

    public void show() {
        normalWindow.setVisible(true);
    }

    private void quit() {
        System.exit(0);
    }

    private GraphicsDevice screenOf(Window window) {
        GraphicsConfiguration config = window.getGraphicsConfiguration();
        GraphicsDevice screen;
        if (config != null) {
            return config.getDevice();
        } else {
            GraphicsEnvironment env =
                GraphicsEnvironment.getLocalGraphicsEnvironment();
            return env.getDefaultScreenDevice();
        }
    }

    private void toggleFullScreen() {
        if (normalWindow.isAncestorOf(contents)) {
            GraphicsDevice screen = screenOf(normalWindow);

            normalWindow.setVisible(false);

            fullScreenWindow.getContentPane().add(contents);
            fullScreenWindow.pack();
            screen.setFullScreenWindow(fullScreenWindow);
        } else {
            GraphicsDevice screen = screenOf(fullScreenWindow);

            screen.setFullScreenWindow(null);
            fullScreenWindow.setVisible(false);

            normalWindow.getContentPane().add(contents);
            normalWindow.pack();
            normalWindow.setVisible(true);
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            FullScreenDemo demo = new FullScreenDemo();
            demo.show();
        });
    }
}

Upvotes: 0

Related Questions