/* * The MIT License (MIT) * * Copyright (c) 2016-2019 TweetWallFX * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.tweetwallfx.controls.steps; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import javafx.animation.FadeTransition; import javafx.animation.ParallelTransition; import javafx.animation.SequentialTransition; import javafx.animation.Transition; import javafx.geometry.BoundingBox; import javafx.geometry.Bounds; import javafx.scene.CacheHint; import javafx.scene.effect.GaussianBlur; import javafx.scene.image.ImageView; import javafx.scene.layout.Pane; import javafx.util.Duration; import org.tweetwallfx.controls.WordleSkin; import org.tweetwallfx.stepengine.api.DataProvider; import org.tweetwallfx.stepengine.api.Step; import org.tweetwallfx.stepengine.api.StepEngine.MachineContext; import org.tweetwallfx.stepengine.api.config.StepEngineSettings; import org.tweetwallfx.stepengine.dataproviders.ImageMosaicDataProvider; import org.tweetwallfx.stepengine.dataproviders.ImageMosaicDataProvider.ImageStore; import org.tweetwallfx.transitions.LocationTransition; import org.tweetwallfx.transitions.SizeTransition; public class ImageMosaicStep implements Step { private ImageMosaicStep() { // prevent external instantiation } private static final Random RANDOM = new SecureRandom(); private final ImageView[][] rects = new ImageView[6][5]; private final Bounds[][] bounds = new Bounds[6][5]; private final Set<Integer> highlightedIndexes = new HashSet<>(); private Pane pane; private int count = 0; @Override public void doStep(final MachineContext context) { WordleSkin wordleSkin = (WordleSkin) context.get("WordleSkin"); ImageMosaicDataProvider dataProvider = context.getDataProvider(ImageMosaicDataProvider.class); pane = wordleSkin.getPane(); if (dataProvider.getImages().size() < 35) { context.proceed(); } else { Transition createMosaicTransition = createMosaicTransition(dataProvider.getImages()); createMosaicTransition.setOnFinished(event -> executeAnimations(context)); createMosaicTransition.play(); } } @Override public java.time.Duration preferredStepDuration(final MachineContext context) { return java.time.Duration.ofSeconds(1); } private void executeAnimations(final MachineContext context) { ImageWallAnimationTransition highlightAndZoomTransition = createHighlightAndZoomTransition(); highlightAndZoomTransition.transition.play(); highlightAndZoomTransition.transition.setOnFinished(event1 -> { Transition revert = createReverseHighlightAndZoomTransition(highlightAndZoomTransition.column, highlightAndZoomTransition.row); revert.setDelay(Duration.seconds(3)); revert.play(); revert.setOnFinished(event -> { count++; if (count < 3) { executeAnimations(context); } else { count = 0; ParallelTransition cleanup = new ParallelTransition(); for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { FadeTransition ft = new FadeTransition(Duration.seconds(0.5), rects[i][j]); ft.setToValue(0); cleanup.getChildren().addAll(ft); } } cleanup.setOnFinished(cleanUpDown -> { for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { pane.getChildren().remove(rects[i][j]); } } highlightedIndexes.clear(); context.proceed(); }); cleanup.play(); } }); }); } private Transition createMosaicTransition(final List<ImageStore> imageStores) { final SequentialTransition fadeIn = new SequentialTransition(); final List<FadeTransition> allFadeIns = new ArrayList<>(); final double width = pane.getWidth() / 6.0 - 10; final double height = pane.getHeight() / 5.0 - 8; final List<ImageStore> distillingList = new ArrayList<>(imageStores); for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { int index = RANDOM.nextInt(distillingList.size()); ImageStore selectedImage = distillingList.remove(index); ImageView imageView = new ImageView(selectedImage.getImage()); imageView.setCache(true); imageView.setCacheHint(CacheHint.SPEED); imageView.setFitWidth(width); imageView.setFitHeight(height); imageView.setEffect(new GaussianBlur(0)); rects[i][j] = imageView; bounds[i][j] = new BoundingBox(i * (width + 10) + 5, j * (height + 8) + 4, width, height); rects[i][j].setOpacity(0); rects[i][j].setLayoutX(bounds[i][j].getMinX()); rects[i][j].setLayoutY(bounds[i][j].getMinY()); pane.getChildren().add(rects[i][j]); FadeTransition ft = new FadeTransition(Duration.seconds(0.3), imageView); ft.setToValue(1); allFadeIns.add(ft); } } Collections.shuffle(allFadeIns); fadeIn.getChildren().addAll(allFadeIns); return fadeIn; } private ImageWallAnimationTransition createHighlightAndZoomTransition() { // select next random not but not previously shown image int index; do { index = RANDOM.nextInt(30); } while (!highlightedIndexes.add(index)); int column = index % 6; int row = index / 6; ImageView randomView = rects[column][row]; randomView.toFront(); ParallelTransition firstParallelTransition = new ParallelTransition(); ParallelTransition secondParallelTransition = new ParallelTransition(); for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { if ((i == column) && (j == row)) { continue; } FadeTransition ft = new FadeTransition(Duration.seconds(1), rects[i][j]); ft.setToValue(0.3); firstParallelTransition.getChildren().add(ft); } } for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { if ((i == column) && (j == row)) { continue; } GaussianBlur blur = (GaussianBlur) rects[i][j].getEffect(); if (null == blur) { blur = new GaussianBlur(0); rects[i][j].setEffect(blur); } // BlurTransition blurTransition = new BlurTransition(Duration.seconds(0.5), blur); // blurTransition.setToRadius(10); // secondParallelTransition.getChildren().addAll(blurTransition); } } double maxWidth = pane.getWidth() * 0.8; double maxHeight = pane.getHeight() * 0.8; double realWidth = randomView.getImage().getWidth(); double realHeight = randomView.getImage().getHeight(); double scaleFactor = Math.min(maxWidth / realWidth, maxHeight / realHeight); double targetWidth = realWidth * scaleFactor; double targetheight = realHeight * scaleFactor; final SizeTransition zoomBox = new SizeTransition(Duration.seconds(2.5), randomView.fitWidthProperty(), randomView.fitHeightProperty()) .withWidth(randomView.getLayoutBounds().getWidth(), targetWidth) .withHeight(randomView.getLayoutBounds().getHeight(), targetheight); final LocationTransition trans = new LocationTransition(Duration.seconds(2.5), randomView) .withX(randomView.getLayoutX(), pane.getWidth() / 2 - targetWidth / 2) .withY(randomView.getLayoutY(), pane.getHeight() / 2 - targetheight / 2); secondParallelTransition.getChildren().addAll(trans, zoomBox); SequentialTransition seqT = new SequentialTransition(); seqT.getChildren().addAll(firstParallelTransition, secondParallelTransition); firstParallelTransition.setOnFinished(event -> { // DropShadow ds = new DropShadow(); // ds.setOffsetY(10.0); // ds.setOffsetX(10.0); // ds.setColor(Color.GRAY); // randomView.setEffect(ds); }); return new ImageWallAnimationTransition(seqT, column, row); } private Transition createReverseHighlightAndZoomTransition(final int column, final int row) { ImageView randomView = rects[column][row]; randomView.toFront(); ParallelTransition firstParallelTransition = new ParallelTransition(); ParallelTransition secondParallelTransition = new ParallelTransition(); for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { if ((i == column) && (j == row)) { continue; } FadeTransition ft = new FadeTransition(Duration.seconds(1), rects[i][j]); ft.setFromValue(0.3); ft.setToValue(1.0); firstParallelTransition.getChildren().add(ft); } } double width = pane.getWidth() / 6.0 - 10; double height = pane.getHeight() / 5.0 - 8; final SizeTransition zoomBox = new SizeTransition(Duration.seconds(2.5), randomView.fitWidthProperty(), randomView.fitHeightProperty()) .withWidth(randomView.getLayoutBounds().getWidth(), width) .withHeight(randomView.getLayoutBounds().getHeight(), height); final LocationTransition trans = new LocationTransition(Duration.seconds(2.5), randomView) .withX(randomView.getLayoutX(), bounds[column][row].getMinX()) .withY(randomView.getLayoutY(), bounds[column][row].getMinY()); secondParallelTransition.getChildren().addAll(trans, zoomBox); SequentialTransition seqT = new SequentialTransition(); seqT.getChildren().addAll(secondParallelTransition, firstParallelTransition); secondParallelTransition.setOnFinished(event -> randomView.setEffect(null)); return seqT; } private static class ImageWallAnimationTransition { private final Transition transition; private final int column; private final int row; public ImageWallAnimationTransition(final Transition transition, final int column, final int row) { this.transition = transition; this.column = column; this.row = row; } } /** * Implementation of {@link Step.Factory} as Service implementation creating * {@link ImageMosaicStep}. */ public static final class FactoryImpl implements Step.Factory { @Override public ImageMosaicStep create(final StepEngineSettings.StepDefinition stepDefinition) { return new ImageMosaicStep(); } @Override public Class<ImageMosaicStep> getStepClass() { return ImageMosaicStep.class; } @Override public Collection<Class<? extends DataProvider>> getRequiredDataProviders(final StepEngineSettings.StepDefinition stepSettings) { return Arrays.asList(ImageMosaicDataProvider.class); } } }