Feat/game-ui #180
@@ -0,0 +1,89 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.control.Label;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
|
||||
/**
|
||||
* Controller-Klasse für das Chat-System innerhalb der Spieloberfläche.
|
||||
*
|
||||
* Verwaltet das Anzeigen von Chatnachrichten, das Eingabefeld für eigene Nachrichten
|
||||
* sowie den Senden-Button. Unterstützt drei Arten von Nachrichten:
|
||||
* - Player-to-Player (Privat)
|
||||
* - Lobby-Chat (Raum)
|
||||
* - Globaler Chat (Serverweit)
|
||||
*
|
||||
* Nachrichten werden in einem {@link VBox}-Container als {@link Label} angezeigt.
|
||||
* Eigene Nachrichten werden über {@link #onSendToNetwork(String)} an das
|
||||
* Netzwerkprotokoll weitergeleitet, während eingehende Nachrichten über
|
||||
* {@link #receiveMessage(String, String)} verarbeitet und angezeigt werden.
|
||||
*
|
||||
* Hinweis: Einige TODOs stehen in der zugehörigen FXML-Datei
|
||||
*/
|
||||
public class ChatController {
|
||||
|
||||
@FXML
|
||||
private VBox chatVBox;
|
||||
|
||||
@FXML
|
||||
private TextField inputField;
|
||||
|
||||
@FXML
|
||||
private Button sendButton;
|
||||
|
||||
@FXML
|
||||
private ScrollPane chatScrollPane;
|
||||
|
||||
/**
|
||||
* Initialisiert den ChatController nach dem Laden der FXML.
|
||||
*/
|
||||
public void initialize() {
|
||||
inputField.setOnAction(event -> sendMessage());
|
||||
chatScrollPane.vvalueProperty().bind(chatVBox.heightProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode wird vom Senden-Button oder Enter ausgelöst.
|
||||
* Sie gibt die eigene Nachricht an das Netzwerkprotokoll weiter.
|
||||
*/
|
||||
@FXML
|
||||
private void sendMessage() {
|
||||
String message = inputField.getText().trim();
|
||||
if (!message.isEmpty()) {
|
||||
inputField.clear();
|
||||
|
||||
// Hier wird die eigene Nachricht ans Netzwerkprotokoll übergeben
|
||||
onSendToNetwork(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Funktion muss vom Netzwerkprotokoll aufgerufen werden,
|
||||
* wenn eine neue Nachricht von einem anderen Spieler kommt.
|
||||
* @param player Name des Spielers
|
||||
* @param message Nachricht des Spielers
|
||||
*/
|
||||
public void receiveMessage(String player, String message) {
|
||||
String time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"));
|
||||
Label label = new Label("[" + time + "] " + player + ": " + message);
|
||||
label.getStyleClass().add("info-text");
|
||||
label.setWrapText(true); // Zeilenumbruch aktivieren
|
||||
label.maxWidthProperty().bind(chatVBox.widthProperty().subtract(20));
|
||||
chatVBox.getChildren().add(label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schnittstelle zum Netzwerkprotokoll.
|
||||
* Diese Funktion wird automatisch aufgerufen, wenn der Benutzer eine eigene Nachricht sendet.
|
||||
* @param message Nachricht, die der Benutzer abgeschickt hat
|
||||
*/
|
||||
public void onSendToNetwork(String message) {
|
||||
// TODO: Netzwerkcode einfügen
|
||||
receiveMessage("Du", message);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui;
|
||||
|
||||
// import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
// import javafx.scene.control.TextField;
|
||||
// import javafx.scene.input.KeyCode;
|
||||
// import javafx.scene.input.KeyEvent;
|
||||
// import javafx.scene.input.MouseEvent;
|
||||
// import javafx.scene.layout.HBox;
|
||||
// import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
// import javafx.stage.Stage;
|
||||
// import javafx.scene.Scene;
|
||||
// import javafx.scene.web.WebView;
|
||||
|
||||
/**
|
||||
* Controller für die Casino-Spielfläche.
|
||||
*
|
||||
* Verantwortlich für:
|
||||
* - die Darstellung des Pokertisches und der Spieleroberfläche,
|
||||
* - die Verarbeitung von Benutzereingaben,
|
||||
* - die Schnittstelle zur GameEngine und zum Netzwerkprotokoll.
|
||||
*
|
||||
* Hinweise:
|
||||
* - Die Methode `onTableClick()` dient aktuell nur als Test-Logik.
|
||||
* Sie ist ggf. nicht mehr funktionsfähig und wird zukünftig
|
||||
* durch die finale Spielinteraktion ersetzt.
|
||||
*/
|
||||
public class CasinoGameController {
|
||||
|
||||
@FXML private Label welcomeText;
|
||||
@FXML private VBox casinoTable;
|
||||
|
||||
// TODO: Test-Logik: wird durch echte Spielinteraktionen ersetzt, sobald die GameEngine fertig ist
|
||||
@FXML
|
||||
public void onTableClick() {
|
||||
welcomeText.setText("Einsatz akzeptiert!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Hauptklasse für das Casino-Spiel-UI.
|
||||
*
|
||||
* Startet die JavaFX-Anwendung, lädt die grafische Oberfläche aus der FXML-Datei
|
||||
* und initialisiert die Haupt-Stage für das Spiel.
|
||||
*
|
||||
* Aufgaben:
|
||||
* - Lädt die FXML-Oberfläche "/ui-structure/Casinogameui.fxml".
|
||||
* - Lädt das Anwendungs-Icon aus "/images/logoinverted.png".
|
||||
* - Startet die Anwendung im Vollbildmodus.
|
||||
*/
|
||||
public class CasinoGameUI extends Application {
|
||||
|
||||
/**
|
||||
* Startet die Haupt-Stage der Anwendung.
|
||||
*
|
||||
* @param stage Die vom System bereitgestellte Haupt-Stage.
|
||||
* @throws IOException Wenn die FXML-Datei oder Ressourcen nicht geladen werden können.
|
||||
*/
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/casinogameui.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load(), 1200, 800);
|
||||
stage.setTitle("Casono (GAME)");
|
||||
|
||||
stage.getIcons().add(new javafx.scene.image.Image(getClass().getResource("/images/logoinverted.png").toExternalForm()));
|
||||
stage.setScene(scene);
|
||||
stage.setFullScreen(true);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Startpunkt der Anwendung.
|
||||
* @param args Befehlszeilenargumente.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
launch();
|
||||
}
|
||||
}
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.net.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Experimenteller integrierter Browser für Casono.
|
||||
*
|
||||
* Diese Klasse implementiert einen einfachen eingebetteten Webbrowser
|
||||
* auf Basis von {@link javafx.scene.web.WebView}. Der Browser dient
|
||||
* primär als Hilfswerkzeug innerhalb des Spiels, um externe Inhalte
|
||||
* wie Webseiten oder Videos anzuzeigen.
|
||||
*
|
||||
* Status
|
||||
* Der Browser befindet sich derzeit in einer experimentellen Phase.
|
||||
* Einige Sicherheitsmechanismen basieren auf experimentellen
|
||||
* KI-gestützten Empfehlungen und können sich in zukünftigen
|
||||
* Versionen noch ändern.
|
||||
*
|
||||
* Zweck
|
||||
* Der Browser wird aktuell experimentell genutzt, um:
|
||||
* Pokerregeln direkt im Spiel zu erklären
|
||||
* Hilfeseiten oder Dokumentationen anzuzeigen
|
||||
* Videos (z.B. Tutorials oder Erklärungen) über Plattformen wie YouTube abzuspielen
|
||||
*
|
||||
* Sicherheitsmechanismen
|
||||
* Da externe Webseiten geladen werden können, wurden einige
|
||||
* grundlegende Schutzmaßnahmen integriert:
|
||||
* - HTTPS-Zwang für Webseiten
|
||||
* - Whitelist für bekannte Domains
|
||||
* - Warnung bei unbekannten Webseiten
|
||||
* - JavaScript standardmäßig deaktiviert (man kann es jedoch für Google etc. einschalten)
|
||||
* - Popup-Blocker
|
||||
* - Automatische Cookie-Löschung beim Schließen
|
||||
*/
|
||||
public class CasinoBrowserController {
|
||||
private static final Set<String> TRUSTED_DOMAINS = new HashSet<>();
|
||||
private static final CookieManager COOKIE_MANAGER =
|
||||
new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||
|
||||
static {
|
||||
CookieHandler.setDefault(COOKIE_MANAGER);
|
||||
|
||||
TRUSTED_DOMAINS.add("wikipedia.org");
|
||||
TRUSTED_DOMAINS.add("github.com");
|
||||
TRUSTED_DOMAINS.add("oracle.com");
|
||||
TRUSTED_DOMAINS.add("stackoverflow.com");
|
||||
TRUSTED_DOMAINS.add("docs.oracle.com");
|
||||
TRUSTED_DOMAINS.add("developer.mozilla.org");
|
||||
TRUSTED_DOMAINS.add("maven.apache.org");
|
||||
TRUSTED_DOMAINS.add("gradle.org");
|
||||
TRUSTED_DOMAINS.add("spring.io");
|
||||
TRUSTED_DOMAINS.add("jetbrains.com");
|
||||
TRUSTED_DOMAINS.add("google.com");
|
||||
TRUSTED_DOMAINS.add("bing.com");
|
||||
TRUSTED_DOMAINS.add("duckduckgo.com");
|
||||
TRUSTED_DOMAINS.add("metager.de");
|
||||
TRUSTED_DOMAINS.add("unibas.ch");
|
||||
TRUSTED_DOMAINS.add("mojeek.com");
|
||||
TRUSTED_DOMAINS.add("searx.be ");
|
||||
TRUSTED_DOMAINS.add("startpage.com");
|
||||
}
|
||||
|
||||
private static boolean javascriptEnabled = false;
|
||||
|
||||
/**
|
||||
* Löscht alle gespeicherten Cookies der aktuellen Browser-Sitzung.
|
||||
*/
|
||||
private static void clearCookies() {
|
||||
try {
|
||||
COOKIE_MANAGER.getCookieStore().removeAll();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet ein neues Browserfenster und lädt eine angegebene Webseite.
|
||||
*
|
||||
* Falls die Webseite nicht zur Liste vertrauenswürdiger Domains gehört,
|
||||
* wird der Benutzer gefragt, ob die Seite dennoch geladen werden soll.
|
||||
*
|
||||
* @param url die Startadresse der Webseite, die geladen werden soll
|
||||
*/
|
||||
public static void open(String url) {
|
||||
Platform.runLater(() -> {
|
||||
Stage stage = new Stage();
|
||||
|
||||
WebView webView = new WebView();
|
||||
WebEngine engine = webView.getEngine();
|
||||
|
||||
engine.setJavaScriptEnabled(false);
|
||||
|
||||
// Popup Blocker
|
||||
engine.setCreatePopupHandler(config -> {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||
alert.setTitle("Popup blockiert");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText("Popup wurde aus Sicherheitsgründen blockiert.");
|
||||
|
||||
try {
|
||||
Image logo =
|
||||
new Image(CasinoBrowserController.class.getResourceAsStream("/images/logoinverted.png"));
|
||||
Image logomain =
|
||||
new Image(CasinoBrowserController.class.getResourceAsStream("/images/logo.png"));
|
||||
|
||||
if (logo != null) {
|
||||
Stage alertStage = (Stage) alert.getDialogPane().getScene().getWindow();
|
||||
if (alertStage != null) {
|
||||
alertStage.getIcons().add(logo);
|
||||
}
|
||||
}
|
||||
|
||||
if (logomain != null) {
|
||||
ImageView logoView = new ImageView(logomain);
|
||||
logoView.setFitHeight(40);
|
||||
logoView.setPreserveRatio(true);
|
||||
alert.setGraphic(logoView);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Logo konnte nicht geladen werden");
|
||||
}
|
||||
|
||||
alert.showAndWait();
|
||||
return null;
|
||||
});
|
||||
|
||||
webView.getStyleClass().add("web-view");
|
||||
|
||||
StackPane webContainer = new StackPane(webView);
|
||||
webContainer.getStyleClass().add("browser-web-view");
|
||||
VBox.setVgrow(webContainer, Priority.ALWAYS);
|
||||
|
||||
Rectangle clip = new Rectangle();
|
||||
clip.setArcWidth(40);
|
||||
clip.setArcHeight(40);
|
||||
webView.setClip(clip);
|
||||
|
||||
webView.widthProperty().addListener((o,a,b)->clip.setWidth(b.doubleValue()));
|
||||
webView.heightProperty().addListener((o,a,b)->clip.setHeight(b.doubleValue()));
|
||||
|
||||
ImageView browserLogo = new ImageView();
|
||||
|
||||
try {
|
||||
Image logo =
|
||||
new Image(CasinoBrowserController.class.getResourceAsStream("/images/logoinverted.png"));
|
||||
Image logomain =
|
||||
new Image(CasinoBrowserController.class.getResourceAsStream("/images/logo.png"));
|
||||
|
||||
if (logo != null) {
|
||||
stage.getIcons().add(logo);
|
||||
}
|
||||
|
||||
if (logomain != null) {
|
||||
browserLogo.setImage(logomain);
|
||||
browserLogo.setFitHeight(30);
|
||||
browserLogo.setPreserveRatio(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Logo konnte nicht geladen werden");
|
||||
}
|
||||
|
||||
// URL Feld
|
||||
TextField urlField = new TextField(url);
|
||||
urlField.getStyleClass().add("gray-input-field");
|
||||
HBox.setHgrow(urlField, Priority.ALWAYS);
|
||||
|
||||
Label securityLabel = new Label("SICHER");
|
||||
securityLabel.getStyleClass().add("security-label");
|
||||
|
||||
// JS Toggle
|
||||
Button jsToggle = new Button("JS EINSCHALTEN");
|
||||
jsToggle.getStyleClass().add("red-button");
|
||||
|
||||
jsToggle.setOnAction(e -> {
|
||||
javascriptEnabled = !javascriptEnabled;
|
||||
engine.setJavaScriptEnabled(javascriptEnabled);
|
||||
|
||||
if (javascriptEnabled) {
|
||||
jsToggle.setText("JS AUSSCHALTEN");
|
||||
jsToggle.getStyleClass().removeAll("red-button");
|
||||
jsToggle.getStyleClass().add("yellow-button");
|
||||
|
||||
} else {
|
||||
jsToggle.setText("JS EINSCHALTEN");
|
||||
jsToggle.getStyleClass().removeAll("yellow-button");
|
||||
jsToggle.getStyleClass().add("red-button");
|
||||
}
|
||||
});
|
||||
|
||||
// Navigation
|
||||
Button backBtn = new Button("<");
|
||||
backBtn.getStyleClass().add("gray-button");
|
||||
backBtn.setOnAction(e -> {
|
||||
if(engine.getHistory().getCurrentIndex() > 0){
|
||||
engine.getHistory().go(-1);
|
||||
}
|
||||
});
|
||||
|
||||
Button fwdBtn = new Button(">");
|
||||
fwdBtn.getStyleClass().add("gray-button");
|
||||
fwdBtn.setOnAction(e -> {
|
||||
if(engine.getHistory().getCurrentIndex() <
|
||||
engine.getHistory().getEntries().size()-1){
|
||||
engine.getHistory().go(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Reload Button
|
||||
Button reloadBtn = new Button("⟳");
|
||||
reloadBtn.getStyleClass().add("gray-button");
|
||||
reloadBtn.setOnAction(e -> engine.reload());
|
||||
|
||||
// Close
|
||||
Button closeBtn = new Button("X");
|
||||
closeBtn.getStyleClass().add("red-button");
|
||||
closeBtn.setOnAction(e -> {
|
||||
webView.getEngine().load("about:blank");
|
||||
clearCookies();
|
||||
stage.close();
|
||||
|
||||
});
|
||||
|
||||
// URL Enter
|
||||
urlField.setOnAction(e ->
|
||||
loadUrlSafely(engine, urlField.getText(), securityLabel));
|
||||
engine.locationProperty().addListener((obs,o,n)->urlField.setText(n));
|
||||
|
||||
// Taskbar
|
||||
HBox taskbar = new HBox(
|
||||
10,
|
||||
browserLogo,
|
||||
backBtn,
|
||||
fwdBtn,
|
||||
reloadBtn,
|
||||
urlField,
|
||||
jsToggle,
|
||||
securityLabel,
|
||||
closeBtn
|
||||
);
|
||||
|
||||
taskbar.getStyleClass().add("taskbar-browser");
|
||||
taskbar.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// Root
|
||||
VBox root = new VBox(10, taskbar, webContainer);
|
||||
root.getStyleClass().add("browser-root");
|
||||
root.setPadding(new Insets(10));
|
||||
Scene scene = new Scene(root,1200,800);
|
||||
var css = CasinoBrowserController.class.getResource("/ui-structure/casinogameui.css");
|
||||
|
||||
if(css != null) {
|
||||
scene.getStylesheets().add(css.toExternalForm());
|
||||
}
|
||||
|
||||
// F5 Reload
|
||||
scene.setOnKeyPressed(event -> {
|
||||
switch (event.getCode()) {
|
||||
case F5 -> engine.reload();
|
||||
}
|
||||
});
|
||||
|
||||
stage.setScene(scene);
|
||||
stage.setTitle("Casono Browser");
|
||||
loadUrlSafely(engine, url, securityLabel);
|
||||
stage.show();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt eine URL in den Browser, nachdem grundlegende Sicherheitsprüfungen
|
||||
* durchgeführt wurden.
|
||||
*
|
||||
* Vor dem Laden einer Seite werden folgende Prüfungen durchgeführt:
|
||||
* - Überprüfung des Protokolls (nur HTTPS erlaubt)
|
||||
* - Überprüfung der Domain gegen eine Whitelist
|
||||
* - Schutz vor Domain-Spoofing (Domain-Vortäuschung)
|
||||
*
|
||||
* Falls eine Domain nicht als vertrauenswürdig eingestuft wird,
|
||||
* muss der Benutzer bestätigen, dass die Seite dennoch geöffnet
|
||||
* werden darf.
|
||||
*
|
||||
* @param engine der WebEngine-Renderer des Browsers
|
||||
* @param url die zu ladende Webadresse
|
||||
* @param securityLabel Label zur Anzeige des aktuellen Sicherheitsstatus
|
||||
*/
|
||||
private static void loadUrlSafely(WebEngine engine, String url, Label securityLabel) {
|
||||
try {
|
||||
if(!url.startsWith("http")) {
|
||||
url = "https://" + url;
|
||||
}
|
||||
|
||||
URI uri = new URI(url);
|
||||
|
||||
// HTTPS Pflicht
|
||||
if(!"https".equalsIgnoreCase(uri.getScheme())) {
|
||||
securityLabel.setText("BLOCKIERT");
|
||||
return;
|
||||
}
|
||||
|
||||
String host = uri.getHost();
|
||||
if(host == null) {
|
||||
securityLabel.setText("ERROR");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sicherer Domain Check
|
||||
boolean trusted = TRUSTED_DOMAINS.stream().anyMatch(domain ->
|
||||
host.equals(domain) || host.endsWith("." + domain));
|
||||
|
||||
if(!trusted) {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
|
||||
alert.setTitle("Unbekannte Website");
|
||||
alert.setHeaderText("Diese Website ist nicht bekannt");
|
||||
alert.setContentText(
|
||||
host + "\n\nDiese Seite ist nicht vom Casono Browser verifiziert.\nMöchten Sie sie trotzdem öffnen?"
|
||||
);
|
||||
|
||||
Image logo = new Image(CasinoBrowserController.class.getResourceAsStream("/images/logoinverted.png"));
|
||||
if (logo != null) {
|
||||
Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
|
||||
if(stage != null) {
|
||||
stage.getIcons().add(logo);
|
||||
}
|
||||
ImageView logomain = new ImageView(logo);
|
||||
logomain.setFitHeight(50);
|
||||
logomain.setPreserveRatio(true);
|
||||
alert.setGraphic(logomain);
|
||||
}
|
||||
|
||||
alert.getDialogPane().setPrefSize(600,300);
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
|
||||
if(result.isEmpty() || result.get() != ButtonType.OK) {
|
||||
securityLabel.setText("BLOCKIERT");
|
||||
return;
|
||||
}
|
||||
|
||||
securityLabel.setText("UNBEKANNT");
|
||||
} else {
|
||||
securityLabel.setText("SICHER");
|
||||
}
|
||||
engine.load(uri.toString());
|
||||
}
|
||||
|
||||
catch (Exception e) {
|
||||
securityLabel.setText("ERROR");
|
||||
}
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
/**
|
||||
* Controller für die interaktive Taskleiste innerhalb der Poker-UI.
|
||||
*
|
||||
* Verantwortlich für:
|
||||
* - Drag-and-Drop-Verschieben der Taskleiste,
|
||||
* - Eingabe und Verwaltung von Spieleinsätzen,
|
||||
* - Steuerung allgemeiner Menüfunktionen wie Exit.
|
||||
*/
|
||||
public class TaskbarController {
|
||||
|
||||
@FXML private HBox taskbar;
|
||||
@FXML private TextField taskbarInput;
|
||||
|
||||
private double xOffset = 0;
|
||||
private double yOffset = 0;
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn die Taskleiste mit der Maus gedrückt wird.
|
||||
* Speichert die relative Position, um später korrekt zu verschieben.
|
||||
*
|
||||
* @param event Das Mausereignis
|
||||
*/
|
||||
@FXML
|
||||
private void onTaskbarPressed(MouseEvent event) {
|
||||
xOffset = event.getSceneX() - taskbar.getLayoutX();
|
||||
yOffset = event.getSceneY() - taskbar.getLayoutY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, während die Taskleiste mit der Maus gezogen wird.
|
||||
* Aktualisiert die Position und skaliert die Taskleiste leicht zur visuellen Rückmeldung.
|
||||
*
|
||||
* TODO: Es muss noch gefixt werden, dass die Taskleiste nicht aus dem Fenster verschwinden kann.
|
||||
*
|
||||
* @param event Das Mausereignis
|
||||
*/
|
||||
@FXML
|
||||
private void onTaskbarDragged(MouseEvent event) {
|
||||
taskbar.setLayoutX(event.getSceneX() - xOffset);
|
||||
taskbar.setLayoutY(event.getSceneY() - yOffset);
|
||||
|
||||
taskbar.setScaleX(0.9);
|
||||
taskbar.setScaleY(0.9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn die Maus über der Taskleiste losgelassen wird.
|
||||
* Setzt die Skalierung der Taskleiste wieder auf Normalgröße.
|
||||
*
|
||||
* @param event Das Mausereignis
|
||||
*/
|
||||
@FXML
|
||||
private void onTaskbarReleased(MouseEvent event) {
|
||||
taskbar.setScaleX(1.0);
|
||||
taskbar.setScaleY(1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn im Textfeld die Enter-Taste gedrückt wird.
|
||||
*
|
||||
* @param event Das Tastaturereignis
|
||||
*/
|
||||
@FXML
|
||||
private void onInputSubmitted(KeyEvent event) {
|
||||
if (event.getCode() == KeyCode.ENTER) {
|
||||
processBet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn der Submit-Button in der Taskleiste gedrückt wird.
|
||||
* Löst die Verarbeitung des Einsatzes aus.
|
||||
*/
|
||||
@FXML
|
||||
private void onInputSubmittedAction() {
|
||||
processBet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn der Exit-Button in der Taskleiste gedrückt wird.
|
||||
*
|
||||
* TODO: Logik implementieren, um zur Lobby zurückzukehren,
|
||||
* ohne die gesamte Anwendung zu schließen (kein System.exit/Platform.exit).
|
||||
*/
|
||||
@FXML
|
||||
private void onExitButtonClick() {
|
||||
Platform.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet den im Textfeld eingegebenen Einsatz.
|
||||
* Es werden ausschließlich ganzzahlige Werte im Bereich von 5 bis 100.000
|
||||
* Credits akzeptiert, die einem Vielfachen von 5 entsprechen (5er-Schritte).
|
||||
* Der Einsatz wird aktuell nur auf der Konsole ausgegeben.
|
||||
*/
|
||||
private void processBet() {
|
||||
String input = taskbarInput.getText();
|
||||
try {
|
||||
int credits = Integer.parseInt(input.trim());
|
||||
|
||||
if (credits >= 5 && credits <= 100000 && credits % 5 == 0) {
|
||||
// TODO: Credits müssen an die GameEngine gesendet werden
|
||||
System.out.println("Einsatz gesetzt: " + credits + " Casono Credits");
|
||||
taskbarInput.clear();
|
||||
} else {
|
||||
System.out.println("Fehler: Nur 5er-Schritte (5, 10, ... 100.000) erlaubt!");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
System.out.println("Fehler: Bitte nur eine Zahl eingeben!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet den integrierten Casono Webbrowser.
|
||||
*
|
||||
* TODO: Ersetze die Start-URL durch die offizielle Projekt-Website (z.B. Tipps & Tricks Seite),
|
||||
* sobald die Inhalte für Strategien und Support bereitstehen.
|
||||
*/
|
||||
@FXML
|
||||
private void onBrowserButtonClick() {
|
||||
CasinoBrowserController.open("wikipedia.org");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.image.*?>
|
||||
|
||||
<!-- Hauptcontainer: Verknüpft die UI mit dem CasinoGameController und lädt casinogameui.css -->
|
||||
<AnchorPane xmlns="http://javafx.com/javafx/21"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameController"
|
||||
styleClass="background"
|
||||
stylesheets="@casinogameui.css">
|
||||
|
||||
<GridPane prefWidth="1200" prefHeight="800" AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0">
|
||||
<columnConstraints>
|
||||
<!-- Tisch-Box: 70% -->
|
||||
<ColumnConstraints percentWidth="70.0" hgrow="ALWAYS" />
|
||||
<!-- Leere-Box: 5% -->
|
||||
<ColumnConstraints percentWidth="5.0" hgrow="ALWAYS" />
|
||||
<!-- Chat-Box: 20% -->
|
||||
<ColumnConstraints percentWidth="25.0" hgrow="ALWAYS" />
|
||||
</columnConstraints>
|
||||
|
||||
<!-- Sorgt dafür, dass diese Zeile den gesamten verfügbaren vertikalen Platz nutzt -->
|
||||
<rowConstraints>
|
||||
<RowConstraints vgrow="ALWAYS" />
|
||||
</rowConstraints>
|
||||
|
||||
<children>
|
||||
<!-- Tisch-Box in Spalte 0 -->
|
||||
<StackPane GridPane.columnIndex="0">
|
||||
<!-- Innenabstand: Hält den Inhalt 10 Pixel von oben/unten und 20 Pixel von den Seiten fern -->
|
||||
<padding>
|
||||
<Insets top="20" right="10" bottom="120" left="20"/>
|
||||
</padding>
|
||||
|
||||
<!-- Zentraler Casinotisch: Zentriert den Inhalt und nutzt durch die maximale Größe (1.7976931348623157E308) das gesamte Fenster -->
|
||||
<VBox fx:id="casinoTable"
|
||||
alignment="CENTER"
|
||||
styleClass="casino-table"
|
||||
spacing="20"
|
||||
onMouseClicked="#onTableClick"
|
||||
maxWidth="Infinity"
|
||||
maxHeight="Infinity">
|
||||
|
||||
<!-- TODO: Platzhalter für die Poker-Spielfläche: Hier werden später dynamisch Karten, Chips und Einsätze der Gegner angezeigt -->
|
||||
<HBox alignment="CENTER" spacing="15">
|
||||
<VBox styleClass="dealer-box" prefWidth="80" prefHeight="120" />
|
||||
<VBox styleClass="dealer-box" prefWidth="80" prefHeight="120" />
|
||||
<VBox styleClass="dealer-box" prefWidth="80" prefHeight="120" />
|
||||
</HBox>
|
||||
|
||||
<VBox alignment="CENTER" spacing="10">
|
||||
<!-- Label mit Logo -->
|
||||
<Label text="CAS0NO" styleClass="table-title">
|
||||
<graphic>
|
||||
<ImageView fitHeight="30.0" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@/images/logo.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Label>
|
||||
|
||||
<!-- TODO: Platzhalter: Wird ersetzt, sobald die GameEngine fertig ist und echte Einsätze verarbeiten kann -->
|
||||
<Label fx:id="welcomeText" text="Setzen Sie Ihren Einsatz" styleClass="table-title" />
|
||||
</VBox>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
|
||||
<!-- Leere-Box in Spalte 1: Schafft Platz zwischen dem Tisch-Box und der Chat-Box -->
|
||||
<VBox GridPane.columnIndex="1"
|
||||
alignment="CENTER"
|
||||
minWidth="100"
|
||||
minHeight="100">
|
||||
<!-- Bleibt leer -->
|
||||
</VBox>
|
||||
|
||||
<!-- TODO: Platzhalter: Chat-Box in Spalte 2: -->
|
||||
<fx:include source="components/chatbox.fxml" GridPane.columnIndex="2"/>
|
||||
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
<!-- verschiebbare Taskbar -->
|
||||
<AnchorPane>
|
||||
<fx:include fx:id="taskbarInclude" source="gameuicomponents/taskbar.fxml"/>
|
||||
</AnchorPane>
|
||||
|
||||
<!-- Gegner-Status 1: Eines von drei Panels, das Icon, Name und Kontostand des Mitspielers anzeigt -->
|
||||
<GridPane AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0"
|
||||
AnchorPane.topAnchor="50">
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="5"/>
|
||||
<ColumnConstraints percentWidth="16"/>
|
||||
</columnConstraints>
|
||||
|
||||
<children>
|
||||
<fx:include source="gameuicomponents/playerstatus.fxml" GridPane.columnIndex="1"/>
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
<!-- Gegner-Status 2: Eines von drei Panels, das Icon, Name und Kontostand des Mitspielers anzeigt -->
|
||||
<GridPane AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0"
|
||||
AnchorPane.topAnchor="20">
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="30"/>
|
||||
<ColumnConstraints percentWidth="16"/>
|
||||
</columnConstraints>
|
||||
|
||||
<children>
|
||||
<fx:include source="gameuicomponents/playerstatus.fxml" GridPane.columnIndex="1"/>
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
<!-- Gegner-Status 3: Eines von drei Panels, das Icon, Name und Kontostand des Mitspielers anzeigt -->
|
||||
<GridPane AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0"
|
||||
AnchorPane.topAnchor="50">
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="55"/>
|
||||
<ColumnConstraints percentWidth="16"/>
|
||||
</columnConstraints>
|
||||
|
||||
<children>
|
||||
<fx:include source="gameuicomponents/playerstatus.fxml" GridPane.columnIndex="1"/>
|
||||
</children>
|
||||
</GridPane>
|
||||
</AnchorPane>
|
||||
@@ -0,0 +1,251 @@
|
||||
.root {
|
||||
-fx-background-color: #000000;
|
||||
-fx-font-family: "Monospaced", "Courier New"; /* Schriftart */
|
||||
}
|
||||
|
||||
.background {
|
||||
-fx-background-image: url("/images/background.png");
|
||||
-fx-background-size: cover; /* Bild füllt das ganze Fenster aus */
|
||||
/* Zentriert das Bild und verhindert, dass es sich kachelartig wiederholt */
|
||||
-fx-background-position: center center;
|
||||
-fx-background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
.casino-table {
|
||||
-fx-background-color: #0d9e3b;
|
||||
-fx-background-radius: 210;
|
||||
-fx-border-radius: 200;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 12;
|
||||
/*-fx-min-height: 60vh;*/
|
||||
/*-fx-max-height: 60vh;*/
|
||||
/*-fx-min-width: 90vh;*/
|
||||
/*-fx-max-width: 90vh;*/
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0,0,0,0.8), 0, 0, 15, 15);
|
||||
}
|
||||
|
||||
.dealer-box {
|
||||
-fx-background-color: #ffffff;
|
||||
-fx-border-color: #000000;
|
||||
-fx-border-width: 3;
|
||||
-fx-background-radius: 0;
|
||||
-fx-border-radius: 0;
|
||||
-fx-min-width: 70;
|
||||
-fx-min-height: 100;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0,0,0,0.8), 0, 0, 4, 4);
|
||||
}
|
||||
|
||||
.table-title {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 28px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-effect: dropshadow(one-pass-box, #000000, 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.taskbar {
|
||||
-fx-background-color: rgba(13, 158, 59);
|
||||
-fx-background-radius: 23;
|
||||
-fx-border-radius: 20;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 3;
|
||||
-fx-padding: 10 25;
|
||||
-fx-cursor: move;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0,0,0,0.8), 0, 0, 5, 5);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.taskbar:hover {
|
||||
-fx-border-color: #ffffff;
|
||||
}
|
||||
|
||||
.black-input-field {
|
||||
-fx-background-color: #1a1a1a;
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-background-radius: 8;
|
||||
-fx-border-radius: 8;
|
||||
-fx-border-color: #444444;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 5 12;
|
||||
}
|
||||
|
||||
.black-input-field:hover {
|
||||
-fx-translate-y: -3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
|
||||
.black-input-field:focused {
|
||||
-fx-border-color: #ffffff;
|
||||
-fx-background-color: #000000;
|
||||
}
|
||||
|
||||
.gray-input-field {
|
||||
-fx-background-color: #333333;
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-background-radius: 8;
|
||||
-fx-border-radius: 8;
|
||||
-fx-border-color: #666666;
|
||||
-fx-color-color: #cccccc;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 5 12;
|
||||
}
|
||||
|
||||
.gray-input-field:hover {
|
||||
-fx-translate-y: -3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.gray-input-field:focused {
|
||||
-fx-border-color: #ffffff;
|
||||
-fx-background-color: #333333;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.gray-button {
|
||||
-fx-background-color: #333333;
|
||||
-fx-border-color: #666666;
|
||||
-fx-text-fill: #cccccc;
|
||||
}
|
||||
|
||||
.gray-button, .yellow-button, .red-button {
|
||||
-fx-background-radius: 12;
|
||||
-fx-border-radius: 12;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 6 15;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.gray-button:hover, .yellow-button:hover, .red-button:hover {
|
||||
-fx-translate-y: -3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.player-status-pane {
|
||||
-fx-background-color: rgba(13, 158, 59, 0);
|
||||
-fx-background-radius: 15;
|
||||
-fx-border-radius: 15;
|
||||
/* -fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 2;
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 10, 0, 0, 4);*/
|
||||
}
|
||||
|
||||
.player-status-pane:hover {
|
||||
-fx-translate-y: -3;
|
||||
}
|
||||
|
||||
.status-inner-box-top {
|
||||
-fx-background-color: rgba(13, 158, 59);
|
||||
-fx-background-radius: 19;
|
||||
-fx-border-radius: 15;
|
||||
-fx-padding: 5 10;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.status-inner-box-midle {
|
||||
-fx-background-color: rgba(13, 158, 59, 0);
|
||||
}
|
||||
|
||||
.status-inner-box-bottom {
|
||||
-fx-background-color: rgba(13, 158, 59);
|
||||
-fx-background-radius: 19;
|
||||
-fx-border-radius: 15;
|
||||
-fx-padding: 5 10;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.status-label-small {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-family: "Monospaced"; /* Schriftart */
|
||||
-fx-font-size: 15px;
|
||||
}
|
||||
|
||||
.status-value-text {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-family: "Monospaced"; /* Schriftart */
|
||||
-fx-font-size: 15px;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.status-value-money {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-family: "Monospaced"; /* Schriftart */
|
||||
-fx-font-size: 15px;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.status-circle {
|
||||
-fx-background-color: #000000;
|
||||
-fx-background-radius: 50;
|
||||
-fx-border-color: #ffffff;
|
||||
-fx-border-width: 3;
|
||||
-fx-border-radius: 50;
|
||||
}
|
||||
|
||||
.browser-root {
|
||||
-fx-background-image: url("/images/background.png");
|
||||
-fx-background-size: cover;
|
||||
-fx-background-position: center;
|
||||
-fx-background-color: #0b2d15;
|
||||
}
|
||||
|
||||
.browser-web-view {
|
||||
-fx-background-color: rgb(13, 158, 59);
|
||||
-fx-background-radius: 23;
|
||||
-fx-border-radius: 20;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 3;
|
||||
-fx-padding: 0;
|
||||
-fx-cursor: default;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0,0,0,0.8), 0, 0, 5, 5);
|
||||
}
|
||||
|
||||
.web-view {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.taskbar-browser {
|
||||
-fx-background-color: rgb(13, 158, 59);
|
||||
-fx-background-radius: 23;
|
||||
-fx-border-radius: 20;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 3;
|
||||
-fx-padding: 10 25;
|
||||
-fx-cursor: default;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0,0,0,0.8), 0, 0, 5, 5);
|
||||
}
|
||||
|
||||
/*.taskbar-browser:hover {*/
|
||||
/* -fx-border-color: #ffffff;*/
|
||||
/*}*/
|
||||
|
||||
.security-label {
|
||||
-fx-text-fill: #000000;
|
||||
-fx-font-weight: bold;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
|
||||
<!--
|
||||
Dieses Fenster wird später mit den Serveranfragen verknüpft,
|
||||
um Nachrichten von Mitspielern und Systemmeldungen in Echtzeit anzuzeigen.
|
||||
|
||||
Der Chat unterscheidet intern zwischen:
|
||||
- Player-to-Player (Privater 2-Personen-Chat)
|
||||
- Lobby-Chat (Aktueller Raum)
|
||||
- Globaler Chat (Gesamter Server)
|
||||
|
||||
Features, die später implementiert werden sollen:
|
||||
- Schicke Chat-Bubbles mit Namen und Uhrzeit.
|
||||
-->
|
||||
|
||||
<VBox xmlns="http://javafx.com/javafx/21"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatController"
|
||||
alignment="CENTER"
|
||||
stylesheets="@chatui.css">
|
||||
|
||||
<!-- Innenabstand: Schafft oben, rechts und unten 30 Pixel Platz, links nur 10 Pixel (asymmetrisch) -->
|
||||
<padding>
|
||||
<Insets top="30" right="30" bottom="30" left="10"/>
|
||||
</padding>
|
||||
|
||||
<children>
|
||||
<VBox VBox.vgrow="ALWAYS"
|
||||
styleClass="chat-box"
|
||||
spacing="10"
|
||||
maxWidth="Infinity">
|
||||
|
||||
<children>
|
||||
<Label text="== CHAT ==" styleClass="chat-header" alignment="CENTER" maxWidth="Infinity"/>
|
||||
<Region minHeight="4" styleClass="chat-separator"/>
|
||||
|
||||
<!-- Chatnachrichten werden hier hinzugefügt -->
|
||||
<ScrollPane fx:id="chatScrollPane" fitToWidth="true" vbarPolicy="AS_NEEDED" hbarPolicy="NEVER" VBox.vgrow="ALWAYS" styleClass="chat-scroll-pane">
|
||||
<content>
|
||||
<VBox fx:id="chatVBox" spacing="10" styleClass="chat-VBox"/>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
|
||||
<!-- Eingabebereich -->
|
||||
<HBox spacing="10">
|
||||
<!-- TODO: Größe des TextFields dynamisch anpassen -->
|
||||
<TextField fx:id="inputField" HBox.hgrow="ALWAYS" promptText="Nachricht eingeben..." styleClass="gray-input-field" onAction="#sendMessage"/>
|
||||
<Button fx:id="sendButton" text="SENDEN" onAction="#sendMessage" styleClass="yellow-button"/>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -0,0 +1,155 @@
|
||||
.chat-box {
|
||||
-fx-background-color: #0d9e3b;
|
||||
-fx-background-radius: 58;
|
||||
-fx-border-radius: 50;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 12;
|
||||
-fx-padding: 20;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0,0,0,1), 0, 0, 15, 15);
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 22px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-padding: 0 0 10 0;
|
||||
-fx-effect: dropshadow(one-pass-box, #000000, 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.chat-separator {
|
||||
-fx-background-color: #ffffff;
|
||||
-fx-min-height: 4px;
|
||||
-fx-effect: dropshadow(one-pass-box, #000000, 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.black-input-field {
|
||||
-fx-background-color: #1a1a1a;
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-background-radius: 8;
|
||||
-fx-border-radius: 8;
|
||||
-fx-border-color: #444444;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 5 12;
|
||||
}
|
||||
|
||||
.black-input-field:hover {
|
||||
-fx-translate-y: -3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
|
||||
.black-input-field:focused {
|
||||
-fx-border-color: #ffffff;
|
||||
-fx-background-color: #000000;
|
||||
}
|
||||
|
||||
.gray-input-field {
|
||||
-fx-background-color: #333333;
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-background-radius: 8;
|
||||
-fx-border-radius: 8;
|
||||
-fx-border-color: #666666;
|
||||
-fx-color-color: #cccccc;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 5 12;
|
||||
}
|
||||
|
||||
.gray-input-field:hover {
|
||||
-fx-translate-y: -3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.gray-input-field:focused {
|
||||
-fx-border-color: #ffffff;
|
||||
-fx-background-color: #333333;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.gray-button {
|
||||
-fx-background-color: #333333;
|
||||
-fx-border-color: #666666;
|
||||
-fx-text-fill: #cccccc;
|
||||
}
|
||||
|
||||
.gray-button, .yellow-button, .red-button {
|
||||
-fx-background-radius: 12;
|
||||
-fx-border-radius: 12;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 6 15;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.gray-button:hover, .yellow-button:hover, .red-button:hover {
|
||||
-fx-translate-y: -3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.chat-scroll-pane {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background: transparent;
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.chat-VBox {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background: transparent;
|
||||
}
|
||||
.scroll-pane {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar:vertical {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar .track {
|
||||
-fx-background-color: #5c3d10;
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar .thumb {
|
||||
-fx-background-color: #3d260a;
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-radius: 5;
|
||||
-fx-pref-width: 6;
|
||||
-fx-pref-height: 6;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar:horizontal {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar:horizontal .track {
|
||||
-fx-background-color: #5c3d10;
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar:horizontal .thumb {
|
||||
-fx-background-color: #3d260a;
|
||||
-fx-background-radius: 5;
|
||||
-fx-pref-height: 6;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar:horizontal .thumb:hover {
|
||||
-fx-pref-height: 10;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
|
||||
<!--
|
||||
TODO: Haupt-platzhalter für spieler-status & eingabe.
|
||||
Dieses Layout dient aktuell der visuellen Struktur. Sobald die GameEngine und
|
||||
Serveranfragen (API/Requests) durchgeführt werden können, werden Name, Kontostand
|
||||
und Status-Anzeigen dynamisch mit Echtzeit-Daten vom Server befüllt.
|
||||
-->
|
||||
|
||||
<VBox fx:id="playerStatusBox"
|
||||
xmlns="http://javafx.com/javafx/21"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
styleClass="player-status-pane"
|
||||
prefWidth="200"
|
||||
prefHeight="100"
|
||||
spacing="0">
|
||||
|
||||
<children>
|
||||
<!-- Bereich für den Spielernamen -->
|
||||
<HBox styleClass="status-inner-box-top"
|
||||
alignment="CENTER_LEFT"
|
||||
maxWidth="Infinity"
|
||||
prefHeight="60">
|
||||
|
||||
<padding>
|
||||
<Insets left="15"/>
|
||||
</padding>
|
||||
|
||||
<Label text="NAME: " styleClass="status-label-small"/>
|
||||
<!-- TODO: fx:id="playerName": Wird später durch den User-Namen aus der Server-Abfrage ersetzt -->
|
||||
<Label fx:id="playerName" text="SPIELER" styleClass="status-value-text"/>
|
||||
</HBox>
|
||||
|
||||
<!-- Schaft platz zwischen den Boxen -->
|
||||
<HBox styleClass="status-inner-box-midle"
|
||||
alignment="CENTER_LEFT"
|
||||
maxWidth="Infinity"
|
||||
prefHeight="10">
|
||||
|
||||
<padding>
|
||||
<Insets left="15"/>
|
||||
</padding>
|
||||
</HBox>
|
||||
|
||||
<!-- Anzeige des Kapitals/Geldstands -->
|
||||
<HBox styleClass="status-inner-box-bottom"
|
||||
alignment="CENTER_LEFT"
|
||||
maxWidth="Infinity"
|
||||
prefHeight="30">
|
||||
|
||||
<padding>
|
||||
<Insets left="15"/>
|
||||
</padding>
|
||||
|
||||
<Label text="GELD: " styleClass="status-label-small"/>
|
||||
<Label fx:id="playerMoney" text="0 $" styleClass="status-value-money"/>
|
||||
</HBox>
|
||||
|
||||
<!-- TODO: User-Logo oder Icon: Platzhalter für das Profilbild oder das individuelle Logo des Spielers -->
|
||||
<Pane prefHeight="0" maxWidth="Infinity">
|
||||
<Region styleClass="status-circle"
|
||||
layoutX="-10"
|
||||
layoutY="-105"
|
||||
prefWidth="30"
|
||||
prefHeight="30"/>
|
||||
</Pane>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
|
||||
<!-- verschiebbare Taskbar -->
|
||||
<HBox 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.TaskbarController"
|
||||
fx:id="taskbar"
|
||||
styleClass="taskbar"
|
||||
spacing="15"
|
||||
alignment="CENTER"
|
||||
onMousePressed="#onTaskbarPressed"
|
||||
onMouseDragged="#onTaskbarDragged"
|
||||
onMouseReleased="#onTaskbarReleased"
|
||||
layoutX="50.0" layoutY="710.0">
|
||||
|
||||
<!-- Platzhalter-Buttons -->
|
||||
<Button text="EINSTELLUNGEN" styleClass="gray-button" />
|
||||
<Button text="INFO" styleClass="gray-button" />
|
||||
<!-- Buten für den Casono Browser -->
|
||||
<Button text="HILFE" onAction="#onBrowserButtonClick" styleClass="gray-button" />
|
||||
|
||||
<!-- Vertikale-Trennlinie: Erzeugt eine optische Abgrenzung zwischen zwei Butten-Bereichen -->
|
||||
<Separator orientation="VERTICAL" prefHeight="20" opacity="0.3" />
|
||||
|
||||
<!-- Eingabefeld: Reagiert auf die Enter-Taste, um den Einsatz zu bestätigen -->
|
||||
<TextField fx:id="taskbarInput"
|
||||
promptText="Betrag setzen..."
|
||||
onKeyPressed="#onInputSubmitted"
|
||||
styleClass="gray-input-field"
|
||||
prefWidth="150" />
|
||||
|
||||
<!-- Bestätigungs-Button: Alternative zum Enter-Key, um den Einsatz abzusenden -->
|
||||
<Button text="SETZTEN"
|
||||
onAction="#onInputSubmittedAction"
|
||||
styleClass="yellow-button"/>
|
||||
|
||||
<!-- Vertikale-Trennlinie: Erzeugt eine optische Abgrenzung zwischen zwei Butten-Bereichen -->
|
||||
<Separator orientation="VERTICAL" prefHeight="20" opacity="0.3" />
|
||||
|
||||
<!-- Menü-Button: Bricht das aktuelle Spiel ab und kehrt zum Hauptmenü zurück -->
|
||||
<Button text="VERLASSEN"
|
||||
onAction="#onExitButtonClick"
|
||||
styleClass="red-button"/>
|
||||
|
||||
<!-- Innenabstand: Hält den Inhalt 10 Pixel von oben/unten und 20 Pixel von den Seiten fern -->
|
||||
<padding>
|
||||
<Insets top="10" right="20" bottom="10" left="20"/>
|
||||
</padding>
|
||||
</HBox>
|
||||
Reference in New Issue
Block a user