Merge branch 'feat/chat-visual-improvement' into 'main'

Feat/chat visual improvement

Closes #107

See merge request cs108-fs26/Gruppe-13!159
This commit was merged in pull request #315.
This commit is contained in:
Mathis Ginkel
2026-04-26 21:50:55 +00:00
5 changed files with 127 additions and 47 deletions
@@ -1,6 +1,7 @@
package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController.ChatKey;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
@@ -20,7 +21,6 @@ import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
@@ -30,16 +30,10 @@ public class ChatBoxController {
private final ChatController chatController;
@FXML private VBox chatBox;
@FXML private TabPane chatTabPane;
@FXML private MenuButton addWhisperChatButton;
@FXML private HBox menuBox;
private FXMLLoader fxmlLoader;
private final List<String> activeWhisperChats;
@FXML private final Map<String, Tab> usernameTabMap;
@@ -76,7 +70,7 @@ public class ChatBoxController {
ChatModel globalChatModel = new ChatModel(global, username, -1, null);
chatController.getChatModelMap().put(new ChatController.ChatKey(global), globalChatModel);
addChatTab("GLOBAL", globalChatModel, global);
addWhisperChatButton.setOnAction(event -> addWhisperChatButton.show());
addWhisperChatButton.setOnAction(_ -> addWhisperChatButton.show());
}
/**
@@ -96,7 +90,7 @@ public class ChatBoxController {
MenuItem menuItem = new MenuItem(targetUserName);
addWhisperChatButton.getItems().add(menuItem);
menuItem.setOnAction(
event ->
_ ->
addWhisperChat(
targetUserName,
new ChatModel(
@@ -130,7 +124,7 @@ public class ChatBoxController {
if (oldUsername.equals(item.getText())) {
item.setText(newUsername);
item.setOnAction(
event ->
_ ->
addWhisperChat(
newUsername,
new ChatModel(
@@ -165,11 +159,21 @@ public class ChatBoxController {
if (!activeWhisperChats.contains(target)) {
activeWhisperChats.add(target);
ChatType whisper = ChatType.WHISPER;
chatController
.getChatModelMap()
.put(new ChatController.ChatKey(whisper, target), chatModel);
addChatTab(target, chatModel, whisper);
chatController.getChatModelMap().put(new ChatKey(whisper, target), chatModel);
ChatViewController chatViewController = addChatTab(target, chatModel, whisper);
Tab chatTab = chatViewController.getChatTab();
chatTab.setOnCloseRequest(
_ -> {
chatViewController.tabIsOpen = false;
chatTabPane.getTabs().remove(chatTab);
});
} else {
ChatViewController chatViewController =
chatController.activeChatControllers.get(new ChatKey(ChatType.WHISPER, target));
if (!chatViewController.tabIsOpen) {
reopenChatTab(chatViewController.getChatTab());
chatViewController.tabIsOpen = true;
}
chatTabPane.getSelectionModel().select(usernameTabMap.get(target));
}
}
@@ -183,30 +187,40 @@ public class ChatBoxController {
* @param chatModel The {@link ChatModel} containing the data and logic for this specific chat.
* @throws RuntimeException If the FXML resource for the chat tab cannot be loaded.
*/
public void addChatTab(String title, ChatModel chatModel, ChatType chatType) {
public ChatViewController addChatTab(String title, ChatModel chatModel, ChatType chatType) {
String ressource = "/ui-structure/components/chatui/chattab.fxml";
URL resource = getClass().getResource(ressource);
FXMLLoader fxmlLoader = new FXMLLoader(resource);
runOnPlatformSynchronized(
return runOnPlatformSynchronized(
() -> {
try {
ChatViewController chatViewController =
new ChatViewController(
this.chatController, chatModel, this.username);
chatController.activeChatControllers.put(
new ChatController.ChatKey(chatType), chatViewController);
this.chatController, chatModel, this);
ChatKey chatKey;
if (chatType.equals(ChatType.WHISPER)) {
chatKey = new ChatKey(chatType, title);
} else {
chatKey = new ChatKey(chatType);
}
chatController.activeChatControllers.put(chatKey, chatViewController);
fxmlLoader.setController(chatViewController);
Node load = fxmlLoader.load();
VBox.setVgrow(load, Priority.ALWAYS);
VBox vbox = new VBox();
VBox.setVgrow(vbox, Priority.ALWAYS);
vbox.getChildren().add(load);
chatModel.addListener((msg) -> chatViewController.showMessage(msg));
chatModel.addListener(chatViewController::showMessage);
Tab newChat = new Tab(title, vbox);
usernameTabMap.put(title, newChat);
this.chatTabPane.getTabs().add(newChat);
chatTabPane.getTabs().add(newChat);
this.chatTabPane.getSelectionModel().select(newChat);
return newChat;
chatViewController.setChatTab(newChat);
if (!chatType.equals(ChatType.WHISPER)) {
newChat.setClosable(false);
}
return chatViewController;
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -267,4 +281,12 @@ public class ChatBoxController {
}
}
}
public void reopenChatTab(Tab chatTab) {
Platform.runLater(
() -> {
chatTabPane.getTabs().add(chatTab);
chatTabPane.getSelectionModel().select(chatTab);
});
}
}
@@ -11,37 +11,48 @@ import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
/** Responsible for the presentation of the ChatModel to the Client */
public class ChatViewController implements Initializable {
private final ChatBoxController chatBoxController;
@FXML private Button sendButton;
@FXML private TextField inputField;
@FXML private VBox chatInterfaceVBox;
@FXML private HBox controlBar;
@FXML private ScrollPane scrollPane;
@FXML private VBox chat;
private final ChatModel chatModel;
public Tab getChatTab() {
return chatTab;
}
private final String username;
public void setChatTab(Tab chatTab) {
this.chatTab = chatTab;
}
@FXML private Tab chatTab;
public Boolean tabIsOpen;
private final ChatModel chatModel;
private final ChatController controller;
private static final int CHAT_PADDING = 20;
public ChatViewController(ChatController chatController, ChatModel chatModel, String username) {
public ChatViewController(
ChatController chatController,
ChatModel chatModel,
ChatBoxController chatBoxController) {
this.controller = chatController;
this.username = username;
this.chatModel = chatModel;
this.chatBoxController = chatBoxController;
tabIsOpen = true;
}
/**
@@ -54,8 +65,8 @@ public class ChatViewController implements Initializable {
*/
@Override
public void initialize(URL location, ResourceBundle resourceBundle) {
inputField.setOnAction(event -> sendMessage());
sendButton.setOnAction(event -> sendMessage());
inputField.setOnAction(_ -> sendMessage());
sendButton.setOnAction(_ -> sendMessage());
scrollPane.vvalueProperty().bind(chat.heightProperty());
}
@@ -88,6 +99,10 @@ public class ChatViewController implements Initializable {
* @param msg The {@link Message} object containing the content and metadata to display.
*/
public void showMessage(Message msg) {
if (!tabIsOpen) {
tabIsOpen = true;
chatBoxController.reopenChatTab(chatTab);
}
Platform.runLater(
() -> {
String msgText =
@@ -3,13 +3,14 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.skin.TabPaneSkin.TabContentRegion?>
<VBox xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1"
fx:id="chatBox"
VBox.vgrow="ALWAYS"
stylesheets="@chatui.css"
styleClass="chat-box"
spacing="10"
spacing="20"
maxWidth="Infinity">
<HBox spacing="10"
@@ -17,14 +18,14 @@
<Label text="== CHAT ==" styleClass="chat-header" alignment="CENTER" maxWidth="Infinity"/>
<Region HBox.hgrow="ALWAYS"/>
<MenuButton fx:id="addWhisperChatButton" styleClass="yellow-button" text="WHISPER CHAT"
<MenuButton fx:id="addWhisperChatButton" styleClass="menu-button" text="WHISPER CHAT"
alignment="CENTER_RIGHT">
</MenuButton>
</HBox>
<TabPane fx:id="chatTabPane"
VBox.vgrow="ALWAYS"
styleClass="tab-header-area">
styleClass="tab-pane">
</TabPane>
@@ -8,7 +8,7 @@
<VBox fx:id="chatInterfaceVBox"
maxWidth="Infinity"
spacing="10"
styleClass="chat-box"
styleClass="tab-chat-box"
stylesheets="@chatui.css"
VBox.vgrow="ALWAYS"
xmlns="http://javafx.com/javafx/17.0.12"
@@ -5,7 +5,17 @@
-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);*/
-fx-effect: dropshadow(one-pass-box, rgba(0,0,0,1), 0, 0, 15, 15);
}
.tab-chat-box {
-fx-background-color: #0d9e3b;
-fx-background-radius: 58;
-fx-border-radius: 50;
-fx-border-color: #0d763b #0d623b #0d623b #0d763b;
-fx-border-width: 12;
-fx-padding: 20;
}
.chat-header {
@@ -163,25 +173,25 @@
-fx-border-width: 5;
-fx-padding: 10;
-fx-effect: dropshadow(one-pass-box, rgba(0,0,0,1), 0, 0, 15, 15);
-fx-max-height: 10;
-fx-min-height: 10;
}
.tab-pane .tab {
-fx-background-color: #0d9e3b;
-fx-background-radius: 18;
-fx-background-color: #0d983b;
-fx-background-radius: 28;
-fx-border-radius: 10;
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
-fx-border-color: #0d873b #0d733b #0d733b #0d873b;
-fx-border-width: 5;
-fx-padding: 10;
-fx-padding: 6 15;
}
.tab-pane .tab:selected {
-fx-background-color: #0d763b;
-fx-background-color: #0d873b;
-fx-background-radius: 18;
-fx-border-radius: 10;
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
-fx-border-color: #0d763b #0d623b #0d623b #0d763b;
-fx-border-width: 5;
-fx-padding: 10;
-fx-padding: 6 15;
}
.tab-pane .tab .tab-close-button {
@@ -192,7 +202,6 @@
-fx-background-color: #0d9e3b;
}
.tab .tab-label {
-fx-alignment: CENTER;
-fx-text-fill: #ffffff;
@@ -204,3 +213,36 @@
-fx-alignment: CENTER;
-fx-text-fill: #ffffff;
}
.menu-button {
-fx-background-color: #b8860b;
-fx-border-color: #ffd700;
-fx-text-fill: #ffffff;
-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;
}
.menu-button .context-menu {
-fx-background-color: #b8860b;
-fx-background-radius: 4;
-fx-padding: 6 15;
-fx-cursor: hand;
}
.menu-button:hover {
-fx-translate-y: -3;
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
}
.menu-button .menu-item {
-fx-background-color: #b8860b;
-fx-border-color: #ffd700;
-fx-text-fill: #ffffff
}