package de.calette.mephisto3.ui; import callete.api.Callete; import callete.api.services.impl.music.network.Mp3Folder; import callete.api.services.music.model.Album; import callete.api.services.music.model.PlaylistItem; import callete.api.services.music.model.Song; import callete.api.services.music.player.PlaybackChangeEvent; import callete.api.services.music.player.PlaybackChangeListener; import de.calette.mephisto3.Mephisto3; import de.calette.mephisto3.control.ControlListener; import de.calette.mephisto3.control.ServiceControlEvent; import de.calette.mephisto3.control.ServiceController; import de.calette.mephisto3.resources.menu.MenuResourceLoader; import de.calette.mephisto3.util.ComponentUtil; import de.calette.mephisto3.util.Executor; import de.calette.mephisto3.util.TransitionUtil; import javafx.animation.*; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.canvas.Canvas; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import static de.calette.mephisto3.util.TransitionUtil.*; /** * Pane this displays the album cover, name and artist for the slider. */ public class AlbumBox extends ControllableHBoxItemPanelBase<Album> implements ControlListener, PlaybackChangeListener { private final static Logger LOG = LoggerFactory.getLogger(AlbumBox.class); public static final int COVER_WIDTH = 200; public static final int BOX_WIDTH = 220; public static final int COVER_HEIGHT = 200; public static final int TRACKS_WIDTH = 380; public static final int TRACK_ITEM_HEIGHT = 30; public static final int MAX_DISPLAY_TRACKS = 11; //index when the tracks should be starting scrolling public static final int SCROLL_INDEX = 7; public static final int TRACKS_BOX_WIDTH = Mephisto3.WIDTH-COVER_WIDTH-10; public static final int TOP_PADDING = 15; public static final int SHADOW_WIDTH = 3; private double scaleFactor = 1.05; private VBox albumInfoBox = new VBox(4); private VBox tracksBox; private int selectionIndex = -1; public AlbumBox(ControllableSelectorPanel parentControl, Mp3Folder album) { this(parentControl, album, true); } public AlbumBox(ControllableSelectorPanel parentControl, Album album) { this(parentControl, album, true); } public AlbumBox(ControllableSelectorPanel parentControl, Album album, boolean backButton) { super(10, parentControl, album); this.getStyleClass().add("album-box"); setMinWidth(BOX_WIDTH); setMaxHeight(345); setPadding(new Insets(0, 0, 0, 10)); //box for compact view in slider mode VBox compactView = new VBox(); compactView.setPadding(new Insets(TOP_PADDING, 0, 0, 0)); //box used for labels below the cover albumInfoBox.setPadding(new Insets(5, 5, 5, 5)); albumInfoBox.getStyleClass().add("album-info-text"); if(album != null) { createAlbumInfoBox(album, compactView); } else { setAlignment(Pos.BASELINE_RIGHT); String resource = "1.png"; if(backButton) { resource = "back.png"; } setPadding(new Insets(80, 0, 0, 0)); scaleFactor = 1.2; Canvas back = ComponentUtil.createImageCanvas(MenuResourceLoader.getResource(resource), 100, 100); compactView.getChildren().add(back); } getChildren().add(compactView); } /** * Shows the details of the album, including the title selector. * That's why this panel takes control when mode is enabled. */ public void switchToDetailsMode() { //scale no normal size: remove the selection highlighting createScaler(this, 1).play(); Callete.getMusicPlayer().addPlaybackChangeEventListener(this); //expand panel to full width Transition maxWidthTransition = createMaxWidthTransition(this, COVER_WIDTH, TRACKS_BOX_WIDTH, true); //hide cover labels final FadeTransition outFader = createOutFader(albumInfoBox); outFader.setOnFinished(actionEvent -> { //remove title name name... albumInfoBox.getChildren().clear(); //...add tracks, year and genre instead. ComponentUtil.createLabel(getModel().getName(), "default-16", albumInfoBox); ComponentUtil.createLabel(getModel().getArtist(), "default-16", albumInfoBox); ComponentUtil.createCustomLabel(getModel().getSize() + " Titel, " + getModel().getDuration(), "player-info-label", albumInfoBox); String pos4 = ""; if(getModel().getYear() > 0) { pos4 = String.valueOf(getModel().getYear()); } if(pos4.length() > 0 && !StringUtils.isEmpty(getModel().getGenre())) { pos4 += ", " + getModel().getGenre(); } ComponentUtil.createCustomLabel(pos4, "player-info-label", albumInfoBox); createInFader(albumInfoBox).play(); createTracksBox(); getChildren().add(tracksBox); FadeTransition inFader = createInFader(tracksBox); inFader.setOnFinished(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { //add control listener to this panel ServiceController.getInstance().addControlListener(AlbumBox.this); } }); inFader.play(); }); ParallelTransition pt = new ParallelTransition(maxWidthTransition, outFader); pt.play(); } @Override public void playbackChanged(PlaybackChangeEvent event) { if(tracksBox == null || tracksBox.getChildren().isEmpty()) { return; } LOG.info("Received playback change event for " + this + " with item " + event.getActiveItem()); final PlaylistItem activeItem = event.getActiveItem(); Platform.runLater(() -> { for(Node node : tracksBox.getChildren()) { TrackBox track = (TrackBox) node; track.setActive(activeItem != null && track.getUserData().equals(activeItem)); } }); } @Override public double getScaleFactor() { return scaleFactor; } @Override public void controlEvent(ServiceControlEvent event) { if(event.getEventType().equals(ServiceControlEvent.EVENT_TYPE.PUSH)) { if(selectionIndex == -1) { switchToSliderMode(); } else { Platform.runLater(new Runnable() { @Override public void run() { final Node node = tracksBox.getChildren().get(selectionIndex); FadeTransition blink = TransitionUtil.createBlink(node); blink.setOnFinished(actionEvent -> Executor.run(() -> { Song song = (Song) node.getUserData(); Callete.getMusicPlayer().getPlaylist().setPlaylist(getModel()); Callete.getMusicPlayer().getPlaylist().setActiveItem(song); Callete.getMusicPlayer().play(); })); blink.play(); } }); } } else if(event.getEventType().equals(ServiceControlEvent.EVENT_TYPE.LONG_PUSH)) { selectionIndex = -1; switchToSliderMode(); } else if(event.getEventType().equals(ServiceControlEvent.EVENT_TYPE.NEXT)) { if(selectionIndex < getModel().getSize() - 1) { int oldIndex = selectionIndex; selectionIndex++; updateSelection(oldIndex, selectionIndex); } } else if(event.getEventType().equals(ServiceControlEvent.EVENT_TYPE.PREVIOUS)) { if(selectionIndex >= 0) { int oldIndex = selectionIndex; selectionIndex--; updateSelection(oldIndex, selectionIndex); } } } //----------------------- Helper methods ------------------------------------------ /** * Creates the additional track list panel shown when * the details mode of an album is shown. */ private void createTracksBox() { //create track box with initial opacity //box used for details mode tracksBox = new VBox(0); tracksBox.setPadding(new Insets(TOP_PADDING, 0, 0, 0)); tracksBox.setMaxWidth(TRACKS_BOX_WIDTH - 30); tracksBox.setOpacity(0); final List<Song> songs = getModel().getSongs(); PlaylistItem item = Callete.getMusicPlayer().getPlaylist().getActiveItem(); for(Song song : songs) { boolean active = item != null && item instanceof Song && item.equals(song); tracksBox.getChildren().add(new TrackBox(song, active)); } } /** * Updates the background to indicate the selection, * additionally scrolls down if the playlist lengths exceeds the height. * * @param oldIndex the index to remove the background from * @param newIndex the index to add the background to */ private void updateSelection(int oldIndex, int newIndex) { if(oldIndex >= 0) { Node node = tracksBox.getChildren().get(oldIndex); node.getStyleClass().remove("track-selection"); } if(newIndex >= 0 && newIndex <= tracksBox.getChildren().size() - 1) { Node node = tracksBox.getChildren().get(newIndex); node.getStyleClass().add("track-selection"); } //scroll down if(getModel().getSize() > MAX_DISPLAY_TRACKS && (newIndex >= SCROLL_INDEX || oldIndex >= SCROLL_INDEX)) { if(oldIndex < newIndex) { //scroll down boolean doScroll = getModel().getSize() - newIndex >= 5; if(doScroll) { scroll(newIndex + 4, newIndex - SCROLL_INDEX, -TRACK_ITEM_HEIGHT); } } else { //scroll up boolean doScroll = getModel().getSize() - newIndex > 5; if(doScroll) { scroll(oldIndex - SCROLL_INDEX, oldIndex + 4, TRACK_ITEM_HEIGHT); } } } } private void scroll(int inFaderIndex, int outFaderIndex, int scrollWidth) { ServiceController.getInstance().setControlEnabled(false); final FadeTransition outFader = createOutFader(tracksBox.getChildren().get(outFaderIndex)); final FadeTransition inFader = createInFader(tracksBox.getChildren().get(inFaderIndex)); final TranslateTransition translateByYTransition = createTranslateByYTransition(tracksBox, 200, scrollWidth); ParallelTransition pt = new ParallelTransition(outFader, inFader, translateByYTransition); pt.setOnFinished(actionEvent -> ServiceController.getInstance().setControlEnabled(true)); pt.play(); } private void createAlbumInfoBox(Album album, VBox compactView) { BorderPane p = new BorderPane(); p.setMaxHeight(COVER_WIDTH + SHADOW_WIDTH); p.setMaxWidth(COVER_HEIGHT + SHADOW_WIDTH); p.getStyleClass().add("cover-box"); LazyAlbumCoverCache.loadImageViewFor(p, album); ComponentUtil.createLabel(getModel().getName(), "default-16", albumInfoBox); ComponentUtil.createLabel(getModel().getArtist(), "player-info-label", albumInfoBox); p.setBottom(albumInfoBox); compactView.getChildren().add(p); } private void switchToSliderMode() { ServiceController.getInstance().removeControlListener(this); Callete.getMusicPlayer().removePlaybackChangeEventListener(this); final FadeTransition outFader = createOutFader(tracksBox); outFader.setOnFinished(actionEvent -> { final ScaleTransition scaler = createScaler(AlbumBox.this, scaleFactor); final Transition maxWidth = createMaxWidthTransition(AlbumBox.this, BOX_WIDTH + TRACKS_BOX_WIDTH, TRACKS_BOX_WIDTH, false); final FadeTransition labelFader = createOutFader(albumInfoBox); ParallelTransition pt = new ParallelTransition(scaler, maxWidth, labelFader); pt.setOnFinished(actionEvent1 -> { albumInfoBox.getChildren().clear(); ComponentUtil.createLabel(getModel().getName(), "default-16", albumInfoBox); ComponentUtil.createLabel(getModel().getArtist(), "player-info-label", albumInfoBox); selectionIndex = -1; tracksBox.getChildren().clear(); AlbumBox.this.getChildren().remove(tracksBox); createInFader(albumInfoBox).play(); ServiceController.getInstance().addControlListener(getParentControlPanel()); }); pt.play(); }); outFader.play(); } @Override public String toString() { return "Album Box '" + getModel() + "'"; } }