Improve Music Player showcase
Major visual lift-up
This commit is contained in:
parent
9a59e25ec1
commit
5b8f5ddd36
@ -8,9 +8,9 @@ import javafx.geometry.Pos;
|
|||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Hyperlink;
|
import javafx.scene.control.Hyperlink;
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.StageStyle;
|
import javafx.stage.StageStyle;
|
||||||
import org.kordamp.ikonli.feather.Feather;
|
import org.kordamp.ikonli.feather.Feather;
|
||||||
@ -21,7 +21,7 @@ import static atlantafx.base.theme.Styles.ACCENT;
|
|||||||
public abstract class ShowcasePage extends AbstractPage {
|
public abstract class ShowcasePage extends AbstractPage {
|
||||||
|
|
||||||
protected static final PseudoClass SHOWCASE_MODE = PseudoClass.getPseudoClass("showcase-mode");
|
protected static final PseudoClass SHOWCASE_MODE = PseudoClass.getPseudoClass("showcase-mode");
|
||||||
protected final AnchorPane showcase = new AnchorPane();
|
protected final StackPane showcase = new StackPane();
|
||||||
protected final HBox expandBox = new HBox(10);
|
protected final HBox expandBox = new HBox(10);
|
||||||
protected final HBox collapseBox = new HBox(10);
|
protected final HBox collapseBox = new HBox(10);
|
||||||
|
|
||||||
|
@ -7,10 +7,12 @@ import atlantafx.base.theme.Tweaks;
|
|||||||
import atlantafx.sampler.page.showcase.ShowcasePage;
|
import atlantafx.sampler.page.showcase.ShowcasePage;
|
||||||
import atlantafx.sampler.util.Containers;
|
import atlantafx.sampler.util.Containers;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
import org.kordamp.ikonli.feather.Feather;
|
import org.kordamp.ikonli.feather.Feather;
|
||||||
@ -111,18 +113,22 @@ public class FileManagerPage extends ShowcasePage {
|
|||||||
(obs, old, val) -> splitPane.setDividerPosition(0, 200 / splitPane.getWidth())
|
(obs, old, val) -> splitPane.setDividerPosition(0, 200 / splitPane.getWidth())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var aboutBox = new HBox(new Text("Simple file manager. You can traverse through the file system and open files via default system application."));
|
||||||
|
aboutBox.setPadding(new Insets(5, 0, 5, 0));
|
||||||
|
aboutBox.getStyleClass().add("about");
|
||||||
|
|
||||||
var creditsBox = new HBox(5,
|
var creditsBox = new HBox(5,
|
||||||
new Spacer(),
|
new Text("Inspired by ©"),
|
||||||
new Text("Inspired by"),
|
|
||||||
hyperlink("Gnome Files", URI.create("https://gitlab.gnome.org/GNOME/nautilus"))
|
hyperlink("Gnome Files", URI.create("https://gitlab.gnome.org/GNOME/nautilus"))
|
||||||
);
|
);
|
||||||
creditsBox.getStyleClass().add("credits");
|
creditsBox.getStyleClass().add("credits");
|
||||||
|
creditsBox.setAlignment(Pos.CENTER_RIGHT);
|
||||||
creditsBox.setPadding(new Insets(5, 0, 5, 0));
|
creditsBox.setPadding(new Insets(5, 0, 5, 0));
|
||||||
|
|
||||||
var root = new BorderPane();
|
var root = new BorderPane();
|
||||||
root.getStyleClass().add("file-manager-showcase");
|
root.getStyleClass().add("file-manager-showcase");
|
||||||
root.getStylesheets().add(STYLESHEET_URL);
|
root.getStylesheets().add(STYLESHEET_URL);
|
||||||
root.setTop(topBar);
|
root.setTop(new VBox(aboutBox, topBar));
|
||||||
root.setCenter(splitPane);
|
root.setCenter(splitPane);
|
||||||
root.setBottom(creditsBox);
|
root.setBottom(creditsBox);
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import javafx.scene.input.KeyCodeCombination;
|
|||||||
|
|
||||||
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
|
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
|
||||||
|
|
||||||
public class RightClickMenu extends ContextMenu {
|
final class RightClickMenu extends ContextMenu {
|
||||||
|
|
||||||
public RightClickMenu() {
|
public RightClickMenu() {
|
||||||
super();
|
super();
|
||||||
|
@ -39,8 +39,6 @@ final class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void openFile(Path path) {
|
public static void openFile(Path path) {
|
||||||
if (Desktop.isDesktopSupported()) {
|
if (Desktop.isDesktopSupported()) {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
.file-manager-showcase .bookmark-list {
|
.file-manager-showcase .bookmark-list {
|
||||||
-fx-border-width: 0 0 1 1;
|
-fx-border-width: 0 0 1 1;
|
||||||
}
|
}
|
||||||
|
.file-manager-showcase .bookmark-list .ikonli-font-icon {
|
||||||
|
-fx-icon-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager-showcase .tool-bar {
|
||||||
|
-fx-background-insets: 0, 1;
|
||||||
|
}
|
||||||
|
|
||||||
.file-manager-showcase .table-directory-view .table-view {
|
.file-manager-showcase .table-directory-view .table-view {
|
||||||
-color-header-bg: -color-bg-default;
|
-color-header-bg: -color-bg-default;
|
||||||
-color-cell-bg-selected: -color-accent-emphasis;
|
-color-cell-bg-selected: -color-accent-emphasis;
|
||||||
@ -10,16 +18,8 @@
|
|||||||
-fx-border-width: 0 1 1 0;
|
-fx-border-width: 0 1 1 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-manager-showcase .tool-bar {
|
|
||||||
-fx-background-insets: 0, 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* setting opacity directly to the .table-cell or .table-row-cell
|
/* setting opacity directly to the .table-cell or .table-row-cell
|
||||||
leads to incorrect table row height calculation #javafx-bug */
|
leads to incorrect table row height calculation #javafx-bug */
|
||||||
.file-manager-showcase .table-row-cell:hidden >.table-cell > * {
|
.file-manager-showcase .table-row-cell:hidden >.table-cell > * {
|
||||||
-fx-opacity: 0.6;
|
-fx-opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-manager-showcase .bookmark-list .ikonli-font-icon {
|
|
||||||
-fx-icon-size: 18px;
|
|
||||||
}
|
|
||||||
|
@ -32,16 +32,17 @@ record MediaFile(File file) {
|
|||||||
// MediaPlayer and that player has transitioned to Status.READY status.
|
// MediaPlayer and that player has transitioned to Status.READY status.
|
||||||
mediaPlayer.setOnReady(() -> {
|
mediaPlayer.setOnReady(() -> {
|
||||||
Map<String, Object> metadata = media.getMetadata();
|
Map<String, Object> metadata = media.getMetadata();
|
||||||
callback.accept(METADATA_CACHE.computeIfAbsent(file.getAbsolutePath(), k ->
|
callback.accept(METADATA_CACHE.computeIfAbsent(file.getAbsolutePath(), k -> {
|
||||||
|
var image = getTag(metadata, "image", Image.class, null);
|
||||||
// clone everything to make sure media player will be garbage collected
|
// clone everything to make sure media player will be garbage collected
|
||||||
new Metadata(
|
return new Metadata(
|
||||||
new String(getTag(metadata, "title", String.class, NO_TITLE)),
|
new String(getTag(metadata, "title", String.class, NO_TITLE)),
|
||||||
copyImage(getTag(metadata, "image", Image.class, NO_IMAGE)),
|
image != null ? copyImage(image) : null,
|
||||||
new String(getTag(metadata, "artist", String.class, NO_ARTIST)),
|
new String(getTag(metadata, "artist", String.class, NO_ARTIST)),
|
||||||
new String(getTag(metadata, "album", String.class, NO_ALBUM)),
|
new String(getTag(metadata, "album", String.class, NO_ALBUM)),
|
||||||
media.getDuration().toMillis()
|
media.getDuration().toMillis()
|
||||||
))
|
|
||||||
);
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
mediaPlayer.dispose();
|
mediaPlayer.dispose();
|
||||||
});
|
});
|
||||||
@ -63,6 +64,11 @@ record MediaFile(File file) {
|
|||||||
static final Image NO_IMAGE = new Image(
|
static final Image NO_IMAGE = new Image(
|
||||||
Resources.getResourceAsStream("images/no-image.png"), 150, 150, true, false
|
Resources.getResourceAsStream("images/no-image.png"), 150, 150, true, false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static final Image NO_IMAGE_ALT = new Image(
|
||||||
|
Resources.getResourceAsStream("images/papirus/mimetypes/audio-mp3.png"), 150, 150, true, false
|
||||||
|
);
|
||||||
|
|
||||||
static final String NO_TITLE = "Unknown title";
|
static final String NO_TITLE = "Unknown title";
|
||||||
static final String NO_ARTIST = "Unknown artist";
|
static final String NO_ARTIST = "Unknown artist";
|
||||||
static final String NO_ALBUM = "Unknown album";
|
static final String NO_ALBUM = "Unknown album";
|
||||||
|
@ -9,7 +9,7 @@ import javafx.scene.paint.Color;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
class Model {
|
final class Model {
|
||||||
|
|
||||||
private final ObservableList<MediaFile> playlist = FXCollections.observableArrayList();
|
private final ObservableList<MediaFile> playlist = FXCollections.observableArrayList();
|
||||||
private final IntegerProperty playlistIndex = new SimpleIntegerProperty();
|
private final IntegerProperty playlistIndex = new SimpleIntegerProperty();
|
||||||
|
@ -1,24 +1,27 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
/* SPDX-License-Identifier: MIT */
|
||||||
package atlantafx.sampler.page.showcase.musicplayer;
|
package atlantafx.sampler.page.showcase.musicplayer;
|
||||||
|
|
||||||
import atlantafx.sampler.Resources;
|
|
||||||
import atlantafx.sampler.page.showcase.ShowcasePage;
|
import atlantafx.sampler.page.showcase.ShowcasePage;
|
||||||
import atlantafx.sampler.util.Containers;
|
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.SplitPane;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static atlantafx.sampler.util.Controls.hyperlink;
|
||||||
|
|
||||||
public class MusicPlayerPage extends ShowcasePage {
|
public class MusicPlayerPage extends ShowcasePage {
|
||||||
|
|
||||||
public static final String NAME = "Music Player";
|
public static final String NAME = "Music Player";
|
||||||
public static final double BACKGROUND_OPACITY = 0.1;
|
public static final double BACKGROUND_OPACITY = 0.2;
|
||||||
|
public static final Set<String> SUPPORTED_MEDIA_TYPES = Set.of("mp3");
|
||||||
private static final String STYLESHEET_URL = Objects.requireNonNull(
|
private static final String STYLESHEET_URL = Objects.requireNonNull(
|
||||||
MusicPlayerPage.class.getResource("music-player.css")).toExternalForm();
|
MusicPlayerPage.class.getResource("music-player.css")).toExternalForm();
|
||||||
private static final Image PLUG_IMAGE = new Image(Resources.getResourceAsStream("images/vinyl.jpg"));
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() { return NAME; }
|
public String getName() { return NAME; }
|
||||||
@ -31,43 +34,40 @@ public class MusicPlayerPage extends ShowcasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createView() {
|
private void createView() {
|
||||||
var player = new Player(model);
|
var startScreen = new StartScreen(model);
|
||||||
player.setVisible(false);
|
var playerScreen = new PlayerScreen(model);
|
||||||
|
|
||||||
BackgroundImage backgroundImage = new BackgroundImage(
|
var aboutBox = new HBox(new Text("Simple music player that able to play MP3 files."));
|
||||||
PLUG_IMAGE,
|
aboutBox.setPadding(new Insets(5, 0, 5, 0));
|
||||||
BackgroundRepeat.REPEAT,
|
aboutBox.getStyleClass().add("about");
|
||||||
BackgroundRepeat.REPEAT,
|
|
||||||
BackgroundPosition.DEFAULT,
|
var creditsBox = new HBox(5,
|
||||||
BackgroundSize.DEFAULT
|
new Text("Inspired by ©"),
|
||||||
|
hyperlink("Amberol", URI.create("https://gitlab.gnome.org/World/amberol"))
|
||||||
);
|
);
|
||||||
var plug = new AnchorPane();
|
creditsBox.getStyleClass().add("credits");
|
||||||
plug.setBackground(new Background(backgroundImage));
|
creditsBox.setAlignment(Pos.CENTER_RIGHT);
|
||||||
plug.setOpacity(0.5);
|
creditsBox.setPadding(new Insets(5, 0, 5, 0));
|
||||||
plug.setMouseTransparent(false);
|
|
||||||
|
// ~
|
||||||
|
|
||||||
|
var root = new BorderPane();
|
||||||
|
root.setCenter(startScreen);
|
||||||
|
root.setTop(aboutBox);
|
||||||
|
root.setBottom(creditsBox);
|
||||||
|
|
||||||
|
root.getStylesheets().add(STYLESHEET_URL);
|
||||||
|
root.getStyleClass().add("music-player-showcase");
|
||||||
|
|
||||||
var playerStack = new StackPane(player, plug);
|
|
||||||
model.playlist().addListener((ListChangeListener<MediaFile>) c -> {
|
model.playlist().addListener((ListChangeListener<MediaFile>) c -> {
|
||||||
if (model.playlist().size() > 0) {
|
if (model.playlist().size() > 0) {
|
||||||
player.setVisible(true);
|
root.setCenter(playerScreen);
|
||||||
plug.setVisible(false);
|
|
||||||
player.toFront();
|
|
||||||
} else {
|
} else {
|
||||||
player.setVisible(false);
|
root.setCenter(startScreen);
|
||||||
plug.setVisible(true);
|
|
||||||
plug.toFront();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var playlist = new Playlist(model);
|
|
||||||
|
|
||||||
var root = new SplitPane();
|
|
||||||
root.getStylesheets().add(STYLESHEET_URL);
|
|
||||||
root.getStyleClass().add("music-player-showcase");
|
|
||||||
root.getItems().setAll(playerStack, playlist);
|
|
||||||
|
|
||||||
showcase.getChildren().setAll(root);
|
showcase.getChildren().setAll(root);
|
||||||
Containers.setAnchors(root, Insets.EMPTY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -7,16 +7,16 @@ import javafx.beans.InvalidationListener;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.geometry.Insets;
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Slider;
|
import javafx.scene.control.Slider;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.media.Media;
|
import javafx.scene.media.Media;
|
||||||
import javafx.scene.media.MediaPlayer;
|
import javafx.scene.media.MediaPlayer;
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.paint.ImagePattern;
|
import javafx.scene.paint.ImagePattern;
|
||||||
import javafx.scene.shape.Circle;
|
import javafx.scene.shape.Circle;
|
||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
@ -26,9 +26,9 @@ import org.kordamp.ikonli.javafx.FontIcon;
|
|||||||
import static atlantafx.base.controls.Popover.ArrowLocation;
|
import static atlantafx.base.controls.Popover.ArrowLocation;
|
||||||
import static atlantafx.base.theme.Styles.*;
|
import static atlantafx.base.theme.Styles.*;
|
||||||
import static atlantafx.sampler.page.showcase.musicplayer.MediaFile.Metadata.*;
|
import static atlantafx.sampler.page.showcase.musicplayer.MediaFile.Metadata.*;
|
||||||
import static atlantafx.sampler.page.showcase.musicplayer.MusicPlayerPage.BACKGROUND_OPACITY;
|
|
||||||
import static atlantafx.sampler.page.showcase.musicplayer.Utils.formatDuration;
|
import static atlantafx.sampler.page.showcase.musicplayer.Utils.formatDuration;
|
||||||
import static atlantafx.sampler.page.showcase.musicplayer.Utils.getDominantColor;
|
import static atlantafx.sampler.page.showcase.musicplayer.Utils.getDominantColor;
|
||||||
|
import static java.lang.Double.MAX_VALUE;
|
||||||
import static javafx.geometry.Orientation.VERTICAL;
|
import static javafx.geometry.Orientation.VERTICAL;
|
||||||
import static javafx.geometry.Pos.CENTER;
|
import static javafx.geometry.Pos.CENTER;
|
||||||
import static org.kordamp.ikonli.material2.Material2AL.CLEAR_ALL;
|
import static org.kordamp.ikonli.material2.Material2AL.CLEAR_ALL;
|
||||||
@ -37,30 +37,53 @@ import static org.kordamp.ikonli.material2.Material2MZ.*;
|
|||||||
import static org.kordamp.ikonli.material2.Material2OutlinedAL.FAST_FORWARD;
|
import static org.kordamp.ikonli.material2.Material2OutlinedAL.FAST_FORWARD;
|
||||||
import static org.kordamp.ikonli.material2.Material2OutlinedAL.FAST_REWIND;
|
import static org.kordamp.ikonli.material2.Material2OutlinedAL.FAST_REWIND;
|
||||||
|
|
||||||
final class Player extends VBox {
|
final class PlayerPane extends VBox {
|
||||||
|
|
||||||
private static final int PANEL_MAX_WIDTH = 220;
|
private static final int PANEL_MAX_WIDTH = 220;
|
||||||
|
|
||||||
|
private final Model model;
|
||||||
private final ObjectProperty<MediaPlayer> currentPlayer = new SimpleObjectProperty<>();
|
private final ObjectProperty<MediaPlayer> currentPlayer = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
public Player(Model model) {
|
private Rectangle coverImage;
|
||||||
Rectangle coverImage = new Rectangle(0, 0, 150, 150);
|
private Label trackTitle;
|
||||||
|
private Label trackArtist;
|
||||||
|
private Label trackAlbum;
|
||||||
|
|
||||||
|
private FontIcon playIcon;
|
||||||
|
private Button playBtn;
|
||||||
|
|
||||||
|
private Slider timeSlider;
|
||||||
|
private Slider volumeSlider;
|
||||||
|
private Label currentTimeLabel;
|
||||||
|
private Label endTimeLabel;
|
||||||
|
|
||||||
|
public PlayerPane(Model model) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.model = model;
|
||||||
|
|
||||||
|
createView();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createView() {
|
||||||
|
coverImage = new Rectangle(0, 0, 150, 150);
|
||||||
coverImage.setArcWidth(20.0);
|
coverImage.setArcWidth(20.0);
|
||||||
coverImage.setArcHeight(20.0);
|
coverImage.setArcHeight(20.0);
|
||||||
coverImage.setFill(new ImagePattern(NO_IMAGE));
|
coverImage.setFill(new ImagePattern(NO_IMAGE));
|
||||||
|
|
||||||
var trackTitle = new Label(NO_TITLE);
|
trackTitle = new Label(NO_TITLE);
|
||||||
trackTitle.setAlignment(CENTER);
|
|
||||||
trackTitle.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
trackTitle.getStyleClass().add(TITLE_3);
|
trackTitle.getStyleClass().add(TITLE_3);
|
||||||
|
trackTitle.setAlignment(CENTER);
|
||||||
|
trackTitle.setMaxWidth(MAX_VALUE);
|
||||||
|
|
||||||
var trackArtist = new Label(NO_ARTIST);
|
trackArtist = new Label(NO_ARTIST);
|
||||||
trackArtist.setAlignment(CENTER);
|
trackArtist.setAlignment(CENTER);
|
||||||
trackArtist.setMaxWidth(Double.MAX_VALUE);
|
trackArtist.setMaxWidth(MAX_VALUE);
|
||||||
|
|
||||||
var trackAlbum = new Label(NO_ALBUM);
|
trackAlbum = new Label(NO_ALBUM);
|
||||||
trackAlbum.setAlignment(CENTER);
|
trackAlbum.setAlignment(CENTER);
|
||||||
trackAlbum.setMaxWidth(Double.MAX_VALUE);
|
trackAlbum.setMaxWidth(MAX_VALUE);
|
||||||
|
|
||||||
// == Media controls ==
|
// == Media controls ==
|
||||||
|
|
||||||
@ -71,11 +94,10 @@ final class Player extends VBox {
|
|||||||
prevBtn.disableProperty().bind(model.canGoBackProperty().not());
|
prevBtn.disableProperty().bind(model.canGoBackProperty().not());
|
||||||
prevBtn.setOnAction(e -> model.playPrevious());
|
prevBtn.setOnAction(e -> model.playPrevious());
|
||||||
|
|
||||||
var playIcon = new FontIcon(PLAY_ARROW);
|
playIcon = new FontIcon(PLAY_ARROW);
|
||||||
playIcon.setIconSize(32);
|
|
||||||
|
|
||||||
var playBtn = new Button("", playIcon);
|
playBtn = new Button("", playIcon);
|
||||||
playBtn.getStyleClass().addAll(BUTTON_CIRCLE);
|
playBtn.getStyleClass().addAll("play", BUTTON_CIRCLE);
|
||||||
playBtn.setShape(new Circle(50));
|
playBtn.setShape(new Circle(50));
|
||||||
|
|
||||||
var nextBtn = new Button("", new FontIcon(FAST_FORWARD));
|
var nextBtn = new Button("", new FontIcon(FAST_FORWARD));
|
||||||
@ -86,20 +108,21 @@ final class Player extends VBox {
|
|||||||
nextBtn.setTooltip(new Tooltip("Next"));
|
nextBtn.setTooltip(new Tooltip("Next"));
|
||||||
|
|
||||||
var mediaControls = new HBox(20);
|
var mediaControls = new HBox(20);
|
||||||
|
mediaControls.getStyleClass().add("media-controls");
|
||||||
mediaControls.getChildren().setAll(prevBtn, playBtn, nextBtn);
|
mediaControls.getChildren().setAll(prevBtn, playBtn, nextBtn);
|
||||||
mediaControls.setAlignment(CENTER);
|
mediaControls.setAlignment(CENTER);
|
||||||
|
|
||||||
// == Time controls ==
|
// == Time controls ==
|
||||||
|
|
||||||
var timeSlider = new Slider(0, 1, 0);
|
timeSlider = new Slider(0, 1, 0);
|
||||||
timeSlider.setMaxWidth(Double.MAX_VALUE);
|
timeSlider.getStyleClass().add("time-slider");
|
||||||
timeSlider.setMinWidth(PANEL_MAX_WIDTH);
|
timeSlider.setMinWidth(PANEL_MAX_WIDTH);
|
||||||
timeSlider.setMaxWidth(PANEL_MAX_WIDTH);
|
timeSlider.setMaxWidth(PANEL_MAX_WIDTH);
|
||||||
|
|
||||||
var currentTimeLabel = new Label("0.0");
|
currentTimeLabel = new Label("0.0");
|
||||||
currentTimeLabel.getStyleClass().add(TEXT_SMALL);
|
currentTimeLabel.getStyleClass().add(TEXT_SMALL);
|
||||||
|
|
||||||
var endTimeLabel = new Label("5.0");
|
endTimeLabel = new Label("5.0");
|
||||||
endTimeLabel.getStyleClass().add(TEXT_SMALL);
|
endTimeLabel.getStyleClass().add(TEXT_SMALL);
|
||||||
|
|
||||||
var timeMarkersBox = new HBox(5);
|
var timeMarkersBox = new HBox(5);
|
||||||
@ -120,7 +143,7 @@ final class Player extends VBox {
|
|||||||
shuffleBtn.setTooltip(new Tooltip("Shuffle"));
|
shuffleBtn.setTooltip(new Tooltip("Shuffle"));
|
||||||
shuffleBtn.setOnAction(e -> model.shuffle());
|
shuffleBtn.setOnAction(e -> model.shuffle());
|
||||||
|
|
||||||
var volumeSlider = new Slider(0, 1, 0.75);
|
volumeSlider = new Slider(0, 1, 0.75);
|
||||||
volumeSlider.setOrientation(VERTICAL);
|
volumeSlider.setOrientation(VERTICAL);
|
||||||
|
|
||||||
var volumeBar = new VBox(5);
|
var volumeBar = new VBox(5);
|
||||||
@ -140,11 +163,12 @@ final class Player extends VBox {
|
|||||||
extraControls.getChildren().setAll(clearPlaylistBtn, shuffleBtn, new Spacer(), volumeBtn);
|
extraControls.getChildren().setAll(clearPlaylistBtn, shuffleBtn, new Spacer(), volumeBtn);
|
||||||
extraControls.setMaxWidth(PANEL_MAX_WIDTH);
|
extraControls.setMaxWidth(PANEL_MAX_WIDTH);
|
||||||
|
|
||||||
// == Root ==
|
// ~
|
||||||
|
|
||||||
setSpacing(5);
|
|
||||||
getStyleClass().add("player");
|
getStyleClass().add("player");
|
||||||
setAlignment(CENTER);
|
setAlignment(CENTER);
|
||||||
|
setSpacing(5);
|
||||||
|
setMinWidth(300);
|
||||||
getChildren().setAll(
|
getChildren().setAll(
|
||||||
new Spacer(VERTICAL),
|
new Spacer(VERTICAL),
|
||||||
new StackPane(coverImage),
|
new StackPane(coverImage),
|
||||||
@ -161,13 +185,15 @@ final class Player extends VBox {
|
|||||||
extraControls,
|
extraControls,
|
||||||
new Spacer(VERTICAL)
|
new Spacer(VERTICAL)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// == Play ==
|
private void init() {
|
||||||
|
heightProperty().addListener((obs, old, val) -> {
|
||||||
backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
if (val == null) { return; }
|
||||||
Color color = model.backgroundColorProperty().get();
|
int size = val.intValue() < 600 ? 150 : 250;
|
||||||
return new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY));
|
coverImage.setWidth(size);
|
||||||
}, model.backgroundColorProperty()));
|
coverImage.setHeight(size);
|
||||||
|
});
|
||||||
|
|
||||||
playBtn.setOnAction(e -> {
|
playBtn.setOnAction(e -> {
|
||||||
MediaPlayer player = currentPlayer.get();
|
MediaPlayer player = currentPlayer.get();
|
||||||
@ -179,7 +205,7 @@ final class Player extends VBox {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
InvalidationListener timeChangeListener = obs -> {
|
InvalidationListener mediaTimeChangeListener = obs -> {
|
||||||
if (currentPlayer.get() == null) { return; }
|
if (currentPlayer.get() == null) { return; }
|
||||||
|
|
||||||
var duration = currentPlayer.get().getCurrentTime();
|
var duration = currentPlayer.get().getCurrentTime();
|
||||||
@ -189,15 +215,14 @@ final class Player extends VBox {
|
|||||||
currentTimeLabel.setText(seconds > 0 ? formatDuration(duration) : "0.0");
|
currentTimeLabel.setText(seconds > 0 ? formatDuration(duration) : "0.0");
|
||||||
};
|
};
|
||||||
|
|
||||||
InvalidationListener sliderChangeListener = obs -> {
|
timeSlider.valueProperty().addListener(obs -> {
|
||||||
if (currentPlayer.get() == null) { return; }
|
if (currentPlayer.get() == null) { return; }
|
||||||
long max = (long) currentPlayer.get().getMedia().getDuration().toSeconds();
|
long max = (long) currentPlayer.get().getMedia().getDuration().toSeconds();
|
||||||
long sliderVal = (long) timeSlider.getValue();
|
long sliderVal = (long) timeSlider.getValue();
|
||||||
if (sliderVal <= max && timeSlider.isValueChanging()) {
|
if (sliderVal <= max && timeSlider.isValueChanging()) {
|
||||||
currentPlayer.get().seek(Duration.seconds(sliderVal));
|
currentPlayer.get().seek(Duration.seconds(sliderVal));
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
timeSlider.valueProperty().addListener(sliderChangeListener);
|
|
||||||
|
|
||||||
model.currentTrackProperty().addListener((obs, old, val) -> {
|
model.currentTrackProperty().addListener((obs, old, val) -> {
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
@ -215,7 +240,7 @@ final class Player extends VBox {
|
|||||||
mediaPlayer.setOnReady(() -> {
|
mediaPlayer.setOnReady(() -> {
|
||||||
Image image = getTag(media, "image", Image.class, NO_IMAGE);
|
Image image = getTag(media, "image", Image.class, NO_IMAGE);
|
||||||
coverImage.setFill(new ImagePattern(image));
|
coverImage.setFill(new ImagePattern(image));
|
||||||
model.setBackgroundColor(image != NO_IMAGE ? getDominantColor(image, BACKGROUND_OPACITY) : null);
|
model.setBackgroundColor(image != NO_IMAGE ? getDominantColor(image, 1.0) : null);
|
||||||
|
|
||||||
trackTitle.setText(getTag(media, "title", String.class, NO_TITLE));
|
trackTitle.setText(getTag(media, "title", String.class, NO_TITLE));
|
||||||
trackArtist.setText(getTag(media, "artist", String.class, NO_ARTIST));
|
trackArtist.setText(getTag(media, "artist", String.class, NO_ARTIST));
|
||||||
@ -234,7 +259,7 @@ final class Player extends VBox {
|
|||||||
}, mediaPlayer.statusProperty()));
|
}, mediaPlayer.statusProperty()));
|
||||||
|
|
||||||
mediaPlayer.volumeProperty().bind(volumeSlider.valueProperty());
|
mediaPlayer.volumeProperty().bind(volumeSlider.valueProperty());
|
||||||
mediaPlayer.currentTimeProperty().addListener(timeChangeListener);
|
mediaPlayer.currentTimeProperty().addListener(mediaTimeChangeListener);
|
||||||
});
|
});
|
||||||
mediaPlayer.setOnEndOfMedia(model::playNext);
|
mediaPlayer.setOnEndOfMedia(model::playNext);
|
||||||
|
|
||||||
@ -247,7 +272,7 @@ final class Player extends VBox {
|
|||||||
if (old != null) {
|
if (old != null) {
|
||||||
old.stop();
|
old.stop();
|
||||||
old.volumeProperty().unbind();
|
old.volumeProperty().unbind();
|
||||||
old.currentTimeProperty().removeListener(timeChangeListener);
|
old.currentTimeProperty().removeListener(mediaTimeChangeListener);
|
||||||
playIcon.iconCodeProperty().unbind();
|
playIcon.iconCodeProperty().unbind();
|
||||||
old.dispose();
|
old.dispose();
|
||||||
}
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
package atlantafx.sampler.page.showcase.musicplayer;
|
||||||
|
|
||||||
|
import javafx.scene.control.SplitPane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static atlantafx.sampler.util.JColorUtils.toHexWithAlpha;
|
||||||
|
|
||||||
|
final class PlayerScreen extends SplitPane {
|
||||||
|
|
||||||
|
private final Model model;
|
||||||
|
|
||||||
|
public PlayerScreen(Model model) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.model = model;
|
||||||
|
|
||||||
|
createView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createView() {
|
||||||
|
var player = new PlayerPane(model);
|
||||||
|
var playlist = new PlaylistPane(model);
|
||||||
|
|
||||||
|
getStyleClass().add("player-screen");
|
||||||
|
getItems().setAll(player, playlist);
|
||||||
|
|
||||||
|
model.backgroundColorProperty().addListener((obs, old, val) -> {
|
||||||
|
var domColor = Objects.equals(Color.TRANSPARENT, val) ?
|
||||||
|
Color.TRANSPARENT :
|
||||||
|
Color.color(val.getRed(), val.getGreen(), val.getBlue(), 1);
|
||||||
|
var domColor20 = Objects.equals(Color.TRANSPARENT, val) ?
|
||||||
|
Color.TRANSPARENT :
|
||||||
|
Color.color(val.getRed(), val.getGreen(), val.getBlue(), 0.2);
|
||||||
|
var domColor50 = Objects.equals(Color.TRANSPARENT, val) ?
|
||||||
|
Color.TRANSPARENT :
|
||||||
|
Color.color(val.getRed(), val.getGreen(), val.getBlue(), 0.5);
|
||||||
|
var domColor70 = Objects.equals(Color.TRANSPARENT, val) ?
|
||||||
|
Color.TRANSPARENT :
|
||||||
|
Color.color(val.getRed(), val.getGreen(), val.getBlue(), 0.7);
|
||||||
|
setStyle("-color-dominant:" + toHexWithAlpha(domColor) + ";" +
|
||||||
|
"-color-dominant-20:" + toHexWithAlpha(domColor20) + ";" +
|
||||||
|
"-color-dominant-50:" + toHexWithAlpha(domColor50) + ";" +
|
||||||
|
"-color-dominant-70:" + toHexWithAlpha(domColor70) + ";" +
|
||||||
|
"-color-dominant-border:" + toHexWithAlpha(domColor70) + ";"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,11 @@ import atlantafx.base.controls.Spacer;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
import javafx.css.PseudoClass;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.paint.ImagePattern;
|
import javafx.scene.paint.ImagePattern;
|
||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
@ -18,7 +20,8 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static atlantafx.base.theme.Styles.*;
|
import static atlantafx.base.theme.Styles.*;
|
||||||
import static atlantafx.sampler.page.showcase.musicplayer.Utils.toWebColor;
|
import static atlantafx.sampler.page.showcase.musicplayer.MediaFile.Metadata.NO_IMAGE_ALT;
|
||||||
|
import static atlantafx.sampler.page.showcase.musicplayer.MusicPlayerPage.SUPPORTED_MEDIA_TYPES;
|
||||||
import static java.lang.Double.MAX_VALUE;
|
import static java.lang.Double.MAX_VALUE;
|
||||||
import static javafx.geometry.Pos.CENTER_LEFT;
|
import static javafx.geometry.Pos.CENTER_LEFT;
|
||||||
import static javafx.scene.layout.Priority.ALWAYS;
|
import static javafx.scene.layout.Priority.ALWAYS;
|
||||||
@ -26,31 +29,50 @@ import static javafx.stage.FileChooser.ExtensionFilter;
|
|||||||
import static org.kordamp.ikonli.material2.Material2AL.ADD;
|
import static org.kordamp.ikonli.material2.Material2AL.ADD;
|
||||||
import static org.kordamp.ikonli.material2.Material2MZ.PLAYLIST_PLAY;
|
import static org.kordamp.ikonli.material2.Material2MZ.PLAYLIST_PLAY;
|
||||||
|
|
||||||
final class Playlist extends VBox {
|
final class PlaylistPane extends VBox {
|
||||||
|
|
||||||
public Playlist(Model model) {
|
private final Model model;
|
||||||
|
|
||||||
|
private Label sizeLabel;
|
||||||
|
private Label sizeDescLabel;
|
||||||
|
private ProgressBar loadProgress;
|
||||||
|
private Button addButton;
|
||||||
|
private ListView<MediaFile> playlist;
|
||||||
|
|
||||||
|
public PlaylistPane(Model model) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.model = model;
|
||||||
|
|
||||||
|
createView();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createView() {
|
||||||
var headerLabel = new Label("Playlist");
|
var headerLabel = new Label("Playlist");
|
||||||
headerLabel.getStyleClass().setAll(TEXT_CAPTION);
|
headerLabel.getStyleClass().setAll(TEXT_CAPTION);
|
||||||
// There's probably some #javafx-bug here. This label uses CSS class that
|
// There's probably some #javafx-bug here. This label uses CSS class that
|
||||||
// changes font size & weight. When switching between themes it _sometimes_
|
// changes font size and weight. When switching between themes it _sometimes_
|
||||||
// ignores new color variables and remains using old fg color. Like it
|
// ignores new color variables and continues using old fg color. Like it
|
||||||
// caches old font or something. The below rule forces it to use proper color.
|
// caches old font or something. The below rule forces it to use proper color.
|
||||||
headerLabel.setStyle("-fx-text-fill: -color-fg-default;");
|
headerLabel.setStyle("-fx-text-fill: -color-fg-default;");
|
||||||
|
|
||||||
var sizeLabel = new Label("");
|
sizeLabel = new Label("");
|
||||||
sizeLabel.getStyleClass().add(TEXT_SMALL);
|
sizeLabel.getStyleClass().add(TEXT_SMALL);
|
||||||
|
|
||||||
var sizeDescLabel = new Label("empty");
|
sizeDescLabel = new Label("empty");
|
||||||
sizeDescLabel.getStyleClass().add(TEXT_SMALL);
|
sizeDescLabel.getStyleClass().add(TEXT_SMALL);
|
||||||
|
|
||||||
var loadProgress = new ProgressBar(1);
|
loadProgress = new ProgressBar(1);
|
||||||
loadProgress.getStyleClass().add(SMALL);
|
loadProgress.getStyleClass().add(SMALL);
|
||||||
loadProgress.setMaxWidth(MAX_VALUE);
|
loadProgress.setMaxWidth(MAX_VALUE);
|
||||||
loadProgress.setVisible(false);
|
loadProgress.setVisible(false);
|
||||||
|
|
||||||
var addButton = new Button("Add", new FontIcon(ADD));
|
addButton = new Button("Add", new FontIcon(ADD));
|
||||||
|
addButton.getStyleClass().add(FLAT);
|
||||||
|
|
||||||
var controlsBox = new HBox();
|
var controlsBox = new HBox();
|
||||||
|
controlsBox.setPadding(new Insets(0, 0, 10, 0));
|
||||||
controlsBox.getStyleClass().add("controls");
|
controlsBox.getStyleClass().add("controls");
|
||||||
controlsBox.getChildren().setAll(
|
controlsBox.getChildren().setAll(
|
||||||
new VBox(5, headerLabel, sizeDescLabel),
|
new VBox(5, headerLabel, sizeDescLabel),
|
||||||
@ -59,16 +81,18 @@ final class Playlist extends VBox {
|
|||||||
);
|
);
|
||||||
controlsBox.setAlignment(CENTER_LEFT);
|
controlsBox.setAlignment(CENTER_LEFT);
|
||||||
|
|
||||||
var playlist = new ListView<>(model.playlist());
|
playlist = new ListView<>(model.playlist());
|
||||||
playlist.setCellFactory(param -> new MediaCell(model));
|
playlist.setCellFactory(param -> new MediaCell(model));
|
||||||
playlist.setPlaceholder(new Label("No Content"));
|
playlist.setPlaceholder(new Label("No Content"));
|
||||||
|
VBox.setVgrow(playlist, ALWAYS);
|
||||||
|
|
||||||
getStyleClass().add("playlist");
|
getStyleClass().add("playlist");
|
||||||
setSpacing(10);
|
setPadding(new Insets(10));
|
||||||
|
setSpacing(5);
|
||||||
getChildren().setAll(controlsBox, loadProgress, playlist);
|
getChildren().setAll(controlsBox, loadProgress, playlist);
|
||||||
|
}
|
||||||
|
|
||||||
// ~
|
private void init() {
|
||||||
|
|
||||||
model.currentTrackProperty().addListener((obs, old, val) -> playlist.refresh());
|
model.currentTrackProperty().addListener((obs, old, val) -> playlist.refresh());
|
||||||
|
|
||||||
model.playlist().addListener((ListChangeListener<MediaFile>) c -> {
|
model.playlist().addListener((ListChangeListener<MediaFile>) c -> {
|
||||||
@ -82,15 +106,13 @@ final class Playlist extends VBox {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
model.backgroundColorProperty().addListener((obs, old, val) -> {
|
|
||||||
var color = model.backgroundColorProperty().get();
|
|
||||||
playlist.setStyle("-color-cell-bg:" + toWebColor(color) + ";");
|
|
||||||
setBackground(new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY)));
|
|
||||||
});
|
|
||||||
|
|
||||||
addButton.setOnAction(e -> {
|
addButton.setOnAction(e -> {
|
||||||
FileChooser fileChooser = new FileChooser();
|
var extensions = SUPPORTED_MEDIA_TYPES.stream().map(s -> "*." + s).toList();
|
||||||
fileChooser.getExtensionFilters().addAll(new ExtensionFilter("MP3 files (*.mp3)", "*.mp3"));
|
var fileChooser = new FileChooser();
|
||||||
|
fileChooser.getExtensionFilters().addAll(new ExtensionFilter(
|
||||||
|
"MP3 files (" + String.join(", ", extensions) + ")",
|
||||||
|
extensions
|
||||||
|
));
|
||||||
List<File> files = fileChooser.showOpenMultipleDialog(getScene().getWindow());
|
List<File> files = fileChooser.showOpenMultipleDialog(getScene().getWindow());
|
||||||
if (files == null || files.isEmpty()) { return; }
|
if (files == null || files.isEmpty()) { return; }
|
||||||
|
|
||||||
@ -120,6 +142,8 @@ final class Playlist extends VBox {
|
|||||||
|
|
||||||
private static class MediaCell extends ListCell<MediaFile> {
|
private static class MediaCell extends ListCell<MediaFile> {
|
||||||
|
|
||||||
|
private static final PseudoClass PLAYING = PseudoClass.getPseudoClass("playing");
|
||||||
|
|
||||||
private final Model model;
|
private final Model model;
|
||||||
private final HBox root;
|
private final HBox root;
|
||||||
private final Rectangle coverImage;
|
private final Rectangle coverImage;
|
||||||
@ -166,10 +190,15 @@ final class Playlist extends VBox {
|
|||||||
} else {
|
} else {
|
||||||
setGraphic(root);
|
setGraphic(root);
|
||||||
|
|
||||||
playMark.setVisible(Objects.equals(mediaFile, model.currentTrackProperty().get()));
|
var playing = Objects.equals(mediaFile, model.currentTrackProperty().get());
|
||||||
|
|
||||||
|
pseudoClassStateChanged(PLAYING, playing);
|
||||||
|
playMark.setVisible(playing);
|
||||||
|
|
||||||
mediaFile.readMetadata(metadata -> {
|
mediaFile.readMetadata(metadata -> {
|
||||||
coverImage.setFill(new ImagePattern(metadata.image()));
|
coverImage.setFill(new ImagePattern(
|
||||||
|
metadata.image() != null ? metadata.image() : NO_IMAGE_ALT
|
||||||
|
));
|
||||||
titleLabel.setText(metadata.title());
|
titleLabel.setText(metadata.title());
|
||||||
artistLabel.setText(metadata.artist());
|
artistLabel.setText(metadata.artist());
|
||||||
});
|
});
|
@ -0,0 +1,105 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
package atlantafx.sampler.page.showcase.musicplayer;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.scene.text.TextAlignment;
|
||||||
|
import javafx.scene.text.TextFlow;
|
||||||
|
import javafx.stage.DirectoryChooser;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.stage.FileChooser.ExtensionFilter;
|
||||||
|
import org.kordamp.ikonli.feather.Feather;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static atlantafx.sampler.page.showcase.musicplayer.MusicPlayerPage.SUPPORTED_MEDIA_TYPES;
|
||||||
|
|
||||||
|
final class StartScreen extends BorderPane {
|
||||||
|
|
||||||
|
private final Model model;
|
||||||
|
|
||||||
|
public StartScreen(Model model) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.model = model;
|
||||||
|
|
||||||
|
createView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createView() {
|
||||||
|
var jumboIcon = new FontIcon(Feather.MUSIC);
|
||||||
|
|
||||||
|
var noteText = new TextFlow(new Text(
|
||||||
|
"Select a file or a folder."
|
||||||
|
));
|
||||||
|
noteText.setMaxWidth(400);
|
||||||
|
noteText.setTextAlignment(TextAlignment.CENTER);
|
||||||
|
|
||||||
|
var addFolderBtn = new Button("Add Folder");
|
||||||
|
addFolderBtn.getStyleClass().add(Styles.ACCENT);
|
||||||
|
addFolderBtn.setPrefWidth(150);
|
||||||
|
addFolderBtn.setOnAction(e -> addFolder());
|
||||||
|
|
||||||
|
var addFileBtn = new Button("Add File");
|
||||||
|
addFileBtn.setPrefWidth(150);
|
||||||
|
addFileBtn.setOnAction(e -> addFile());
|
||||||
|
|
||||||
|
var controls = new VBox(10, addFolderBtn, addFileBtn);
|
||||||
|
controls.setAlignment(Pos.CENTER);
|
||||||
|
controls.setFillWidth(true);
|
||||||
|
|
||||||
|
var content = new VBox(30, jumboIcon, noteText, controls);
|
||||||
|
content.getStyleClass().add("content");
|
||||||
|
content.setAlignment(Pos.CENTER);
|
||||||
|
content.setFillWidth(true);
|
||||||
|
|
||||||
|
setCenter(content);
|
||||||
|
setPadding(new Insets(100));
|
||||||
|
getStyleClass().add("start-screen");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFile() {
|
||||||
|
var extensions = SUPPORTED_MEDIA_TYPES.stream().map(s -> "*." + s).toList();
|
||||||
|
var fileChooser = new FileChooser();
|
||||||
|
fileChooser.getExtensionFilters().addAll(new ExtensionFilter(
|
||||||
|
"MP3 files (" + String.join(", ", extensions) + ")",
|
||||||
|
extensions
|
||||||
|
));
|
||||||
|
List<File> files = fileChooser.showOpenMultipleDialog(getScene().getWindow());
|
||||||
|
if (files == null || files.isEmpty()) { return; }
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
model.addFile(new MediaFile(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFolder() {
|
||||||
|
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||||
|
File dir = directoryChooser.showDialog(getScene().getWindow());
|
||||||
|
if (dir == null) { return; }
|
||||||
|
|
||||||
|
var path = dir.toPath();
|
||||||
|
if (!Files.isDirectory(path) || !Files.isReadable(path)) { return; }
|
||||||
|
|
||||||
|
try (Stream<Path> stream = Files.list(path)) {
|
||||||
|
stream.forEach(p -> {
|
||||||
|
for (String s : SUPPORTED_MEDIA_TYPES) {
|
||||||
|
if (!p.toAbsolutePath().toString().endsWith(s)) { continue; }
|
||||||
|
model.addFile(new MediaFile(p.toFile()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -37,12 +37,4 @@ final class Utils {
|
|||||||
String.format("%02d:%02d", (seconds % 3600) / 60, seconds % 60) :
|
String.format("%02d:%02d", (seconds % 3600) / 60, seconds % 60) :
|
||||||
String.format("%d:%02d:%02d", seconds / 3600, (seconds % 3600) / 60, seconds % 60);
|
String.format("%d:%02d:%02d", seconds / 3600, (seconds % 3600) / 60, seconds % 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String toWebColor(Color color) {
|
|
||||||
int r = ((int) Math.round(color.getRed() * 255)) << 24;
|
|
||||||
int g = ((int) Math.round(color.getGreen() * 255)) << 16;
|
|
||||||
int b = ((int) Math.round(color.getBlue() * 255)) << 8;
|
|
||||||
int a = ((int) Math.round(color.getOpacity() * 255));
|
|
||||||
return String.format("#%08X", (r + g + b + a));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,52 @@
|
|||||||
/** SPDX-License-Identifier: MIT */
|
/** SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
.music-player-showcase {
|
.music-player-showcase > .start-screen {
|
||||||
|
-fx-border-color: -color-border-muted;
|
||||||
-fx-border-width: 1;
|
-fx-border-width: 1;
|
||||||
-fx-border-color: -color-border-default;
|
}
|
||||||
|
.music-player-showcase > .start-screen > .content > .ikonli-font-icon {
|
||||||
|
-fx-icon-color: -color-fg-muted;
|
||||||
|
-fx-fill: -color-fg-muted;
|
||||||
|
-fx-icon-size: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-player-showcase .playlist {
|
.music-player-showcase > .player-screen {
|
||||||
-fx-padding: 10;
|
-fx-border-color: -color-border-muted;
|
||||||
|
-fx-border-width: 1;
|
||||||
|
-color-dominant: transparent;
|
||||||
|
-color-dominant-20: transparent;
|
||||||
|
-color-dominant-50: transparent;
|
||||||
|
-color-dominant-70: transparent;
|
||||||
|
-color-dominant-border: -color-border-muted;
|
||||||
|
}
|
||||||
|
.music-player-showcase > .player-screen > * > .player {
|
||||||
|
-fx-background-color: radial-gradient(radius 100%, -color-dominant-50, -color-dominant-20);
|
||||||
|
}
|
||||||
|
.music-player-showcase > .player-screen > * > .player > .media-controls > .play > .ikonli-font-icon {
|
||||||
|
-fx-icon-size: 32px;
|
||||||
|
}
|
||||||
|
.music-player-showcase > .player-screen > .split-pane-divider {
|
||||||
|
-fx-background-color: -color-dominant-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-player-showcase .playlist > .list-view {
|
.music-player-showcase > .player-screen > * > .playlist {
|
||||||
|
-fx-background-color: radial-gradient(radius 100%, -color-dominant-20, -color-dominant-50);
|
||||||
|
}
|
||||||
|
.music-player-showcase > .player-screen > * > .playlist > .controls {
|
||||||
|
-fx-border-color: -color-dominant-border;
|
||||||
|
-fx-border-width: 0 0 0.75px 0;
|
||||||
|
}
|
||||||
|
.music-player-showcase > .player-screen > * > .playlist > .controls > .button {
|
||||||
|
-color-button-bg-hover: transparent;
|
||||||
|
-color-button-bg-focused: transparent;
|
||||||
|
-color-button-bg-pressed: transparent;
|
||||||
|
}
|
||||||
|
.music-player-showcase > .player-screen > * > .playlist > .list-view {
|
||||||
-fx-border-width: 0;
|
-fx-border-width: 0;
|
||||||
}
|
}
|
||||||
.music-player-showcase .playlist > .list-view .list-cell {
|
.music-player-showcase > .player-screen > * > .playlist > .list-view .list-cell {
|
||||||
|
-color-cell-bg: transparent;
|
||||||
|
-color-cell-bg-selected: transparent;
|
||||||
|
-color-cell-border: transparent;
|
||||||
-fx-cell-size: 4em;
|
-fx-cell-size: 4em;
|
||||||
-fx-background-color: transparent;
|
|
||||||
-fx-background-radius: 6px;
|
|
||||||
}
|
|
||||||
.music-player-showcase .playlist > .list-view:focused>.virtual-flow>.clipped-container>.sheet>.list-cell:filled:selected {
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
}
|
|
||||||
.music-player-showcase .playlist > .list-view:focused>.virtual-flow>.clipped-container>.sheet>.list-cell:selected:hover,
|
|
||||||
.music-player-showcase .playlist > .list-view:focused>.virtual-flow>.clipped-container>.sheet>.list-cell:filled:hover {
|
|
||||||
-fx-background-color: derive(-color-cell-bg, -5%);
|
|
||||||
}
|
}
|
@ -846,4 +846,12 @@ public class JColorUtils {
|
|||||||
(float) color.getOpacity()
|
(float) color.getOpacity()
|
||||||
).getColorHexShorthandWithAlpha();
|
).getColorHexShorthandWithAlpha();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String toHexOpaque(Color color) {
|
||||||
|
return JColor.color(
|
||||||
|
(float) color.getRed(),
|
||||||
|
(float) color.getGreen(),
|
||||||
|
(float) color.getBlue()
|
||||||
|
).getColorHexShorthandWithAlpha();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,10 @@
|
|||||||
-fx-spacing: 0;
|
-fx-spacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.about {
|
||||||
|
@include hide();
|
||||||
|
}
|
||||||
|
|
||||||
.credits {
|
.credits {
|
||||||
@include hide();
|
@include hide();
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 94 KiB |
@ -15,6 +15,7 @@ $material-icons: (
|
|||||||
"minus": "M 17,13 H 7 v -2 h 10 z",
|
"minus": "M 17,13 H 7 v -2 h 10 z",
|
||||||
"more-vert": "M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z",
|
"more-vert": "M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z",
|
||||||
"plus": "M 18,12.857142 H 12.857142 V 18 H 11.142858 V 12.857142 H 6 v -1.714284 h 5.142858 V 6 h 1.714284 v 5.142858 H 18 Z",
|
"plus": "M 18,12.857142 H 12.857142 V 18 H 11.142858 V 12.857142 H 6 v -1.714284 h 5.142858 V 6 h 1.714284 v 5.142858 H 18 Z",
|
||||||
|
"unfold-more": "M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z",
|
||||||
);
|
);
|
||||||
|
|
||||||
@mixin get($id, $scale: true) {
|
@mixin get($id, $scale: true) {
|
||||||
|
Loading…
Reference in New Issue
Block a user