diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index e537b1e8542..a6ba2c5d1cb 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -730,8 +730,6 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { return; } - LOGGER.debug("Setting " + frame.getTitle() + " active"); - activeFrame = frame; desktopPane.moveToFront(activeFrame); activeFrame.setBounds(0, 0, desktopPane.getWidth(), desktopPane.getHeight()); diff --git a/Mage.Client/src/main/java/mage/client/MagePane.java b/Mage.Client/src/main/java/mage/client/MagePane.java index d44c83cfc13..0fce76161a1 100644 --- a/Mage.Client/src/main/java/mage/client/MagePane.java +++ b/Mage.Client/src/main/java/mage/client/MagePane.java @@ -1,5 +1,8 @@ package mage.client; + import mage.client.game.GamePane; + + import javax.swing.*; import java.awt.*; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -7,7 +10,7 @@ /** * GUI: basic class for all full screen frames/tabs (example: game pane, deck editor pane, card viewer, etc) * - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public abstract class MagePane extends javax.swing.JLayeredPane { @@ -42,9 +45,16 @@ MageFrame.getDesktop().remove(this); } + /** + * Called on frame go to topmost (example: start new game, open deck editor, switched to another game) + * Use it to load game/gui settings + */ public void activated() { } + /** + * Called on frame go from topmost to hidden (use it to save current gui/game settings) + */ public void deactivated() { } diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index 47c0a880edc..00aaaa617ce 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -145,7 +145,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE = "gameManaAutopaymentOnlyOne"; // Size of frame to check if divider locations should be used - public static final String KEY_MAGE_PANEL_LAST_SIZE = "gamepanelLastSize"; + public static final String KEY_MAGE_PANEL_LAST_SIZE = "gamepanelLastSize"; // TODO: remove? // pref settings of table settings and filtering public static final String KEY_TABLES_FILTER_SETTINGS = "tablePanelFilterSettings"; @@ -183,6 +183,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_GAMEPANEL_DIVIDER_LOCATION_0 = "gamepanelDividerLocation0"; public static final String KEY_GAMEPANEL_DIVIDER_LOCATION_1 = "gamepanelDividerLocation1"; + public static final String KEY_GAMEPANEL_DIVIDER_LOCATIONS_HAND_STACK = "gamepanelDividerLocationsHandStack"; public static final String KEY_GAMEPANEL_DIVIDER_LOCATION_2 = "gamepanelDividerLocation2"; public static final String KEY_TOURNAMENT_PLAYER_COLUMNS_WIDTH = "tournamentPlayerPanelColumnWidth"; @@ -3083,7 +3084,6 @@ public class PreferencesDialog extends javax.swing.JDialog { }//GEN-LAST:event_showFullImagePathActionPerformed private void cbSaveToZipFilesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbSaveToZipFilesActionPerformed - // TODO add your handling code here: }//GEN-LAST:event_cbSaveToZipFilesActionPerformed private void btnBrowseImageLocationActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnBrowseImageLocationActionPerformed diff --git a/Mage.Client/src/main/java/mage/client/game/GamePane.java b/Mage.Client/src/main/java/mage/client/game/GamePane.java index f8585b6e4c8..1c7e4674b37 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePane.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePane.java @@ -13,16 +13,12 @@ import mage.client.MagePane; */ public class GamePane extends MagePane { - /** - * Creates new form GamePane - */ public GamePane() { initComponents(); SwingUtilities.invokeLater(() -> { gamePanel.setJLayeredPane(this); gamePanel.installComponents(); }); - } public void showGame(UUID currentTableId, UUID parentTableId, UUID gameId, UUID playerId) { @@ -71,6 +67,13 @@ public class GamePane extends MagePane { gamePanel.replayGame(gameId); } + /** + * Called on game frame prepared and showed as top panel (on new game started, on panel switch from main menu, etc) + */ + public void onShow() { + gamePanel.restoreDividerLocations(); + } + @SuppressWarnings("unchecked") private void initComponents() { @@ -101,12 +104,12 @@ public class GamePane extends MagePane { @Override public void deactivated() { - gamePanel.deactivated(); + gamePanel.onDeactivated(); } @Override public void activated() { - gamePanel.activated(); + gamePanel.onActivated(); } @Override diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 83b25995e5a..74d7395baea 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -1,5 +1,7 @@ package mage.client.game; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import mage.cards.Card; import mage.cards.MageCard; import mage.cards.action.ActionCallback; @@ -45,6 +47,7 @@ import java.awt.*; import java.awt.event.*; import java.beans.PropertyVetoException; import java.io.Serializable; +import java.lang.reflect.Type; import java.util.List; import java.util.*; import java.util.concurrent.CancellationException; @@ -76,6 +79,14 @@ public final class GamePanel extends javax.swing.JPanel { private static final String CMD_AUTO_ORDER_NAME_LAST = "cmdAutoOrderNameLast"; private static final String CMD_AUTO_ORDER_RESET_ALL = "cmdAutoOrderResetAll"; + // on window resize: 0.0 keep left size (hand), 1.0 keep right size (stack), 0.5 keep proportion + private static final double DIVIDER_KEEP_LEFT_COMPONENT = 0.0; + private static final double DIVIDER_KEEP_RIGHT_COMPONENT = 1.0; + private static final double DIVIDER_KEEP_PROPORTION = 0.5; + private static final int DIVIDER_POSITION_DEFAULT = -1; + private static final int DIVIDER_POSITION_HIDDEN_LEFT_OR_TOP = -2; + private static final int DIVIDER_POSITION_HIDDEN_RIGHT_OR_BOTTOM = -3; + private final Map players = new LinkedHashMap<>(); private final Map playersWhoLeft = new LinkedHashMap<>(); @@ -220,9 +231,14 @@ public final class GamePanel extends javax.swing.JPanel { pnlCommandsSkipAndStack.setOpaque(false); pnlCommandsSkipAndStack.add(pnlShortCuts, BorderLayout.NORTH); pnlCommandsSkipAndStack.add(stackObjects, BorderLayout.CENTER); + // ... split: feedback + hand <|> skip + stack + jSplitPaneHandStack.setLeftComponent(pnlCommandsFeedbackAndHand); + jSplitPaneHandStack.setRightComponent(pnlCommandsSkipAndStack); + jSplitPaneHandStack.setResizeWeight(DIVIDER_KEEP_RIGHT_COMPONENT); + pnlCommandsFeedbackAndHand.setMinimumSize(new Dimension(0, 0)); // allow any sizes for hand + pnlCommandsSkipAndStack.setMinimumSize(new Dimension(0, 0)); // allow any sizes for stack // ... all - pnlCommandsRoot.add(pnlCommandsFeedbackAndHand, BorderLayout.CENTER); - pnlCommandsRoot.add(pnlCommandsSkipAndStack, BorderLayout.EAST); + pnlCommandsRoot.add(jSplitPaneHandStack, BorderLayout.CENTER); pnlHelperHandButtonsStackArea.add(pnlCommandsRoot, BorderLayout.SOUTH); // prepare commands buttons panel with flow layout (instead custom from IDE) @@ -316,6 +332,7 @@ public final class GamePanel extends javax.swing.JPanel { })); pnlHelperHandButtonsStackArea.addComponentListener(componentAdapterPlayField); + initComponents = false; setGUISize(true); @@ -325,6 +342,7 @@ public final class GamePanel extends javax.swing.JPanel { Map components = new HashMap<>(); components.put("jSplitPane1", jSplitPane1); + components.put("jSplitPaneHandStack", jSplitPaneHandStack); components.put("pnlBattlefield", pnlBattlefield); components.put("pnlHelperHandButtonsStackArea", pnlHelperHandButtonsStackArea); components.put("hand", handContainer); @@ -338,7 +356,13 @@ public final class GamePanel extends javax.swing.JPanel { public void cleanUp() { MageFrame.removeGame(gameId); + + // TODO: on 2+ games it called AFTER new game started, so second game will get old divider settings + // must be another way to save changed settings + // (not on change location -- there are big picture hotkeys to hide/show - it uses divider) + // So current solution: remove hidden big pucture feature and add save on change saveDividerLocations(); + this.gameChatPanel.cleanUp(); this.userChatPanel.cleanUp(); @@ -481,6 +505,7 @@ public final class GamePanel extends javax.swing.JPanel { private void setGUISize(boolean themeReload) { jSplitPane0.setDividerSize(GUISizeHelper.dividerBarSize); jSplitPane1.setDividerSize(GUISizeHelper.dividerBarSize); + jSplitPaneHandStack.setDividerSize(GUISizeHelper.dividerBarSize); jSplitPane2.setDividerSize(GUISizeHelper.dividerBarSize); txtHoldPriority.setFont(new Font(GUISizeHelper.gameFeedbackPanelFont.getFontName(), Font.BOLD, GUISizeHelper.gameFeedbackPanelFont.getSize())); @@ -620,37 +645,138 @@ public final class GamePanel extends javax.swing.JPanel { } private void saveDividerLocations() { + saveCurrentDividerLocation(jSplitPaneHandStack, PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATIONS_HAND_STACK); + + // TODO: migrate to new version // save panel sizes and divider locations. Rectangle rec = MageFrame.getDesktop().getBounds(); String sb = Double.toString(rec.getWidth()) + 'x' + rec.getHeight(); PreferencesDialog.saveValue(PreferencesDialog.KEY_MAGE_PANEL_LAST_SIZE, sb); PreferencesDialog.saveValue(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATION_0, Integer.toString(this.jSplitPane0.getDividerLocation())); PreferencesDialog.saveValue(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATION_1, Integer.toString(this.jSplitPane1.getDividerLocation())); + //PreferencesDialog.saveValue(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATION_HAND_STACK, Integer.toString(this.jSplitPaneHandStack.getDividerLocation())); PreferencesDialog.saveValue(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATION_2, Integer.toString(this.jSplitPane2.getDividerLocation())); } - private void restoreDividerLocations() { - Rectangle rec = MageFrame.getDesktop().getBounds(); - if (rec != null) { - String size = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_MAGE_PANEL_LAST_SIZE, null); - String sb = Double.toString(rec.getWidth()) + 'x' + rec.getHeight(); - // use divider positions only if screen size is the same as it was the time the settings were saved - if (size != null && size.equals(sb)) { + private Map loadAllDividerLocations(String settingsKey) { + Map res; + Type type = new TypeToken>() { + }.getType(); + try { + String savedData = PreferencesDialog.getCachedValue(settingsKey, ""); + res = new Gson().fromJson(savedData, type); + } catch (Exception e) { + res = null; + logger.error("Found broken data for divider locations " + settingsKey, e); + } - String location = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATION_0, null); - if (location != null && jSplitPane0 != null) { - jSplitPane0.setDividerLocation(Integer.parseInt(location)); - } - location = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATION_1, null); - if (location != null && jSplitPane1 != null) { - jSplitPane1.setDividerLocation(Integer.parseInt(location)); - } - location = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATION_2, null); - if (location != null && jSplitPane2 != null) { - jSplitPane2.setDividerLocation(Integer.parseInt(location)); - } + if (res == null) { + res = new HashMap<>(); + } + + return res; + } + + private void saveAllDividerLocations(Map newValues, String settingsKey) { + PreferencesDialog.saveValue(settingsKey, new Gson().toJson(newValues)); + } + + private void saveCurrentDividerLocation(JSplitPane splitPane, String settingsKey) { + Map allLocations = loadAllDividerLocations(settingsKey); + + Rectangle screenRec = MageFrame.getDesktop().getBounds(); + String screenKey = String.format("%d_x_%d", screenRec.width, screenRec.height); + + // store location or hidden state (left/right) + int newLocation = splitPane.getDividerLocation(); + if (newLocation == splitPane.getMinimumDividerLocation()) { + newLocation = DIVIDER_POSITION_HIDDEN_LEFT_OR_TOP; + } else if (newLocation == splitPane.getMaximumDividerLocation()) { + newLocation = DIVIDER_POSITION_HIDDEN_RIGHT_OR_BOTTOM; + } + + allLocations.put(screenKey, newLocation); + saveAllDividerLocations(allLocations, settingsKey); + } + + /** + * Restore split position from last time used + * + * @param splitPane + * @param settingsKey + * @param defaultProportion 0.25 means 25% for left component and 75% for right + */ + private void restoreCurrentDividerLocation(JSplitPane splitPane, String settingsKey, double defaultProportion) { + Map allLocations = loadAllDividerLocations(settingsKey); + + Rectangle screenRec = MageFrame.getDesktop().getBounds(); + String screenKey = String.format("%d_x_%d", screenRec.width, screenRec.height); + + // on first run it has nothing in saved values, so make sure to use default location (depends on inner components preferred sizes) + // WARNING, new divider location must be restored independently in swing threads + // (possible bug: size is zero until other dividers recalculated, e.g. game panel with hand + stack + chats) + int newLocation = allLocations.getOrDefault(screenKey, DIVIDER_POSITION_DEFAULT); + if (newLocation == DIVIDER_POSITION_DEFAULT) { + // use default location + SwingUtilities.invokeLater(() -> { + splitPane.setDividerLocation(defaultProportion); + }); + } else if (newLocation == DIVIDER_POSITION_HIDDEN_LEFT_OR_TOP) { + // use hidden (hide left) + SwingUtilities.invokeLater(() -> { + splitPane.setDividerLocation(defaultProportion); + splitPane.getLeftComponent().setMinimumSize(new Dimension()); + splitPane.setDividerLocation(0.0d); + }); + } else if (newLocation == DIVIDER_POSITION_HIDDEN_RIGHT_OR_BOTTOM) { + // use hidden (hide right) + SwingUtilities.invokeLater(() -> { + splitPane.setDividerLocation(defaultProportion); + splitPane.getLeftComponent().setMinimumSize(new Dimension()); + splitPane.setDividerLocation(1.0d); + }); + } else { + // use saved location + SwingUtilities.invokeLater(() -> { + splitPane.setDividerLocation(newLocation); + }); + } + } + + public void restoreDividerLocations() { + // split/divider locations must be restored after game panel will be visible, e.g. on game show + + // TODO: migrate to new version + // restore for each screen size + Rectangle rec = MageFrame.getDesktop().getBounds(); + + // TODO: save for each size, not last only, need code research + String size = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_MAGE_PANEL_LAST_SIZE, null); + String sb = Double.toString(rec.getWidth()) + 'x' + rec.getHeight(); + + // use divider positions only if screen size is the same as it was the time the settings were saved + if (size != null && size.equals(sb)) { + String location = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATION_0, null); + if (location != null && jSplitPane0 != null) { + jSplitPane0.setDividerLocation(Integer.parseInt(location)); + } + location = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATION_1, null); + if (location != null && jSplitPane1 != null) { + jSplitPane1.setDividerLocation(Integer.parseInt(location)); + } + location = PreferencesDialog.getCachedValue(KEY_GAMEPANEL_DIVIDER_LOCATIONS_HAND_STACK, null); + if (location != null && jSplitPaneHandStack != null) { + // TODO: enable + //jSplitPaneHandStack.setDividerLocation(Integer.parseInt(location)); + } + location = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATION_2, null); + if (location != null && jSplitPane2 != null) { + jSplitPane2.setDividerLocation(Integer.parseInt(location)); } } + + // new version + restoreCurrentDividerLocation(jSplitPaneHandStack, KEY_GAMEPANEL_DIVIDER_LOCATIONS_HAND_STACK, 0.70); } private boolean isSmallMode() { @@ -1414,8 +1540,10 @@ public final class GamePanel extends javax.swing.JPanel { jPhases.invalidate(); } - // Called if the game frame is deactivated because the tabled the deck editor or other frames go to foreground - public void deactivated() { + public void onDeactivated() { + // save current dividers location + saveDividerLocations(); + // hide the non modal windows (because otherwise they are shown on top of the new active pane) // TODO: is it need to hide other dialogs like graveyards (CardsView)? for (CardInfoWindowDialog windowDialog : exiles.values()) { @@ -1441,8 +1569,11 @@ public final class GamePanel extends javax.swing.JPanel { } } - // Called if the game frame comes to front again - public void activated() { + public void onActivated() { + // restore divider positions + // must be called by swing after all pack and paint done (possible bug: zero size in restored divider) + SwingUtilities.invokeLater(this::restoreDividerLocations); + // hide the non modal windows (because otherwise they are shown on top of the new active pane) for (CardInfoWindowDialog windowDialog : exiles.values()) { windowDialog.show(); @@ -2100,6 +2231,7 @@ public final class GamePanel extends javax.swing.JPanel { private void initComponents() { abilityPicker = new mage.client.components.ability.AbilityPicker(GUISizeHelper.dialogGuiScale); jSplitPane1 = new javax.swing.JSplitPane(); + jSplitPaneHandStack = new javax.swing.JSplitPane(); jSplitPane0 = new javax.swing.JSplitPane(); jPanel2 = new javax.swing.JPanel(); pnlHelperHandButtonsStackArea = new javax.swing.JPanel(); @@ -2172,18 +2304,22 @@ public final class GamePanel extends javax.swing.JPanel { stackObjects = new mage.client.cards.Cards(); jSplitPane1.setBorder(null); - jSplitPane1.setDividerSize(7); - jSplitPane1.setResizeWeight(1.0); + jSplitPane1.setDividerSize(7); // TODO: add gui scale support? + jSplitPane1.setResizeWeight(DIVIDER_KEEP_RIGHT_COMPONENT); jSplitPane1.setOneTouchExpandable(true); - jSplitPane1.setMinimumSize(new java.awt.Dimension(26, 48)); + jSplitPane1.setMinimumSize(new java.awt.Dimension(26, 48)); // TODO: add gui scale support? + + jSplitPaneHandStack.setBorder(null); + jSplitPaneHandStack.setDividerSize(7); + jSplitPaneHandStack.setResizeWeight(DIVIDER_KEEP_RIGHT_COMPONENT); + jSplitPaneHandStack.setOneTouchExpandable(true); + jSplitPaneHandStack.setMinimumSize(new java.awt.Dimension(26, 48)); jSplitPane0.setBorder(null); jSplitPane0.setDividerSize(7); - jSplitPane0.setResizeWeight(1.0); + jSplitPane0.setResizeWeight(DIVIDER_KEEP_RIGHT_COMPONENT); jSplitPane0.setOneTouchExpandable(true); - restoreDividerLocations(); - lblPhase.setLabelFor(txtPhase); lblPhase.setText("Phase:"); @@ -2570,7 +2706,7 @@ public final class GamePanel extends javax.swing.JPanel { pnlReplay.setOpaque(false); jSplitPane2.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); - jSplitPane2.setResizeWeight(0.5); + jSplitPane2.setResizeWeight(DIVIDER_KEEP_PROPORTION); jSplitPane2.setLeftComponent(userChatPanel); jSplitPane2.setBottomComponent(gameChatPanel); @@ -2580,8 +2716,8 @@ public final class GamePanel extends javax.swing.JPanel { phasesContainer.add(jPhases); // split: battlefield <|> chats - jSplitPane1.setLeftComponent(pnlHelperHandButtonsStackArea); - jSplitPane1.setRightComponent(jSplitPane2); + jSplitPane1.setLeftComponent(pnlHelperHandButtonsStackArea); // TODO: research + jSplitPane1.setRightComponent(jSplitPane2); // TODO: reseach // Set individual area sizes of big card pane // TODO: research - is it possible to use border layout without all custom code @@ -2601,12 +2737,13 @@ public final class GamePanel extends javax.swing.JPanel { jPanel2.setOpaque(false); - // game pane and chat/log pane - jSplitPane0.setLeftComponent(jSplitPane1); + // split: game <|> chat/log + jSplitPane0.setLeftComponent(jSplitPane1); // TODO: research -- possible typo, must be jSplitPane1? // big card and buttons - jSplitPane0.setRightComponent(jPanel2); + jSplitPane0.setRightComponent(jPanel2); // TODO: research // TODO: need reseach, possible reason of weird scrolls restore + // TODO: remove javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -3096,6 +3233,7 @@ public final class GamePanel extends javax.swing.JPanel { private javax.swing.JPanel pnlHelperHandButtonsStackArea; private javax.swing.JSplitPane jSplitPane0; private javax.swing.JSplitPane jSplitPane1; + private javax.swing.JSplitPane jSplitPaneHandStack; private javax.swing.JLabel lblActivePlayer; private javax.swing.JLabel lblPhase; private javax.swing.JLabel lblPriority; diff --git a/Mage.Client/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java b/Mage.Client/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java index 5f0a9b67a74..50f034f7250 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java +++ b/Mage.Client/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java @@ -84,6 +84,7 @@ public class ThemePluginImpl implements ThemePlugin { // TODO: research - is all components used? And why it make transparent? unsetOpaque(ui.get("jSplitPane1")); + unsetOpaque(ui.get("jSplitPaneHandStack")); // TODO: miss jSplitPane0? unsetOpaque(ui.get("pnlBattlefield")); unsetOpaque(ui.get("pnlHelperHandButtonsStackArea")); unsetOpaque(ui.get("hand"));