Implement first draft app view

This commit is contained in:
2026-06-09 22:58:53 +03:30
parent fa5f8ab267
commit 305aef7473
5 changed files with 465 additions and 27 deletions
+1 -1
View File
@@ -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>
+23 -23
View File
@@ -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>
+4
View File
@@ -0,0 +1,4 @@
package sbu.javafx;
public class Song {
}
+189 -3
View File
@@ -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>
+248
View File
@@ -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;
}