Feat/chat visual improvement #315

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