develop #4
@@ -8,7 +8,5 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="21" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<junit.version>5.10.2</junit.version> </properties>
|
||||
<junit.version>5.10.2</junit.version> </properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -24,8 +24,13 @@
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>21</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-media</artifactId>
|
||||
<version>21</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
|
||||
@@ -2,7 +2,11 @@ package sbu.javafx;
|
||||
|
||||
import javafx.application.Application;
|
||||
|
||||
public class Launcher{
|
||||
public static void main(String[] args) {
|
||||
// TODO: Launch the JavaFX application by calling Application.launch(Main.class) }
|
||||
public class Launcher
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
// TODO: Launch the JavaFX application by calling Application.launch(Main.class)
|
||||
Application.launch(MusicApp.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,43 @@ package sbu.javafx;
|
||||
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;
|
||||
import java.util.Objects;
|
||||
|
||||
public class MusicApp extends Application {
|
||||
public class MusicApp extends Application
|
||||
{
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException
|
||||
{
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(MusicApp.class.getResource("App-view.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load());
|
||||
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(
|
||||
Objects.requireNonNull(MusicApp.class.getResource("App-view.fxml"),
|
||||
"Cannot find App-view.fxml in resources (src/main/resources/sbu/javafx/)")
|
||||
);
|
||||
|
||||
Scene scene = new Scene(fxmlLoader.load(), 520, 300);
|
||||
stage.setTitle("Music Player");
|
||||
stage.setScene(scene);
|
||||
//TODO: add icon to the stage
|
||||
|
||||
try
|
||||
{
|
||||
Image icon = new Image(
|
||||
Objects.requireNonNull(MusicApp.class.getResourceAsStream("icon.png"),
|
||||
"Cannot find icon.png in resources (src/main/resources/sbu/javafx/)")
|
||||
);
|
||||
stage.getIcons().add(icon);
|
||||
}
|
||||
catch (Exception ignored) {}
|
||||
|
||||
stage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,381 @@
|
||||
package sbu.javafx;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.media.Media;
|
||||
import javafx.scene.media.MediaPlayer;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import javafx.scene.Parent;
|
||||
|
||||
public class MusicController {
|
||||
public class MusicController
|
||||
{
|
||||
|
||||
// TODO: Define your UI components using the @FXML annotation.
|
||||
@FXML private VBox songContainer;
|
||||
@FXML private Label nowPlayingLabel;
|
||||
@FXML private Label currentTrackTitle;
|
||||
@FXML private Label timeLabel;
|
||||
@FXML private Slider progressSlider;
|
||||
@FXML private Slider volumeSlider;
|
||||
@FXML private Button playPauseButton;
|
||||
@FXML private Button repeatButton;
|
||||
@FXML private Button shuffleButton;
|
||||
@FXML private Button likeButton;
|
||||
@FXML private StackPane visualizerContainer;
|
||||
|
||||
//TODO: Implement the logic to play a song. This method should update the UI to show which song is currently playing.
|
||||
private MediaPlayer mediaPlayer;
|
||||
private int currentSongIndex = 0;
|
||||
private boolean isRepeat = false;
|
||||
private boolean isShuffle = false;
|
||||
private boolean isLiked = false;
|
||||
private final Random random = new Random();
|
||||
private Timeline visualizerAnimation;
|
||||
|
||||
private final String[] songFiles = {
|
||||
"Maison.mp3",
|
||||
"KUSURA_BAKMA.mp3",
|
||||
"Hiting_Motion.mp3",
|
||||
"Lost_in_the_haze.mp3",
|
||||
"North.mp3",
|
||||
"Longing.mp3",
|
||||
"After_Math.mp3",
|
||||
"On_The_Roads.mp3",
|
||||
"Bumpup.mp3",
|
||||
"SEVMEYİ_DENEMEDİN.mp3"
|
||||
|
||||
};
|
||||
|
||||
private final Map<String, String> songDisplayNames = new HashMap<>();
|
||||
private static final ObservableList<String> playlist = FXCollections.observableArrayList();
|
||||
private static final ObservableList<String> favorites = FXCollections.observableArrayList();
|
||||
|
||||
@FXML
|
||||
public void handlePlayAction() {
|
||||
//Update labels, change button icons, etc.
|
||||
public void initialize()
|
||||
{
|
||||
for (int i = 0; i < songFiles.length; i++)
|
||||
{
|
||||
createSongCard(i);
|
||||
}
|
||||
setupVolumeControl();
|
||||
createVisualizer();
|
||||
}
|
||||
|
||||
//TODO: Logic to add a specific song to the user's playlist.
|
||||
private void createVisualizer()
|
||||
{
|
||||
HBox bars = new HBox(3);
|
||||
bars.setAlignment(Pos.BOTTOM_CENTER);
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Region bar = new Region();
|
||||
bar.setPrefWidth(4);
|
||||
bar.setPrefHeight(5);
|
||||
bar.setStyle("-fx-background-color: #1DB954; -fx-background-radius: 2;");
|
||||
bars.getChildren().add(bar);
|
||||
}
|
||||
visualizerContainer.getChildren().add(bars);
|
||||
|
||||
public void addToPlaylist(String songName) {
|
||||
//Add the song to a list or update a ListView.
|
||||
visualizerAnimation = new Timeline();
|
||||
for (Node node : bars.getChildren())
|
||||
{
|
||||
double randomHeight = 10 + Math.random() * 15;
|
||||
KeyFrame kf = new KeyFrame(Duration.millis(150 + Math.random() * 100),
|
||||
new KeyValue(((Region)node).prefHeightProperty(), randomHeight)
|
||||
);
|
||||
visualizerAnimation.getKeyFrames().add(kf);
|
||||
}
|
||||
visualizerAnimation.setCycleCount(Timeline.INDEFINITE);
|
||||
visualizerAnimation.setAutoReverse(true);
|
||||
}
|
||||
|
||||
// TODO:This method should open a new window or change the current scene to display the "Playlist" page.
|
||||
private void updateLikeButton(String displayName)
|
||||
{
|
||||
if (displayName == null || likeButton == null)
|
||||
{
|
||||
if (likeButton != null)
|
||||
{
|
||||
likeButton.setText("♡");
|
||||
likeButton.getStyleClass().remove("btn-heart-liked");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public void openPlaylistWindow() {
|
||||
//Create a new stage and show the playlist UI.
|
||||
if (favorites.contains(displayName))
|
||||
{
|
||||
likeButton.setText("♥");
|
||||
if (!likeButton.getStyleClass().contains("btn-heart-liked"))
|
||||
{
|
||||
likeButton.getStyleClass().add("btn-heart-liked");
|
||||
}
|
||||
} else
|
||||
{
|
||||
likeButton.setText("♡");
|
||||
likeButton.getStyleClass().remove("btn-heart-liked");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void toggleLike()
|
||||
{
|
||||
String currentSongName = nowPlayingLabel.getText();
|
||||
if (currentSongName.equals("None") || currentSongName.equals("Not Playing")) return;
|
||||
|
||||
isLiked = !isLiked;
|
||||
if (isLiked)
|
||||
{
|
||||
likeButton.setText("♥");
|
||||
likeButton.setStyle("-fx-text-fill: #ff4d4d; -fx-font-size: 16px;");
|
||||
if (!favorites.contains(currentSongName)) favorites.add(currentSongName);
|
||||
} else
|
||||
{
|
||||
likeButton.setText("♡");
|
||||
likeButton.setStyle("-fx-text-fill: inherit; -fx-font-size: 16px;");
|
||||
favorites.remove(currentSongName);
|
||||
}
|
||||
}
|
||||
|
||||
private void createSongCard(int index)
|
||||
{
|
||||
String fileName = songFiles[index];
|
||||
String displayName = cleanFileName(fileName);
|
||||
|
||||
HBox card = new HBox(14);
|
||||
card.setAlignment(Pos.CENTER_LEFT);
|
||||
card.getStyleClass().add("song-card");
|
||||
|
||||
|
||||
ImageView coverView = createCoverImageView(fileName);
|
||||
|
||||
VBox infoBox = new VBox(4);
|
||||
Label titleLabel = new Label(displayName);
|
||||
titleLabel.getStyleClass().add("song-title");
|
||||
|
||||
|
||||
Label subtitleLabel = new Label("00:00");
|
||||
subtitleLabel.getStyleClass().add("song-subtitle");
|
||||
|
||||
|
||||
subtitleLabel.getStyleClass().add("song-meta");
|
||||
infoBox.getChildren().addAll(titleLabel, subtitleLabel);
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
Button cardLikeBtn = new Button(favorites.contains(displayName) ? "♥" : "♡");
|
||||
cardLikeBtn.getStyleClass().add("btn-ghost");
|
||||
cardLikeBtn.setStyle("-fx-font-size: 16px; -fx-text-fill: #ff4d6d;");
|
||||
cardLikeBtn.setOnAction(event ->
|
||||
{
|
||||
if (favorites.contains(displayName))
|
||||
{
|
||||
favorites.remove(displayName);
|
||||
cardLikeBtn.setText("♡");
|
||||
} else {
|
||||
favorites.add(displayName);
|
||||
cardLikeBtn.setText("♥");
|
||||
}
|
||||
updateLikeButton(displayName);
|
||||
});
|
||||
|
||||
Button addButton = new Button("+");
|
||||
addButton.getStyleClass().add("btn-ghost");
|
||||
addButton.setStyle("-fx-font-size: 18px;");
|
||||
addButton.setOnAction(event ->
|
||||
{
|
||||
if (!playlist.contains(displayName))
|
||||
{
|
||||
playlist.add(displayName);
|
||||
}
|
||||
});
|
||||
|
||||
Button playButton = new Button("▶");
|
||||
playButton.getStyleClass().add("btn-green-small");
|
||||
playButton.setOnAction(event ->
|
||||
{
|
||||
currentSongIndex = index;
|
||||
playMedia(fileName);
|
||||
});
|
||||
|
||||
cardLikeBtn.setOnMouseClicked(e -> e.consume());
|
||||
addButton.setOnMouseClicked(e -> e.consume());
|
||||
playButton.setOnMouseClicked(e -> e.consume());
|
||||
|
||||
card.getChildren().addAll(coverView, infoBox, spacer, cardLikeBtn, addButton, playButton);
|
||||
songContainer.getChildren().add(card);
|
||||
}
|
||||
|
||||
private void playMedia(String fileName)
|
||||
{
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
mediaPlayer.stop(); mediaPlayer.dispose();
|
||||
}
|
||||
|
||||
URL resource = getClass().getResource("/music/" + fileName);
|
||||
if (resource == null) return;
|
||||
|
||||
Media media = new Media(resource.toExternalForm());
|
||||
mediaPlayer = new MediaPlayer(media);
|
||||
mediaPlayer.volumeProperty().bind(volumeSlider.valueProperty());
|
||||
|
||||
mediaPlayer.setOnReady(() -> {
|
||||
String name = cleanFileName(fileName);
|
||||
nowPlayingLabel.setText(name);
|
||||
currentTrackTitle.setText(name);
|
||||
playPauseButton.setText("Pause");
|
||||
visualizerAnimation.play();
|
||||
|
||||
isLiked = favorites.contains(name);
|
||||
likeButton.setText(isLiked ? "♥" : "♡");
|
||||
likeButton.setStyle(isLiked ? "-fx-text-fill: #ff4d4d; -fx-font-size: 16px;" : "");
|
||||
});
|
||||
|
||||
mediaPlayer.currentTimeProperty().addListener((obs, old, cur) ->
|
||||
{
|
||||
timeLabel.setText(formatDuration(cur) + " / " + formatDuration(media.getDuration()));
|
||||
if (!progressSlider.isValueChanging())
|
||||
{
|
||||
progressSlider.setValue((cur.toSeconds() / media.getDuration().toSeconds()) * 100);
|
||||
}
|
||||
});
|
||||
|
||||
mediaPlayer.setOnEndOfMedia(this::nextTrack);
|
||||
mediaPlayer.play();
|
||||
}
|
||||
|
||||
private ImageView createCoverImageView(String mp3FileName)
|
||||
{
|
||||
String baseName = mp3FileName.substring(0, mp3FileName.lastIndexOf("."));
|
||||
String[] extensions = {".jpg", ".jpeg", ".png", ".webp"};
|
||||
for (String ext : extensions)
|
||||
{
|
||||
URL url = getClass().getResource("/music/" + baseName + ext);
|
||||
if (url != null)
|
||||
{
|
||||
ImageView iv = new ImageView(new Image(url.toExternalForm()));
|
||||
iv.setFitWidth(50); iv.setFitHeight(50); iv.setPreserveRatio(true);
|
||||
return iv;
|
||||
}
|
||||
}
|
||||
return new ImageView();
|
||||
}
|
||||
|
||||
private void loadMetadata(String fileName, Label metaLabel)
|
||||
{
|
||||
URL resource = getClass().getResource("/music/" + fileName);
|
||||
if (resource == null) return;
|
||||
|
||||
Media media = new Media(resource.toExternalForm());
|
||||
MediaPlayer tempPlayer = new MediaPlayer(media);
|
||||
|
||||
tempPlayer.setOnReady(() ->
|
||||
{
|
||||
metaLabel.setText(formatDuration(media.getDuration()));
|
||||
tempPlayer.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@FXML private void nextTrack()
|
||||
{
|
||||
if (isShuffle) currentSongIndex = random.nextInt(songFiles.length);
|
||||
else currentSongIndex = (currentSongIndex + 1) % songFiles.length;
|
||||
playMedia(songFiles[currentSongIndex]);
|
||||
}
|
||||
|
||||
@FXML private void previousTrack()
|
||||
{
|
||||
currentSongIndex = (currentSongIndex - 1 + songFiles.length) % songFiles.length;
|
||||
playMedia(songFiles[currentSongIndex]);
|
||||
}
|
||||
|
||||
@FXML private void togglePlayPause()
|
||||
{
|
||||
if (mediaPlayer == null) return;
|
||||
if (mediaPlayer.getStatus() == MediaPlayer.Status.PLAYING)
|
||||
{
|
||||
mediaPlayer.pause();
|
||||
playPauseButton.setText("Play");
|
||||
visualizerAnimation.pause();
|
||||
} else
|
||||
{
|
||||
mediaPlayer.play();
|
||||
playPauseButton.setText("Pause");
|
||||
visualizerAnimation.play();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML private void toggleRepeat()
|
||||
{
|
||||
isRepeat = !isRepeat; repeatButton.setStyle(isRepeat ? "-fx-text-fill: #1DB954;" : "");
|
||||
}
|
||||
|
||||
@FXML private void toggleShuffle()
|
||||
{
|
||||
isShuffle = !isShuffle; shuffleButton.setStyle(isShuffle ? "-fx-text-fill: #1DB954;" : "");
|
||||
}
|
||||
|
||||
private void setupVolumeControl()
|
||||
{
|
||||
volumeSlider.setValue(0.5);
|
||||
}
|
||||
|
||||
private String cleanFileName(String f)
|
||||
{
|
||||
return f.replace(".mp3", "").replace("_", " ").replace("-", " ");
|
||||
}
|
||||
|
||||
private String formatDuration(Duration d)
|
||||
{
|
||||
int s = (int) Math.floor(d.toSeconds()); return String.format("%02d:%02d", s / 60, s % 60);
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void goToPlaylistScene(ActionEvent event) throws IOException
|
||||
{
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("Playlist-view.fxml"));
|
||||
Parent root = loader.load();
|
||||
|
||||
PlaylistController controller = loader.getController();
|
||||
controller.setPlaylist(playlist);
|
||||
controller.setPreviousScene(((Node) event.getSource()).getScene());
|
||||
|
||||
Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
|
||||
stage.setTitle("Playlist");
|
||||
stage.setScene(new Scene(root));
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void goToFavoritesScene(ActionEvent event) throws IOException
|
||||
{
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("Playlist-view.fxml"));
|
||||
Parent root = loader.load();
|
||||
|
||||
PlaylistController controller = loader.getController();
|
||||
controller.setPlaylist(favorites);
|
||||
controller.setPreviousScene(((Node) event.getSource()).getScene());
|
||||
|
||||
Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
|
||||
stage.setTitle("Favorites");
|
||||
stage.setScene(new Scene(root));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package sbu.javafx;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class PlaylistController
|
||||
{
|
||||
|
||||
@FXML
|
||||
private ListView<String> playlistListView;
|
||||
|
||||
private ObservableList<String> playlist;
|
||||
private Scene previousScene;
|
||||
|
||||
/**
|
||||
* Called from MusicController after loading Playlist-view.fxml
|
||||
*/
|
||||
public void setPlaylist(ObservableList<String> playlist)
|
||||
{
|
||||
this.playlist = playlist;
|
||||
|
||||
if (playlistListView != null)
|
||||
{
|
||||
playlistListView.setItems(playlist);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the previous scene so we can go back to it.
|
||||
*/
|
||||
public void setPreviousScene(Scene previousScene)
|
||||
{
|
||||
this.previousScene = previousScene;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void removeSelected(ActionEvent e)
|
||||
{
|
||||
if (playlistListView == null) return;
|
||||
|
||||
String selected = playlistListView.getSelectionModel().getSelectedItem();
|
||||
if (selected == null) return;
|
||||
|
||||
if (playlist != null)
|
||||
{
|
||||
playlist.remove(selected);
|
||||
} else
|
||||
{
|
||||
playlistListView.getItems().remove(selected);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void goBack(ActionEvent e)
|
||||
{
|
||||
if (playlistListView == null) return;
|
||||
|
||||
Stage stage = (Stage) playlistListView.getScene().getWindow();
|
||||
if (previousScene != null)
|
||||
{
|
||||
stage.setScene(previousScene);
|
||||
stage.setTitle("Music Player");
|
||||
stage.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 275 KiB |
@@ -1,8 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns="http://javafx.com/fxml" alignment="CENTER" spacing="10">
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<!-- TODO: Add your code here! -->
|
||||
<BorderPane xmlns="http://javafx.com/javafx/21"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="sbu.javafx.MusicController"
|
||||
stylesheets="@spotify.css">
|
||||
|
||||
</VBox>
|
||||
<top>
|
||||
<HBox styleClass="header" spacing="12" alignment="CENTER_LEFT" style="-fx-padding: 15;">
|
||||
<Label text="Music Player" styleClass="h1"/>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<Label text="Now Playing:" styleClass="muted"/>
|
||||
<Label fx:id="nowPlayingLabel" text="None" styleClass="song-title"/>
|
||||
</HBox>
|
||||
</top>
|
||||
|
||||
<center>
|
||||
<VBox spacing="12" style="-fx-padding: 18;">
|
||||
|
||||
<ScrollPane fitToWidth="true"
|
||||
style="-fx-background-color: transparent; -fx-background: transparent;"
|
||||
VBox.vgrow="ALWAYS">
|
||||
<VBox fx:id="songContainer"
|
||||
spacing="12"
|
||||
style="-fx-background-color: transparent;" />
|
||||
</ScrollPane>
|
||||
|
||||
<VBox spacing="10" styleClass="song-card">
|
||||
|
||||
<HBox alignment="CENTER" spacing="20">
|
||||
<HBox alignment="CENTER_LEFT" spacing="12" prefWidth="250">
|
||||
<StackPane fx:id="visualizerContainer" prefWidth="30" prefHeight="30" />
|
||||
<VBox alignment="CENTER_LEFT">
|
||||
<Label fx:id="currentTrackTitle" text="Not Playing" styleClass="song-title" style="-fx-font-size: 13;"/>
|
||||
<Label fx:id="timeLabel" text="00:00 / 00:00" styleClass="song-meta"/>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
<Slider fx:id="progressSlider" min="0" max="100" value="0" HBox.hgrow="ALWAYS"/>
|
||||
</HBox>
|
||||
|
||||
<HBox spacing="15" alignment="CENTER">
|
||||
<Button fx:id="shuffleButton" text="Shuffle" onAction="#toggleShuffle" styleClass="btn-ghost"/>
|
||||
<Button text="Prev" onAction="#previousTrack" styleClass="btn-ghost"/>
|
||||
<Button fx:id="likeButton" text="♡" onAction="#toggleLike" styleClass="btn-ghost" style="-fx-font-size: 16px;"/>
|
||||
<Button fx:id="playPauseButton" text="Pause" onAction="#togglePlayPause" styleClass="btn-green" prefWidth="80"/>
|
||||
<Button text="Next" onAction="#nextTrack" styleClass="btn-ghost"/>
|
||||
<Button fx:id="repeatButton" text="Repeat" onAction="#toggleRepeat" styleClass="btn-ghost"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
|
||||
<HBox spacing="10" alignment="CENTER" style="-fx-padding: 5;">
|
||||
<Label text="Vol:" styleClass="song-meta"/>
|
||||
<Slider fx:id="volumeSlider" min="0" max="100" value="50" prefWidth="150"/>
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<Button text="View Favorites" styleClass="btn-ghost" onAction="#goToFavoritesScene" style="-fx-margin-right: 10;"/>
|
||||
<Button text="View Playlist" styleClass="btn-ghost" onAction="#goToPlaylistScene"/>
|
||||
</HBox>
|
||||
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<BorderPane xmlns="http://javafx.com/javafx/21"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="sbu.javafx.PlaylistController"
|
||||
stylesheets="@spotify.css">
|
||||
|
||||
<top>
|
||||
<HBox styleClass="header" spacing="10" alignment="CENTER_LEFT">
|
||||
<Button text="← Back" styleClass="btn-ghost" onAction="#goBack"/>
|
||||
<Label text="Playlist" styleClass="h1"/>
|
||||
</HBox>
|
||||
</top>
|
||||
|
||||
<center>
|
||||
<VBox spacing="12" style="-fx-padding: 18;">
|
||||
<ListView fx:id="playlistListView" VBox.vgrow="ALWAYS"/>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
<Button text="Remove Selected" onAction="#removeSelected"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
||||
@@ -0,0 +1,113 @@
|
||||
.root {
|
||||
-fx-font-family: "Segoe UI", "Inter", "Roboto", "Arial";
|
||||
-fx-background-color: #121212;
|
||||
}
|
||||
|
||||
.header {
|
||||
-fx-background-color: #181818;
|
||||
-fx-padding: 14 18 14 18;
|
||||
-fx-border-color: #242424;
|
||||
-fx-border-width: 0 0 1 0;
|
||||
}
|
||||
|
||||
.h1 {
|
||||
-fx-text-fill: #FFFFFF;
|
||||
-fx-font-size: 20px;
|
||||
-fx-font-weight: 700;
|
||||
}
|
||||
|
||||
.muted {
|
||||
-fx-text-fill: #B3B3B3;
|
||||
}
|
||||
|
||||
.song-card {
|
||||
-fx-background-color: #181818;
|
||||
-fx-background-radius: 12;
|
||||
-fx-padding: 12 14 12 14;
|
||||
-fx-border-color: #242424;
|
||||
-fx-border-radius: 12;
|
||||
}
|
||||
|
||||
.song-card:hover {
|
||||
-fx-background-color: #202020;
|
||||
}
|
||||
|
||||
.song-title {
|
||||
-fx-text-fill: #FFFFFF;
|
||||
-fx-font-size: 14px;
|
||||
-fx-font-weight: 600;
|
||||
}
|
||||
|
||||
.song-meta {
|
||||
-fx-text-fill: #B3B3B3;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
.button {
|
||||
-fx-background-radius: 999;
|
||||
-fx-padding: 8 14 8 14;
|
||||
-fx-font-weight: 600;
|
||||
-fx-cursor: hand;
|
||||
-fx-background-color: #2A2A2A;
|
||||
-fx-text-fill: #FFFFFF;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
-fx-background-color: #333333;
|
||||
}
|
||||
|
||||
.btn-green {
|
||||
-fx-background-color: #1DB954;
|
||||
-fx-text-fill: #0B0B0B;
|
||||
}
|
||||
|
||||
.btn-green:hover {
|
||||
-fx-background-color: #22C55E;
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: #2A2A2A;
|
||||
-fx-border-width: 1;
|
||||
-fx-text-fill: #FFFFFF;
|
||||
}
|
||||
|
||||
.btn-ghost:hover {
|
||||
-fx-background-color: #1E1E1E;
|
||||
}
|
||||
|
||||
.list-view {
|
||||
-fx-background-color: #121212;
|
||||
-fx-control-inner-background: #121212;
|
||||
-fx-background-insets: 0;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.list-cell {
|
||||
-fx-background-color: #181818;
|
||||
-fx-text-fill: #FFFFFF;
|
||||
-fx-padding: 10 12 10 12;
|
||||
-fx-border-color: #242424;
|
||||
-fx-border-width: 0 0 1 0;
|
||||
}
|
||||
|
||||
.list-cell:filled:selected {
|
||||
-fx-background-color: #1F1F1F;
|
||||
}
|
||||
.btn-green-small {
|
||||
-fx-background-color: #1db954;
|
||||
-fx-text-fill: black;
|
||||
-fx-background-radius: 50;
|
||||
-fx-padding: 5 10;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-green-small:hover {
|
||||
-fx-background-color: #1ed760;
|
||||
-fx-scale-x: 1.1;
|
||||
-fx-scale-y: 1.1;
|
||||
}
|
||||
|
||||
.btn-heart-liked {
|
||||
-fx-text-fill: #ff4d6d !important;
|
||||
}
|
||||