Chat page UI implemented.
@@ -0,0 +1,277 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ChatPageController {
|
||||
|
||||
// ===== messages area =====
|
||||
@FXML private VBox messageContainer;
|
||||
@FXML private ScrollPane messageScrollPane;
|
||||
|
||||
// ===== input area =====
|
||||
@FXML private TextArea messageInput;
|
||||
@FXML private Button sendButton;
|
||||
|
||||
@FXML private Button attachmentButton;
|
||||
@FXML private ImageView attachmentIcon; // <ImageView> inside the attachment button
|
||||
|
||||
// ===== header =====
|
||||
@FXML private ImageView userAvatar; // 36x36 in the FXML
|
||||
@FXML private Label chatTitle; // contact/group title
|
||||
@FXML private Label chatStatus; // last seen / online
|
||||
|
||||
@FXML private Button searchInChatButton; // magnifier button
|
||||
@FXML private ImageView searchIcon;
|
||||
|
||||
@FXML private Button moreButton; // 3-dots button
|
||||
@FXML private ImageView moreIcon;
|
||||
@FXML private ContextMenu moreMenu;
|
||||
@FXML private MenuItem viewProfileItem;
|
||||
@FXML private MenuItem deleteChatItem;
|
||||
|
||||
// ===== send icon =====
|
||||
@FXML private ImageView sendIcon;
|
||||
|
||||
// ===== state =====
|
||||
private String chatName;
|
||||
private final ThemeManager themeManager = ThemeManager.getInstance();
|
||||
|
||||
// Where your icons live
|
||||
private static final String ICON_BASE = "/org/to/telegramfinalproject/Icons/";
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// Send button
|
||||
if (sendButton != null) {
|
||||
sendButton.setOnAction(e -> sendMessage());
|
||||
}
|
||||
|
||||
// ENTER = send, SHIFT+ENTER = newline
|
||||
messageInput.addEventFilter(javafx.scene.input.KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.getCode() == javafx.scene.input.KeyCode.ENTER) {
|
||||
if (e.isShiftDown()) {
|
||||
// let newline happen
|
||||
} else {
|
||||
e.consume(); // block newline
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Enable/disable send button by content (ignore spaces/newlines)
|
||||
messageInput.textProperty().addListener((obs, oldV, newV) -> {
|
||||
boolean hasRealText = newV != null && !newV.trim().isEmpty();
|
||||
sendButton.setDisable(!hasRealText);
|
||||
|
||||
// toggle style class for color state
|
||||
var sc = sendButton.getStyleClass();
|
||||
sc.removeAll("send-empty", "send-ready");
|
||||
sc.add(hasRealText ? "send-ready" : "send-empty");
|
||||
});
|
||||
|
||||
// Auto-resize input box like Telegram
|
||||
messageInput.textProperty().addListener((obs, oldText, newText) -> {
|
||||
Platform.runLater(() -> {
|
||||
var textNode = messageInput.lookup(".text");
|
||||
if (textNode != null) {
|
||||
double textHeight = textNode.getBoundsInLocal().getHeight();
|
||||
double padding = 20; // top + bottom padding
|
||||
double newHeight = textHeight + padding;
|
||||
|
||||
if (newHeight < 40) newHeight = 40; // min (1 row)
|
||||
if (newHeight > 120) newHeight = 120; // max (~5 rows)
|
||||
|
||||
messageInput.setPrefHeight(newHeight);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Start in "empty" state
|
||||
sendButton.getStyleClass().add("send-empty");
|
||||
|
||||
// attach file
|
||||
if (attachmentButton != null) {
|
||||
attachmentButton.setOnAction(e -> openFileChooser());
|
||||
}
|
||||
|
||||
// Hook "More" button → show menu under it
|
||||
if (moreButton != null) {
|
||||
moreButton.setOnAction(e -> {
|
||||
if (!moreMenu.isShowing()) {
|
||||
moreMenu.show(moreButton, Side.BOTTOM, 0, 0);
|
||||
} else {
|
||||
moreMenu.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Menu item actions
|
||||
viewProfileItem.setOnAction(e -> {
|
||||
System.out.println("Viewing profile of " + chatName);
|
||||
// TODO: open profile UI
|
||||
});
|
||||
|
||||
deleteChatItem.setOnAction(e -> {
|
||||
System.out.println("Deleting chat with " + chatName);
|
||||
// TODO: delete logic
|
||||
});
|
||||
|
||||
// Initial icon sync once the Scene is ready (stylesheet applied)
|
||||
Platform.runLater(this::syncIconsWithTheme);
|
||||
|
||||
// Auto-Scroll
|
||||
messageContainer.heightProperty().addListener((obs, oldVal, newVal) -> {
|
||||
Platform.runLater(() -> {
|
||||
var timeline = new javafx.animation.Timeline();
|
||||
var kv = new javafx.animation.KeyValue(
|
||||
messageScrollPane.vvalueProperty(), 1.0, javafx.animation.Interpolator.EASE_BOTH
|
||||
);
|
||||
var kf = new javafx.animation.KeyFrame(javafx.util.Duration.millis(200), kv);
|
||||
timeline.getKeyFrames().add(kf);
|
||||
timeline.play();
|
||||
});
|
||||
});
|
||||
|
||||
// Smooth scroll feel
|
||||
messageScrollPane.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
messageScrollPane.setPannable(true);
|
||||
messageScrollPane.setFitToWidth(true);
|
||||
messageScrollPane.setFitToHeight(false);
|
||||
messageScrollPane.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.00003; // smaller = smoother
|
||||
messageScrollPane.setVvalue(messageScrollPane.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
// React to theme changes everywhere
|
||||
themeManager.darkModeProperty().addListener((o, oldVal, isDark) -> syncIconsWithTheme());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void openSearchPanel() {
|
||||
MainController.getInstance().showSearchPanel();
|
||||
}
|
||||
|
||||
/** Called by main controller when opening a chat. */
|
||||
public void setChat(String chatName, String avatarPath) {
|
||||
this.chatName = chatName;
|
||||
|
||||
// Header text (if present)
|
||||
if (chatTitle != null) chatTitle.setText(chatName);
|
||||
if (chatStatus != null) chatStatus.setText("last seen recently"); // or live status
|
||||
|
||||
// Load avatar if provided
|
||||
if (userAvatar != null && avatarPath != null) {
|
||||
try {
|
||||
Image avatarImg = new Image(getClass().getResourceAsStream(avatarPath));
|
||||
userAvatar.setImage(avatarImg);
|
||||
Circle clip = new Circle(18, 18, 18); // x, y, radius
|
||||
userAvatar.setClip(clip);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Could not load avatar: " + avatarPath);
|
||||
}
|
||||
}
|
||||
|
||||
addSystemMessage("Chat with " + chatName + " opened.");
|
||||
Platform.runLater(() -> messageInput.requestFocus());
|
||||
}
|
||||
|
||||
// ----- actions -----
|
||||
|
||||
private void sendMessage() {
|
||||
String text = messageInput.getText() == null ? "" : messageInput.getText().trim();
|
||||
if (!text.isEmpty()) {
|
||||
addMessage("You", text);
|
||||
messageInput.clear();
|
||||
// TODO: send to server
|
||||
}
|
||||
}
|
||||
|
||||
private void openFileChooser() {
|
||||
FileChooser fc = new FileChooser();
|
||||
fc.setTitle("Select a file to send");
|
||||
File file = fc.showOpenDialog(attachmentButton.getScene().getWindow());
|
||||
if (file != null) {
|
||||
System.out.println("Selected file: " + file.getAbsolutePath());
|
||||
// TODO: actually send file
|
||||
addSystemMessage("Attached file: " + file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// ----- UI helpers -----
|
||||
|
||||
/** Add a normal message bubble (very simple for now). */
|
||||
public void addMessage(String sender, String content) {
|
||||
Label msg = new Label(sender + ": " + content);
|
||||
msg.setWrapText(true);
|
||||
|
||||
boolean dark = themeManager.isDarkMode();
|
||||
String bubbleColor = dark ? "#20405a" : "#4fa8f0";
|
||||
String textColor = dark ? "#e8f1f8" : "#0f141a";
|
||||
msg.setStyle(
|
||||
"-fx-background-color: " + bubbleColor + ";" +
|
||||
"-fx-text-fill: " + textColor + ";" +
|
||||
"-fx-padding: 6 10; -fx-background-radius: 10;"
|
||||
);
|
||||
|
||||
messageContainer.getChildren().add(msg);
|
||||
}
|
||||
|
||||
private void addSystemMessage(String content) {
|
||||
Label sys = new Label(content);
|
||||
sys.setStyle("-fx-text-fill: gray; -fx-font-size: 11;");
|
||||
messageContainer.getChildren().add(sys);
|
||||
messageScrollPane.layout();
|
||||
messageScrollPane.setVvalue(1.0);
|
||||
}
|
||||
|
||||
/** Update all header/footer icons according to current theme. */
|
||||
private void syncIconsWithTheme() {
|
||||
boolean dark = themeManager.isDarkMode();
|
||||
// We use “_light” icons on dark backgrounds, and “_dark” on light backgrounds.
|
||||
String suffix = dark ? "_light.png" : "_dark.png";
|
||||
|
||||
// attachment
|
||||
if (attachmentIcon != null) {
|
||||
attachmentIcon.setImage(loadIcon("attachment" + suffix));
|
||||
}
|
||||
// send
|
||||
if (sendIcon != null) {
|
||||
sendIcon.setImage(loadIcon("send_cyan2.png"));
|
||||
}
|
||||
// header icons
|
||||
if (searchIcon != null) {
|
||||
searchIcon.setImage(loadIcon("search" + suffix));
|
||||
}
|
||||
if (moreIcon != null) {
|
||||
moreIcon.setImage(loadIcon("more" + suffix));
|
||||
}
|
||||
|
||||
// header text tint (if you’re not fully relying on CSS)
|
||||
if (chatTitle != null) chatTitle.setStyle(dark ? "-fx-text-fill:#e8f1f8;" : "-fx-text-fill:#0f141a;");
|
||||
if (chatStatus != null) chatStatus.setStyle(dark ? "-fx-text-fill:#8ea1b2;" : "-fx-text-fill:#7e8a97;");
|
||||
|
||||
// View profile icon in more button
|
||||
((ImageView) viewProfileItem.getGraphic())
|
||||
.setImage(loadIcon("view_profile" + suffix));
|
||||
}
|
||||
|
||||
private Image loadIcon(String filename) {
|
||||
var url = getClass().getResource(ICON_BASE + filename);
|
||||
if (url == null) {
|
||||
System.err.println("Icon not found: " + ICON_BASE + filename);
|
||||
return null;
|
||||
}
|
||||
return new Image(url.toExternalForm());
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,9 @@ import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -22,23 +18,46 @@ import java.io.IOException;
|
||||
|
||||
public class MainController {
|
||||
|
||||
// === LEFT PANE ===
|
||||
@FXML private VBox chatListContainer; // inside the scrollPane
|
||||
@FXML private ScrollPane scrollPane; // chat list scroll
|
||||
@FXML private VBox chatSearchPane; // search results panel
|
||||
@FXML private ListView<String> chatSearchResults;
|
||||
@FXML private MenuButton scopeDropdown;
|
||||
|
||||
// === TOP BAR ===
|
||||
@FXML private TextField searchBar; // global search bar
|
||||
@FXML private Button menuButton;
|
||||
|
||||
// === RIGHT PANE ===
|
||||
@FXML private VBox leftPane;
|
||||
@FXML private javafx.scene.layout.StackPane chatDisplayArea;
|
||||
@FXML private Label placeholderLabel;
|
||||
|
||||
// === Main root ===
|
||||
@FXML private StackPane mainRoot;
|
||||
@FXML private VBox chatListContainer;
|
||||
@FXML private StackPane chatDisplayArea;
|
||||
@FXML private SplitPane mainSplitPane;
|
||||
|
||||
// === Sidebar ===
|
||||
@FXML private Pane overlay;
|
||||
@FXML private ImageView menuIcon;
|
||||
@FXML private ScrollPane scrollPane;
|
||||
@FXML private SplitPane mainSplitPane;
|
||||
@FXML private VBox leftPane;
|
||||
@FXML private Button menuButton;
|
||||
@FXML private TextField searchBar;
|
||||
|
||||
// === STATE ===
|
||||
private static MainController instance;
|
||||
private Node sidebarRoot;
|
||||
private boolean isSidebarOpen = false;
|
||||
|
||||
// Add ThemeManager handle
|
||||
private final ThemeManager themeManager = ThemeManager.getInstance();
|
||||
|
||||
public MainController() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static MainController getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
addSampleChats();
|
||||
@@ -79,6 +98,44 @@ public class MainController {
|
||||
if (!mainSplitPane.getDividers().isEmpty()) {
|
||||
mainSplitPane.getDividers().get(0).positionProperty().addListener((o, ov, nv) -> clampDivider());
|
||||
}
|
||||
|
||||
// Search in chat field listener
|
||||
searchBar.textProperty().addListener((obs, oldV, newV) -> {
|
||||
if (!chatSearchPane.isVisible()) return; // only react if in search mode
|
||||
|
||||
if (newV.trim().isEmpty()) {
|
||||
chatSearchResults.setVisible(false);
|
||||
chatSearchResults.setManaged(false);
|
||||
} else {
|
||||
chatSearchResults.setVisible(true);
|
||||
chatSearchResults.setManaged(true);
|
||||
chatSearchResults.getItems().setAll(
|
||||
"Result 1: " + newV,
|
||||
"Result 2: " + newV,
|
||||
"Result 3: " + newV
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Called from ChatPageController when user clicks search button
|
||||
public void showSearchPanel() {
|
||||
scrollPane.setVisible(false);
|
||||
scrollPane.setManaged(false);
|
||||
|
||||
chatSearchPane.setVisible(true);
|
||||
chatSearchPane.setManaged(true);
|
||||
|
||||
searchBar.requestFocus();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void closeSearchPanel() {
|
||||
chatSearchPane.setVisible(false);
|
||||
chatSearchPane.setManaged(false);
|
||||
|
||||
scrollPane.setVisible(true);
|
||||
scrollPane.setManaged(true);
|
||||
}
|
||||
|
||||
private void addSampleChats() {
|
||||
@@ -117,8 +174,20 @@ public class MainController {
|
||||
}
|
||||
|
||||
private void openChat(String chatName) {
|
||||
chatDisplayArea.getChildren().clear();
|
||||
chatDisplayArea.getChildren().add(new javafx.scene.control.Label("Chat with " + chatName));
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_page.fxml"));
|
||||
Node chatPage = loader.load();
|
||||
|
||||
ChatPageController controller = loader.getController();
|
||||
controller.setChat("Alice", "/org/to/telegramfinalproject/Avatars/profile_test.png");
|
||||
|
||||
|
||||
chatDisplayArea.getChildren().clear();
|
||||
chatDisplayArea.getChildren().add(chatPage);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.to.telegramfinalproject.UI;
|
||||
import javafx.animation.TranslateTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -43,7 +42,7 @@ public class SidebarMenuController {
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// Load default profile image
|
||||
Image profile = loadImage("/org/to/telegramfinalproject/Images/profile.png");
|
||||
Image profile = loadImage("/org/to/telegramfinalproject/Avatars/profile.png");
|
||||
if (profile != null) profileImage.setImage(profile);
|
||||
|
||||
setupButtonActions();
|
||||
|
||||
@@ -4,6 +4,7 @@ package org.to.telegramfinalproject.UI;
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -18,6 +19,11 @@ public class TelegramApplication extends Application {
|
||||
|
||||
stage.setTitle("Telegram");
|
||||
stage.setScene(scene);
|
||||
|
||||
// Add icon to the stage
|
||||
Image icon = new Image(TelegramApplication.class.getResourceAsStream("/org/to/telegramfinalproject/Images/telegram_icon.png"));
|
||||
stage.getIcons().add(icon);
|
||||
|
||||
stage.show();
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 934 B |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -58,3 +58,121 @@
|
||||
-fx-border-width: 0 1px 0 1px; /* 1px vertical line */
|
||||
-fx-padding: 0 0 0 0.5px; /* big invisible hitbox for the mouse */
|
||||
}
|
||||
|
||||
.placeholder-label {
|
||||
-fx-text-fill: #aaa; /* text color */
|
||||
-fx-font-size: 13px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-background-color: #2a2a2a; /* bubble background */
|
||||
-fx-padding: 8 10 8 10; /* top/right/bottom/left spacing */
|
||||
-fx-background-radius: 20; /* rounded corners */
|
||||
}
|
||||
|
||||
/* ===== Chat page (dark) ===== */
|
||||
.chat-root { -fx-background-color: #0f1c26; }
|
||||
|
||||
/* Header bar */
|
||||
.chat-header {
|
||||
-fx-background-color: #0f1c26;
|
||||
-fx-border-color: #2e3948;
|
||||
-fx-border-width: 0 0 1 0;
|
||||
}
|
||||
.chat-title { -fx-text-fill: #e8f1f8; -fx-font-size: 14px; -fx-font-weight: bold; }
|
||||
.chat-status { -fx-text-fill: #8ea1b2; -fx-font-size: 12px; }
|
||||
|
||||
/* Icon-only buttons */
|
||||
.icon-btn {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 6 8 6 8;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
.icon-btn:hover {
|
||||
-fx-background-color: rgba(255,255,255,0.06);
|
||||
-fx-background-radius: 6;
|
||||
}
|
||||
|
||||
/* Messages area */
|
||||
.chat-scroll { -fx-background-color: transparent; }
|
||||
|
||||
/* Input bar */
|
||||
.chat-input-bar {
|
||||
-fx-background-color: #15222d;
|
||||
-fx-border-color: #2e3948;
|
||||
-fx-border-width: 1 0 0 0;
|
||||
}
|
||||
|
||||
/* Send button (telegram-ish blue) */
|
||||
.send-btn {
|
||||
-fx-background-color: #2ca4ff;
|
||||
-fx-background-radius: 18;
|
||||
-fx-padding: 8 12 8 12;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
.send-btn:hover { -fx-background-color: #1996f3; }
|
||||
|
||||
/* Active/empty states (matches controller’s styleClass) */
|
||||
.send-btn.send-empty {
|
||||
-fx-opacity: 0.5; /* look inactive */
|
||||
}
|
||||
.send-btn.send-ready {
|
||||
-fx-opacity: 1.0; /* fully active */
|
||||
}
|
||||
.send-btn:disabled {
|
||||
-fx-opacity: 0.4;
|
||||
-fx-cursor: default;
|
||||
}
|
||||
|
||||
/* Input bubble */
|
||||
.msg-input {
|
||||
-fx-background-color: white;
|
||||
-fx-background-radius: 18;
|
||||
-fx-padding: 6 12 6 12;
|
||||
-fx-text-fill: #0f141a;
|
||||
-fx-prompt-text-fill: #9aa6b2;
|
||||
-fx-border-color: transparent;
|
||||
|
||||
-fx-control-inner-background: white;
|
||||
-fx-focus-color: transparent;
|
||||
-fx-faint-focus-color: transparent;
|
||||
}
|
||||
.msg-input .scroll-pane {
|
||||
-fx-vbar-policy: never;
|
||||
-fx-hbar-policy: never;
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
.msg-input .scroll-pane .corner {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
-fx-background-color: #1b2735;
|
||||
-fx-background-radius: 6;
|
||||
-fx-padding: 4;
|
||||
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.4), 8, 0, 0, 2);
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
-fx-font-size: 13px;
|
||||
-fx-text-fill: #e8f1f8;
|
||||
-fx-padding: 6 12;
|
||||
}
|
||||
|
||||
.menu-item:focused {
|
||||
-fx-background-color: rgba(255,255,255,0.08);
|
||||
-fx-background-radius: 4;
|
||||
}
|
||||
|
||||
/* ===== Search panel ===== */
|
||||
.chat-search-pane {
|
||||
-fx-background-color: #1b2735; /* matches sidebar */
|
||||
}
|
||||
|
||||
.chat-search-bar {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 8 10;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
-fx-text-fill: #8ea1b2;
|
||||
-fx-font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -58,3 +58,121 @@
|
||||
-fx-border-width: 0 1px 0 1px; /* 1px vertical line */
|
||||
-fx-padding: 0 0 0 0.5px; /* big invisible hitbox for the mouse */
|
||||
}
|
||||
|
||||
.placeholder-label {
|
||||
-fx-text-fill: #2e3948; /* text color */
|
||||
-fx-font-size: 13px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-background-color: #f1f1f1; /* bubble background */
|
||||
-fx-padding: 8 10 8 10; /* top/right/bottom/left spacing */
|
||||
-fx-background-radius: 20; /* rounded corners */
|
||||
}
|
||||
|
||||
/* ===== Chat page (light) ===== */
|
||||
.chat-root { -fx-background-color: #ffffff; }
|
||||
|
||||
/* Header bar */
|
||||
.chat-header {
|
||||
-fx-background-color: #ffffff;
|
||||
-fx-border-color: #e6e6e6;
|
||||
-fx-border-width: 0 0 1 0;
|
||||
}
|
||||
.chat-title { -fx-text-fill: #0f141a; -fx-font-size: 14px; -fx-font-weight: bold; }
|
||||
.chat-status { -fx-text-fill: #7e8a97; -fx-font-size: 12px; }
|
||||
|
||||
/* Icon-only buttons */
|
||||
.icon-btn {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 6 8 6 8;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
.icon-btn:hover {
|
||||
-fx-background-color: rgba(0,0,0,0.05);
|
||||
-fx-background-radius: 6;
|
||||
}
|
||||
|
||||
/* Messages area */
|
||||
.chat-scroll { -fx-background-color: transparent; }
|
||||
|
||||
/* Input bar */
|
||||
.chat-input-bar {
|
||||
-fx-background-color: #f5f5f5;
|
||||
-fx-border-color: #e6e6e6;
|
||||
-fx-border-width: 1 0 0 0;
|
||||
}
|
||||
|
||||
/* Send button (telegram-ish blue) */
|
||||
.send-btn {
|
||||
-fx-background-color: #2ca4ff;
|
||||
-fx-background-radius: 18;
|
||||
-fx-padding: 8 12 8 12;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
.send-btn:hover { -fx-background-color: #1996f3; }
|
||||
|
||||
/* Active/empty states (matches controller’s styleClass) */
|
||||
.send-btn.send-empty {
|
||||
-fx-opacity: 0.5; /* look inactive */
|
||||
}
|
||||
.send-btn.send-ready {
|
||||
-fx-opacity: 1.0; /* fully active */
|
||||
}
|
||||
.send-btn:disabled {
|
||||
-fx-opacity: 0.4;
|
||||
-fx-cursor: default;
|
||||
}
|
||||
|
||||
/* Input bubble */
|
||||
.msg-input {
|
||||
-fx-background-color: white;
|
||||
-fx-background-radius: 18;
|
||||
-fx-padding: 6 12 6 12;
|
||||
-fx-text-fill: #0f141a;
|
||||
-fx-prompt-text-fill: #9aa6b2;
|
||||
-fx-border-color: transparent;
|
||||
|
||||
-fx-control-inner-background: white;
|
||||
-fx-focus-color: transparent;
|
||||
-fx-faint-focus-color: transparent;
|
||||
}
|
||||
.msg-input .scroll-pane {
|
||||
-fx-vbar-policy: never;
|
||||
-fx-hbar-policy: never;
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
.msg-input .scroll-pane .corner {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
-fx-background-color: white;
|
||||
-fx-background-radius: 6;
|
||||
-fx-padding: 4;
|
||||
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 8, 0, 0, 2);
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
-fx-font-size: 13px;
|
||||
-fx-text-fill: #0f141a;
|
||||
-fx-padding: 6 12;
|
||||
}
|
||||
|
||||
.menu-item:focused {
|
||||
-fx-background-color: #e6e6e6;
|
||||
-fx-background-radius: 4;
|
||||
}
|
||||
|
||||
/* ===== Search panel ===== */
|
||||
.chat-search-pane {
|
||||
-fx-background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.chat-search-bar {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 8 10;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
-fx-text-fill: #7e8a97;
|
||||
-fx-font-size: 13px;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 124 KiB |
@@ -0,0 +1,134 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<VBox xmlns="http://javafx.com/javafx/17"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.to.telegramfinalproject.UI.ChatPageController"
|
||||
spacing="0"
|
||||
styleClass="chat-root">
|
||||
|
||||
<!-- HEADER: avatar + name/status (left) | search + more (right) -->
|
||||
<HBox fx:id="chatHeader" spacing="10" styleClass="chat-header">
|
||||
<padding>
|
||||
<Insets top="8" right="12" bottom="8" left="12"/>
|
||||
</padding>
|
||||
|
||||
<!-- Avatar -->
|
||||
<ImageView fx:id="userAvatar" fitWidth="36" fitHeight="36" preserveRatio="true"/>
|
||||
|
||||
<!-- Title + status -->
|
||||
<VBox alignment="CENTER_LEFT">
|
||||
<Label fx:id="chatTitle" text="User name" styleClass="chat-title"/>
|
||||
<Label fx:id="chatStatus" text="last seen recently" styleClass="chat-status"/>
|
||||
</VBox>
|
||||
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
|
||||
<!-- Search-in-chat -->
|
||||
<Button fx:id="searchInChatButton" styleClass="icon-btn" onAction="#openSearchPanel">
|
||||
<graphic>
|
||||
<ImageView fx:id="searchIcon" fitWidth="20" fitHeight="20" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@/org/to/telegramfinalproject/Icons/search_light.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
|
||||
<!-- More / overflow -->
|
||||
<Button fx:id="moreButton" styleClass="icon-btn">
|
||||
<graphic>
|
||||
<ImageView fx:id="moreIcon" fitWidth="18" fitHeight="18" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@/org/to/telegramfinalproject/Icons/more_light.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<contextMenu>
|
||||
<ContextMenu fx:id="moreMenu">
|
||||
<items>
|
||||
<MenuItem fx:id="viewProfileItem" text="View profile">
|
||||
<graphic>
|
||||
<ImageView fitWidth="16" fitHeight="16" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@/org/to/telegramfinalproject/Icons/view_profile_light.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
<MenuItem fx:id="deleteChatItem" text="Delete chat" style="-fx-text-fill: red;">
|
||||
<graphic>
|
||||
<ImageView fitWidth="16" fitHeight="16" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@/org/to/telegramfinalproject/Icons/delete_red.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
</items>
|
||||
</ContextMenu>
|
||||
</contextMenu>
|
||||
</Button>
|
||||
</HBox>
|
||||
|
||||
<!-- MESSAGES -->
|
||||
<ScrollPane fx:id="messageScrollPane"
|
||||
fitToWidth="true"
|
||||
vbarPolicy="AS_NEEDED"
|
||||
hbarPolicy="NEVER"
|
||||
VBox.vgrow="ALWAYS"
|
||||
styleClass="chat-scroll">
|
||||
<content>
|
||||
<VBox fx:id="messageContainer" spacing="10">
|
||||
<padding>
|
||||
<Insets top="10" right="10" bottom="10" left="10"/>
|
||||
</padding>
|
||||
</VBox>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
|
||||
<!-- INPUT BAR -->
|
||||
<HBox fx:id="inputBar" spacing="8" styleClass="chat-input-bar">
|
||||
<padding>
|
||||
<Insets top="8" right="12" bottom="8" left="12"/>
|
||||
</padding>
|
||||
|
||||
<!-- Attachment -->
|
||||
<Button fx:id="attachmentButton" styleClass="icon-btn">
|
||||
<graphic>
|
||||
<ImageView fx:id="attachmentIcon" fitWidth="20" fitHeight="20" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@/org/to/telegramfinalproject/Icons/attachment_light.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
|
||||
<!-- Expanding input -->
|
||||
<TextArea fx:id="messageInput"
|
||||
HBox.hgrow="ALWAYS"
|
||||
promptText="Write a message..."
|
||||
styleClass="msg-input"
|
||||
wrapText="true"
|
||||
prefRowCount="1"
|
||||
minHeight="40"
|
||||
maxHeight="120"
|
||||
VBox.vgrow="NEVER" />
|
||||
|
||||
<!-- Send -->
|
||||
<Button fx:id="sendButton" styleClass="send-btn">
|
||||
<graphic>
|
||||
<ImageView fx:id="sendIcon" fitWidth="20" fitHeight="20" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@/org/to/telegramfinalproject/Icons/send_cyan2.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</HBox>
|
||||
</VBox>
|
||||
@@ -1,69 +1,134 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.image.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<StackPane fx:id="mainRoot" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.to.telegramfinalproject.UI.MainController">
|
||||
<StackPane fx:id="mainRoot"
|
||||
xmlns="http://javafx.com/javafx/8"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="org.to.telegramfinalproject.UI.MainController">
|
||||
|
||||
<children>
|
||||
<!-- SplitPane: left (chat list / search) + right (chat panel) -->
|
||||
<SplitPane fx:id="mainSplitPane"
|
||||
dividerPositions="0.32"
|
||||
orientation="HORIZONTAL"
|
||||
prefHeight="700" prefWidth="1100"
|
||||
styleClass="thin-splitpane">
|
||||
<items>
|
||||
|
||||
<!-- Use SplitPane so the two sides are resizable -->
|
||||
<SplitPane fx:id="mainSplitPane" dividerPositions="0.32" orientation="HORIZONTAL" prefHeight="700" prefWidth="1100" styleClass="thin-splitpane">
|
||||
<items>
|
||||
|
||||
<!-- LEFT: Chat list -->
|
||||
<VBox fx:id="leftPane" minWidth="72" spacing="10" style="-fx-background-color: #FFFFFF;"> <!-- keep at least avatar width when collapsed -->
|
||||
<children>
|
||||
<HBox spacing="10">
|
||||
<padding>
|
||||
<Insets bottom="10" left="10" right="10" top="10" />
|
||||
</padding>
|
||||
<children>
|
||||
<Button fx:id="menuButton" minHeight="29.0" minWidth="40" onAction="#toggleSidebar" prefHeight="29.0" prefWidth="40.0" style="-fx-background-color: transparent;">
|
||||
<graphic>
|
||||
<ImageView fx:id="menuIcon" fitHeight="24" fitWidth="17" preserveRatio="true" smooth="true">
|
||||
<image>
|
||||
<Image url="@/org/to/telegramfinalproject/Icons/menu_dark.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
|
||||
<TextField fx:id="searchBar" prefHeight="36" prefWidth="430.0" promptText="Search" style="-fx-background-color: #f5f5f5; -fx-background-radius: 15;" />
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
<ScrollPane fx:id="scrollPane" fitToWidth="true" hbarPolicy="NEVER" style="-fx-background-color: transparent;" vbarPolicy="AS_NEEDED">
|
||||
<content>
|
||||
<VBox fx:id="chatListContainer" spacing="5" style="-fx-background-color: transparent;">
|
||||
<padding>
|
||||
<Insets bottom="10" left="10" right="10" top="10" />
|
||||
</padding>
|
||||
</VBox>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
<!-- RIGHT: Chat panel -->
|
||||
<StackPane fx:id="chatDisplayArea" minWidth="360" style="-fx-background-color: #FAFAFA;"> <!-- don't let chat page collapse -->
|
||||
<children>
|
||||
<Label style="-fx-text-fill: #888888;" text="Select a chat to start messaging">
|
||||
<font>
|
||||
<Font size="15" />
|
||||
</font>
|
||||
</Label>
|
||||
</children>
|
||||
</StackPane>
|
||||
</items>
|
||||
<!-- LEFT: Sidebar -->
|
||||
<VBox fx:id="leftPane" minWidth="72" style="-fx-background-color: #FFFFFF;">
|
||||
|
||||
<!-- Top bar (always visible) -->
|
||||
<HBox spacing="10">
|
||||
<padding>
|
||||
<Insets bottom="10" left="10" right="10" top="10"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Button fx:id="menuButton" minHeight="29.0" minWidth="40"
|
||||
onAction="#toggleSidebar"
|
||||
prefHeight="29.0" prefWidth="40.0"
|
||||
style="-fx-background-color: transparent;">
|
||||
<graphic>
|
||||
<ImageView fx:id="menuIcon" fitHeight="24" fitWidth="17"
|
||||
preserveRatio="true" smooth="true">
|
||||
<image>
|
||||
<Image url="@/org/to/telegramfinalproject/Icons/menu_dark.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
|
||||
<TextField fx:id="searchBar"
|
||||
prefHeight="36" prefWidth="430.0"
|
||||
promptText="Search"
|
||||
style="-fx-background-color: #f5f5f5; -fx-background-radius: 15;"/>
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
<!-- Content area (chat list vs search) -->
|
||||
<StackPane VBox.vgrow="ALWAYS">
|
||||
|
||||
<!-- Chat list (default) -->
|
||||
<ScrollPane fx:id="scrollPane" fitToWidth="true" hbarPolicy="NEVER"
|
||||
style="-fx-background-color: transparent;"
|
||||
vbarPolicy="AS_NEEDED"
|
||||
visible="true" managed="true">
|
||||
<content>
|
||||
<VBox fx:id="chatListContainer" spacing="5"
|
||||
style="-fx-background-color: transparent;">
|
||||
<padding>
|
||||
<Insets bottom="10" left="10" right="10" top="10"/>
|
||||
</padding>
|
||||
</VBox>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
|
||||
<!-- Search panel (hidden initially) -->
|
||||
<VBox fx:id="chatSearchPane" visible="false" managed="false"
|
||||
styleClass="chat-search-pane">
|
||||
|
||||
<!-- Section label -->
|
||||
<Label text="Search messages in"
|
||||
style="-fx-text-fill: #7e8a97; -fx-font-size: 11px; -fx-padding: 10 0 5 10;"/>
|
||||
|
||||
<!-- Tiny separator line -->
|
||||
<Separator style="-fx-background-color: #e6e6e6;" prefHeight="0.5"/>
|
||||
|
||||
<!-- User profile + "This chat" + close -->
|
||||
<HBox spacing="10" alignment="CENTER_LEFT" style="-fx-padding: 8 10; -fx-border-color: #e6e6e6; -fx-border-width: 0 0 1 0;">
|
||||
<!-- Avatar -->
|
||||
<ImageView fx:id="searchChatAvatar" fitWidth="32" fitHeight="32" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@/org/to/telegramfinalproject/Avatars/profile_test.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
|
||||
<!-- scope -->
|
||||
<VBox alignment="CENTER_LEFT">
|
||||
<Label text="This chat" style="-fx-text-fill: #7e8a97; -fx-font-size: 11px;"/>
|
||||
</VBox>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<!-- Close button -->
|
||||
<Button text="✕" onAction="#closeSearchPanel"
|
||||
style="-fx-background-color: transparent; -fx-cursor: hand;"/>
|
||||
</HBox>
|
||||
|
||||
<!-- Placeholder -->
|
||||
<VBox alignment="CENTER" spacing="10" prefHeight="300">
|
||||
<ImageView fx:id="searchIconLarge" fitWidth="64" fitHeight="64" preserveRatio="true"/>
|
||||
<Label text="Search for messages" styleClass="search-placeholder"/>
|
||||
</VBox>
|
||||
|
||||
<!-- Results -->
|
||||
<ListView fx:id="chatSearchResults" visible="false" managed="false"/>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
</VBox>
|
||||
|
||||
<!-- RIGHT: Chat panel -->
|
||||
<StackPane fx:id="chatDisplayArea" minWidth="360" style="-fx-background-color: #FAFAFA;">
|
||||
<children>
|
||||
<Label fx:id="placeholderLabel" alignment="CENTER"
|
||||
prefHeight="21.0" prefWidth="248.0"
|
||||
styleClass="placeholder-label"
|
||||
text="Select a chat to start messaging">
|
||||
<font><Font size="15"/></font>
|
||||
</Label>
|
||||
</children>
|
||||
</StackPane>
|
||||
</items>
|
||||
</SplitPane>
|
||||
|
||||
<!-- Overlay (for sidebar) -->
|
||||
<Pane fx:id="overlay" mouseTransparent="true" onMouseClicked="#closeSidebar" style="-fx-background-color: rgba(0,0,0,0.3);" visible="false" />
|
||||
<Pane fx:id="overlay"
|
||||
mouseTransparent="true"
|
||||
onMouseClicked="#closeSidebar"
|
||||
style="-fx-background-color: rgba(0,0,0,0.3);"
|
||||
visible="false"/>
|
||||
</children>
|
||||
</StackPane>
|
||||
|
||||
|
After Width: | Height: | Size: 521 B |
|
After Width: | Height: | Size: 513 B |
|
After Width: | Height: | Size: 210 B |
|
After Width: | Height: | Size: 160 B |
|
After Width: | Height: | Size: 258 B |
|
After Width: | Height: | Size: 871 B |
|
After Width: | Height: | Size: 755 B |
|
After Width: | Height: | Size: 560 B |
|
After Width: | Height: | Size: 471 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 505 B |
|
After Width: | Height: | Size: 514 B |