Added functional servers list to launcher.

This commit is contained in:
Andrew Lalis 2021-06-24 09:13:03 +02:00
parent ee6d7a00cb
commit dec56be4af
7 changed files with 230 additions and 128 deletions

View File

@ -4,6 +4,6 @@ jpackage^
--name "Ace-of-Shades"^
--description "Top-down 2D shooter game inspired by Ace of Spades."^
--module-path "aos-client-3.1.jar;../../core/target/aos-core-3.1.jar"^
--module aos_client/nl.andrewlalis.aos_client.Client^
--module aos_client/nl.andrewlalis.aos_client.launcher.Launcher^
--win-shortcut^
--win-dir-chooser

View File

@ -1,6 +1,5 @@
package nl.andrewlalis.aos_client;
import nl.andrewlalis.aos_client.view.ConnectDialog;
import nl.andrewlalis.aos_client.view.GameFrame;
import nl.andrewlalis.aos_client.view.GamePanel;
import nl.andrewlalis.aos_core.model.Player;
@ -122,11 +121,4 @@ public class Client {
System.out.println("Sound manager closed.");
this.frame.dispose();
}
public static void main(String[] args) {
ConnectDialog dialog = new ConnectDialog();
dialog.setVisible(true);
}
}

View File

@ -1,16 +1,26 @@
package nl.andrewlalis.aos_client.launcher;
import nl.andrewlalis.aos_client.Client;
import nl.andrewlalis.aos_client.launcher.servers.ServerInfo;
import nl.andrewlalis.aos_client.launcher.servers.ServerInfoCellRenderer;
import nl.andrewlalis.aos_client.launcher.servers.ServerInfoListModel;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* Launcher application for starting the game. Because the client is only
* actually started when connecting to a server, this user interface serves as
* the menu that the user interacts with before joining a game.
*/
public class Launcher extends JFrame {
private static final Pattern addressPattern = Pattern.compile("(.+):(\\d+)");
@ -18,6 +28,7 @@ public class Launcher extends JFrame {
super("Ace of Shades - Launcher");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setContentPane(this.buildContent());
this.setPreferredSize(new Dimension(400, 500));
this.pack();
this.setLocationRelativeTo(null);
}
@ -25,12 +36,10 @@ public class Launcher extends JFrame {
private Container buildContent() {
JTabbedPane mainPanel = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
mainPanel.addTab("Connect", null, this.getConnectPanel(), "Connect to a server and play.");
JPanel serversPanel = new JPanel();
mainPanel.addTab("Servers", null, serversPanel, "View a list of available servers.");
JPanel settingsPanel = new JPanel();
mainPanel.addTab("Settings", null, settingsPanel, "Change game settings.");
mainPanel.addTab("Servers", null, this.getServersPanel(), "View a list of available servers.");
//
// JPanel settingsPanel = new JPanel();
// mainPanel.addTab("Settings", null, settingsPanel, "Change game settings.");
return mainPanel;
}
@ -58,7 +67,7 @@ public class Launcher extends JFrame {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (validateInput(addressField, usernameField)) {
if (validateConnectInput(addressField, usernameField)) {
connect(addressField, usernameField);
}
}
@ -72,7 +81,7 @@ public class Launcher extends JFrame {
cancelButton.addActionListener(e -> this.dispose());
JButton connectButton = new JButton("Connect");
connectButton.addActionListener(e -> {
if (validateInput(addressField, usernameField)) {
if (validateConnectInput(addressField, usernameField)) {
connect(addressField, usernameField);
}
});
@ -80,12 +89,72 @@ public class Launcher extends JFrame {
buttonPanel.add(connectButton);
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(new JLabel("Directly connect to a server and play."), BorderLayout.NORTH);
mainPanel.add(inputPanel, BorderLayout.CENTER);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
return mainPanel;
}
private boolean validateInput(JTextField addressField, JTextField usernameField) {
private Container getServersPanel() {
JPanel panel = new JPanel(new BorderLayout());
ServerInfoListModel listModel = new ServerInfoListModel();
listModel.add(new ServerInfo("one", "localhost:8035", "andrew"));
listModel.add(new ServerInfo("two", "localhost:25565", null));
JList<ServerInfo> serversList = new JList<>(listModel);
serversList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
serversList.setCellRenderer(new ServerInfoCellRenderer());
JScrollPane scrollPane = new JScrollPane(serversList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
panel.add(scrollPane, BorderLayout.CENTER);
serversList.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
serversList.setSelectedIndex(serversList.locationToIndex(e.getPoint()));
ServerInfo server = serversList.getSelectedValue();
if (server == null) return;
JPopupMenu menu = new JPopupMenu();
JMenuItem connectItem = new JMenuItem("Connect");
connectItem.addActionListener(a -> connect(server));
menu.add(connectItem);
JMenuItem editItem = new JMenuItem("Edit");
editItem.addActionListener(a -> {
// TODO: Open edit dialog.
listModel.serverEdited();
});
menu.add(editItem);
JMenuItem removeItem = new JMenuItem("Remove");
removeItem.addActionListener(a -> {
int choice = JOptionPane.showConfirmDialog(panel, "Are you sure you want to remove this server?", "Confirm Server Removal", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
if (choice == JOptionPane.OK_OPTION) {
listModel.remove(serversList.getSelectedValue());
}
});
menu.add(removeItem);
menu.show(serversList, e.getX(), e.getY());
} else if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
serversList.setSelectedIndex(serversList.locationToIndex(e.getPoint()));
ServerInfo server = serversList.getSelectedValue();
if (server == null) return;
connect(server);
}
}
});
JPanel buttonPanel = new JPanel();
JButton addServerButton = new JButton("Add Server");
addServerButton.addActionListener(e -> {
// TODO: Add server dialog.
});
buttonPanel.add(addServerButton);
panel.add(buttonPanel, BorderLayout.SOUTH);
return panel;
}
private boolean validateConnectInput(JTextField addressField, JTextField usernameField) {
List<String> warnings = new ArrayList<>();
if (addressField.getText() == null || addressField.getText().isBlank()) {
warnings.add("Address must not be empty.");
@ -124,6 +193,20 @@ public class Launcher extends JFrame {
}
}
private void connect(ServerInfo serverInfo) {
String username = serverInfo.getUsername();
if (username == null) {
username = JOptionPane.showInputDialog(this, "Enter a username.", "Username", JOptionPane.PLAIN_MESSAGE);
if (username == null || username.isBlank()) return;
}
try {
new Client(serverInfo.getHostAddress(), serverInfo.getHostPort(), username);
} catch (IOException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, "Could not connect:\n" + e.getMessage(), "Connection Error", JOptionPane.WARNING_MESSAGE);
}
}
public static void main(String[] args) {
Launcher launcher = new Launcher();

View File

@ -0,0 +1,67 @@
package nl.andrewlalis.aos_client.launcher.servers;
import java.util.Objects;
public class ServerInfo implements Comparable<ServerInfo> {
private String name;
private String host;
private String username;
public ServerInfo(String name, String host, String username) {
this.name = name;
this.host = host;
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getHostAddress() {
return this.host.split(":")[0];
}
public int getHostPort() {
String[] parts = this.host.split(":");
if (parts.length < 2) return 8035;
return Integer.parseInt(parts[1]);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerInfo that = (ServerInfo) o;
return getName().equals(that.getName()) && getHost().equals(that.getHost()) && Objects.equals(getUsername(), that.getUsername());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getHost(), getUsername());
}
@Override
public int compareTo(ServerInfo o) {
return this.name.compareTo(o.name);
}
}

View File

@ -0,0 +1,28 @@
package nl.andrewlalis.aos_client.launcher.servers;
import javax.swing.*;
import java.awt.*;
public class ServerInfoCellRenderer implements ListCellRenderer<ServerInfo> {
@Override
public Component getListCellRendererComponent(JList<? extends ServerInfo> list, ServerInfo value, int index, boolean isSelected, boolean cellHasFocus) {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createTitledBorder(value.getName()));
JTextField hostField = new JTextField(value.getHost());
hostField.setEditable(false);
panel.add(hostField, BorderLayout.CENTER);
if (value.getUsername() != null) {
panel.add(new JLabel("Username: " + value.getUsername()), BorderLayout.SOUTH);
}
if (isSelected) {
panel.setBackground(list.getSelectionBackground());
panel.setForeground(list.getSelectionForeground());
} else {
panel.setBackground(list.getBackground());
panel.setForeground(list.getForeground());
}
return panel;
}
}

View File

@ -0,0 +1,42 @@
package nl.andrewlalis.aos_client.launcher.servers;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class ServerInfoListModel extends AbstractListModel<ServerInfo> {
private final List<ServerInfo> servers;
public ServerInfoListModel() {
this.servers = new ArrayList<>();
}
public void add(ServerInfo server) {
if (this.servers.contains(server)) return;
this.servers.add(server);
this.servers.sort(Comparator.naturalOrder());
this.fireContentsChanged(this, 0, this.getSize());
}
public void remove(ServerInfo server) {
int index = this.servers.indexOf(server);
if (index == -1) return;
this.servers.remove(index);
this.fireIntervalRemoved(this, index, index);
}
public void serverEdited() {
this.fireContentsChanged(this, 0, this.getSize());
}
@Override
public int getSize() {
return this.servers.size();
}
@Override
public ServerInfo getElementAt(int index) {
return this.servers.get(index);
}
}

View File

@ -1,110 +0,0 @@
package nl.andrewlalis.aos_client.view;
import nl.andrewlalis.aos_client.Client;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class ConnectDialog extends JDialog {
private static final Pattern addressPattern = Pattern.compile("(.+):(\\d+)");
public ConnectDialog() {
super((Frame) null, "Connect to Server", false);
JPanel inputPanel = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(5, 5, 5, 5);
c.gridx = 0;
c.gridy = 0;
inputPanel.add(new JLabel("Address"), c);
JTextField addressField = new JTextField(20);
c.gridx = 1;
inputPanel.add(addressField, c);
c.gridy = 1;
c.gridx = 0;
inputPanel.add(new JLabel("Username"), c);
JTextField usernameField = new JTextField(20);
c.gridx = 1;
inputPanel.add(usernameField, c);
var enterListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (validateInput(addressField, usernameField)) {
connect(addressField, usernameField);
}
}
}
};
addressField.addKeyListener(enterListener);
usernameField.addKeyListener(enterListener);
JPanel buttonPanel = new JPanel(new FlowLayout());
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(e -> this.dispose());
JButton connectButton = new JButton("Connect");
connectButton.addActionListener(e -> {
if (validateInput(addressField, usernameField)) {
connect(addressField, usernameField);
}
});
buttonPanel.add(cancelButton);
buttonPanel.add(connectButton);
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(inputPanel, BorderLayout.CENTER);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
this.setContentPane(mainPanel);
this.pack();
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}
private boolean validateInput(JTextField addressField, JTextField usernameField) {
List<String> warnings = new ArrayList<>();
if (addressField.getText() == null || addressField.getText().isBlank()) {
warnings.add("Address must not be empty.");
}
if (usernameField.getText() == null || usernameField.getText().isBlank()) {
warnings.add("Username must not be empty.");
}
if (usernameField.getText() != null && usernameField.getText().length() > 16) {
warnings.add("Username is too long.");
}
if (addressField.getText() != null && !addressPattern.matcher(addressField.getText()).matches()) {
warnings.add("Address must be in the form HOST:PORT.");
}
if (!warnings.isEmpty()) {
JOptionPane.showMessageDialog(
this,
String.join("\n", warnings),
"Invalid Input",
JOptionPane.WARNING_MESSAGE
);
}
return warnings.isEmpty();
}
private void connect(JTextField addressField, JTextField usernameField) {
String hostAndPort = addressField.getText();
String[] parts = hostAndPort.split(":");
String host = parts[0].trim();
int port = Integer.parseInt(parts[1]);
String username = usernameField.getText();
try {
new Client(host, port, username);
} catch (IOException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(null, "Could not connect:\n" + ex.getMessage(), "Connection Error", JOptionPane.WARNING_MESSAGE);
}
}
}