Files
runelite/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
Twiglet1022 223057a136 Use SwingUtilities#invokeLater
This ensures that the calls to setExtendedState are thread safe as that
is probably what was causing the occasional game crash before.
2019-04-25 23:46:01 +01:00

923 lines
25 KiB
Java

/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.ui;
import com.google.common.base.Strings;
import java.applet.Applet;
import java.awt.Canvas;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.TrayIcon;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import static javax.swing.JOptionPane.ERROR_MESSAGE;
import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.GameState;
import net.runelite.api.Player;
import net.runelite.api.Point;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.RuneLite;
import net.runelite.client.RuneLiteProperties;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.config.ExpandResizeType;
import net.runelite.client.config.Keybind;
import net.runelite.client.config.RuneLiteConfig;
import net.runelite.client.config.WarningOnExit;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.NavigationButtonAdded;
import net.runelite.client.events.NavigationButtonRemoved;
import net.runelite.client.input.KeyManager;
import net.runelite.client.input.MouseAdapter;
import net.runelite.client.input.MouseListener;
import net.runelite.client.input.MouseManager;
import net.runelite.client.ui.skin.SubstanceRuneLiteLookAndFeel;
import net.runelite.client.util.HotkeyListener;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.OSType;
import net.runelite.client.util.OSXUtil;
import net.runelite.client.util.SwingUtil;
import org.pushingpixels.substance.internal.SubstanceSynapse;
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceTitlePaneUtilities;
/**
* Client UI.
*/
@Slf4j
@Singleton
public class ClientUI
{
private static final String CONFIG_GROUP = "runelite";
private static final String CONFIG_CLIENT_BOUNDS = "clientBounds";
private static final String CONFIG_CLIENT_MAXIMIZED = "clientMaximized";
private static final int CLIENT_WELL_HIDDEN_MARGIN = 160;
private static final int CLIENT_WELL_HIDDEN_MARGIN_TOP = 10;
public static final BufferedImage ICON = ImageUtil.getResourceStreamFromClass(ClientUI.class, "/runelite.png");
@Getter
private TrayIcon trayIcon;
private final RuneLiteProperties properties;
private final RuneLiteConfig config;
private final KeyManager keyManager;
private final MouseManager mouseManager;
private final Applet client;
private final ConfigManager configManager;
private final Provider<ClientThread> clientThreadProvider;
private final CardLayout cardLayout = new CardLayout();
private final Rectangle sidebarButtonPosition = new Rectangle();
private boolean withTitleBar;
private BufferedImage sidebarOpenIcon;
private BufferedImage sidebarClosedIcon;
private ContainableFrame frame;
private JPanel navContainer;
private PluginPanel pluginPanel;
private ClientPluginToolbar pluginToolbar;
private ClientTitleToolbar titleToolbar;
private JButton currentButton;
private NavigationButton currentNavButton;
private boolean sidebarOpen;
private JPanel container;
private NavigationButton sidebarNavigationButton;
private JButton sidebarNavigationJButton;
private Dimension lastClientSize;
@Inject
private ClientUI(
RuneLiteProperties properties,
RuneLiteConfig config,
KeyManager keyManager,
MouseManager mouseManager,
@Nullable Applet client,
ConfigManager configManager,
Provider<ClientThread> clientThreadProvider)
{
this.properties = properties;
this.config = config;
this.keyManager = keyManager;
this.mouseManager = mouseManager;
this.client = client;
this.configManager = configManager;
this.clientThreadProvider = clientThreadProvider;
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (!event.getGroup().equals("runelite") ||
event.getKey().equals(CONFIG_CLIENT_MAXIMIZED) ||
event.getKey().equals(CONFIG_CLIENT_BOUNDS))
{
return;
}
SwingUtilities.invokeLater(() -> updateFrameConfig(event.getKey().equals("lockWindowSize")));
}
@Subscribe
public void onNavigationButtonAdded(final NavigationButtonAdded event)
{
SwingUtilities.invokeLater(() ->
{
final NavigationButton navigationButton = event.getButton();
final PluginPanel pluginPanel = navigationButton.getPanel();
final boolean inTitle = !event.getButton().isTab() && withTitleBar;
final int iconSize = 16;
if (pluginPanel != null)
{
navContainer.add(pluginPanel.getWrappedPanel(), navigationButton.getTooltip());
}
final JButton button = SwingUtil.createSwingButton(navigationButton, iconSize, (navButton, jButton) ->
{
final PluginPanel panel = navButton.getPanel();
if (panel == null)
{
return;
}
boolean doClose = currentButton != null && currentButton == jButton && currentButton.isSelected();
if (doClose)
{
contract();
currentButton.setSelected(false);
currentNavButton.setSelected(false);
currentButton = null;
currentNavButton = null;
}
else
{
if (currentButton != null)
{
currentButton.setSelected(false);
}
if (currentNavButton != null)
{
currentNavButton.setSelected(false);
}
currentButton = jButton;
currentNavButton = navButton;
currentButton.setSelected(true);
currentNavButton.setSelected(true);
expand(navButton);
}
});
if (inTitle)
{
titleToolbar.addComponent(event.getButton(), button);
titleToolbar.revalidate();
}
else
{
pluginToolbar.addComponent(event.getButton(), button);
pluginToolbar.revalidate();
}
});
}
@Subscribe
public void onNavigationButtonRemoved(final NavigationButtonRemoved event)
{
SwingUtilities.invokeLater(() ->
{
pluginToolbar.removeComponent(event.getButton());
pluginToolbar.revalidate();
titleToolbar.removeComponent(event.getButton());
titleToolbar.revalidate();
final PluginPanel pluginPanel = event.getButton().getPanel();
if (pluginPanel != null)
{
navContainer.remove(pluginPanel.getWrappedPanel());
}
});
}
@Subscribe
public void onGameStateChanged(final GameStateChanged event)
{
if (event.getGameState() != GameState.LOGGED_IN || !(client instanceof Client) || !config.usernameInTitle())
{
return;
}
final Client client = (Client)this.client;
final ClientThread clientThread = clientThreadProvider.get();
// Keep scheduling event until we get our name
clientThread.invokeLater(() ->
{
if (client.getGameState() != GameState.LOGGED_IN)
{
return true;
}
final Player player = client.getLocalPlayer();
if (player == null)
{
return false;
}
final String name = player.getName();
if (Strings.isNullOrEmpty(name))
{
return false;
}
frame.setTitle(properties.getTitle() + " - " + name);
return true;
});
}
/**
* Initialize UI.
*
* @param runelite runelite instance that will be shut down on exit
* @throws Exception exception that can occur during creation of the UI
*/
public void open(final RuneLite runelite) throws Exception
{
SwingUtilities.invokeAndWait(() ->
{
// Set some sensible swing defaults
SwingUtil.setupDefaults();
// Use substance look and feel
SwingUtil.setTheme(new SubstanceRuneLiteLookAndFeel());
// Use custom UI font
SwingUtil.setFont(FontManager.getRunescapeFont());
// Create main window
frame = new ContainableFrame();
// Try to enable fullscreen on OSX
OSXUtil.tryEnableFullscreen(frame);
frame.setTitle(properties.getTitle());
frame.setIconImage(ICON);
frame.getLayeredPane().setCursor(Cursor.getDefaultCursor()); // Prevent substance from using a resize cursor for pointing
frame.setLocationRelativeTo(frame.getOwner());
frame.setResizable(true);
SwingUtil.addGracefulExitCallback(frame,
() ->
{
saveClientBoundsConfig();
runelite.shutdown();
},
this::showWarningOnExit
);
container = new JPanel();
container.setLayout(new BoxLayout(container, BoxLayout.X_AXIS));
container.add(new ClientPanel(client));
navContainer = new JPanel();
navContainer.setLayout(cardLayout);
navContainer.setMinimumSize(new Dimension(0, 0));
navContainer.setMaximumSize(new Dimension(0, 0));
navContainer.setPreferredSize(new Dimension(0, 0));
// To reduce substance's colorization (tinting)
navContainer.putClientProperty(SubstanceSynapse.COLORIZATION_FACTOR, 1.0);
container.add(navContainer);
pluginToolbar = new ClientPluginToolbar();
titleToolbar = new ClientTitleToolbar();
frame.add(container);
// Add key listener
final HotkeyListener sidebarListener = new HotkeyListener(() ->
new Keybind(KeyEvent.VK_F11, InputEvent.CTRL_DOWN_MASK))
{
@Override
public void hotkeyPressed()
{
toggleSidebar();
}
};
keyManager.registerKeyListener(sidebarListener);
// Add mouse listener
final MouseListener mouseListener = new MouseAdapter()
{
@Override
public MouseEvent mousePressed(MouseEvent mouseEvent)
{
if (SwingUtilities.isLeftMouseButton(mouseEvent) && sidebarButtonPosition.contains(mouseEvent.getPoint()))
{
SwingUtilities.invokeLater(ClientUI.this::toggleSidebar);
mouseEvent.consume();
}
return mouseEvent;
}
};
mouseManager.registerMouseListener(mouseListener);
// Decorate window with custom chrome and titlebar if needed
withTitleBar = config.enableCustomChrome();
frame.setUndecorated(withTitleBar);
if (withTitleBar)
{
frame.getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
final JComponent titleBar = SubstanceCoreUtilities.getTitlePaneComponent(frame);
titleToolbar.putClientProperty(SubstanceTitlePaneUtilities.EXTRA_COMPONENT_KIND, SubstanceTitlePaneUtilities.ExtraComponentKind.TRAILING);
titleBar.add(titleToolbar);
// Substance's default layout manager for the title bar only lays out substance's components
// This wraps the default manager and lays out the TitleToolbar as well.
LayoutManager delegate = titleBar.getLayout();
titleBar.setLayout(new LayoutManager()
{
@Override
public void addLayoutComponent(String name, Component comp)
{
delegate.addLayoutComponent(name, comp);
}
@Override
public void removeLayoutComponent(Component comp)
{
delegate.removeLayoutComponent(comp);
}
@Override
public Dimension preferredLayoutSize(Container parent)
{
return delegate.preferredLayoutSize(parent);
}
@Override
public Dimension minimumLayoutSize(Container parent)
{
return delegate.minimumLayoutSize(parent);
}
@Override
public void layoutContainer(Container parent)
{
delegate.layoutContainer(parent);
final int width = titleToolbar.getPreferredSize().width;
titleToolbar.setBounds(titleBar.getWidth() - 75 - width, 0, width, titleBar.getHeight());
}
});
}
// Update config
updateFrameConfig(true);
// Create hide sidebar button
sidebarOpenIcon = ImageUtil.getResourceStreamFromClass(ClientUI.class, withTitleBar ? "open.png" : "open_rs.png");
sidebarClosedIcon = ImageUtil.flipImage(sidebarOpenIcon, true, false);
sidebarNavigationButton = NavigationButton
.builder()
.priority(100)
.icon(sidebarClosedIcon)
.onClick(this::toggleSidebar)
.build();
sidebarNavigationJButton = SwingUtil.createSwingButton(
sidebarNavigationButton,
0,
null);
titleToolbar.addComponent(sidebarNavigationButton, sidebarNavigationJButton);
toggleSidebar();
// Layout frame
frame.pack();
frame.revalidateMinimumSize();
// Create tray icon (needs to be created after frame is packed)
trayIcon = SwingUtil.createTrayIcon(ICON, properties.getTitle(), frame);
// Move frame around (needs to be done after frame is packed)
if (config.rememberScreenBounds())
{
try
{
Rectangle clientBounds = configManager.getConfiguration(
CONFIG_GROUP, CONFIG_CLIENT_BOUNDS, Rectangle.class);
if (clientBounds != null)
{
frame.setBounds(clientBounds);
frame.revalidateMinimumSize();
}
else
{
frame.setLocationRelativeTo(frame.getOwner());
}
if (configManager.getConfiguration(CONFIG_GROUP, CONFIG_CLIENT_MAXIMIZED) != null)
{
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
}
}
catch (Exception ex)
{
log.warn("Failed to set window bounds", ex);
frame.setLocationRelativeTo(frame.getOwner());
}
}
else
{
frame.setLocationRelativeTo(frame.getOwner());
}
// If the frame is well hidden (e.g. unplugged 2nd screen),
// we want to move it back to default position as it can be
// hard for the user to reposition it themselves otherwise.
Rectangle clientBounds = frame.getBounds();
Rectangle screenBounds = frame.getGraphicsConfiguration().getBounds();
if (clientBounds.x + clientBounds.width - CLIENT_WELL_HIDDEN_MARGIN < screenBounds.getX() ||
clientBounds.x + CLIENT_WELL_HIDDEN_MARGIN > screenBounds.getX() + screenBounds.getWidth() ||
clientBounds.y + CLIENT_WELL_HIDDEN_MARGIN_TOP < screenBounds.getY() ||
clientBounds.y + CLIENT_WELL_HIDDEN_MARGIN > screenBounds.getY() + screenBounds.getHeight())
{
frame.setLocationRelativeTo(frame.getOwner());
}
// Show frame
frame.setVisible(true);
frame.toFront();
requestFocus();
giveClientFocus();
log.info("Showing frame {}", frame);
});
// Show out of date dialog if needed
if (client == null)
{
SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(frame,
"Error loading client! Check your logs for more details.",
"Unable to load client",
ERROR_MESSAGE));
}
else if (!(client instanceof Client))
{
SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(frame,
"RuneLite has not yet been updated to work with the latest\n"
+ "game update, it will work with reduced functionality until then.",
"RuneLite is outdated", INFORMATION_MESSAGE));
}
}
private boolean showWarningOnExit()
{
if (config.warningOnExit() == WarningOnExit.ALWAYS)
{
return true;
}
if (config.warningOnExit() == WarningOnExit.LOGGED_IN && client instanceof Client)
{
return ((Client) client).getGameState() != GameState.LOGIN_SCREEN;
}
return false;
}
/**
* Paint this component to target graphics
*
* @param graphics the graphics
*/
public void paint(final Graphics graphics)
{
assert SwingUtilities.isEventDispatchThread() : "paint must be called on EDT";
frame.paint(graphics);
}
/**
* Gets component width.
*
* @return the width
*/
public int getWidth()
{
return frame.getWidth();
}
/**
* Gets component height.
*
* @return the height
*/
public int getHeight()
{
return frame.getHeight();
}
/**
* Returns true if this component has focus.
*
* @return true if component has focus
*/
public boolean isFocused()
{
return frame.isFocused();
}
/**
* Request focus on this component and then on client component
*/
public void requestFocus()
{
if (OSType.getOSType() == OSType.MacOS)
{
OSXUtil.requestFocus();
}
// The workaround for Windows is to minimise and then un-minimise the client to bring
// it to the front because java.awt.Window#toFront doesn't work reliably.
else if (OSType.getOSType() == OSType.Windows && !frame.isFocused())
{
if ((frame.getExtendedState() & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH)
{
SwingUtilities.invokeLater(() ->
{
frame.setExtendedState(JFrame.ICONIFIED);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
});
}
else
{
SwingUtilities.invokeLater(() ->
{
// If the client is snapped to the top and bottom edges of the screen, setExtendedState will
// will reset it so setSize and setLocation ensure that the client doesn't move or resize.
// It is done this way because Windows does not support JFrame.MAXIMIZED_VERT
int x = frame.getLocation().x;
int y = frame.getLocation().y;
int width = frame.getWidth();
int height = frame.getHeight();
frame.setExtendedState(JFrame.ICONIFIED);
frame.setExtendedState(JFrame.NORMAL);
frame.setLocation(x, y);
frame.setSize(width, height);
});
}
}
frame.requestFocus();
giveClientFocus();
}
/**
* Get offset of game canvas in game window
*
* @return game canvas offset
*/
public Point getCanvasOffset()
{
if (client instanceof Client)
{
final Canvas canvas = ((Client) client).getCanvas();
if (canvas != null)
{
final java.awt.Point point = SwingUtilities.convertPoint(canvas, 0, 0, frame);
return new Point(point.x, point.y);
}
}
return new Point(0, 0);
}
/**
* Paint UI related overlays to target graphics
* @param graphics target graphics
*/
public void paintOverlays(final Graphics2D graphics)
{
if (!(client instanceof Client) || withTitleBar)
{
return;
}
final Client client = (Client) this.client;
final int x = client.getRealDimensions().width - sidebarOpenIcon.getWidth() - 5;
// Offset sidebar button if resizable mode logout is visible
final Widget logoutButton = client.getWidget(WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_LOGOUT_BUTTON);
final int y = logoutButton != null && !logoutButton.isHidden() && logoutButton.getParent() != null
? logoutButton.getHeight() + logoutButton.getRelativeY()
: 5;
final BufferedImage image = sidebarOpen ? sidebarClosedIcon : sidebarOpenIcon;
final Rectangle sidebarButtonRange = new Rectangle(x - 15, 0, image.getWidth() + 25, client.getRealDimensions().height);
final Point mousePosition = new Point(
client.getMouseCanvasPosition().getX() + client.getViewportXOffset(),
client.getMouseCanvasPosition().getY() + client.getViewportYOffset());
if (sidebarButtonRange.contains(mousePosition.getX(), mousePosition.getY()))
{
graphics.drawImage(image, x, y, null);
}
// Update button dimensions
sidebarButtonPosition.setBounds(x, y, image.getWidth(), image.getHeight());
}
public GraphicsConfiguration getGraphicsConfiguration()
{
return frame.getGraphicsConfiguration();
}
private void toggleSidebar()
{
// Toggle sidebar open
boolean isSidebarOpen = sidebarOpen;
sidebarOpen = !sidebarOpen;
// Select/deselect buttons
if (currentButton != null)
{
currentButton.setSelected(sidebarOpen);
}
if (currentNavButton != null)
{
currentNavButton.setSelected(sidebarOpen);
}
if (isSidebarOpen)
{
sidebarNavigationJButton.setIcon(new ImageIcon(sidebarOpenIcon));
sidebarNavigationJButton.setToolTipText("Open SideBar");
contract();
// Remove plugin toolbar
container.remove(pluginToolbar);
}
else
{
sidebarNavigationJButton.setIcon(new ImageIcon(sidebarClosedIcon));
sidebarNavigationJButton.setToolTipText("Close SideBar");
// Try to restore last panel
expand(currentNavButton);
// Add plugin toolbar back
container.add(pluginToolbar);
}
// Revalidate sizes of affected Swing components
container.revalidate();
giveClientFocus();
if (sidebarOpen)
{
frame.expandBy(pluginToolbar.getWidth());
}
else
{
frame.contractBy(pluginToolbar.getWidth());
}
}
private void expand(@Nullable NavigationButton button)
{
if (button == null)
{
return;
}
final PluginPanel panel = button.getPanel();
if (panel == null)
{
return;
}
if (!sidebarOpen)
{
toggleSidebar();
}
int width = panel.getWrappedPanel().getPreferredSize().width;
int expandBy = pluginPanel != null ? pluginPanel.getWrappedPanel().getPreferredSize().width - width : width;
pluginPanel = panel;
// Expand sidebar
navContainer.setMinimumSize(new Dimension(width, 0));
navContainer.setMaximumSize(new Dimension(width, Integer.MAX_VALUE));
navContainer.setPreferredSize(new Dimension(width, 0));
navContainer.revalidate();
cardLayout.show(navContainer, button.getTooltip());
// panel.onActivate has to go after giveClientFocus so it can get focus if it needs.
giveClientFocus();
panel.onActivate();
// Check if frame was really expanded or contracted
if (expandBy > 0)
{
frame.expandBy(expandBy);
}
else if (expandBy < 0)
{
frame.contractBy(expandBy);
}
}
private void contract()
{
if (pluginPanel == null)
{
return;
}
pluginPanel.onDeactivate();
navContainer.setMinimumSize(new Dimension(0, 0));
navContainer.setMaximumSize(new Dimension(0, 0));
navContainer.setPreferredSize(new Dimension(0, 0));
navContainer.revalidate();
giveClientFocus();
frame.contractBy(pluginPanel.getWrappedPanel().getPreferredSize().width);
pluginPanel = null;
}
private void giveClientFocus()
{
if (client instanceof Client)
{
final Canvas c = ((Client) client).getCanvas();
if (c != null)
{
c.requestFocusInWindow();
}
}
else if (client != null)
{
client.requestFocusInWindow();
}
}
private void updateFrameConfig(boolean updateResizable)
{
if (frame == null)
{
return;
}
if (config.usernameInTitle() && (client instanceof Client))
{
final Player player = ((Client)client).getLocalPlayer();
if (player != null && player.getName() != null)
{
frame.setTitle(properties.getTitle() + " - " + player.getName());
}
}
else
{
frame.setTitle(properties.getTitle());
}
if (frame.isAlwaysOnTopSupported())
{
frame.setAlwaysOnTop(config.gameAlwaysOnTop());
}
if (updateResizable)
{
frame.setResizable(!config.lockWindowSize());
}
frame.setExpandResizeType(config.automaticResizeType());
frame.setContainedInScreen(config.containInScreen() && withTitleBar);
if (!config.rememberScreenBounds())
{
configManager.unsetConfiguration(CONFIG_GROUP, CONFIG_CLIENT_MAXIMIZED);
configManager.unsetConfiguration(CONFIG_GROUP, CONFIG_CLIENT_BOUNDS);
}
if (client == null)
{
return;
}
// The upper bounds are defined by the applet's max size
// The lower bounds are defined by the client's fixed size
int width = Math.max(Math.min(config.gameSize().width, 7680), Constants.GAME_FIXED_WIDTH);
int height = Math.max(Math.min(config.gameSize().height, 2160), Constants.GAME_FIXED_HEIGHT);
final Dimension size = new Dimension(width, height);
if (!size.equals(lastClientSize))
{
lastClientSize = size;
client.setSize(size);
client.setPreferredSize(size);
client.getParent().setPreferredSize(size);
client.getParent().setSize(size);
if (frame.isVisible())
{
frame.pack();
}
}
}
private void saveClientBoundsConfig()
{
if ((frame.getExtendedState() & JFrame.MAXIMIZED_BOTH) != 0)
{
configManager.setConfiguration(CONFIG_GROUP, CONFIG_CLIENT_MAXIMIZED, true);
}
else
{
final Rectangle bounds = frame.getBounds();
// Try to expand sidebar
if (!sidebarOpen)
{
bounds.width += pluginToolbar.getWidth();
}
if (config.automaticResizeType() == ExpandResizeType.KEEP_GAME_SIZE)
{
// Try to contract plugin panel
if (pluginPanel != null)
{
bounds.width -= pluginPanel.getWrappedPanel().getPreferredSize().width;
}
}
configManager.unsetConfiguration(CONFIG_GROUP, CONFIG_CLIENT_MAXIMIZED);
configManager.setConfiguration(CONFIG_GROUP, CONFIG_CLIENT_BOUNDS, bounds);
}
}
}