User interface makeover

This commit is contained in:
kuroppoi 2023-01-26 23:30:34 +01:00
parent 0d5023da59
commit 20df62398e
7 changed files with 193 additions and 56 deletions

View file

@ -89,9 +89,10 @@ public class Bootstrap {
UIManager.put("Brainwine.powerIcon", new ImageIcon(getClass().getResource("/powerIcon16x.png")));
UIManager.put("Brainwine.consoleFont", new Font("Consolas", Font.PLAIN, 12));
UIManager.put("Spinner.editorAlignment", JTextField.LEFT);
UIManager.put("TitlePane.unifiedBackground", false);
UIManager.put("Button.foreground", UIManager.get("MenuBar.foreground"));
SwingUtils.setDefaultFontSize(Math.min(28, Math.max(10, GuiPreferences.getInt(GuiPreferences.FONT_SIZE_KEY, 14))));
SwingUtils.setMenuBarEmbedded(GuiPreferences.getBoolean(GuiPreferences.EMBED_MENU_BAR_KEY, true));
GuiPreferences.createPropertyListeners();
// Create view
mainView = new MainView(this);

View file

@ -2,21 +2,32 @@ package brainwine.gui;
import static brainwine.gui.GuiConstants.DEEPWORLD_ASSEMBLY_PATH;
import static brainwine.gui.GuiConstants.DEEPWORLD_PLAYERPREFS;
import static brainwine.gui.GuiConstants.GUI_MARKER;
import static brainwine.gui.GuiConstants.HTTP_COMMUNITY_HUB_URL;
import static brainwine.gui.GuiConstants.HTTP_STEAM_DOWNLOAD_URL;
import static brainwine.gui.GuiConstants.STEAM_COMMUNITY_HUB_URL;
import static brainwine.gui.GuiConstants.STEAM_REGISTRY_LOCATION;
import static brainwine.gui.GuiConstants.STEAM_RUN_GAME_URL;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.LinearGradientPaint;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import brainwine.gui.component.ImagePanel;
import brainwine.util.DesktopUtils;
import brainwine.util.ProcessResult;
import brainwine.util.RegistryKey;
@ -24,8 +35,11 @@ import brainwine.util.RegistryUtils;
import brainwine.util.SwingUtils;
@SuppressWarnings("serial")
public class GamePanel extends JPanel {
public class GamePanel extends ImagePanel {
private static final Logger logger = LogManager.getLogger();
private final LinearGradientPaint gradientPaint = new LinearGradientPaint(0, 0, 0, 15,
new float[] {0.0F, 1.0F}, new Color[] {Color.BLACK, new Color(0, 0, 0, 0)});
private final JButton startGameButton;
private final JButton communityHubButton;
@ -46,12 +60,34 @@ public class GamePanel extends JPanel {
// Button panel
JPanel buttonPanel = new JPanel(new GridBagLayout());
buttonPanel.setOpaque(false);
JPanel topPanel = new JPanel(new GridLayout(1, 2));
topPanel.setOpaque(false);
topPanel.add(hostSettingsButton);
topPanel.add(communityHubButton);
buttonPanel.add(topPanel, SwingUtils.createConstraints(0, 0));
buttonPanel.add(startGameButton, SwingUtils.createConstraints(0, 1, 2, 1));
add(buttonPanel);
// Load & set background image
try {
setImage(ImageIO.read(getClass().getResourceAsStream("/background.jpg")));
} catch (IllegalArgumentException | IOException e) {
logger.error(GUI_MARKER, "Could not load background image", e);
}
}
@Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
// Draw shadow gradient below the title bar if this panel has a background image
if(getImage() != null) {
Graphics2D g2d = (Graphics2D)graphics;
g2d.setPaint(gradientPaint);
g2d.fillRect(0, 0, getWidth(), 15);
}
}
private void startGame() {

View file

@ -1,18 +1,13 @@
package brainwine.gui;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.swing.UIManager;
import brainwine.util.SwingUtils;
public class GuiPreferences {
public static final String ROOT_KEY = "brainwine";
public static final String THEME_KEY = "theme";
public static final String TAB_PLACEMENT_KEY = "tabPlacement";
public static final String FONT_SIZE_KEY = "fontSize";
public static final String EMBED_MENU_BAR_KEY = "embedMenuBar";
public static final String GATEWAY_HOST_KEY = "gatewayHost";
@ -27,29 +22,6 @@ public class GuiPreferences {
return preferences;
}
public static void createPropertyListeners() {
UIManager.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
if(event.getPropertyName().equals("lookAndFeel")) {
setString(THEME_KEY, UIManager.getLookAndFeel().getClass().getName());
}
}
});
UIManager.getDefaults().addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
String name = event.getPropertyName();
switch(name) {
case "defaultFont": setInt(FONT_SIZE_KEY, SwingUtils.getDefaultFontSize()); break;
case "TitlePane.menuBarEmbedded": setBoolean(EMBED_MENU_BAR_KEY, SwingUtils.isMenuBarEmbedded()); break;
}
}
});
}
public static void setString(String key, String value) {
get().put(key, value);
}

View file

@ -2,6 +2,7 @@ package brainwine.gui;
import static brainwine.gui.GuiConstants.DEEPWORLD_PLAYERPREFS;
import static brainwine.gui.GuiConstants.GITHUB_REPOSITORY_URL;
import static brainwine.gui.GuiConstants.GUI_MARKER;
import java.awt.BorderLayout;
import java.awt.Dimension;
@ -15,12 +16,12 @@ import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.UIManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.formdev.flatlaf.extras.FlatAnimatedLafChange;
import com.formdev.flatlaf.extras.components.FlatTabbedPane;
import com.formdev.flatlaf.extras.components.FlatTabbedPane.TabAlignment;
@ -42,25 +43,23 @@ public class MainView {
private final SettingsPanel settingsPanel;
public MainView(Bootstrap bootstrap) {
logger.info("Creating main view ...");
logger.info(GUI_MARKER, "Creating main view ...");
// Panels
// Panel
panel = new JPanel(new BorderLayout());
serverPanel = new ServerPanel(bootstrap);
settingsPanel = new SettingsPanel();
// Tabs
tabbedPane = new FlatTabbedPane();
tabbedPane.setShowContentSeparators(true);
tabbedPane.setTabPlacement(JTabbedPane.LEFT);
tabbedPane.setTabAlignment(TabAlignment.leading);
setTabPlacement(GuiPreferences.getInt(GuiPreferences.TAB_PLACEMENT_KEY, 1), false);
if(OperatingSystem.isWindows()) {
tabbedPane.addTab("Play Game", UIManager.getIcon("Brainwine.playIcon"), new GamePanel(this));
}
tabbedPane.addTab("Server", UIManager.getIcon("Brainwine.serverIcon"), serverPanel);
tabbedPane.addTab("Settings", UIManager.getIcon("Brainwine.settingsIcon"), settingsPanel);
tabbedPane.addTab("Server", UIManager.getIcon("Brainwine.serverIcon"), serverPanel = new ServerPanel(bootstrap));
tabbedPane.addTab("Settings", UIManager.getIcon("Brainwine.settingsIcon"), settingsPanel = new SettingsPanel(this));
panel.add(tabbedPane);
// Menu
@ -87,7 +86,7 @@ public class MainView {
}
});
frame.setJMenuBar(menuBar);
frame.setMinimumSize(new Dimension(848, 480));
frame.setMinimumSize(new Dimension(848, 520));
frame.setFocusable(true);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.add(panel);
@ -96,6 +95,26 @@ public class MainView {
frame.setVisible(true);
}
public void setTabPlacement(int tabPlacement) {
setTabPlacement(tabPlacement, true);
}
public void setTabPlacement(int tabPlacement, boolean animateChange) {
if(animateChange) {
FlatAnimatedLafChange.showSnapshot();
}
tabbedPane.setTabPlacement(Math.min(4, Math.max(1, tabPlacement)));
if(animateChange) {
FlatAnimatedLafChange.hideSnapshotWithAnimation();
}
}
public int getTabPlacement() {
return tabbedPane.getTabPlacement();
}
public void showHostSettings() {
tabbedPane.setSelectedComponent(settingsPanel);
settingsPanel.focusHostSettings();

View file

@ -13,7 +13,6 @@ import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
@ -34,13 +33,17 @@ import brainwine.util.SwingUtils;
@SuppressWarnings("serial")
public class SettingsPanel extends JPanel {
private final MainView mainView;
private JComboBox<Theme> themeBox;
private JComboBox<String> tabPlacementBox;
private JSpinner fontSizeSpinner;
private JCheckBox embedMenuBarCheckox;
private JCheckBox embedMenuBarCheckbox;
private FlatTextField gatewayHostField;
private FlatTextField apiHostField;
public SettingsPanel() {
public SettingsPanel(MainView mainView) {
this.mainView = mainView;
// Reset Button
JButton resetButton = new JButton("Reset to Defaults");
resetButton.addActionListener(event -> resetSettings(true));
@ -51,6 +54,7 @@ public class SettingsPanel extends JPanel {
// Button Panel
JPanel buttonPanel = new JPanel(new GridLayout(1, 2));
buttonPanel.setBorder(createCategoryBorder("Reset Settings"));
buttonPanel.add(resetButton);
buttonPanel.add(clearButton);
@ -62,8 +66,7 @@ public class SettingsPanel extends JPanel {
settingsPanel.add(createGameSettingsPanel(), SwingUtils.createConstraints(0, 1));
}
settingsPanel.add(new JSeparator(), SwingUtils.createConstraints(0, 2));
settingsPanel.add(buttonPanel, SwingUtils.createConstraints(0, 3));
settingsPanel.add(buttonPanel, SwingUtils.createConstraints(0, 2));
// Scroll pane (TODO doesn't actually scroll)
FlatScrollPane scrollPane = new FlatScrollPane();
@ -79,26 +82,33 @@ public class SettingsPanel extends JPanel {
themeBox = new JComboBox<>();
ThemeManager.getThemes().forEach(themeBox::addItem);
themeBox.setSelectedItem(ThemeManager.getCurrentTheme());
themeBox.addItemListener(item -> SwingUtilities.invokeLater(() -> ThemeManager.setTheme((Theme)item.getItem())));
themeBox.addActionListener(event -> setThemePreference((Theme)themeBox.getSelectedItem()));
// Tabbed pane orientation box
tabPlacementBox = new JComboBox<>(new String[] {"Top", "Left", "Bottom", "Right"});
tabPlacementBox.setSelectedIndex(mainView.getTabPlacement() - 1);
tabPlacementBox.addActionListener(event -> setTabPlacementPreference(tabPlacementBox.getSelectedIndex() + 1));
// Font size changer
fontSizeSpinner = new JSpinner(new SpinnerNumberModel(SwingUtils.getDefaultFontSize(), 10, 28, 1));
fontSizeSpinner.addChangeListener(event -> SwingUtils.setDefaultFontSize((int)fontSizeSpinner.getValue()));
fontSizeSpinner.addChangeListener(event -> setFontSizePreference((int)fontSizeSpinner.getValue()));
// Menu bar embed checkbox
embedMenuBarCheckox = new JCheckBox();
embedMenuBarCheckox.setSelected(SwingUtils.isMenuBarEmbedded());
embedMenuBarCheckox.addChangeListener(event -> SwingUtils.setMenuBarEmbedded(embedMenuBarCheckox.isSelected()));
embedMenuBarCheckbox = new JCheckBox();
embedMenuBarCheckbox.setSelected(SwingUtils.isMenuBarEmbedded());
embedMenuBarCheckbox.addChangeListener(event -> setEmbedMenuBarPreference(embedMenuBarCheckbox.isSelected()));
// Panel
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(createCategoryBorder("Visual Settings"));
panel.add(new JLabel("Theme"), SwingUtils.createConstraints(0, 0));
panel.add(themeBox, SwingUtils.createConstraints(1, 0));
panel.add(new JLabel("Font Size"), SwingUtils.createConstraints(0, 1));
panel.add(fontSizeSpinner, SwingUtils.createConstraints(1, 1));
panel.add(new JLabel("Embed Menu Bar"), SwingUtils.createConstraints(0, 2));
panel.add(embedMenuBarCheckox, SwingUtils.createConstraints(1, 2));
panel.add(new JLabel("Tab Placement"), SwingUtils.createConstraints(0, 1));
panel.add(tabPlacementBox, SwingUtils.createConstraints(1, 1));
panel.add(new JLabel("Font Size"), SwingUtils.createConstraints(0, 2));
panel.add(fontSizeSpinner, SwingUtils.createConstraints(1, 2));
panel.add(new JLabel("Embed Menu Bar"), SwingUtils.createConstraints(0, 3));
panel.add(embedMenuBarCheckbox, SwingUtils.createConstraints(1, 3));
return panel;
}
@ -149,13 +159,40 @@ public class SettingsPanel extends JPanel {
gatewayHostField.requestFocus();
}
private void setThemePreference(Theme theme) {
SwingUtilities.invokeLater(() -> {
ThemeManager.setTheme(theme);
});
GuiPreferences.setString(GuiPreferences.THEME_KEY, theme.getClassName());
}
private void setTabPlacementPreference(int tabPlacement) {
SwingUtilities.invokeLater(() -> {
mainView.setTabPlacement(tabPlacement);
});
GuiPreferences.setInt(GuiPreferences.TAB_PLACEMENT_KEY, tabPlacement);
}
private void setFontSizePreference(int fontSize) {
SwingUtils.setDefaultFontSize(fontSize);
GuiPreferences.setInt(GuiPreferences.FONT_SIZE_KEY, fontSize);
}
private void setEmbedMenuBarPreference(boolean embedMenuBar) {
SwingUtils.setMenuBarEmbedded(embedMenuBar);
GuiPreferences.setBoolean(GuiPreferences.EMBED_MENU_BAR_KEY, embedMenuBar);
}
private void resetSettings(boolean showPrompt) {
if(!showPrompt || JOptionPane.showConfirmDialog(getRootPane(),
"Are you sure you want to reset all settings to their default values?",
"Confirmation", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
themeBox.setSelectedItem(ThemeManager.getTheme(FlatMaterialDarkerIJTheme.class));
tabPlacementBox.setSelectedIndex(0);
fontSizeSpinner.setValue(14);
embedMenuBarCheckox.setSelected(true);
embedMenuBarCheckbox.setSelected(true);
if(OperatingSystem.isWindows()) {
gatewayHostField.setText("local");

View file

@ -0,0 +1,72 @@
package brainwine.gui.component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import javax.swing.JPanel;
/**
* A {@link JPanel} with a scaling and extending background image that always maintains its aspect ratio.
*/
@SuppressWarnings("serial")
public class ImagePanel extends JPanel {
private Image image;
public ImagePanel() {
super();
}
public ImagePanel(Image image) {
this.image = image;
}
@Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
if(image == null) {
return;
}
// Can this be simpler?
// Probably. Who knows. Don't really care.
int containerWidth = getWidth();
int containerHeight = getHeight();
int drawWidth = containerWidth;
int drawHeight = containerHeight;
float targetRatio = image.getWidth(this) / (float)image.getHeight(this);
float aspectRatio = drawWidth / (float)drawHeight;
float inverseTargetRatio = 1.0F / targetRatio;
if(aspectRatio > targetRatio) {
drawWidth = Math.round(drawHeight * targetRatio);
} else if(aspectRatio < targetRatio) {
drawHeight = Math.round(drawWidth * inverseTargetRatio);
}
if(drawWidth < containerWidth) {
drawHeight += (containerWidth - drawWidth) / targetRatio;
drawWidth = containerWidth;
} else if(drawHeight < containerHeight) {
drawWidth += (containerHeight - drawHeight) / inverseTargetRatio;
drawHeight = containerHeight;
}
int x = (containerWidth - drawWidth) / 2;
int y = (containerHeight - drawHeight) / 2;
Graphics2D g2d = (Graphics2D)graphics;
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(image, x, y, drawWidth, drawHeight, this);
}
public void setImage(Image image) {
this.image = image;
}
public Image getImage() {
return image;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB