Merge pull request #115 from rubenpirreram/GUI-2.0

G UI 2.0
This commit is contained in:
rubenpirreram
2026-05-20 19:19:21 +02:00
committed by GitHub
5 changed files with 319 additions and 108 deletions
@@ -3,111 +3,180 @@ package it.polimi.ingsw.gc14.View.GUI;
import it.polimi.ingsw.gc14.Controller.ClientController;
import it.polimi.ingsw.gc14.Network.RMI.Client.RMIClient;
import it.polimi.ingsw.gc14.Network.TCP.Client.TCPClient;
import javafx.animation.*;
import javafx.application.Platform;
import javafx.css.PseudoClass;
import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D;
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.stage.Screen;
import javafx.util.Duration;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.net.*;
import java.util.Enumeration;
import java.util.List;
public class LoginFXMLController {
@FXML private ImageView backgroundImage;
@FXML private TextField campoNome;
@FXML private TextField campoNumPlayers;
@FXML private TextField campoIP;
@FXML private RadioButton btnRMI;
@FXML private RadioButton btnTCP;
@FXML private Button btnAccedi;
@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;
// Definiamo lo pseudo-stato custom per il CSS corrispondente a ":active-protocol"
private final PseudoClass activeProtocolPseudo = PseudoClass.getPseudoClass("active-protocol");
public void setController(ClientController controller) {
this.controller = controller;
}
@FXML
public void initialize() {
ToggleGroup group = new ToggleGroup();
btnRMI.setToggleGroup(group);
btnTCP.setToggleGroup(group);
// Background setup
Image img = new Image(getClass().getResourceAsStream("/GUIImages/Background.png"));
backgroundImage.setImage(img);
Rectangle2D screenBounds = Screen.getPrimary().getBounds();
backgroundImage.setFitWidth(screenBounds.getWidth());
backgroundImage.setFitHeight(screenBounds.getHeight());
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();
// Protocol toggle configuration
protocolToggle.setOnMouseClicked(e -> switchProtocol());
protocolToggle.setStyle(protocolToggle.getStyle() + " -fx-cursor: hand;");
// Inizializza lo stato grafico iniziale (RMI attivo)
labelRMI.pseudoClassStateChanged(activeProtocolPseudo, true);
labelTCP.pseudoClassStateChanged(activeProtocolPseudo, false);
// Login Action
btnAccedi.setOnAction(e -> onAccediClick());
}
private void switchProtocol() {
isRMI = !isRMI;
TranslateTransition tt = new TranslateTransition(Duration.millis(200), toggleThumb);
tt.setToX(isRMI ? 0 : 110);
tt.play();
// Modifica in modo sicuro lo stato senza distruggere i parametri di layout dei nodi
labelRMI.pseudoClassStateChanged(activeProtocolPseudo, isRMI);
labelTCP.pseudoClassStateChanged(activeProtocolPseudo, !isRMI);
}
@FXML
private void onAccediClick() {
String nome = campoNome.getText().trim();
controller.setMyUsername(nome);
String ip = campoIP.getText().trim();
if (nome.isEmpty()) {
showError("Please select a name.");
return;
}
int numPlayers;
try {
numPlayers = Integer.parseInt(campoNumPlayers.getText().trim());
} catch (NumberFormatException e) {
labelErrore.setText("Numero giocatori non valido.");
showError("Invalid number of players.");
return;
}
if (nome.isEmpty() || ip.isEmpty()) {
labelErrore.setText("Compila tutti i campi.");
return;
}
System.setProperty("java.rmi.server.hostname", ip);
controller.setMyUsername(nome);
int networkType = btnRMI.isSelected() ? 0 : 1;
String interfaccia = getInterfaccia();
// Stesso thread di rete che era in ClientLauncher
new Thread(() -> {
if (networkType == 0) {
RMIClient client = new RMIClient(controller, ip, 1099, interfaccia);
if (client.connect(nome, numPlayers)) {
controller.setClient(client);
} else {
Platform.runLater(() -> labelErrore.setText("Connessione RMI fallita."));
}
} else {
TCPClient client = new TCPClient(controller, ip, 8080, 8081);
if (client.connect(nome, numPlayers)) {
controller.setClient(client);
} else {
Platform.runLater(() -> labelErrore.setText("Connessione TCP fallita."));
}
try {
String localInterface = resolveLocalInterface(ip);
System.setProperty("java.rmi.server.hostname", localInterface);
connect(nome, ip, numPlayers, localInterface);
} catch (Exception ex) {
Platform.runLater(() -> showError("Network error: " + ex.getMessage()));
}
}).start();
}
private String getInterfaccia() {
String selected = comboInterfaccia.getValue();
if (selected == null) return "";
return selected.contains("->") ? selected.split("->")[1].trim() : selected;
private String resolveLocalInterface(String serverIp) throws Exception {
if (serverIp.equalsIgnoreCase("localhost") || serverIp.startsWith("127.") || serverIp.isEmpty()) {
return "127.0.0.1";
}
InetAddress serverAddr = InetAddress.getByName(serverIp);
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();
}
}
}
try (DatagramSocket socket = new DatagramSocket()) {
socket.connect(serverAddr, 9);
return socket.getLocalAddress().getHostAddress();
}
}
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);
}
}
@@ -471,16 +471,15 @@ public class MainFXMLController {
StackPane card = createOrder(Integer.toString(numPlayers));
Map<Integer, double[]> slotPositions = Map.of(
2, new double[]{0.229 ,0.413},
3, new double[]{0.183,0.367, 0.548},
4, new double[]{0.142,0.316, 0.503, 0.690},
5, new double[]{0.066, 0.251, 0.433, 0.617,0.802}
2, new double[]{0.229, 0.413},
3, new double[]{0.183, 0.367, 0.548},
4, new double[]{0.142, 0.316, 0.503, 0.690},
5, new double[]{0.066, 0.251, 0.433, 0.617, 0.802}
);
double[] positions = slotPositions.get(numPlayers);
if (positions == null) return;
Pane overlay = new Pane();
overlay.setPickOnBounds(false);
overlay.setMouseTransparent(true);
@@ -488,43 +487,42 @@ public class MainFXMLController {
for (int i = 0; i < controller.miniModel.orderLogicCard.playerList.size(); i++) {
OrderPlayer op = controller.miniModel.orderLogicCard.playerList.get(i);
// colore dal totem del giocatore
Color color = totemColor(op.player.totem);
// Carica immagine totem come in createSlot
ImageView totem = new ImageView(loadImage(
"/GUIImages/Totems/totem_" + op.player.totem.toString().toLowerCase(Locale.ROOT) + ".png"
));
totem.setPreserveRatio(true);
Rectangle rect = new Rectangle(48, 26, color);
rect.setArcWidth(4);
rect.setArcHeight(4);
if (controller.miniModel.slotPlayerMap.containsValue(op.player)) {
rect.setVisible(false);
totem.setVisible(false);
}
final double yRatio = positions[i];
overlay.prefHeightProperty().bind(card.heightProperty());
overlay.prefWidthProperty().bind(card.widthProperty());
// posizione X fissa, Y proporzionale
// Scala altezza totem proporzionalmente come in createSlot (0.127 della card)
totem.fitHeightProperty().bind(card.heightProperty().multiply(0.323));
// Posizione Y proporzionale, X fissa
card.heightProperty().addListener((obs, ov, nv) -> {
rect.setY(nv.doubleValue() * yRatio);
rect.setHeight(0.127*nv.doubleValue());
totem.setLayoutY(nv.doubleValue() * (yRatio-0.196));
});
card.widthProperty().addListener((obs, ov, nv) -> {
rect.setX(nv.doubleValue() * 0.323);
rect.setWidth(0.375*nv.doubleValue());
totem.setLayoutX(nv.doubleValue() * 0.364);
});
if (card.getHeight() > 0 && card.getWidth() > 0) {
rect.setY(card.getHeight() * yRatio);
rect.setHeight(0.127*yRatio);
rect.setX(card.getHeight() * 0.323);
rect.setWidth(0.375*card.getHeight());
}
// Inizializzazione immediata se già dimensionato
if (card.getHeight() > 0) totem.setLayoutY(card.getHeight() *(yRatio-0.196));
if (card.getWidth() > 0) totem.setLayoutX(card.getWidth() * 0.364);
overlay.getChildren().add(rect);
overlay.getChildren().add(totem);
}
card.getChildren().add(overlay);
board.getChildren().add(card);
}
private Color totemColor(Totems totem) {
return switch (totem) {
case ORANGE -> Color.rgb(223, 76, 17);