package com.faltro.houdoku.util; import javafx.geometry.Pos; import javafx.geometry.Rectangle2D; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.effect.ColorAdjust; import javafx.scene.effect.DropShadow; import javafx.scene.image.ImageView; import javafx.scene.layout.FlowPane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; public class LayoutHelpers { /** * ColorAdjust for the banner on the {@link com.faltro.houdoku.controller.SeriesController} * page. */ public static final ColorAdjust BANNER_ADJUST = new ColorAdjust(0, 0, -0.5, 0); /** * ColorAdjust for stacked content in the background. */ public static final ColorAdjust BACKGROUND_ADJUST = new ColorAdjust(0, 0, -0.75, 0); /** * ColorAdjust for non-hovered cover images. */ public static final ColorAdjust COVER_ADJUST_DEFAULT = new ColorAdjust(0, 0, -0.2, 0); /** * DropShadow for cover images. */ private static final DropShadow COVER_DROPSHADOW = new DropShadow(5, Color.BLACK); /** * ColorAdjust for hovered cover images. */ private static final ColorAdjust COVER_ADJUST_HOVER = new ColorAdjust(0, 0, -0.5, 0); /** * The height/width ratio of displayed cover images. */ private static final double COVER_RATIO = 1.5; static { COVER_ADJUST_DEFAULT.setInput(COVER_DROPSHADOW); COVER_ADJUST_HOVER.setInput(COVER_DROPSHADOW); } /** * Apply the correct aspect ratio for covers to the given ImageView. * * <p>This method does not specify a width for the ImageView itself. * * <p>The ImageView's image should be set AFTER this method is called, but the aspect ratio will * be applied whenever the image is changed. * * @param imageView the ImageView for a cover to apply the aspect ratio to */ public static void applyCoverSizing(ImageView imageView) { imageView.setPreserveRatio(true); imageView.imageProperty().addListener((observableValue, oldValue, image) -> { double ih = image.getHeight(); double iw = image.getWidth(); double image_ratio = ih / iw; double vp_width = image_ratio < COVER_RATIO ? ih / COVER_RATIO : iw; double vp_height = image_ratio < COVER_RATIO ? ih : iw * COVER_RATIO; double offset_x = (iw - vp_width) / 2; double offset_y = (ih - vp_height) / 2; imageView.setViewport(new Rectangle2D(offset_x, offset_y, vp_width, vp_height)); }); } /** * Create the container for displaying a series cover, for use in FlowPane layouts where covers * are shown in a somewhat grid-like fashion. * * @param container the parent FlowPane container * @param title the title of the series being represented * @param cover the cover of the series being represented; this ImageView is not modified, a * copy is made to be used in the new container * @return a StackPane which displays the provided title and cover and can be added to the * FlowPane */ public static StackPane createCoverContainer(FlowPane container, String title, ImageView cover) { StackPane result_pane = new StackPane(); result_pane.setAlignment(Pos.BOTTOM_LEFT); // create the label for showing the series title Label label = new Label(); label.setText(title); label.getStyleClass().add("coverLabel"); label.setWrapText(true); // We create a new ImageView for the cell instead of using the result's cover ImageView // since we may not want to mess with the sizing of the result's cover -- particularly if we // want to have additional result layouts. ImageView image_view = new ImageView(); applyCoverSizing(image_view); image_view.fitWidthProperty().bind(result_pane.prefWidthProperty()); image_view.imageProperty().bind(cover.imageProperty()); image_view.setEffect(COVER_ADJUST_DEFAULT); image_view.getStyleClass().add("coverImage"); // create the mouse event handlers for the result pane result_pane.setOnMouseEntered(t -> { image_view.setEffect(COVER_ADJUST_HOVER); setChildButtonVisible(result_pane, true); }); result_pane.setOnMouseExited(t -> { image_view.setEffect(COVER_ADJUST_DEFAULT); setChildButtonVisible(result_pane, false); }); result_pane.getChildren().addAll(image_view, label); return result_pane; } /** * Create the container for displaying a series cover, for use in FlowPane layouts where covers * are shown in a somewhat grid-like fashion. However this includes a count of the amount of * chapters that have been marked as read. The count is displayed in the top right corner. * * @param container the parent FlowPane container * @param title the title of the series being represented * @param cover the cover of the series being represented; this ImageView is not * modified, a copy is made to be used in the new container * @param numUnreadChapters the amount of Chapters that have been marked as unread * @return a StackPane which displays the provided title and cover and can be added to the * FlowPane */ public static StackPane createCoverContainer(FlowPane container, String title, ImageView cover, int numUnreadChapters) { String amountOfReadChapters = String.valueOf(numUnreadChapters); // create the label for showing the amount of chapters marked as read Label label = new Label(); label.setText(amountOfReadChapters); label.getStyleClass().add("coverLabel"); label.setWrapText(true); // this label will be situated in the top right as the title is located in the bottom left StackPane.setAlignment(label, Pos.TOP_RIGHT); // We call the other createCoverContainer method to provide a filled stackpane with the // title and cover StackPane pane = createCoverContainer(container, title, cover); pane.getChildren().add(label); return pane; } /** * Set whether all Button children of the given Parent are visible. * * @param parent the parent node * @param visible whether the buttons should be visible */ public static void setChildButtonVisible(Parent parent, boolean visible) { for (Node child : parent.getChildrenUnmodifiable()) { if (child instanceof Button) { Button button = (Button) child; button.setVisible(visible); button.setManaged(visible); } else if (child instanceof Parent) { setChildButtonVisible((Parent) child, visible); } } } }