Login remake

This commit is contained in:
2026-05-20 16:38:20 +02:00
parent 428812cda5
commit c48180e1b8
3 changed files with 284 additions and 86 deletions
+2 -2
View File
@@ -8,7 +8,7 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="25" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_26" default="true" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>
@@ -3,29 +3,36 @@ package it.polimi.ingsw.gc14.View.GUI;
import it.polimi.ingsw.gc14.Controller.ClientController; import it.polimi.ingsw.gc14.Controller.ClientController;
import it.polimi.ingsw.gc14.Network.RMI.Client.RMIClient; import it.polimi.ingsw.gc14.Network.RMI.Client.RMIClient;
import it.polimi.ingsw.gc14.Network.TCP.Client.TCPClient; import it.polimi.ingsw.gc14.Network.TCP.Client.TCPClient;
import javafx.animation.*;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.util.Duration;
import java.net.Inet4Address; import java.net.*;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List;
public class LoginFXMLController { public class LoginFXMLController {
@FXML private ImageView backgroundImage;
@FXML private TextField campoNome; @FXML private TextField campoNome;
@FXML private TextField campoNumPlayers; @FXML private TextField campoNumPlayers;
@FXML private TextField campoIP; @FXML private TextField campoIP;
@FXML private RadioButton btnRMI;
@FXML private RadioButton btnTCP;
@FXML private Button btnAccedi; @FXML private Button btnAccedi;
@FXML private Label labelErrore; @FXML private Label labelErrore;
@FXML private ComboBox<String> comboInterfaccia; @FXML private VBox formPanel;
@FXML private StackPane protocolToggle;
@FXML private Region toggleThumb;
@FXML private Label labelRMI;
@FXML private Label labelTCP;
private boolean isRMI = true;
private ClientController controller; private ClientController controller;
public void setController(ClientController controller) { public void setController(ClientController controller) {
@@ -34,83 +41,155 @@ public class LoginFXMLController {
@FXML @FXML
public void initialize() { public void initialize() {
ToggleGroup group = new ToggleGroup(); // Background
btnRMI.setToggleGroup(group); Image img = new Image(getClass().getResourceAsStream("/GUIImages/Background.png"));
btnTCP.setToggleGroup(group); backgroundImage.setImage(img);
Rectangle2D screenBounds = Screen.getPrimary().getBounds();
backgroundImage.setFitWidth(screenBounds.getWidth());
backgroundImage.setFitHeight(screenBounds.getHeight());
// Protocol toggle
protocolToggle.setOnMouseClicked(e -> switchProtocol());
protocolToggle.setStyle(protocolToggle.getStyle() + " -fx-cursor: hand;");
// Button hover
final String BASE_BTN = btnAccedi.getStyle();
btnAccedi.setOnMouseEntered(e -> btnAccedi.setStyle(BASE_BTN +
"-fx-effect: dropshadow(gaussian, rgba(244,192,90,0.45), 18, 0, 0, 0);"));
btnAccedi.setOnMouseExited(e -> btnAccedi.setStyle(BASE_BTN));
new Thread(() -> {
try {
List<String> ips = new ArrayList<>();
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
if (!ni.isUp() || ni.isLoopback() || ni.isVirtual()) continue;
Enumeration<InetAddress> addresses = ni.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
if (addr instanceof Inet4Address)
ips.add(ni.getDisplayName() + " -> " + addr.getHostAddress());
}
}
Platform.runLater(() -> {
comboInterfaccia.getItems().addAll(ips);
if (!ips.isEmpty()) comboInterfaccia.getSelectionModel().selectFirst();
});
} catch (Exception e) {
Platform.runLater(() -> labelErrore.setText("Errore nel rilevare le interfacce."));
}
}).start();
btnAccedi.setOnAction(e -> onAccediClick()); btnAccedi.setOnAction(e -> onAccediClick());
} }
private void switchProtocol() {
isRMI = !isRMI;
TranslateTransition tt = new TranslateTransition(Duration.millis(200), toggleThumb);
tt.setToX(isRMI ? 0 : 110);
tt.play();
labelRMI.setStyle("-fx-font-family: 'Cinzel'; -fx-font-size: 11; -fx-font-weight: bold;" +
"-fx-text-fill: " + (isRMI ? "#0c0601" : "rgba(244,192,90,0.55)") + ";");
labelTCP.setStyle("-fx-font-family: 'Cinzel'; -fx-font-size: 11; -fx-font-weight: bold;" +
"-fx-text-fill: " + (isRMI ? "rgba(244,192,90,0.55)" : "#0c0601") + ";");
}
@FXML @FXML
private void onAccediClick() { private void onAccediClick() {
String nome = campoNome.getText().trim(); String nome = campoNome.getText().trim();
controller.setMyUsername(nome);
String ip = campoIP.getText().trim(); String ip = campoIP.getText().trim();
if (nome.isEmpty()) {
showError("Please select a name.");
return;
}
int numPlayers; int numPlayers;
try { try {
numPlayers = Integer.parseInt(campoNumPlayers.getText().trim()); numPlayers = Integer.parseInt(campoNumPlayers.getText().trim());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
labelErrore.setText("Numero giocatori non valido."); showError("Invalid number of players.");
return; return;
} }
if (nome.isEmpty() || ip.isEmpty()) { controller.setMyUsername(nome);
labelErrore.setText("Compila tutti i campi.");
return;
}
System.setProperty("java.rmi.server.hostname", ip);
int networkType = btnRMI.isSelected() ? 0 : 1; // Resolve the local interface to use, then connect
String interfaccia = getInterfaccia();
// Stesso thread di rete che era in ClientLauncher
new Thread(() -> { new Thread(() -> {
if (networkType == 0) { try {
RMIClient client = new RMIClient(controller, ip, 1099, interfaccia); String localInterface = resolveLocalInterface(ip);
if (client.connect(nome, numPlayers)) { System.setProperty("java.rmi.server.hostname", localInterface);
controller.setClient(client); connect(nome, ip, numPlayers, localInterface);
Platform.runLater(() ->{ labelErrore.setTextFill(Color.GREEN); labelErrore.setText("Connected successfully! Waiting other players");}); } catch (Exception ex) {
} else { Platform.runLater(() -> showError("Network error: " + ex.getMessage()));
Platform.runLater(() ->{ labelErrore.setTextFill(Color.RED); labelErrore.setText("Connection RMI failed.");});
}
} else {
TCPClient client = new TCPClient(controller, ip, 8080, 8081);
if (client.connect(nome, numPlayers)) {
controller.setClient(client);
Platform.runLater(() ->{ labelErrore.setTextFill(Color.GREEN); labelErrore.setText("Connected successfully! Waiting other players");});
} else {
Platform.runLater(() ->{ labelErrore.setTextFill(Color.RED); labelErrore.setText("Connection TCP failed.");});
}
} }
}).start(); }).start();
} }
private String getInterfaccia() { /**
String selected = comboInterfaccia.getValue(); * Given the server IP, finds the local network interface whose subnet
if (selected == null) return ""; * contains that IP. Falls back to the default route for non-LAN addresses.
return selected.contains("->") ? selected.split("->")[1].trim() : selected; * Handles "localhost" and "127.x.x.x" explicitly.
*/
private String resolveLocalInterface(String serverIp) throws Exception {
// localhost shortcut
if (serverIp.equalsIgnoreCase("localhost") ||
serverIp.startsWith("127.") || serverIp.isEmpty()) {
return "127.0.0.1";
}
InetAddress serverAddr = InetAddress.getByName(serverIp);
// Walk every local interface and check if serverAddr is in its subnet
Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
while (ifaces.hasMoreElements()) {
NetworkInterface ni = ifaces.nextElement();
if (!ni.isUp() || ni.isLoopback()) continue;
for (InterfaceAddress ia : ni.getInterfaceAddresses()) {
InetAddress localAddr = ia.getAddress();
if (!(localAddr instanceof Inet4Address)) continue;
int prefix = ia.getNetworkPrefixLength();
if (sameSubnet(localAddr, serverAddr, prefix)) {
return localAddr.getHostAddress();
}
}
}
// No subnet match — use the address the OS would naturally pick
// by opening a UDP socket toward the server (no data is sent)
try (DatagramSocket socket = new DatagramSocket()) {
socket.connect(serverAddr, 9); // port 9 = discard, never actually used
return socket.getLocalAddress().getHostAddress();
}
}
/**
* Returns true if {@code a} and {@code b} are in the same /prefix subnet.
*/
private boolean sameSubnet(InetAddress a, InetAddress b, int prefix) {
if (prefix < 0 || prefix > 32) return false;
int maskBits = prefix == 0 ? 0 : (0xFFFFFFFF << (32 - prefix));
int addrA = toInt(a.getAddress()) & maskBits;
int addrB = toInt(b.getAddress()) & maskBits;
return addrA == addrB;
}
private int toInt(byte[] bytes) {
return ((bytes[0] & 0xFF) << 24) |
((bytes[1] & 0xFF) << 16) |
((bytes[2] & 0xFF) << 8) |
(bytes[3] & 0xFF);
}
private void connect(String nome, String ip, int numPlayers, String localInterface) {
if (isRMI) {
RMIClient client = new RMIClient(controller, ip, 1099, localInterface);
if (client.connect(nome, numPlayers)) {
controller.setClient(client);
Platform.runLater(() -> showSuccess("Connected! Waiting for other players…"));
} else {
Platform.runLater(() -> showError("RMI connection failed."));
}
} else {
TCPClient client = new TCPClient(controller, ip, 8080, 8081);
if (client.connect(nome, numPlayers)) {
controller.setClient(client);
Platform.runLater(() -> showSuccess("Connected! Waiting for other players…"));
} else {
Platform.runLater(() -> showError("TCP connection failed."));
}
}
}
private void showError(String msg) {
labelErrore.setTextFill(Color.web("#e05050"));
labelErrore.setText(msg);
}
private void showSuccess(String msg) {
labelErrore.setTextFill(Color.web("#6fcf8a"));
labelErrore.setText(msg);
} }
} }
+138 -19
View File
@@ -1,31 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import javafx.geometry.*?> <?import javafx.geometry.*?>
<?import javafx.scene.image.ImageView?>
<StackPane xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="it.polimi.ingsw.gc14.View.GUI.LoginFXMLController"
prefWidth="1280" prefHeight="720"
style="-fx-background-color: #09060300;">
<VBox spacing="10" alignment="CENTER_LEFT" <ImageView fx:id="backgroundImage" fitWidth="1920" fitHeight="1080" preserveRatio="false"/>
xmlns:fx="http://javafx.com/fxml"
fx:controller="it.polimi.ingsw.gc14.View.GUI.LoginFXMLController">
<padding><Insets top="40" right="40" bottom="40" left="40"/></padding> <!-- Corner decorations -->
<Region prefWidth="52" prefHeight="52"
style="-fx-border-color: rgba(200,120,20,0.35); -fx-border-width: 1.5 0 0 1.5;"
StackPane.alignment="TOP_LEFT">
<StackPane.margin><Insets top="18" left="18"/></StackPane.margin>
</Region>
<Region prefWidth="52" prefHeight="52"
style="-fx-border-color: rgba(200,120,20,0.35); -fx-border-width: 1.5 1.5 0 0;"
StackPane.alignment="TOP_RIGHT">
<StackPane.margin><Insets top="18" right="18"/></StackPane.margin>
</Region>
<Region prefWidth="52" prefHeight="52"
style="-fx-border-color: rgba(200,120,20,0.35); -fx-border-width: 0 0 1.5 1.5;"
StackPane.alignment="BOTTOM_LEFT">
<StackPane.margin><Insets bottom="18" left="18"/></StackPane.margin>
</Region>
<Region prefWidth="52" prefHeight="52"
style="-fx-border-color: rgba(200,120,20,0.35); -fx-border-width: 0 1.5 1.5 0;"
StackPane.alignment="BOTTOM_RIGHT">
<StackPane.margin><Insets bottom="18" right="18"/></StackPane.margin>
</Region>
<Label text="Nome utente:"/> <!-- Central panel -->
<TextField fx:id="campoNome" promptText="Nome utente"/> <VBox fx:id="formPanel" alignment="CENTER" spacing="0" maxWidth="420" maxHeight="600"
StackPane.alignment="CENTER"
style="-fx-background-color: rgba(9,6,3,0.72);
-fx-border-color: rgba(200,120,20,0.40);
-fx-border-width: 1;
-fx-border-radius: 4;
-fx-background-radius: 4;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.7), 40, 0, 0, 8);">
<padding><Insets top="44" bottom="44" left="48" right="48"/></padding>
<Label text="Num. giocatori:"/> <!-- Title -->
<TextField fx:id="campoNumPlayers" promptText="Numero di giocatori"/> <Label text="MESOS"
style="-fx-font-family: 'Cinzel Decorative'; -fx-font-size: 28; -fx-font-weight: bold;
-fx-text-fill: #f4c05a;
-fx-effect: dropshadow(gaussian, rgba(244,192,90,0.4), 28, 0, 0, 0);">
<VBox.margin><Insets bottom="4"/></VBox.margin>
</Label>
<Label text="Protocollo:"/> <!-- Divider -->
<RadioButton fx:id="btnRMI" text="RMI" selected="true"/> <Region prefHeight="1" prefWidth="260"
<RadioButton fx:id="btnTCP" text="TCP"/> style="-fx-background-color: linear-gradient(to right, transparent, #c8791a, #f4c05a, #c8791a, transparent);">
<VBox.margin><Insets top="10" bottom="30"/></VBox.margin>
</Region>
<Label text="IP server:"/> <!-- Username -->
<TextField fx:id="campoIP" promptText="IP server"/> <Label text="USERNAME"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 9; -fx-letter-spacing: 3;
-fx-text-fill: rgba(244,192,90,0.7);">
<VBox.margin><Insets bottom="6"/></VBox.margin>
</Label>
<TextField fx:id="campoNome" promptText="Enter your name"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 12;
-fx-text-fill: #f0e0c0; -fx-prompt-text-fill: rgba(200,150,80,0.45);
-fx-background-color: rgba(244,192,90,0.07);
-fx-border-color: rgba(200,120,20,0.45); -fx-border-width: 0 0 1 0;
-fx-background-radius: 0; -fx-border-radius: 0;
-fx-padding: 10 12 10 12;">
<VBox.margin><Insets bottom="20"/></VBox.margin>
</TextField>
<Label text="Interfaccia di rete:"/> <!-- Num players -->
<ComboBox fx:id="comboInterfaccia" maxWidth="Infinity"/> <Label text="NUMBER OF PLAYERS"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 9; -fx-letter-spacing: 3;
-fx-text-fill: rgba(244,192,90,0.7);">
<VBox.margin><Insets bottom="6"/></VBox.margin>
</Label>
<TextField fx:id="campoNumPlayers" promptText="2 5"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 12;
-fx-text-fill: #f0e0c0; -fx-prompt-text-fill: rgba(200,150,80,0.45);
-fx-background-color: rgba(244,192,90,0.07);
-fx-border-color: rgba(200,120,20,0.45); -fx-border-width: 0 0 1 0;
-fx-background-radius: 0; -fx-border-radius: 0;
-fx-padding: 10 12 10 12;">
<VBox.margin><Insets bottom="20"/></VBox.margin>
</TextField>
<Label fx:id="labelErrore" style="-fx-text-fill: red;"/> <!-- Server IP -->
<Button fx:id="btnAccedi" text="Connetti"/> <Label text="SERVER IP"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 9; -fx-letter-spacing: 3;
-fx-text-fill: rgba(244,192,90,0.7);">
<VBox.margin><Insets bottom="6"/></VBox.margin>
</Label>
<TextField fx:id="campoIP" promptText="localhost"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 12;
-fx-text-fill: #f0e0c0; -fx-prompt-text-fill: rgba(200,150,80,0.45);
-fx-background-color: rgba(244,192,90,0.07);
-fx-border-color: rgba(200,120,20,0.45); -fx-border-width: 0 0 1 0;
-fx-background-radius: 0; -fx-border-radius: 0;
-fx-padding: 10 12 10 12;">
<VBox.margin><Insets bottom="20"/></VBox.margin>
</TextField>
</VBox> <!-- Protocol toggle -->
<Label text="PROTOCOL"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 9; -fx-letter-spacing: 3;
-fx-text-fill: rgba(244,192,90,0.7);">
<VBox.margin><Insets bottom="8"/></VBox.margin>
</Label>
<StackPane fx:id="protocolToggle" prefWidth="220" maxWidth="220" prefHeight="36" alignment="CENTER_LEFT">
<VBox.margin><Insets bottom="28"/></VBox.margin>
<Region prefWidth="220" prefHeight="36"
style="-fx-background-color: rgba(244,192,90,0.07);
-fx-border-color: rgba(200,120,20,0.45);
-fx-border-width: 1; -fx-border-radius: 2; -fx-background-radius: 2;"/>
<Region fx:id="toggleThumb" prefWidth="110" maxWidth="110" prefHeight="36"
style="-fx-background-color: linear-gradient(to right, #f4c05a, #c8791a);
-fx-background-radius: 2;"
StackPane.alignment="CENTER_LEFT"/>
<HBox prefWidth="220" prefHeight="36">
<Label fx:id="labelRMI" text="RMI" prefWidth="110" prefHeight="36" alignment="CENTER"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 11; -fx-font-weight: bold;
-fx-text-fill: #0c0601;"/>
<Label fx:id="labelTCP" text="TCP" prefWidth="110" prefHeight="36" alignment="CENTER"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 11; -fx-font-weight: bold;
-fx-text-fill: rgba(244,192,90,0.55);"/>
</HBox>
</StackPane>
<!-- Error label -->
<Label fx:id="labelErrore" maxWidth="Infinity" wrapText="true" textAlignment="CENTER"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 10; -fx-text-fill: #e05050;
-fx-min-height: 18;">
<VBox.margin><Insets bottom="16"/></VBox.margin>
</Label>
<!-- Connect button -->
<Button fx:id="btnAccedi" text="CONNECT" maxWidth="Infinity"
style="-fx-font-family: 'Cinzel'; -fx-font-size: 12; -fx-font-weight: bold;
-fx-letter-spacing: 4; -fx-text-fill: #0c0601;
-fx-background-color: linear-gradient(to right, #f4c05a, #c8791a, #f4c05a);
-fx-padding: 13 48 13 48; -fx-background-radius: 2;
-fx-cursor: hand;"/>
</VBox>
</StackPane>