Implement first draft app view
This commit is contained in:
Generated
+1
-1
@@ -8,7 +8,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="25" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -11,32 +11,44 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<junit.version>5.10.2</junit.version> </properties>
|
||||
<junit.version>5.10.2</junit.version>
|
||||
<maven.compiler.release>23</maven.compiler.release>
|
||||
<javafx.version>23</javafx.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>21</version>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>21</version>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-media</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency> </dependencies>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
@@ -45,30 +57,18 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>23</source>
|
||||
<target>23</target>
|
||||
<release>${maven.compiler.release}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- Default configuration for running with: mvn clean javafx:run -->
|
||||
<id>default-cli</id>
|
||||
<configuration>
|
||||
<mainClass>sbu.javafx/sbu.javafx.MusicApp</mainClass>
|
||||
<launcher>app</launcher>
|
||||
<jlinkZipName>app</jlinkZipName>
|
||||
<jlinkImageName>app</jlinkImageName>
|
||||
<noManPages>true</noManPages>
|
||||
<stripDebug>true</stripDebug>
|
||||
<noHeaderFiles>true</noHeaderFiles>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<mainClass>sbu.javafx/sbu.javafx.MusicApp</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package sbu.javafx;
|
||||
|
||||
public class Song {
|
||||
}
|
||||
@@ -1,8 +1,194 @@
|
||||
<?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.layout.HBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Slider?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
|
||||
<!-- TODO: Add your code here! -->
|
||||
<HBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="sbu.javafx.MusicController"
|
||||
stylesheets="@style.css"
|
||||
spacing="0"
|
||||
prefWidth="700"
|
||||
prefHeight="420">
|
||||
|
||||
</VBox>
|
||||
<!-- ===== Left: Player ===== -->
|
||||
<VBox styleClass="player-panel"
|
||||
alignment="CENTER"
|
||||
spacing="20"
|
||||
prefWidth="320">
|
||||
|
||||
<padding>
|
||||
<Insets top="32" right="28" bottom="32" left="28"/>
|
||||
</padding>
|
||||
|
||||
<!-- Album Art -->
|
||||
<StackPane styleClass="cover-container" alignment="CENTER">
|
||||
<ImageView fx:id="albumCover"
|
||||
fitWidth="160"
|
||||
fitHeight="160"
|
||||
preserveRatio="true"
|
||||
styleClass="album-cover"/>
|
||||
</StackPane>
|
||||
|
||||
<!-- Song Info -->
|
||||
<VBox alignment="CENTER" spacing="6">
|
||||
<Label fx:id="songTitle" text="Song Title" styleClass="song-title"/>
|
||||
<Label fx:id="artistName" text="Artist" styleClass="artist-name"/>
|
||||
</VBox>
|
||||
|
||||
<!-- Progress -->
|
||||
<VBox spacing="4" fillWidth="true">
|
||||
<Slider fx:id="progressSlider" styleClass="progress-slider"/>
|
||||
<HBox>
|
||||
<Label fx:id="currentTime" text="0:00" styleClass="time-label"/>
|
||||
<HBox HBox.hgrow="ALWAYS"/>
|
||||
<Label fx:id="totalTime" text="0:00" styleClass="time-label"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
|
||||
<!-- Controls -->
|
||||
<HBox alignment="CENTER" spacing="16">
|
||||
<Button fx:id="shuffleBtn" text="⇄" styleClass="control-btn"
|
||||
onAction="#handleShuffle"/>
|
||||
<Button text="⏮" styleClass="control-btn"
|
||||
onAction="#handlePrev"/>
|
||||
<Button fx:id="playPauseBtn" text="▶" styleClass="play-btn"
|
||||
onAction="#handlePlayPause"/>
|
||||
<Button text="⏭" styleClass="control-btn"
|
||||
onAction="#handleNext"/>
|
||||
<Button fx:id="repeatBtn" text="⟳" styleClass="control-btn"
|
||||
onAction="#handleRepeat"/>
|
||||
</HBox>
|
||||
|
||||
<!-- Volume -->
|
||||
<HBox alignment="CENTER" spacing="8">
|
||||
<Label text="🔈" styleClass="time-label"/>
|
||||
<Slider fx:id="volumeSlider"
|
||||
prefWidth="160"
|
||||
min="0" max="1" value="0.7"
|
||||
styleClass="volume-slider"/>
|
||||
<Label text="🔊" styleClass="time-label"/>
|
||||
</HBox>
|
||||
|
||||
</VBox>
|
||||
|
||||
<!-- ===== Right: Playlist Panel ===== -->
|
||||
<VBox styleClass="playlist-panel"
|
||||
spacing="0"
|
||||
HBox.hgrow="ALWAYS">
|
||||
|
||||
<!-- Tab Buttons -->
|
||||
<HBox styleClass="tab-bar" spacing="0">
|
||||
<Button fx:id="libraryTabBtn"
|
||||
text="🎵 Library"
|
||||
styleClass="tab-btn, tab-active"
|
||||
maxWidth="Infinity"
|
||||
HBox.hgrow="ALWAYS"
|
||||
onAction="#showLibrary"/>
|
||||
<Button fx:id="playlistTabBtn"
|
||||
text="📋 Playlists"
|
||||
styleClass="tab-btn"
|
||||
maxWidth="Infinity"
|
||||
HBox.hgrow="ALWAYS"
|
||||
onAction="#showPlaylists"/>
|
||||
</HBox>
|
||||
|
||||
<!-- StackPane: two panels on top of each other -->
|
||||
<StackPane VBox.vgrow="ALWAYS">
|
||||
|
||||
<!-- ===== Tab 1: Library ===== -->
|
||||
<VBox fx:id="libraryPane"
|
||||
spacing="10"
|
||||
styleClass="tab-content">
|
||||
|
||||
<padding>
|
||||
<Insets top="16" right="16" bottom="16" left="16"/>
|
||||
</padding>
|
||||
|
||||
<!-- Header -->
|
||||
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||
<Label fx:id="libraryCount"
|
||||
text="0 Songs"
|
||||
styleClass="tab-header"
|
||||
HBox.hgrow="ALWAYS"/>
|
||||
<Button text="+ Add Song"
|
||||
styleClass="add-btn"
|
||||
onAction="#handleAddSong"/>
|
||||
</HBox>
|
||||
|
||||
<!-- List -->
|
||||
<ListView fx:id="libraryView"
|
||||
VBox.vgrow="ALWAYS"
|
||||
styleClass="playlist"/>
|
||||
|
||||
</VBox>
|
||||
|
||||
<!-- ===== Tab 2: Playlists ===== -->
|
||||
<VBox fx:id="playlistsPane"
|
||||
spacing="10"
|
||||
styleClass="tab-content"
|
||||
visible="false"
|
||||
managed="false">
|
||||
|
||||
<padding>
|
||||
<Insets top="16" right="16" bottom="16" left="16"/>
|
||||
</padding>
|
||||
|
||||
<!-- Header -->
|
||||
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||
<Label text="My Playlists"
|
||||
styleClass="tab-header"
|
||||
HBox.hgrow="ALWAYS"/>
|
||||
<Button text="+ New"
|
||||
styleClass="add-btn"
|
||||
onAction="#handleNewPlaylist"/>
|
||||
</HBox>
|
||||
|
||||
<!-- Playlists list or selected playlist content -->
|
||||
<StackPane VBox.vgrow="ALWAYS">
|
||||
|
||||
<!-- Playlists list -->
|
||||
<VBox fx:id="playlistsListPane" spacing="8">
|
||||
<ListView fx:id="playlistsView"
|
||||
VBox.vgrow="ALWAYS"
|
||||
styleClass="playlist"/>
|
||||
</VBox>
|
||||
|
||||
<!-- Inside a playlist -->
|
||||
<VBox fx:id="playlistDetailPane"
|
||||
spacing="10"
|
||||
visible="false"
|
||||
managed="false">
|
||||
|
||||
<!-- Back + Playlist Name -->
|
||||
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||
<Button text="← Back"
|
||||
styleClass="back-btn"
|
||||
onAction="#handleBackToPlaylists"/>
|
||||
<Label fx:id="playlistDetailName"
|
||||
text="Playlist Name"
|
||||
styleClass="tab-header"/>
|
||||
</HBox>
|
||||
|
||||
<ListView fx:id="playlistDetailView"
|
||||
VBox.vgrow="ALWAYS"
|
||||
styleClass="playlist"/>
|
||||
|
||||
</VBox>
|
||||
|
||||
</StackPane>
|
||||
|
||||
</VBox>
|
||||
|
||||
</StackPane>
|
||||
|
||||
</VBox>
|
||||
|
||||
</HBox>
|
||||
@@ -0,0 +1,248 @@
|
||||
/* ===== Root ===== */
|
||||
.root {
|
||||
-fx-background-color: #F5F5F5;
|
||||
-fx-font-family: "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
/* ===== Player Panel ===== */
|
||||
.player-panel {
|
||||
-fx-background-color: #FFFFFF;
|
||||
-fx-border-color: #E0E0E0;
|
||||
-fx-border-width: 0 1 0 0;
|
||||
}
|
||||
|
||||
/* ===== Album Cover ===== */
|
||||
.cover-container {
|
||||
-fx-background-color: #EEEEEE;
|
||||
-fx-background-radius: 12px;
|
||||
-fx-min-width: 160px;
|
||||
-fx-min-height: 160px;
|
||||
-fx-max-width: 160px;
|
||||
-fx-max-height: 160px;
|
||||
}
|
||||
|
||||
.album-cover {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.15), 10, 0, 0, 2);
|
||||
}
|
||||
|
||||
/* ===== Song Info ===== */
|
||||
.song-title {
|
||||
-fx-font-size: 18px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-text-fill: #212121;
|
||||
}
|
||||
|
||||
.artist-name {
|
||||
-fx-font-size: 13px;
|
||||
-fx-text-fill: #757575;
|
||||
}
|
||||
|
||||
/* ===== Progress Slider ===== */
|
||||
.progress-slider .track {
|
||||
-fx-background-color: #E0E0E0;
|
||||
-fx-pref-height: 4px;
|
||||
}
|
||||
|
||||
.progress-slider .thumb {
|
||||
-fx-background-color: #212121;
|
||||
-fx-pref-width: 12px;
|
||||
-fx-pref-height: 12px;
|
||||
-fx-background-radius: 6px;
|
||||
}
|
||||
|
||||
.progress-slider:focused .thumb {
|
||||
-fx-effect: none;
|
||||
}
|
||||
|
||||
/* ===== Time Labels ===== */
|
||||
.time-label {
|
||||
-fx-font-size: 11px;
|
||||
-fx-text-fill: #9E9E9E;
|
||||
}
|
||||
|
||||
/* ===== Control Buttons ===== */
|
||||
.control-btn {
|
||||
-fx-background-color: transparent;
|
||||
-fx-font-size: 16px;
|
||||
-fx-cursor: hand;
|
||||
-fx-padding: 6 10 6 10;
|
||||
-fx-background-radius: 50%;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
-fx-background-color: #F0F0F0;
|
||||
}
|
||||
|
||||
.control-btn:pressed {
|
||||
-fx-background-color: #E0E0E0;
|
||||
}
|
||||
|
||||
/* ===== Play Button ===== */
|
||||
.play-btn {
|
||||
-fx-background-color: #212121;
|
||||
-fx-text-fill: #FFFFFF;
|
||||
-fx-font-size: 18px;
|
||||
-fx-cursor: hand;
|
||||
-fx-min-width: 48px;
|
||||
-fx-min-height: 48px;
|
||||
-fx-background-radius: 50%;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.play-btn:hover {
|
||||
-fx-background-color: #424242;
|
||||
}
|
||||
|
||||
.play-btn:pressed {
|
||||
-fx-background-color: #616161;
|
||||
}
|
||||
|
||||
/* ===== Active state for shuffle/repeat ===== */
|
||||
.active-btn {
|
||||
-fx-text-fill: #1DB954;
|
||||
}
|
||||
|
||||
/* ===== Volume Slider ===== */
|
||||
.volume-slider .track {
|
||||
-fx-background-color: #E0E0E0;
|
||||
-fx-pref-height: 3px;
|
||||
}
|
||||
|
||||
.volume-slider .thumb {
|
||||
-fx-background-color: #212121;
|
||||
-fx-pref-width: 10px;
|
||||
-fx-pref-height: 10px;
|
||||
-fx-background-radius: 5px;
|
||||
}
|
||||
|
||||
.volume-slider:focused .thumb {
|
||||
-fx-effect: none;
|
||||
}
|
||||
|
||||
/* ===== Playlist Panel ===== */
|
||||
.playlist-panel {
|
||||
-fx-background-color: #FAFAFA;
|
||||
}
|
||||
|
||||
.playlist-header {
|
||||
-fx-font-size: 14px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-text-fill: #424242;
|
||||
-fx-padding: 0 0 8 0;
|
||||
}
|
||||
|
||||
/* ===== ListView ===== */
|
||||
.playlist {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.playlist .list-cell {
|
||||
-fx-background-color: transparent;
|
||||
-fx-font-size: 13px;
|
||||
-fx-text-fill: #424242;
|
||||
-fx-padding: 10 12 10 12;
|
||||
-fx-border-color: transparent transparent #F0F0F0 transparent;
|
||||
-fx-border-width: 1px;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.playlist .list-cell:hover {
|
||||
-fx-background-color: #F0F0F0;
|
||||
}
|
||||
|
||||
.playlist .list-cell:selected {
|
||||
-fx-background-color: #EEEEEE;
|
||||
-fx-text-fill: #212121;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
.playlist .scroll-bar:vertical {
|
||||
-fx-pref-width: 6px;
|
||||
}
|
||||
|
||||
.playlist .scroll-bar:vertical .thumb {
|
||||
-fx-background-color: #BDBDBD;
|
||||
-fx-background-radius: 3px;
|
||||
}
|
||||
|
||||
.playlist .scroll-bar:vertical .track {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.playlist .scroll-bar:vertical .increment-button,
|
||||
.playlist .scroll-bar:vertical .decrement-button {
|
||||
-fx-pref-height: 0;
|
||||
-fx-opacity: 0;
|
||||
}
|
||||
|
||||
/* ===== Tab Bar ===== */
|
||||
.tab-bar {
|
||||
-fx-background-color: #F0F0F0;
|
||||
-fx-border-color: transparent transparent #E0E0E0 transparent;
|
||||
-fx-border-width: 1px;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
-fx-background-color: transparent;
|
||||
-fx-font-size: 13px;
|
||||
-fx-text-fill: #757575;
|
||||
-fx-cursor: hand;
|
||||
-fx-padding: 12 0 12 0;
|
||||
-fx-background-radius: 0;
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
-fx-background-color: #E8E8E8;
|
||||
-fx-text-fill: #424242;
|
||||
}
|
||||
|
||||
.tab-active {
|
||||
-fx-text-fill: #212121;
|
||||
-fx-font-weight: bold;
|
||||
-fx-border-color: transparent transparent #212121 transparent;
|
||||
-fx-border-width: 0 0 2 0;
|
||||
}
|
||||
|
||||
/* ===== Tab Content ===== */
|
||||
.tab-content {
|
||||
-fx-background-color: #FAFAFA;
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
-fx-font-size: 13px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-text-fill: #424242;
|
||||
}
|
||||
|
||||
/* ===== Add Button ===== */
|
||||
.add-btn {
|
||||
-fx-background-color: #212121;
|
||||
-fx-text-fill: #FFFFFF;
|
||||
-fx-font-size: 12px;
|
||||
-fx-cursor: hand;
|
||||
-fx-padding: 6 12 6 12;
|
||||
-fx-background-radius: 6px;
|
||||
}
|
||||
|
||||
.add-btn:hover {
|
||||
-fx-background-color: #424242;
|
||||
}
|
||||
|
||||
/* ===== Back Button ===== */
|
||||
.back-btn {
|
||||
-fx-background-color: transparent;
|
||||
-fx-text-fill: #757575;
|
||||
-fx-font-size: 12px;
|
||||
-fx-cursor: hand;
|
||||
-fx-padding: 4 8 4 8;
|
||||
-fx-background-radius: 6px;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
-fx-background-color: #EEEEEE;
|
||||
-fx-text-fill: #212121;
|
||||
}
|
||||
Reference in New Issue
Block a user