Feat: Add highscore popup UI and lobby display #293
+76
@@ -0,0 +1,76 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||
import java.util.List;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/** Controller for the highscore popup window. */
|
||||
public class HighscoreViewController {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(HighscoreViewController.class);
|
||||
|
||||
@FXML private ListView<String> highscoreList;
|
||||
@FXML private Label statusLabel;
|
||||
|
||||
private LobbyClient lobbyClient;
|
||||
|
||||
public void setLobbyClient(LobbyClient lobbyClient) {
|
||||
this.lobbyClient = lobbyClient;
|
||||
}
|
||||
|
||||
/** Loads current highscores from the server and refreshes the list. */
|
||||
@FXML
|
||||
public void refreshHighscores() {
|
||||
if (highscoreList == null || statusLabel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lobbyClient == null) {
|
||||
statusLabel.setText("Lobby client not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> entries = lobbyClient.getHighscores();
|
||||
highscoreList.getItems().setAll(entries);
|
||||
statusLabel.setText(
|
||||
entries.isEmpty() ? "No highscores yet." : entries.size() + " entries loaded.");
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.error("Failed to load highscores: {}", e.getMessage());
|
||||
statusLabel.setText("Failed to load highscores.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears all highscores globally on the server and reloads the list. */
|
||||
@FXML
|
||||
public void clearHighscores() {
|
||||
if (lobbyClient == null || statusLabel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
lobbyClient.clearHighscores();
|
||||
refreshHighscores();
|
||||
statusLabel.setText("Highscores cleared.");
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.error("Failed to clear highscores: {}", e.getMessage());
|
||||
statusLabel.setText("Failed to clear highscores.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Closes the highscore popup window. */
|
||||
@FXML
|
||||
public void closeWindow() {
|
||||
if (highscoreList == null || highscoreList.getScene() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Stage stage = (Stage) highscoreList.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
}
|
||||
+42
@@ -1,10 +1,12 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Player;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerState;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
@@ -1079,4 +1081,44 @@ public class TaskbarController {
|
||||
private void onBrowserButtonClick() {
|
||||
CasinoBrowserController.open("wikipedia.org");
|
||||
}
|
||||
|
||||
/** Opens the highscore popup window from the taskbar. */
|
||||
@FXML
|
||||
private void onHighscoreButtonClick() {
|
||||
try {
|
||||
var shared = ClientApp.getSharedClientService();
|
||||
if (shared == null || shared.isOffline()) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle("Info");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText("No active server connection for highscores.");
|
||||
alert.showAndWait();
|
||||
return;
|
||||
}
|
||||
|
||||
javafx.fxml.FXMLLoader loader =
|
||||
new javafx.fxml.FXMLLoader(
|
||||
getClass()
|
||||
.getResource(
|
||||
"/ui-structure/gameuicomponents/HighscoreView.fxml"));
|
||||
javafx.scene.Parent root = loader.load();
|
||||
|
||||
HighscoreViewController controller = loader.getController();
|
||||
controller.setLobbyClient(new LobbyClient(shared));
|
||||
controller.refreshHighscores();
|
||||
|
||||
javafx.stage.Stage stage = new javafx.stage.Stage();
|
||||
stage.setTitle("Casono Highscores");
|
||||
javafx.scene.image.Image icon =
|
||||
new javafx.scene.image.Image(
|
||||
getClass().getResource("/images/logoinverted.png").toExternalForm());
|
||||
stage.getIcons().add(icon);
|
||||
stage.setScene(new javafx.scene.Scene(root));
|
||||
stage.show();
|
||||
stage.setAlwaysOnTop(true);
|
||||
stage.toFront();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Could not open highscore window from taskbar: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
@@ -4,12 +4,15 @@ import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.HighscoreViewController;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.control.Button;
|
||||
@@ -20,6 +23,7 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -287,4 +291,33 @@ public class CasinomainuiController {
|
||||
LOGGER.error("Failed to create or add lobby: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Opens the highscore popup from the lobby UI. */
|
||||
@FXML
|
||||
public void handleOpenHighscores() {
|
||||
try {
|
||||
FXMLLoader loader =
|
||||
new FXMLLoader(
|
||||
getClass()
|
||||
.getResource(
|
||||
"/ui-structure/gameuicomponents/HighscoreView.fxml"));
|
||||
Parent root = loader.load();
|
||||
|
||||
HighscoreViewController controller = loader.getController();
|
||||
controller.setLobbyClient(lobbyClient);
|
||||
controller.refreshHighscores();
|
||||
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle("Casono Highscores");
|
||||
String iconPath = getClass().getResource("/images/logoinverted.png").toExternalForm();
|
||||
stage.getIcons().add(new Image(iconPath));
|
||||
stage.setScene(new Scene(root));
|
||||
stage.show();
|
||||
stage.setAlwaysOnTop(true);
|
||||
stage.toFront();
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not open highscore window: {}", e.getMessage());
|
||||
showAlert("Could not open highscore window.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,15 +81,17 @@
|
||||
</GridPane.margin>
|
||||
</Button>
|
||||
|
||||
<!-- Username input and login/change button -->
|
||||
<HBox alignment="CENTER_LEFT" spacing="10"
|
||||
GridPane.rowIndex="0"
|
||||
GridPane.columnIndex="0"
|
||||
GridPane.halignment="LEFT"
|
||||
GridPane.valignment="TOP">
|
||||
<GridPane.margin>
|
||||
<Insets top="100" left="20" />
|
||||
</GridPane.margin>
|
||||
<!-- Username input + change name, with highscores directly below -->
|
||||
<VBox alignment="TOP_LEFT" spacing="10"
|
||||
GridPane.rowIndex="0"
|
||||
GridPane.columnIndex="0"
|
||||
GridPane.halignment="LEFT"
|
||||
GridPane.valignment="TOP">
|
||||
<GridPane.margin>
|
||||
<Insets top="140" left="20" />
|
||||
</GridPane.margin>
|
||||
|
||||
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||
<TextField fx:id="usernameField"
|
||||
promptText="Username"
|
||||
styleClass="gray-input-field"
|
||||
@@ -100,6 +102,12 @@
|
||||
styleClass="button-create-lobby" />
|
||||
</HBox>
|
||||
|
||||
<Button text="HIGHSCORES"
|
||||
styleClass="button-create-lobby"
|
||||
fx:id="highscoreButton"
|
||||
onAction="#handleOpenHighscores" />
|
||||
</VBox>
|
||||
|
||||
<VBox fx:id="casinoTable"
|
||||
alignment="CENTER"
|
||||
styleClass="casino-table"
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
.highscore-root {
|
||||
-fx-background-color: rgba(13, 158, 59, 0.96);
|
||||
-fx-background-radius: 16;
|
||||
-fx-border-radius: 16;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 3;
|
||||
}
|
||||
|
||||
.highscore-title {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 24px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-effect: dropshadow(one-pass-box, #000000, 0, 0, 2, 2);
|
||||
}
|
||||
|
||||
.highscore-status {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 13px;
|
||||
}
|
||||
|
||||
.list-view {
|
||||
-fx-background-color: #114d26;
|
||||
-fx-control-inner-background: #114d26;
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-radius: 12;
|
||||
-fx-border-radius: 12;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 2;
|
||||
}
|
||||
|
||||
.list-view .list-cell {
|
||||
-fx-background-color: transparent;
|
||||
-fx-text-fill: #ffe89a;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-size: 13px;
|
||||
}
|
||||
|
||||
.list-view .list-cell:filled {
|
||||
-fx-background-color: rgba(0, 0, 0, 0.14);
|
||||
}
|
||||
|
||||
.list-view .list-cell:filled:hover {
|
||||
-fx-background-color: rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.list-view .list-cell:filled:selected,
|
||||
.list-view .list-cell:filled:selected:focused {
|
||||
-fx-background-color: rgba(0, 0, 0, 0.35);
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
||||
.gray-button,
|
||||
.yellow-button,
|
||||
.red-button {
|
||||
-fx-background-radius: 10;
|
||||
-fx-border-radius: 10;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 6 12;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.gray-button {
|
||||
-fx-background-color: #333333;
|
||||
-fx-border-color: #666666;
|
||||
-fx-text-fill: #cccccc;
|
||||
}
|
||||
|
||||
.yellow-button {
|
||||
-fx-background-color: #b8860b;
|
||||
-fx-border-color: #ffd700;
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
||||
.red-button {
|
||||
-fx-background-color: #8b0000;
|
||||
-fx-border-color: #ff4444;
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
||||
.highscore-root {
|
||||
-fx-background-color: #0d9e3b;
|
||||
-fx-background-radius: 16;
|
||||
-fx-border-radius: 16;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 3;
|
||||
}
|
||||
|
||||
.highscore-title {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 22px;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.highscore-subtitle {
|
||||
-fx-text-fill: #f2f2f2;
|
||||
-fx-font-size: 13px;
|
||||
}
|
||||
|
||||
.highscore-text-area {
|
||||
-fx-control-inner-background: #1b1b1b;
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-size: 14px;
|
||||
-fx-border-color: #666666;
|
||||
-fx-border-width: 2;
|
||||
-fx-border-radius: 10;
|
||||
-fx-background-radius: 10;
|
||||
}
|
||||
|
||||
.gray-button,
|
||||
.green-button,
|
||||
.red-button {
|
||||
-fx-background-radius: 10;
|
||||
-fx-border-radius: 10;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 6 14;
|
||||
}
|
||||
|
||||
.green-button {
|
||||
-fx-background-color: #1f7d33;
|
||||
-fx-border-color: #35c759;
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
||||
.red-button {
|
||||
-fx-background-color: #8b0000;
|
||||
-fx-border-color: #ff4444;
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
||||
.gray-button {
|
||||
-fx-background-color: #333333;
|
||||
-fx-border-color: #666666;
|
||||
-fx-text-fill: #cccccc;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<VBox xmlns="http://javafx.com/javafx/21"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.HighscoreViewController"
|
||||
spacing="12"
|
||||
styleClass="highscore-root"
|
||||
stylesheets="@HighscoreView.css">
|
||||
|
||||
<padding>
|
||||
<Insets top="14" right="14" bottom="14" left="14"/>
|
||||
</padding>
|
||||
|
||||
<Label text="Highscores" styleClass="highscore-title"/>
|
||||
|
||||
<ListView fx:id="highscoreList" prefHeight="320" VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<Label fx:id="statusLabel" text="Ready." styleClass="highscore-status"/>
|
||||
|
||||
<HBox spacing="10">
|
||||
<Button text="Refresh" onAction="#refreshHighscores" styleClass="gray-button" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
|
||||
<Button text="Clear" onAction="#clearHighscores" styleClass="yellow-button" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
|
||||
<Button text="Close" onAction="#closeWindow" styleClass="red-button" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
|
||||
</HBox>
|
||||
|
||||
</VBox>
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<!-- Placeholder buttons -->
|
||||
<Button text="SETTINGS" styleClass="gray-button" />
|
||||
<Button text="INFO" styleClass="gray-button" />
|
||||
<Button text="HIGHSCORE" onAction="#onHighscoreButtonClick" styleClass="gray-button" />
|
||||
<!-- Buten for the Casono Browser -->
|
||||
<Button text="HELP" onAction="#onBrowserButtonClick" styleClass="gray-button" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user