Skip to content

Commit

Permalink
GUI: reworked GUI to support non-blocking UI:
Browse files Browse the repository at this point in the history
- GUI: added non-blocking UI to almost all app and game dialogs;
- GUI: it allows to switch between UI dialogs and use any UI elements at any moment;
- GUI: it allows to use chat, card popup, battlefield, concede and other features while choosing (related to #12670);
- GUI: it allows to download images while playing (related to #4160, not fully tested);
- GUI: enabled by default, can be disabled by java option: -Dxmage.guiModalMode=true
- connection: auto-connect will be visible in main menu on startup;
- connection: removed some unused features (auto-connect by command line);
- connection: added <ESC> button to close connection dialog;
- download: added background images download (see non-blocking UI);
- download: improved cancel stability and fixes that it can't stop preparing/downloading process in some use cases;
- app: fixed freezes on macOS systems in some use cases (related to #12431, #11292, #9300, #4920);
  • Loading branch information
JayDi85 committed Sep 7, 2024
1 parent 2fc4e94 commit 6625db1
Show file tree
Hide file tree
Showing 40 changed files with 614 additions and 532 deletions.
136 changes: 55 additions & 81 deletions Mage.Client/src/main/java/mage/client/MageFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,8 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
private static final String LITE_MODE_ARG = "-lite";
private static final String GRAY_MODE_ARG = "-gray";
private static final String FULL_SCREEN_PROP = "xmage.fullScreen"; // -Dxmage.fullScreen=false
private static final String GUI_MODAL_MODE_PROP = "xmage.guiModalMode"; // -Dxmage.guiModalMode=false
private static final String SKIP_DONE_SYMBOLS = "-skipDoneSymbols";
private static final String USER_ARG = "-user";
private static final String PASSWORD_ARG = "-pw";
private static final String SERVER_ARG = "-server";
private static final String PORT_ARG = "-port";
private static final String DEBUG_ARG = "-debug"; // enable debug button in main menu

private static final String NOT_CONNECTED_TEXT = "<not connected>";
Expand All @@ -121,11 +118,8 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
private static boolean grayMode = false;
private static boolean macOsFullScreenEnabled = true;
private static boolean skipSmallSymbolGenerationForExisting = false;
private static String startUser = null;
private static String startPassword = "";
private static String startServer = "localhost";
private static int startPort = -1;
private static boolean debugMode = false;
private static boolean guiModalModeEnabled = false; // non-blocking UI mode enabled by default

private JToggleButton switchPanelsButton = null; // from main menu
private static String SWITCH_PANELS_BUTTON_NAME = "Switch panels";
Expand Down Expand Up @@ -167,6 +161,10 @@ public static boolean isSkipSmallSymbolGenerationForExisting() {
return skipSmallSymbolGenerationForExisting;
}

public static boolean isGuiModalModeEnabled() {
return guiModalModeEnabled;
}

@Override
public MageVersion getVersion() {
return VERSION;
Expand Down Expand Up @@ -305,10 +303,10 @@ public void componentRemoved(ContainerEvent e) {
SessionHandler.startSession(this);
callbackClient = new CallbackClientImpl(this);
connectDialog = new ConnectDialog();
desktopPane.add(connectDialog, JLayeredPane.MODAL_LAYER);
desktopPane.add(connectDialog, connectDialog.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
errorDialog = new ErrorDialog();
errorDialog.setLocation(100, 100);
desktopPane.add(errorDialog, JLayeredPane.MODAL_LAYER);
desktopPane.add(errorDialog, errorDialog.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);

try {
this.whatsNewDialog = new WhatsNewDialog();
Expand Down Expand Up @@ -387,15 +385,16 @@ public void componentResized(ComponentEvent e) {
setGUISize();
setConnectButtonText(NOT_CONNECTED_BUTTON);
SwingUtilities.invokeLater(() -> {
disableButtons();
updateMemUsageTask.execute();
LOGGER.info("Client start up time: " + ((System.currentTimeMillis() - startTime) / 1000 + " seconds"));
if (autoConnect()) {
enableButtons();

if (Boolean.parseBoolean(MageFrame.getPreferences().get("autoConnect", "false"))) {
startAutoConnect();
} else {
connectDialog.showDialog();
connectDialog.showDialog(this::setWindowTitle);
}
setWindowTitle();

setWindowTitle(); // make sure title is actual on startup
});

// run what's new checks (loading in background)
Expand Down Expand Up @@ -866,13 +865,13 @@ public void showTournament(UUID tableId, UUID tournamentId) {

public void showGameEndDialog(GameEndView gameEndView) {
GameEndDialog gameEndDialog = new GameEndDialog(gameEndView);
desktopPane.add(gameEndDialog, JLayeredPane.MODAL_LAYER);
desktopPane.add(gameEndDialog, gameEndDialog.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
gameEndDialog.showDialog();
}

public void showTableWaitingDialog(UUID roomId, UUID tableId, boolean isTournament) {
TableWaitingDialog tableWaitingDialog = new TableWaitingDialog();
desktopPane.add(tableWaitingDialog, JLayeredPane.MODAL_LAYER);
desktopPane.add(tableWaitingDialog, tableWaitingDialog.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
tableWaitingDialog.showDialog(roomId, tableId, isTournament);
}

Expand All @@ -886,14 +885,23 @@ public static boolean stopConnecting() {
return SessionHandler.stopConnecting();
}

public boolean autoConnect() {
boolean autoConnectParamValue = startUser != null || Boolean.parseBoolean(MageFrame.getPreferences().get("autoConnect", "false"));
boolean status = false;
if (autoConnectParamValue) {
LOGGER.info("Auto-connecting to " + MagePreferences.getServerAddress());
status = performConnect(false);
}
return status;
public void startAutoConnect() {
LOGGER.info("Auto-connecting to " + MagePreferences.getServerAddress());
setConnectButtonText("AUTO-CONNECT to " + MagePreferences.getLastServerAddress());

SwingUtilities.invokeLater(() -> {
// TODO: run it as task, not in GUI thread - it can help to enable auto-connect cancel button like ConnectionDialog
boolean isConnected = false;
try {
isConnected = performConnect(false);
} finally {
// on bad - change text manual
// on good - it will be changed inside connection code
if (!isConnected) {
setConnectButtonText(NOT_CONNECTED_BUTTON);
}
}
});
}

private boolean performConnect(boolean reconnect) {
Expand All @@ -907,7 +915,6 @@ private boolean performConnect(boolean reconnect) {
ProxyType proxyType = ProxyType.valueByText(MageFrame.getPreferences().get("proxyType", "None"));
String proxyUsername = MageFrame.getPreferences().get("proxyUsername", "");
String proxyPassword = MageFrame.getPreferences().get("proxyPassword", "");
setCursor(new Cursor(Cursor.WAIT_CURSOR));
currentConnection = new Connection();
currentConnection.setUsername(userName);
currentConnection.setPassword(password);
Expand All @@ -927,6 +934,7 @@ private boolean performConnect(boolean reconnect) {
setUserPrefsToConnection(currentConnection);
}

setCursor(new Cursor(Cursor.WAIT_CURSOR));
try {
LOGGER.debug("connecting (auto): " + currentConnection.getProxyType().toString()
+ ' ' + currentConnection.getProxyHost() + ' ' + currentConnection.getProxyPort() + ' ' + currentConnection.getProxyUsername());
Expand Down Expand Up @@ -1172,21 +1180,20 @@ private void btnConnectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FI
if (SessionHandler.isConnected()) {
tryDisconnectOrExit(false);
} else {
connectDialog.showDialog();
setWindowTitle();
connectDialog.showDialog(this::setWindowTitle);
}
}//GEN-LAST:event_btnConnectActionPerformed

public void btnAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAboutActionPerformed
JInternalFrame[] windows = desktopPane.getAllFramesInLayer(JLayeredPane.MODAL_LAYER);
JInternalFrame[] windows = desktopPane.getAllFrames();
for (JInternalFrame window : windows) {
if (window instanceof AboutDialog) {
// don't open the window twice.
return;
}
}
AboutDialog aboutDialog = new AboutDialog();
desktopPane.add(aboutDialog, JLayeredPane.MODAL_LAYER);
desktopPane.add(aboutDialog, aboutDialog.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
aboutDialog.showDialog(VERSION);
}//GEN-LAST:event_btnAboutActionPerformed

Expand Down Expand Up @@ -1286,16 +1293,6 @@ private void tryDisconnectOrExit(Boolean needExit) {
}
}

public void enableButtons() {
btnConnect.setEnabled(true);
btnDeckEditor.setEnabled(true);
}

public void disableButtons() {
btnConnect.setEnabled(true);
btnDeckEditor.setEnabled(true);
}

public void hideServerLobby() {
this.tablesPane.hideTables();
updateSwitchPanelsButton();
Expand Down Expand Up @@ -1416,7 +1413,7 @@ public void showUserRequestDialog(final UserRequestMessage userRequestMessage) {
private void innerShowUserRequestDialog(final UserRequestMessage userRequestMessage) {
UserRequestDialog userRequestDialog = new UserRequestDialog();
userRequestDialog.setLocation(100, 100);
desktopPane.add(userRequestDialog, JLayeredPane.MODAL_LAYER);
desktopPane.add(userRequestDialog, userRequestDialog.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
userRequestDialog.showDialog(userRequestMessage);
}

Expand Down Expand Up @@ -1463,8 +1460,10 @@ public void showErrorDialog(String errorType, Throwable e) {

public void showErrorDialog(String errorType, String errorTitle, String errorText) {
if (SwingUtilities.isEventDispatchThread()) {
// calls from gui
errorDialog.showDialog(errorType, errorTitle, errorText);
} else {
// calls from another thread like download images or game events
SwingUtilities.invokeLater(() -> errorDialog.showDialog(errorType, errorTitle, errorText));
}
}
Expand Down Expand Up @@ -1519,33 +1518,21 @@ public static void main(final String[] args) {
if (arg.startsWith(GRAY_MODE_ARG)) {
grayMode = true;
}
if (System.getProperty(FULL_SCREEN_PROP) != null) {
macOsFullScreenEnabled = Boolean.parseBoolean(System.getProperty(FULL_SCREEN_PROP));
}
if (arg.startsWith(SKIP_DONE_SYMBOLS)) {
skipSmallSymbolGenerationForExisting = true;
}
if (arg.startsWith(USER_ARG)) {
startUser = args[i + 1];
i++;
}
if (arg.startsWith(PASSWORD_ARG)) {
startPassword = args[i + 1];
i++;
}
if (arg.startsWith(SERVER_ARG)) {
startServer = args[i + 1];
i++;
}
if (arg.startsWith(PORT_ARG)) {
startPort = Integer.parseInt(args[i + 1]);
i++;
}
if (arg.startsWith(DEBUG_ARG)) {
debugMode = true;
}
}

if (System.getProperty(FULL_SCREEN_PROP) != null) {
macOsFullScreenEnabled = Boolean.parseBoolean(System.getProperty(FULL_SCREEN_PROP));
}
if (System.getProperty(GUI_MODAL_MODE_PROP) != null) {
guiModalModeEnabled = Boolean.parseBoolean(System.getProperty(GUI_MODAL_MODE_PROP));
}

// enable debug menu by default for developer build (if you run it from source code)
debugMode |= VERSION.isDeveloperBuild();

Expand All @@ -1571,19 +1558,19 @@ public static void main(final String[] args) {
if (settingsVersion == 0) {
// fresh install or first run after 2024-08-14
// find best GUI size settings due screen resolution and DPI
LOGGER.info("settings: it's a first run, trying to apply GUI size settings");
LOGGER.info("Settings: it's a first run, trying to apply GUI size settings");

int screenDPI = Toolkit.getDefaultToolkit().getScreenResolution();
int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
LOGGER.info(String.format("settings: screen DPI - %d, screen height - %d", screenDPI, screenHeight));
LOGGER.info(String.format("Settings: screen DPI - %d, screen height - %d", screenDPI, screenHeight));

// find preset for
String preset = PreferencesDialog.getDefaultSizeSettings().findBestPreset(screenDPI, screenHeight);
if (preset != null) {
LOGGER.info("settings: selected preset " + preset);
LOGGER.info("Settings: selected preset " + preset);
PreferencesDialog.getDefaultSizeSettings().applyPreset(preset);
} else {
LOGGER.info("settings: WARNING, can't find compatible preset, use Preferences - GUI Size to setup your app");
LOGGER.info("Settings: WARNING, can't find compatible preset, use Preferences - GUI Size to setup your app");
}

PreferencesDialog.saveValue(PreferencesDialog.KEY_SETTINGS_VERSION, String.valueOf(1));
Expand All @@ -1598,21 +1585,12 @@ public static void main(final String[] args) {
}

// debug menu
if (debugMode) {
LOGGER.info("Settings: debug menu enabled");
}
instance.separatorDebug.setVisible(debugMode);
instance.btnDebug.setVisible(debugMode);

if (startUser != null) {
instance.currentConnection = new Connection();
instance.currentConnection.setUsername(startUser);
instance.currentConnection.setHost(startServer);
if (startPort > 0) {
instance.currentConnection.setPort(startPort);
} else {
instance.currentConnection.setPort(MagePreferences.getServerPortWithDefault(ClientDefaultSettings.port));
}
PreferencesDialog.setProxyInformation(instance.currentConnection);
instance.currentConnection.setPassword(startPassword);
}
instance.setVisible(true);
});
}
Expand Down Expand Up @@ -1735,7 +1713,6 @@ public int getPanelsCount(boolean onlyActive) {
public void connected(final String message) {
SwingUtilities.invokeLater(() -> {
setConnectButtonText(message);
enableButtons();
});
}

Expand All @@ -1756,7 +1733,6 @@ public void disconnected(boolean askToReconnect, boolean keepMySessionActive) {
// TODO: why it ignore askToReconnect here, but use custom reconnect dialog later?! Need research
SessionHandler.disconnect(false, keepMySessionActive);
setConnectButtonText(NOT_CONNECTED_BUTTON);
disableButtons();
hideGames();
hideServerLobby();
if (askToReconnect) {
Expand Down Expand Up @@ -1845,9 +1821,7 @@ public void sendUserReplay(PlayerAction playerAction, UserRequestMessage userReq
SessionHandler.removeTable(userRequestMessage.getRoomId(), userRequestMessage.getTableId());
break;
case CLIENT_RECONNECT:
if (performConnect(true)) {
enableButtons();
}
performConnect(true);
break;
case CLIENT_REPLAY_ACTION:
SessionHandler.stopReplay(userRequestMessage.getGameId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,12 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
dlg = optionPane.createDialog("Generating Deck");
dlg.setResizable(false);
dlg.setVisible(true);
dlg.dispose();
if (dlg.isModal()) {
// on modal - it's done here
dlg.dispose();
} else {
// on non-modal - it's do nothing yet
}
}

private void enableAdvancedPanel(boolean enable) {
Expand Down
Loading

0 comments on commit 6625db1

Please sign in to comment.