Sidebar UI implemented.

This commit is contained in:
Asal Lotfi
2025-08-12 22:45:46 +03:30
parent fc2c0f858b
commit 054f3db155
23 changed files with 284 additions and 24 deletions
@@ -1,11 +1,26 @@
package org.to.telegramfinalproject.UI; package org.to.telegramfinalproject.UI;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
public class SidebarMenuController { import java.net.URL;
public class SidebarMenuController {
private static final String CSS_PATH = "/org/to/telegramfinalproject/CSS/";
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
@FXML private VBox sidebarRoot; // fx:id must match FXML
@FXML private ImageView profileImage; @FXML private ImageView profileImage;
@FXML private Label usernameLabel; @FXML private Label usernameLabel;
@@ -18,14 +33,143 @@ public class SidebarMenuController {
@FXML private Button telegramFeaturesButton; @FXML private Button telegramFeaturesButton;
@FXML private Button telegramQnAButton; @FXML private Button telegramQnAButton;
@FXML private ToggleButton nightModeToggle; // Custom Telegram-style toggle
@FXML private HBox nightModeToggle;
@FXML private Region toggleThumb;
@FXML private ImageView nightModeIcon;
private boolean nightModeOn = false; // start light by default
@FXML @FXML
public void initialize() { public void initialize() {
Image image = new Image(getClass().getResource("/org/to/telegramfinalproject/Images/profile.png").toExternalForm()); // Load default profile picture (if present)
profileImage.setImage(image); Image profile = loadImage("/org/to/telegramfinalproject/Images/profile.png");
if (profile != null) profileImage.setImage(profile);
// Called automatically after FXML is loaded // Set up handlers and defer theme application until the scene is available
usernameLabel.setText("Asal"); setupButtonActions();
setupToggleAction();
// When the scene is set, apply the initial theme (light) and initialize icons/positions
sidebarRoot.sceneProperty().addListener((obs, oldScene, newScene) -> {
if (newScene != null) {
// Apply light theme at startup
updateTheme(false);
// Position the thumb after layout pass (to use actual widths)
Platform.runLater(() -> {
double initialX = nightModeOn
? nightModeToggle.getWidth() - toggleThumb.getWidth() - 4
: 2;
toggleThumb.setTranslateX(initialX);
});
}
});
} }
private void setupButtonActions() {
myProfileButton.setOnAction(e -> openMyProfile());
newGroupButton.setOnAction(e -> createNewGroup());
newChannelButton.setOnAction(e -> createNewChannel());
contactsButton.setOnAction(e -> openContacts());
savedMessagesButton.setOnAction(e -> openSavedMessages());
settingsButton.setOnAction(e -> openSettings());
telegramFeaturesButton.setOnAction(e -> openTelegramFeatures());
telegramQnAButton.setOnAction(e -> openTelegramQnA());
}
private void setupToggleAction() {
nightModeToggle.setOnMouseClicked(e -> {
nightModeOn = !nightModeOn;
updateTheme(nightModeOn);
// animate thumb
double targetX = nightModeOn
? nightModeToggle.getWidth() - toggleThumb.getWidth() - 4
: 2;
TranslateTransition tt = new TranslateTransition(Duration.millis(200), toggleThumb);
tt.setToX(targetX);
tt.play();
});
}
/**
* Apply theme and swap icons. darkMode == true => apply dark theme (dark background),
* and use light (white) icons. darkMode == false => apply light theme and use dark icons.
*/
private void updateTheme(boolean darkMode) {
Scene scene = sidebarRoot.getScene();
if (scene == null) return; // safety
// clear & apply the correct stylesheet
scene.getStylesheets().clear();
String cssFile = darkMode ? "dark_theme.css" : "light_theme.css";
URL cssUrl = getClass().getResource(CSS_PATH + cssFile);
if (cssUrl != null) {
scene.getStylesheets().add(cssUrl.toExternalForm());
} else {
System.err.println("CSS not found: " + CSS_PATH + cssFile);
}
// Keep toggle's "on" class in sync
if (darkMode) {
if (!nightModeToggle.getStyleClass().contains("on"))
nightModeToggle.getStyleClass().add("on");
} else {
nightModeToggle.getStyleClass().remove("on");
}
// Icon file suffix mapping:
// - For darkMode == true (dark background) we want white icons => use "_light.png"
// - For darkMode == false (light background) we want dark icons => use "_dark.png"
String iconSuffix = darkMode ? "_light.png" : "_dark.png";
setIcons(iconSuffix);
// night mode icon itself (moon)
Image moon = loadImage(ICON_PATH + "night_mode" + iconSuffix);
if (moon != null) nightModeIcon.setImage(moon);
}
private void setIcons(String suffix) {
myProfileButton.setGraphic(makeIcon(ICON_PATH + "my_profile" + suffix));
newGroupButton.setGraphic(makeIcon(ICON_PATH + "new_group" + suffix));
newChannelButton.setGraphic(makeIcon(ICON_PATH + "new_channel" + suffix));
contactsButton.setGraphic(makeIcon(ICON_PATH + "contacts" + suffix));
savedMessagesButton.setGraphic(makeIcon(ICON_PATH + "saved_messages" + suffix));
settingsButton.setGraphic(makeIcon(ICON_PATH + "settings" + suffix));
telegramFeaturesButton.setGraphic(makeIcon(ICON_PATH + "telegram_features" + suffix));
telegramQnAButton.setGraphic(makeIcon(ICON_PATH + "telegram_qna" + suffix)); // prefer safe filename
}
private Image loadImage(String path) {
URL res = getClass().getResource(path);
if (res == null) {
System.err.println("Resource not found: " + path);
return null;
}
return new Image(res.toExternalForm());
}
private ImageView makeIcon(String path) {
ImageView iv = new ImageView();
Image img = loadImage(path);
if (img != null) {
iv.setImage(img);
iv.setFitWidth(22);
iv.setFitHeight(22);
iv.setPreserveRatio(true);
}
return iv;
}
// Example button actions
private void openMyProfile() { System.out.println("Opening My Profile..."); }
private void createNewGroup() { System.out.println("Creating New Group..."); }
private void createNewChannel() { System.out.println("Creating New Channel..."); }
private void openContacts() { System.out.println("Opening Contacts..."); }
private void openSavedMessages() { System.out.println("Opening Saved Messages..."); }
private void openSettings() { System.out.println("Opening Settings..."); }
private void openTelegramFeatures() { System.out.println("Opening Telegram Features..."); }
private void openTelegramQnA() { System.out.println("Opening Telegram Q&A..."); }
} }
@@ -5,7 +5,6 @@ import javafx.application.Application;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.to.telegramfinalproject.HelloApplication;
import java.io.IOException; import java.io.IOException;
@@ -15,7 +14,7 @@ public class TelegramApplication extends Application {
FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/sidebar_menu.fxml")); FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/sidebar_menu.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 1480, 820); Scene scene = new Scene(fxmlLoader.load(), 1480, 820);
scene.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/sidebar_menu.css").toExternalForm()); scene.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/light_theme.css").toExternalForm());
stage.setTitle("Telegram"); stage.setTitle("Telegram");
stage.setScene(scene); stage.setScene(scene);
@@ -0,0 +1,52 @@
/* Sidebar buttons */
.sidebar-btn {
-fx-background-color: transparent;
-fx-text-fill: white;
-fx-font-size: 14px;
-fx-pref-width: 230;
-fx-alignment: CENTER_LEFT;
-fx-padding: 8 0 8 16;
}
.sidebar-btn:hover {
-fx-background-color: #2f3e50;
-fx-cursor: hand;
}
/* Telegram-style toggle switch */
.telegram-toggle {
-fx-background-color: #555;
-fx-background-radius: 15;
-fx-padding: 2;
-fx-pref-width: 40;
-fx-pref-height: 20;
-fx-alignment: center-left;
-fx-cursor: hand;
}
.telegram-toggle.on {
-fx-background-color: #4fa8f0;
}
.toggle-thumb {
-fx-background-color: white;
-fx-background-radius: 50%;
-fx-pref-width: 16;
-fx-pref-height: 16;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 3, 0, 0, 1);
}
/* Separator */
.thin-separator {
-fx-background-color: transparent;
-fx-padding: 0;
}
.thin-separator .line {
-fx-border-color: #2e3948; /* Telegram's subtle dark line */
-fx-border-width: 0.5px;
}
/* Theme background & text */
#sidebarRoot {
-fx-background-color: #1b2735; /* dark theme */
}
.button, .label {
-fx-text-fill: white;
}
@@ -0,0 +1,52 @@
/* Sidebar buttons */
.sidebar-btn {
-fx-background-color: transparent;
-fx-text-fill: black;
-fx-font-size: 14px;
-fx-pref-width: 230;
-fx-alignment: CENTER_LEFT;
-fx-padding: 8 0 8 16;
}
.sidebar-btn:hover {
-fx-background-color: #e6e6e6;
-fx-cursor: hand;
}
/* Telegram-style toggle switch */
.telegram-toggle {
-fx-background-color: #aaa;
-fx-background-radius: 15;
-fx-padding: 2;
-fx-pref-width: 40;
-fx-pref-height: 20;
-fx-alignment: center-left;
-fx-cursor: hand;
}
.telegram-toggle.on {
-fx-background-color: #4fa8f0;
}
.toggle-thumb {
-fx-background-color: white;
-fx-background-radius: 50%;
-fx-pref-width: 16;
-fx-pref-height: 16;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 3, 0, 0, 1);
}
/* Separator */
.thin-separator {
-fx-background-color: transparent;
-fx-padding: 0;
}
.thin-separator .line {
-fx-border-color: #ccc;
-fx-border-width: 0.5px;
}
/* Theme background & text */
#sidebarRoot {
-fx-background-color: white;
}
.button, .label {
-fx-text-fill: black;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -6,9 +6,8 @@
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.control.Separator?> <?import javafx.scene.control.Separator?>
<VBox fx:id="sidebar" spacing="20" alignment="TOP_LEFT" <VBox fx:id="sidebarRoot" spacing="20" alignment="TOP_LEFT"
prefWidth="250.0" prefWidth="250.0"
style="-fx-background-color: #1b2735;"
xmlns="http://javafx.com/javafx/17" xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.to.telegramfinalproject.UI.SidebarMenuController"> fx:controller="org.to.telegramfinalproject.UI.SidebarMenuController">
@@ -17,32 +16,46 @@
<Insets top="20" left="10" right="10" bottom="10"/> <Insets top="20" left="10" right="10" bottom="10"/>
</padding> </padding>
<!-- Profile Section --> <!-- Profile Section -->
<VBox alignment="TOP_LEFT" spacing="10"> <VBox alignment="TOP_LEFT" spacing="10">
<padding> <padding>
<Insets top="10" left="10" bottom="0" right="0"/> <Insets top="10" left="10" bottom="0" right="0"/>
</padding> </padding>
<ImageView fx:id="profileImage" fitWidth="80" fitHeight="80" pickOnBounds="true" preserveRatio="true"/> <ImageView fx:id="profileImage" fitWidth="80" fitHeight="80" pickOnBounds="true" preserveRatio="true"/>
<Label fx:id="usernameLabel" text="Asal Lotfi" textFill="white" style="-fx-font-size: 14px; -fx-font-weight: bold;"/> <Label fx:id="usernameLabel" text=" Asal Lotfi" textFill="white" style="-fx-font-size: 14px; -fx-font-weight: bold;"/>
</VBox> </VBox>
<Separator prefWidth="200"/>
<!-- Menu Buttons --> <!-- Menu Buttons -->
<VBox spacing="8" alignment="TOP_LEFT"> <VBox spacing="8" alignment="TOP_LEFT">
<Button fx:id="btnMyProfile" text=" My Profile" styleClass="sidebar-btn"/>
<Button fx:id="btnNewGroup" text=" New Group" styleClass="sidebar-btn"/> <!-- Thin Telegram-style line -->
<Button fx:id="btnNewChannel" text=" New Channel" styleClass="sidebar-btn"/> <Separator orientation="HORIZONTAL" styleClass="thin-separator" />
<Button fx:id="btnContacts" text=" Contacts" styleClass="sidebar-btn"/>
<Button fx:id="btnCalls" text=" Calls" styleClass="sidebar-btn"/> <Button fx:id="myProfileButton" text=" My Profile" styleClass="sidebar-btn"/>
<Button fx:id="btnSavedMessages" text=" Saved Messages" styleClass="sidebar-btn"/>
<Button fx:id="btnSettings" text=" Settings" styleClass="sidebar-btn"/> <!-- Thin Telegram-style line -->
<Separator orientation="HORIZONTAL" styleClass="thin-separator" />
<Button fx:id="newGroupButton" text=" New Group" styleClass="sidebar-btn"/>
<Button fx:id="newChannelButton" text=" New Channel" styleClass="sidebar-btn"/>
<Button fx:id="contactsButton" text=" Contacts" styleClass="sidebar-btn"/>
<Button fx:id="savedMessagesButton" text=" Saved Messages" styleClass="sidebar-btn"/>
<Button fx:id="settingsButton" text=" Settings" styleClass="sidebar-btn"/>
<Button fx:id="telegramFeaturesButton" text=" Telegram Features" styleClass="sidebar-btn"/>
<Button fx:id="telegramQnAButton" text=" Telegram Q&amp;A" styleClass="sidebar-btn"/>
<!-- Night Mode Toggle --> <!-- Night Mode Toggle -->
<HBox spacing="10" alignment="CENTER_LEFT"> <HBox spacing="10" alignment="CENTER_LEFT">
<padding>
<!-- Match button's left padding -->
<Insets left="15"/>
</padding>
<ImageView fx:id="nightModeIcon" fitWidth="22" fitHeight="22" pickOnBounds="true" preserveRatio="true"/>
<Label text="Night Mode" textFill="white" style="-fx-font-size: 13px;"/> <Label text="Night Mode" textFill="white" style="-fx-font-size: 13px;"/>
<ToggleButton fx:id="nightModeToggle"/> <HBox fx:id="nightModeToggle" styleClass="telegram-toggle">
<Region fx:id="toggleThumb" styleClass="toggle-thumb"/>
</HBox>
</HBox> </HBox>
</VBox> </VBox>
</VBox>
</VBox>