package at.bestsolution.efxclipse.runtime.panels;
import java.util.concurrent.atomic.AtomicReference;
import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import at.bestsolution.efxclipse.runtime.panels.internal.VelocityTracker;
/**
* Fling pane is a scrollable list container like
* you have it on mobile devices
*
* @author Tom Schindl
*/
public class FlingPane extends Region {
private ObjectProperty<Node> content;
private Rectangle clipRect = new Rectangle();
private ObjectProperty<FlingDirection> flingDirection = new SimpleObjectProperty<FlingDirection>(FlingDirection.BOTH, "flingDirection");
private VelocityTracker tracker = new VelocityTracker();
public enum FlingDirection {
BOTH, HORIZONTAL, VERTICAL
}
public FlingPane() {
setFocusTraversable(false);
setClip(clipRect);
contentProperty().addListener(new ChangeListener<Node>() {
@Override
public void changed(ObservableValue<? extends Node> observable, Node oldValue, Node newValue) {
getChildren().clear();
if (newValue != null) {
getChildren().add(newValue);
}
layout();
}
});
final AtomicReference<MouseEvent> deltaEvent = new AtomicReference<MouseEvent>();
final AtomicReference<TranslateTransition> currentTransition = new AtomicReference<TranslateTransition>();
setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
tracker.addMovement(event);
deltaEvent.set(event);
if( currentTransition.get() != null ) {
currentTransition.get().stop();
}
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
tracker.addMovement(event);
if (flingDirection.get() == FlingDirection.HORIZONTAL || flingDirection.get() == FlingDirection.BOTH) {
double delta = event.getX() - deltaEvent.get().getX();
content.get().setTranslateX(content.get().getTranslateX() + delta);
}
if (flingDirection.get() == FlingDirection.VERTICAL || flingDirection.get() == FlingDirection.BOTH) {
double delta = event.getY() - deltaEvent.get().getY();
double targetY = content.get().getTranslateY() + delta;
content.get().setTranslateY(targetY);
}
deltaEvent.set(event);
}
});
setOnMouseReleased(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
tracker.addMovement(event);
tracker.computeCurrentVelocity(500);
float velocityX = tracker.getXVelocity();
float velocityY = tracker.getYVelocity();
if (content != null && content.get() != null) {
TranslateTransition translate = new TranslateTransition(new Duration(1000), content.get());
translate.setInterpolator(Interpolator.EASE_OUT);
final Bounds b = content.get().getLayoutBounds();
Double backBouncingX = null;
Double backBouncingY = null;
Double targetX = null;
Double targetY = null;
if( Math.abs(velocityX) < 10 ) {
velocityX = 0;
}
if( Math.abs(velocityY) < 10 ) {
velocityY = 0;
}
if (flingDirection.get() == FlingDirection.HORIZONTAL || flingDirection.get() == FlingDirection.BOTH) {
targetX = content.get().getTranslateX();
double controlWidth = b.getWidth();
double viewWith = getWidth();
if (controlWidth < viewWith && targetX < controlWidth) {
targetX = 0.0;
} else if (targetX > 0) {
targetX = 0.0;
} else if( (targetX < (controlWidth - viewWith) * -1) ) {
targetX = (controlWidth - viewWith) * -1;
} else {
targetX += velocityX;
if (controlWidth < viewWith && targetX < controlWidth) {
targetX = -100.0;
backBouncingX = 0.0;
} else if (targetX > 0) {
targetX = 100.0;
backBouncingX = 0.0;
} else if (targetX < (controlWidth - viewWith) * -1) {
targetX = (controlWidth - viewWith) * -1 - 100;
backBouncingX = (controlWidth - viewWith) * -1;
}
}
}
if (flingDirection.get() == FlingDirection.VERTICAL || flingDirection.get() == FlingDirection.BOTH) {
targetY = content.get().getTranslateY();
double controlHeight = b.getHeight();
double viewHeight = getHeight();
if (controlHeight < viewHeight && targetY < controlHeight) {
targetY = 0.0;
} else if (targetY > 0) {
targetY = 0.0;
} else if(targetY < (controlHeight - viewHeight) * -1) {
targetY = (controlHeight - viewHeight) * -1;
} else {
targetY += velocityY;
if (controlHeight < viewHeight && targetY < controlHeight) {
targetY = -100.0;
backBouncingY = 0.0;
} else if (targetY > 0) {
targetY = 100.0;
backBouncingY = 0.0;
} else if (targetY < (controlHeight - viewHeight) * -1) {
targetY = (controlHeight - viewHeight) * -1 - 100;
backBouncingY = (controlHeight - viewHeight) * -1;
}
}
}
if (targetX != null) {
translate.setFromX(content.get().getTranslateX());
translate.setToX(targetX);
}
if (targetY != null) {
translate.setFromY(content.get().getTranslateY());
translate.setToY(targetY);
}
if (backBouncingX != null || backBouncingY != null) {
final Double fbackFlingX = backBouncingX;
final Double fbackFlingY = backBouncingY;
translate.setOnFinished(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
currentTransition.set(null);
TranslateTransition translate = new TranslateTransition(new Duration(300), content.get());
if (fbackFlingX != null) {
translate.setFromX(content.get().getTranslateX());
translate.setToX(fbackFlingX);
}
if (fbackFlingY != null) {
translate.setFromY(content.get().getTranslateY());
translate.setToY(fbackFlingY);
}
translate.play();
currentTransition.set(translate);
}
});
} else {
translate.setOnFinished(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
currentTransition.set(null);
}
});
}
translate.play();
currentTransition.set(translate);
}
}
});
}
public final void setContent(Node value) {
contentProperty().set(value);
}
public final Node getContent() {
return content == null ? null : content.get();
}
public final ObjectProperty<Node> contentProperty() {
if (content == null) {
content = new SimpleObjectProperty<Node>(this, "content");
}
return content;
}
public final ObjectProperty<FlingDirection> flingDirection() {
return flingDirection;
}
public void setFlingDirection(FlingDirection value) {
flingDirection.set(value);
}
public FlingDirection getFlingDirection() {
return flingDirection.get();
}
@Override
protected void layoutChildren() {
super.layoutChildren();
clipRect.setWidth(getWidth());
clipRect.setHeight(getHeight());
}
// @Override
// protected double computeMaxHeight(double width) {
// return Double.MAX_VALUE;
// }
//
// @Override
// protected double computeMinWidth(double height) {
// return 0;
// }
//
// @Override
// protected double computeMaxWidth(double height) {
// return Double.MAX_VALUE;
// }
//
// @Override
// protected double computeMinHeight(double width) {
// return 0;
// }
//
// @Override
// protected double computePrefHeight(double width) {
// return Double.MAX_VALUE;
// }
//
// @Override
// protected double computePrefWidth(double height) {
// return Double.MAX_VALUE;
// }
}