jfxtras.labs.internal.scene.control.skin.MagnifierSkin Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jfxtras-labs Show documentation
Show all versions of jfxtras-labs Show documentation
Experimental components for JavaFX 2
/**
* MagnifierSkin.java
*
* Copyright (c) 2011-2015, JFXtras
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the organization nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jfxtras.labs.internal.scene.control.skin;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.SnapshotResult;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Scale;
import javafx.stage.Popup;
import javafx.util.Callback;
import jfxtras.labs.internal.scene.control.behavior.MagnifierBehavior;
import jfxtras.labs.scene.control.Magnifier;
import com.sun.javafx.scene.control.skin.BehaviorSkinBase;
/**
* Skin implementation for the {@link jfxtras.labs.scene.control.Magnifier} control.
*
* @author SaiPradeepDandem
*/
public class MagnifierSkin extends BehaviorSkinBase {
private Scene scene;
private WritableImage writeImg;
private final double shift = 1.0D;
private final double MIN_RADIUS = 50.0D;
private final double RADIUS_DELTA = 5.0D;
private final double ZOOM_DELTA = 0.3D;
private final double ZOOM_MAX = 10.0D;
private final double ZOOM_MIN = 1.0D;
private DoubleProperty localRadius = new SimpleDoubleProperty();
private DoubleProperty localScaleFactor = new SimpleDoubleProperty();
private DoubleProperty prevX = new SimpleDoubleProperty();
private DoubleProperty prevY = new SimpleDoubleProperty();
private Callback callBack;
private SnapshotParameters param;
private ImageView snapView;
private Viewer viewer;
/**
* Instantiates the skin implementation for {@link jfxtras.labs.scene.control.Magnifier}.
*
* @param magnifier
* Instance of {@link jfxtras.labs.scene.control.Magnifier} control.
*/
public MagnifierSkin(Magnifier magnifier) {
super(magnifier, new MagnifierBehavior(magnifier));
initialize();
}
private void initialize() {
final DoubleProperty frameWidthProperty = getSkinnable().frameWidthProperty();
final DoubleProperty scopeLineWidthProperty = getSkinnable().scopeLineWidthProperty();
final BooleanProperty scopeLinesVisibleProperty = getSkinnable().scopeLinesVisibleProperty();
// Adding listener to control "radiusProperty" to add the value to "localRadius".
getSkinnable().radiusProperty().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable arg0) {
localRadius.set(getSkinnable().getRadius());
}
});
localRadius.set(getSkinnable().getRadius());
// Adding listener to control "scaleFactorProperty" to add the value to "localScaleFactor".
getSkinnable().scaleFactorProperty().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable arg0) {
localScaleFactor.set(getSkinnable().getScaleFactor());
}
});
localScaleFactor.set(getSkinnable().getScaleFactor());
snapView = new ImageView();
callBack = new Callback() {
@Override
public Void call(SnapshotResult result) {
return null;
}
};
final Scale scale = new Scale();
scale.xProperty().bind(localScaleFactor);
scale.yProperty().bind(localScaleFactor);
param = new SnapshotParameters();
param.setDepthBuffer(true);
param.setTransform(scale);
final StackPane mainContent = new StackPane();
final Circle frame = new Circle();
frame.getStyleClass().add("magnifier-frame");
frame.radiusProperty().bind(localRadius.add(frameWidthProperty));
final Circle cClip = new Circle();
cClip.radiusProperty().bind(localRadius);
cClip.translateXProperty().bind(localRadius);
cClip.translateYProperty().bind(localRadius);
viewer = new Viewer(localRadius, localRadius);
viewer.setClip(cClip);
final Line vL = new Line();
vL.setStartX(0);
vL.setStartY(0);
vL.setEndX(0);
vL.getStyleClass().add("magnifier-vLine");
vL.strokeWidthProperty().bind(scopeLineWidthProperty);
vL.visibleProperty().bind(scopeLinesVisibleProperty);
vL.endYProperty().bind(localRadius.multiply(2));
final Line hL = new Line();
hL.setStartX(0);
hL.setStartY(0);
hL.setEndY(0);
hL.getStyleClass().add("magnifier-hLine");
hL.strokeWidthProperty().bind(scopeLineWidthProperty);
hL.visibleProperty().bind(scopeLinesVisibleProperty);
hL.endXProperty().bind(localRadius.multiply(2));
// Adding all parts in a container.
mainContent.getChildren().addAll(frame, viewer, vL, hL);
final Popup popUp = new Popup();
popUp.getContent().add(mainContent);
final EventHandler enteredEvent = new EventHandler() {
@Override
public void handle(MouseEvent e) {
popUp.show(getSkinnable(), e.getScreenX() - shift, e.getScreenY() - shift);
takeSnap(e.getX(), e.getY());
}
};
final EventHandler exitedEvent = new EventHandler() {
@Override
public void handle(MouseEvent e) {
popUp.hide();
}
};
final EventHandler movedEvent = new EventHandler() {
@Override
public void handle(MouseEvent e) {
final double r = localRadius.get();
final double s = localScaleFactor.get();
if (e.getSceneX() > (scene.getWidth() - (2 * r))) {
popUp.setX(e.getScreenX() - (2 * r) - shift);
} else {
popUp.setX(e.getScreenX() - shift);
}
if (e.getSceneY() > (scene.getHeight() - (2 * r))) {
popUp.setY(e.getScreenY() - (2 * r) - shift);
} else {
popUp.setY(e.getScreenY() - shift);
}
prevX.set(e.getX());
prevY.set(e.getY());
shiftViewerContent(prevX.get(), prevY.get(), r, s);
}
};
// Adding mask implementation. The below mask is responsible to not access the contents when magnifier is shown.
final StackPane mask = new StackPane();
// Adding listener to activateProperty.
getSkinnable().activeProperty().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable arg0) {
final Magnifier skinnable = getSkinnable();
if (skinnable.isActive()) {
skinnable.addEventFilter(MouseEvent.MOUSE_ENTERED, enteredEvent);
skinnable.addEventFilter(MouseEvent.MOUSE_EXITED, exitedEvent);
skinnable.addEventFilter(MouseEvent.MOUSE_MOVED, movedEvent);
if (!getChildren().contains(mask)) {
getChildren().add(mask);
}
} else {
skinnable.removeEventFilter(MouseEvent.MOUSE_ENTERED, enteredEvent);
skinnable.removeEventFilter(MouseEvent.MOUSE_EXITED, exitedEvent);
skinnable.removeEventFilter(MouseEvent.MOUSE_MOVED, movedEvent);
if (getChildren().contains(mask)) {
getChildren().remove(mask);
}
}
}
});
if (getSkinnable().isActive()) {
final Magnifier skinnable = getSkinnable();
skinnable.addEventFilter(MouseEvent.MOUSE_ENTERED, enteredEvent);
skinnable.addEventFilter(MouseEvent.MOUSE_EXITED, exitedEvent);
skinnable.addEventFilter(MouseEvent.MOUSE_MOVED, movedEvent);
}
// Adding listener to contentProperty.
getSkinnable().contentProperty().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable arg0) {
getChildren().clear();
getChildren().addAll(getSkinnable().getContent(),mask);
}
});
if (getSkinnable().getContent() != null) {
getChildren().addAll(getSkinnable().getContent(),mask);
}
// Handling scroll behavior.
mainContent.setOnScroll(new EventHandler() {
@Override
public void handle(ScrollEvent e) {
// If scrolled with CTRL key press
if (e.isControlDown() && getSkinnable().isScalableOnScroll()) {
double delta = e.getDeltaY();
double newValue = localScaleFactor.get();
if (delta > 0) { // Increasing the zoom.
newValue = newValue + ZOOM_DELTA;
if (newValue > ZOOM_MAX) {
newValue = ZOOM_MAX;
}
} else if (delta < 0) { // Decreasing the zoom.
newValue = newValue - ZOOM_DELTA;
if (newValue < ZOOM_MIN) {
newValue = ZOOM_MIN;
}
}
localScaleFactor.set(newValue);
takeSnap(prevX.get(), prevY.get());
}
// If scrolled with ALT key press
else if (e.isAltDown() && getSkinnable().isResizableOnScroll()) {
final double delta = e.getDeltaY();
if (delta > 0) { // Increasing the size.
localRadius.set(localRadius.get() + RADIUS_DELTA);
shiftViewerContent(prevX.get(), prevY.get(), localRadius.get(), localScaleFactor.get());
} else if (delta < 0) { // Decreasing the size.
if (localRadius.get() > MIN_RADIUS) {
localRadius.set(localRadius.get() - RADIUS_DELTA);
shiftViewerContent(prevX.get(), prevY.get(), localRadius.get(), localScaleFactor.get());
}
}
}
}
});
}
private void takeSnap(double x, double y) {
int w = (int) (getSkinnable().getWidth() * localScaleFactor.get());
int h = (int) (getSkinnable().getHeight() * localScaleFactor.get());
writeImg = new WritableImage(w, h);
// Get snapshot image
getSkinnable().snapshot(callBack, param, writeImg);
snapView.setImage(writeImg);
viewer.setContent(snapView);
shiftViewerContent(x, y, localRadius.get(), localScaleFactor.get());
}
private void shiftViewerContent(double x, double y, double r, double s) {
viewer.transXProperty().set((x * s) - r);
viewer.transYProperty().set((y * s) - r);
}
/**
* {@inheritDoc}
*/
@Override
protected void layoutChildren(double arg0, double arg1, double arg2, double arg3) {
super.layoutChildren(arg0, arg1, arg2, arg3);
if (this.scene == null) {
this.scene = getNode().getScene();
}
}
/**
* {@inheritDoc}
*/
@Override
protected double computeMinWidth(double paramDouble1, double paramDouble2, double paramDouble3, double paramDouble4, double paramDouble5) {
return getSkinnable().getMinWidth();
}
/**
* {@inheritDoc}
*/
@Override
protected double computeMinHeight(double paramDouble1, double paramDouble2, double paramDouble3, double paramDouble4,
double paramDouble5) {
return getSkinnable().getMinHeight();
}
/**
* Region(Viewer) that holds the clipped area of the scaled image.
*/
private class Viewer extends Region {
private Node content;
private final DoubleProperty width = new SimpleDoubleProperty();
private final DoubleProperty height = new SimpleDoubleProperty();
private final Rectangle clip;
private final SimpleDoubleProperty transX = new SimpleDoubleProperty();
private final SimpleDoubleProperty transY = new SimpleDoubleProperty();
public Viewer(DoubleProperty w, DoubleProperty h) {
this.width.bind(w.multiply(2));
this.height.bind(h.multiply(2));
this.clip = new Rectangle();
this.clip.widthProperty().bind(this.width);
this.clip.heightProperty().bind(this.height);
this.clip.translateXProperty().bind(transX);
this.clip.translateYProperty().bind(transY);
}
public void setContent(Node content) {
if (this.content != null) {
this.content.setClip(null);
this.content.translateXProperty().unbind();
this.content.translateYProperty().unbind();
getChildren().clear();
}
this.content = content;
this.content.setClip(this.clip);
this.content.translateXProperty().bind(transX.multiply(-1));
this.content.translateYProperty().bind(transY.multiply(-1));
getChildren().setAll(content);
}
@Override
protected double computeMinWidth(double d) {
return width.get();
}
@Override
protected double computeMinHeight(double d) {
return height.get();
}
@Override
protected double computePrefWidth(double d) {
return width.get();
}
@Override
protected double computePrefHeight(double d) {
return height.get();
}
@Override
protected double computeMaxWidth(double d) {
return width.get();
}
@Override
protected double computeMaxHeight(double d) {
return height.get();
}
public SimpleDoubleProperty transXProperty() {
return transX;
}
public SimpleDoubleProperty transYProperty() {
return transY;
}
}
}