mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 15:10:44 +01:00
Update PopOver component
This commit is contained in:
parent
5458dbe90e
commit
31b08618fd
3 changed files with 502 additions and 225 deletions
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright (c) 2013, ControlsFX
|
||||
/*
|
||||
* Copyright (c) 2013, 2016 ControlsFX
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -24,14 +24,26 @@
|
|||
* (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 bisq.desktop.components.controlsfx.control;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static javafx.scene.input.MouseEvent.MOUSE_CLICKED;
|
||||
import bisq.desktop.components.controlsfx.skin.PopOverSkin;
|
||||
|
||||
import javafx.animation.FadeTransition;
|
||||
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.PopupControl;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
|
@ -42,37 +54,47 @@ import javafx.beans.property.SimpleObjectProperty;
|
|||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.PopupControl;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import javafx.event.WeakEventHandler;
|
||||
|
||||
import javafx.util.Duration;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static javafx.scene.input.MouseEvent.MOUSE_CLICKED;
|
||||
|
||||
/**
|
||||
* The PopOver control provides detailed information about an owning node in a
|
||||
* popup window. The popup window has a very lightweight appearance (no default
|
||||
* window decorations) and an arrow pointing at the owner. Due to the nature of
|
||||
* popup windows the PopOver will move around with the parent window when the
|
||||
* user drags it. <br>
|
||||
* <center> <img src="popover.png"/> </center> <br>
|
||||
* <center> <img src="popover.png" alt="Screenshot of PopOver"> </center> <br>
|
||||
* The PopOver can be detached from the owning node by dragging it away from the
|
||||
* owner. It stops displaying an arrow and starts displaying a title and a close
|
||||
* icon. <br>
|
||||
* <br>
|
||||
* <center> <img src="popover-detached.png"/> </center> <br>
|
||||
* <center> <img src="popover-detached.png"
|
||||
* alt="Screenshot of a detached PopOver"> </center> <br>
|
||||
* The following image shows a popover with an accordion content node. PopOver
|
||||
* controls are automatically resizing themselves when the content node changes
|
||||
* its size.<br>
|
||||
* <br>
|
||||
* <center> <img src="popover-accordion.png"/> </center> <br>
|
||||
* <center> <img src="popover-accordion.png"
|
||||
* alt="Screenshot of PopOver containing an Accordion"> </center> <br>
|
||||
* For styling apply stylesheets to the root pane of the PopOver.
|
||||
*
|
||||
* <h3>Example:</h3>
|
||||
*
|
||||
* <pre>
|
||||
* PopOver popOver = new PopOver();
|
||||
* popOver.getRoot().getStylesheets().add(...);
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class PopOver extends PopupControl {
|
||||
|
||||
|
@ -84,6 +106,12 @@ public class PopOver extends PopupControl {
|
|||
|
||||
private double targetY;
|
||||
|
||||
private final SimpleBooleanProperty animated = new SimpleBooleanProperty(true);
|
||||
private final ObjectProperty<Duration> fadeInDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION);
|
||||
private final ObjectProperty<Duration> fadeOutDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION);
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
/**
|
||||
* Creates a pop over with a label as the content node.
|
||||
*/
|
||||
|
@ -92,43 +120,46 @@ public class PopOver extends PopupControl {
|
|||
|
||||
getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
|
||||
getRoot().getStylesheets().add(
|
||||
requireNonNull(PopOver.class.getResource("popover.css")).toExternalForm()); //$NON-NLS-1$
|
||||
|
||||
setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT);
|
||||
setOnHiding(new EventHandler<WindowEvent>() {
|
||||
@Override
|
||||
public void handle(WindowEvent evt) {
|
||||
setDetached(false);
|
||||
}
|
||||
});
|
||||
setOnHiding(evt -> setDetached(false));
|
||||
|
||||
/*
|
||||
* Create some initial content.
|
||||
*/
|
||||
Label label = new Label("<No Content>"); //$NON-NLS-1$
|
||||
Label label = new Label("No content set"); //$NON-NLS-1$
|
||||
label.setPrefSize(200, 200);
|
||||
label.setPadding(new Insets(4));
|
||||
setContentNode(label);
|
||||
|
||||
ChangeListener<Object> repositionListener = new ChangeListener<Object>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Object> value,
|
||||
Object oldObject, Object newObject) {
|
||||
InvalidationListener repositionListener = observable -> {
|
||||
if (isShowing() && !isDetached()) {
|
||||
show(getOwnerNode(), targetX, targetY);
|
||||
adjustWindowLocation();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
arrowSize.addListener(repositionListener);
|
||||
cornerRadius.addListener(repositionListener);
|
||||
arrowLocation.addListener(repositionListener);
|
||||
arrowIndent.addListener(repositionListener);
|
||||
headerAlwaysVisible.addListener(repositionListener);
|
||||
|
||||
/*
|
||||
* A detached popover should of course not automatically hide itself.
|
||||
*/
|
||||
detached.addListener(it -> setAutoHide(!isDetached()));
|
||||
|
||||
setAutoHide(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pop over with the given node as the content node.
|
||||
*
|
||||
* @param content The content shown by the pop over
|
||||
* @param content
|
||||
* The content shown by the pop over
|
||||
*/
|
||||
public PopOver(Node content) {
|
||||
this();
|
||||
|
@ -141,9 +172,28 @@ public class PopOver extends PopupControl {
|
|||
return new PopOverSkin(this);
|
||||
}
|
||||
|
||||
private final StackPane root = new StackPane();
|
||||
|
||||
/**
|
||||
* The root pane stores the content node of the popover. It is accessible
|
||||
* via this method in order to support proper styling.
|
||||
*
|
||||
* <h3>Example:</h3>
|
||||
*
|
||||
* <pre>
|
||||
* PopOver popOver = new PopOver();
|
||||
* popOver.getRoot().getStylesheets().add(...);
|
||||
* </pre>
|
||||
*
|
||||
* @return the root pane
|
||||
*/
|
||||
public final StackPane getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
// Content support.
|
||||
|
||||
private final ObjectProperty<Node> contentNode = new SimpleObjectProperty<Node>(
|
||||
private final ObjectProperty<Node> contentNode = new SimpleObjectProperty<>(
|
||||
this, "contentNode") { //$NON-NLS-1$
|
||||
@Override
|
||||
public void setValue(Node node) {
|
||||
|
@ -151,7 +201,8 @@ public class PopOver extends PopupControl {
|
|||
throw new IllegalArgumentException(
|
||||
"content node can not be null"); //$NON-NLS-1$
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -186,41 +237,36 @@ public class PopOver extends PopupControl {
|
|||
contentNodeProperty().set(content);
|
||||
}
|
||||
|
||||
private InvalidationListener hideListener = new InvalidationListener() {
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
private final InvalidationListener hideListener = observable -> {
|
||||
if (!isDetached()) {
|
||||
hide(Duration.ZERO);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private WeakInvalidationListener weakHideListener = new WeakInvalidationListener(
|
||||
private final WeakInvalidationListener weakHideListener = new WeakInvalidationListener(
|
||||
hideListener);
|
||||
|
||||
private ChangeListener<Number> xListener = new ChangeListener<Number>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Number> value,
|
||||
Number oldX, Number newX) {
|
||||
setX(getX() + (newX.doubleValue() - oldX.doubleValue()));
|
||||
private final ChangeListener<Number> xListener = (value, oldX, newX) -> {
|
||||
if (!isDetached()) {
|
||||
setAnchorX(getAnchorX() + (newX.doubleValue() - oldX.doubleValue()));
|
||||
}
|
||||
};
|
||||
|
||||
private WeakChangeListener<Number> weakXListener = new WeakChangeListener<>(
|
||||
private final WeakChangeListener<Number> weakXListener = new WeakChangeListener<>(
|
||||
xListener);
|
||||
|
||||
private ChangeListener<Number> yListener = new ChangeListener<Number>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Number> value,
|
||||
Number oldY, Number newY) {
|
||||
setY(getY() + (newY.doubleValue() - oldY.doubleValue()));
|
||||
private final ChangeListener<Number> yListener = (value, oldY, newY) -> {
|
||||
if (!isDetached()) {
|
||||
setAnchorY(getAnchorY() + (newY.doubleValue() - oldY.doubleValue()));
|
||||
}
|
||||
};
|
||||
|
||||
private WeakChangeListener<Number> weakYListener = new WeakChangeListener<>(
|
||||
private final WeakChangeListener<Number> weakYListener = new WeakChangeListener<>(
|
||||
yListener);
|
||||
|
||||
private Window ownerWindow;
|
||||
private final EventHandler<WindowEvent> closePopOverOnOwnerWindowCloseLambda = event -> ownerWindowClosing();
|
||||
private final WeakEventHandler<WindowEvent> closePopOverOnOwnerWindowClose = new WeakEventHandler<>(closePopOverOnOwnerWindowCloseLambda);
|
||||
|
||||
/**
|
||||
* Shows the pop over in a position relative to the edges of the given owner
|
||||
|
@ -286,6 +332,38 @@ public class PopOver extends PopupControl {
|
|||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public final void show(Window owner) {
|
||||
super.show(owner);
|
||||
ownerWindow = owner;
|
||||
|
||||
if (isAnimated()) {
|
||||
showFadeInAnimation(getFadeInDuration());
|
||||
}
|
||||
|
||||
ownerWindow.addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST,
|
||||
closePopOverOnOwnerWindowClose);
|
||||
ownerWindow.addEventFilter(WindowEvent.WINDOW_HIDING,
|
||||
closePopOverOnOwnerWindowClose);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public final void show(Window ownerWindow, double anchorX, double anchorY) {
|
||||
super.show(ownerWindow, anchorX, anchorY);
|
||||
this.ownerWindow = ownerWindow;
|
||||
|
||||
if (isAnimated()) {
|
||||
showFadeInAnimation(getFadeInDuration());
|
||||
}
|
||||
|
||||
ownerWindow.addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST,
|
||||
closePopOverOnOwnerWindowClose);
|
||||
ownerWindow.addEventFilter(WindowEvent.WINDOW_HIDING,
|
||||
closePopOverOnOwnerWindowClose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the pop over visible at the give location and associates it with
|
||||
* the given owner node. The x and y coordinate will be the target location
|
||||
|
@ -300,7 +378,7 @@ public class PopOver extends PopupControl {
|
|||
*/
|
||||
@Override
|
||||
public final void show(Node owner, double x, double y) {
|
||||
show(owner, x, y, DEFAULT_FADE_DURATION);
|
||||
show(owner, x, y, getFadeInDuration());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -315,14 +393,14 @@ public class PopOver extends PopupControl {
|
|||
* @param y
|
||||
* the y coordinate for the pop over arrow tip
|
||||
* @param fadeInDuration
|
||||
* the time it takes for the pop over to be fully visible
|
||||
* the time it takes for the pop over to be fully visible. This duration takes precedence over the fade-in property without setting.
|
||||
*/
|
||||
public final void show(Node owner, double x, double y,
|
||||
Duration fadeInDuration) {
|
||||
|
||||
/*
|
||||
* Calling show() a second time without first closing the
|
||||
* pop over causes it to be placed at the wrong location.
|
||||
* Calling show() a second time without first closing the pop over
|
||||
* causes it to be placed at the wrong location.
|
||||
*/
|
||||
if (ownerWindow != null && isShowing()) {
|
||||
super.hide();
|
||||
|
@ -360,17 +438,14 @@ public class PopOver extends PopupControl {
|
|||
|
||||
/*
|
||||
* The user clicked somewhere into the transparent background. If
|
||||
* this is the case the hide the window (when attached).
|
||||
* this is the case then hide the window (when attached).
|
||||
*/
|
||||
getScene().addEventHandler(MOUSE_CLICKED,
|
||||
new EventHandler<MouseEvent>() {
|
||||
public void handle(MouseEvent evt) {
|
||||
if (evt.getTarget().equals(getScene().getRoot())) {
|
||||
getScene().addEventHandler(MOUSE_CLICKED, mouseEvent -> {
|
||||
if (mouseEvent.getTarget().equals(getScene().getRoot())) {
|
||||
if (!isDetached()) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/*
|
||||
|
@ -382,6 +457,18 @@ public class PopOver extends PopupControl {
|
|||
|
||||
super.show(owner, x, y);
|
||||
|
||||
if (isAnimated()) {
|
||||
showFadeInAnimation(fadeInDuration);
|
||||
}
|
||||
|
||||
// Bug fix - close popup when owner window is closing
|
||||
ownerWindow.addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST,
|
||||
closePopOverOnOwnerWindowClose);
|
||||
ownerWindow.addEventFilter(WindowEvent.WINDOW_HIDING,
|
||||
closePopOverOnOwnerWindowClose);
|
||||
}
|
||||
|
||||
private void showFadeInAnimation(Duration fadeInDuration) {
|
||||
// Fade In
|
||||
Node skinNode = getSkin().getNode();
|
||||
skinNode.setOpacity(0);
|
||||
|
@ -392,6 +479,10 @@ public class PopOver extends PopupControl {
|
|||
fadeIn.play();
|
||||
}
|
||||
|
||||
private void ownerWindowClosing() {
|
||||
hide(Duration.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the pop over by quickly changing its opacity to 0.
|
||||
*
|
||||
|
@ -399,7 +490,7 @@ public class PopOver extends PopupControl {
|
|||
*/
|
||||
@Override
|
||||
public final void hide() {
|
||||
hide(DEFAULT_FADE_DURATION);
|
||||
hide(getFadeOutDuration());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -411,21 +502,32 @@ public class PopOver extends PopupControl {
|
|||
* @since 1.0
|
||||
*/
|
||||
public final void hide(Duration fadeOutDuration) {
|
||||
log.info("hide:" + fadeOutDuration.toString());
|
||||
//We must remove EventFilter in order to prevent memory leak.
|
||||
if (ownerWindow != null) {
|
||||
ownerWindow.removeEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST,
|
||||
closePopOverOnOwnerWindowClose);
|
||||
ownerWindow.removeEventFilter(WindowEvent.WINDOW_HIDING,
|
||||
closePopOverOnOwnerWindowClose);
|
||||
}
|
||||
if (fadeOutDuration == null) {
|
||||
fadeOutDuration = DEFAULT_FADE_DURATION;
|
||||
}
|
||||
|
||||
if (isShowing()) {
|
||||
if (isAnimated()) {
|
||||
// Fade Out
|
||||
Node skinNode = getSkin().getNode();
|
||||
skinNode.setOpacity(0);
|
||||
|
||||
FadeTransition fadeOut = new FadeTransition(fadeOutDuration,
|
||||
skinNode);
|
||||
fadeOut.setFromValue(1);
|
||||
fadeOut.setFromValue(skinNode.getOpacity());
|
||||
fadeOut.setToValue(0);
|
||||
fadeOut.setOnFinished(evt -> super.hide());
|
||||
fadeOut.play();
|
||||
} else {
|
||||
super.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,26 +538,26 @@ public class PopOver extends PopupControl {
|
|||
case TOP_CENTER:
|
||||
case TOP_LEFT:
|
||||
case TOP_RIGHT:
|
||||
setX(getX() + bounds.getMinX() - computeXOffset());
|
||||
setY(getY() + bounds.getMinY() + getArrowSize());
|
||||
setAnchorX(getAnchorX() + bounds.getMinX() - computeXOffset());
|
||||
setAnchorY(getAnchorY() + bounds.getMinY() + getArrowSize());
|
||||
break;
|
||||
case LEFT_TOP:
|
||||
case LEFT_CENTER:
|
||||
case LEFT_BOTTOM:
|
||||
setX(getX() + bounds.getMinX() + getArrowSize());
|
||||
setY(getY() + bounds.getMinY() - computeYOffset());
|
||||
setAnchorX(getAnchorX() + bounds.getMinX() + getArrowSize());
|
||||
setAnchorY(getAnchorY() + bounds.getMinY() - computeYOffset());
|
||||
break;
|
||||
case BOTTOM_CENTER:
|
||||
case BOTTOM_LEFT:
|
||||
case BOTTOM_RIGHT:
|
||||
setX(getX() + bounds.getMinX() - computeXOffset());
|
||||
setY(getY() - bounds.getMinY() - bounds.getMaxY() - 1);
|
||||
setAnchorX(getAnchorX() + bounds.getMinX() - computeXOffset());
|
||||
setAnchorY(getAnchorY() - bounds.getMinY() - bounds.getMaxY() - 1);
|
||||
break;
|
||||
case RIGHT_TOP:
|
||||
case RIGHT_BOTTOM:
|
||||
case RIGHT_CENTER:
|
||||
setX(getX() - bounds.getMinX() - bounds.getMaxX() - 1);
|
||||
setY(getY() + bounds.getMinY() - computeYOffset());
|
||||
setAnchorX(getAnchorX() - bounds.getMinX() - bounds.getMaxX() - 1);
|
||||
setAnchorY(getAnchorY() + bounds.getMinY() - computeYOffset());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -508,6 +610,74 @@ public class PopOver extends PopupControl {
|
|||
}
|
||||
}
|
||||
|
||||
// always show header
|
||||
|
||||
private final BooleanProperty headerAlwaysVisible = new SimpleBooleanProperty(this, "headerAlwaysVisible"); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Determines whether or not the {@link PopOver} header should remain visible, even while attached.
|
||||
*/
|
||||
public final BooleanProperty headerAlwaysVisibleProperty() {
|
||||
return headerAlwaysVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the headerAlwaysVisible property.
|
||||
*
|
||||
* @param visible
|
||||
* if true, then the header is visible even while attached
|
||||
*
|
||||
* @see #headerAlwaysVisibleProperty()
|
||||
*/
|
||||
public final void setHeaderAlwaysVisible(boolean visible) {
|
||||
headerAlwaysVisible.setValue(visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the detachable property.
|
||||
*
|
||||
* @return true if the header is visible even while attached
|
||||
*
|
||||
* @see #headerAlwaysVisibleProperty()
|
||||
*/
|
||||
public final boolean isHeaderAlwaysVisible() {
|
||||
return headerAlwaysVisible.getValue();
|
||||
}
|
||||
|
||||
// enable close button
|
||||
|
||||
private final BooleanProperty closeButtonEnabled = new SimpleBooleanProperty(this, "closeButtonEnabled", true); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Determines whether or not the header's close button should be available.
|
||||
*/
|
||||
public final BooleanProperty closeButtonEnabledProperty() {
|
||||
return closeButtonEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the closeButtonEnabled property.
|
||||
*
|
||||
* @param enabled
|
||||
* if false, the pop over will not be closeable by the header's close button
|
||||
*
|
||||
* @see #closeButtonEnabledProperty()
|
||||
*/
|
||||
public final void setCloseButtonEnabled(boolean enabled) {
|
||||
closeButtonEnabled.setValue(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the closeButtonEnabled property.
|
||||
*
|
||||
* @return true if the header's close button is enabled
|
||||
*
|
||||
* @see #closeButtonEnabledProperty()
|
||||
*/
|
||||
public final boolean isCloseButtonEnabled() {
|
||||
return closeButtonEnabled.getValue();
|
||||
}
|
||||
|
||||
// detach support
|
||||
|
||||
private final BooleanProperty detachable = new SimpleBooleanProperty(this,
|
||||
|
@ -561,7 +731,7 @@ public class PopOver extends PopupControl {
|
|||
* Sets the value of the detached property.
|
||||
*
|
||||
* @param detached
|
||||
* if true the pop over will change its appearance to "detached"
|
||||
* if true the pop over will change its apperance to "detached"
|
||||
* mode
|
||||
*
|
||||
* @see #detachedProperty()
|
||||
|
@ -701,46 +871,42 @@ public class PopOver extends PopupControl {
|
|||
|
||||
// Detached stage title
|
||||
|
||||
private final StringProperty detachedTitle = new SimpleStringProperty(this,
|
||||
"detachedTitle", "Info"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", "No title set"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
/**
|
||||
* Stores the title to display when the pop over becomes detached.
|
||||
* Stores the title to display in the PopOver's header.
|
||||
*
|
||||
* @return the detached title property
|
||||
* @return the title property
|
||||
*/
|
||||
public final StringProperty detachedTitleProperty() {
|
||||
return detachedTitle;
|
||||
public final StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the detached title property.
|
||||
* Returns the value of the title property.
|
||||
*
|
||||
* @return the detached title
|
||||
*
|
||||
* @see #detachedTitleProperty()
|
||||
* @see #titleProperty()
|
||||
*/
|
||||
public final String getDetachedTitle() {
|
||||
return detachedTitleProperty().get();
|
||||
public final String getTitle() {
|
||||
return titleProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the detached title property.
|
||||
* Sets the value of the title property.
|
||||
*
|
||||
* @param title
|
||||
* the title to use when detached
|
||||
*
|
||||
* @see #detachedTitleProperty()
|
||||
* @param title the title to use when detached
|
||||
* @see #titleProperty()
|
||||
*/
|
||||
public final void setDetachedTitle(String title) {
|
||||
public final void setTitle(String title) {
|
||||
if (title == null) {
|
||||
throw new IllegalArgumentException("title can not be null"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
detachedTitleProperty().set(title);
|
||||
titleProperty().set(title);
|
||||
}
|
||||
|
||||
private final ObjectProperty<ArrowLocation> arrowLocation = new SimpleObjectProperty<PopOver.ArrowLocation>(
|
||||
private final ObjectProperty<ArrowLocation> arrowLocation = new SimpleObjectProperty<>(
|
||||
this, "arrowLocation", ArrowLocation.LEFT_TOP); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
|
@ -782,6 +948,93 @@ public class PopOver extends PopupControl {
|
|||
* All possible arrow locations.
|
||||
*/
|
||||
public enum ArrowLocation {
|
||||
LEFT_TOP, LEFT_CENTER, LEFT_BOTTOM, RIGHT_TOP, RIGHT_CENTER, RIGHT_BOTTOM, TOP_LEFT, TOP_CENTER, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT;
|
||||
LEFT_TOP, LEFT_CENTER, LEFT_BOTTOM, RIGHT_TOP, RIGHT_CENTER, RIGHT_BOTTOM, TOP_LEFT, TOP_CENTER, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the fade-in duration. This should be set before calling PopOver.show(..).
|
||||
*
|
||||
* @return the fade-in duration property
|
||||
*/
|
||||
public final ObjectProperty<Duration> fadeInDurationProperty() {
|
||||
return fadeInDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the fade-out duration.
|
||||
*
|
||||
* @return the fade-out duration property
|
||||
*/
|
||||
public final ObjectProperty<Duration> fadeOutDurationProperty() {
|
||||
return fadeOutDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the fade-in duration property.
|
||||
*
|
||||
* @return the fade-in duration
|
||||
* @see #fadeInDurationProperty()
|
||||
*/
|
||||
public final Duration getFadeInDuration() {
|
||||
return fadeInDurationProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the fade-in duration property. This should be set before calling PopOver.show(..).
|
||||
*
|
||||
* @param duration the requested fade-in duration
|
||||
* @see #fadeInDurationProperty()
|
||||
*/
|
||||
public final void setFadeInDuration(Duration duration) {
|
||||
fadeInDurationProperty().setValue(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the fade-out duration property.
|
||||
*
|
||||
* @return the fade-out duration
|
||||
* @see #fadeOutDurationProperty()
|
||||
*/
|
||||
public final Duration getFadeOutDuration() {
|
||||
return fadeOutDurationProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the fade-out duration property.
|
||||
*
|
||||
* @param duration the requested fade-out duration
|
||||
* @see #fadeOutDurationProperty()
|
||||
*/
|
||||
public final void setFadeOutDuration(Duration duration) {
|
||||
fadeOutDurationProperty().setValue(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the "animated" flag. If true then the PopOver will be shown / hidden with a short fade in / out animation.
|
||||
*
|
||||
* @return the "animated" property
|
||||
*/
|
||||
public final BooleanProperty animatedProperty() {
|
||||
return animated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the "animated" property.
|
||||
*
|
||||
* @return true if the PopOver will be shown and hidden with a short fade animation
|
||||
* @see #animatedProperty()
|
||||
*/
|
||||
public final boolean isAnimated() {
|
||||
return animatedProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the "animated" property.
|
||||
*
|
||||
* @param animated if true the PopOver will be shown and hidden with a short fade animation
|
||||
* @see #animatedProperty()
|
||||
*/
|
||||
public final void setAnimated(boolean animated) {
|
||||
animatedProperty().set(animated);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
}
|
||||
|
||||
.popover > .border {
|
||||
-fx-stroke: linear-gradient(to bottom, rgba(0,0,0, .3), rgba(0, 0, 0, .7)) ;
|
||||
-fx-stroke-width: 0.5;
|
||||
-fx-fill: rgba(255.0,255.0,255.0, .95);
|
||||
-fx-effect: dropshadow(gaussian, rgba(0,0,0,.2), 10.0, 0.5, 2.0, 2.0);
|
||||
-fx-stroke: linear-gradient(to bottom, rgba(0, 0, 0, .3), rgba(0, 0, 0, .7));
|
||||
-fx-stroke-width: 1;
|
||||
-fx-fill: rgba(255.0, 255.0, 255.0, .95);
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, .2), 10.0, 0.5, 2.0, 2.0);
|
||||
}
|
||||
|
||||
.popover > .content {
|
||||
|
@ -26,11 +26,11 @@
|
|||
}
|
||||
|
||||
.popover > .content > .title > .icon > .graphics > .circle {
|
||||
-fx-fill: gray ;
|
||||
-fx-effect: innershadow(gaussian, rgba(0,0,0,.2), 3, 0.5, 1.0, 1.0);
|
||||
-fx-fill: gray;
|
||||
-fx-effect: innershadow(gaussian, rgba(0, 0, 0, .2), 3, 0.5, 1.0, 1.0);
|
||||
}
|
||||
|
||||
.popover > .content > .title > .icon > .graphics > .line {
|
||||
-fx-stroke: white ;
|
||||
-fx-stroke: white;
|
||||
-fx-stroke-width: 2;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/*
|
||||
* Copyright (c) 2013 - 2015, ControlsFX
|
||||
* All rights reserved.
|
||||
*
|
||||
|
@ -26,24 +26,11 @@
|
|||
*/
|
||||
package bisq.desktop.components.controlsfx.skin;
|
||||
|
||||
import static java.lang.Double.MAX_VALUE;
|
||||
import static javafx.geometry.Pos.CENTER_LEFT;
|
||||
import static javafx.scene.control.ContentDisplay.GRAPHIC_ONLY;
|
||||
import static bisq.desktop.components.controlsfx.control.PopOver.ArrowLocation.*;
|
||||
import bisq.desktop.components.controlsfx.control.PopOver;
|
||||
import bisq.desktop.components.controlsfx.control.PopOver.ArrowLocation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
|
@ -60,10 +47,28 @@ import javafx.scene.shape.Path;
|
|||
import javafx.scene.shape.PathElement;
|
||||
import javafx.scene.shape.QuadCurveTo;
|
||||
import javafx.scene.shape.VLineTo;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import bisq.desktop.components.controlsfx.control.PopOver;
|
||||
import bisq.desktop.components.controlsfx.control.PopOver.ArrowLocation;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Pos;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
|
||||
import javafx.event.EventHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static bisq.desktop.components.controlsfx.control.PopOver.ArrowLocation.*;
|
||||
import static java.lang.Double.MAX_VALUE;
|
||||
import static javafx.geometry.Pos.CENTER_LEFT;
|
||||
import static javafx.scene.control.ContentDisplay.GRAPHIC_ONLY;
|
||||
import static javafx.scene.paint.Color.YELLOW;
|
||||
|
||||
public class PopOverSkin implements Skin<PopOver> {
|
||||
|
||||
|
@ -74,27 +79,27 @@ public class PopOverSkin implements Skin<PopOver> {
|
|||
|
||||
private boolean tornOff;
|
||||
|
||||
private Label title;
|
||||
private Label closeIcon;
|
||||
private final Path path;
|
||||
private final Path clip;
|
||||
|
||||
private Path path;
|
||||
private BorderPane content;
|
||||
private StackPane titlePane;
|
||||
private StackPane stackPane;
|
||||
private final BorderPane content;
|
||||
private final StackPane titlePane;
|
||||
private final StackPane stackPane;
|
||||
|
||||
private Point2D dragStartLocation;
|
||||
|
||||
private PopOver popOver;
|
||||
private final PopOver popOver;
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
public PopOverSkin(final PopOver popOver) {
|
||||
|
||||
this.popOver = popOver;
|
||||
|
||||
stackPane = new StackPane();
|
||||
stackPane.getStylesheets().add(
|
||||
PopOver.class.getResource("popover.css").toExternalForm()); //$NON-NLS-1$
|
||||
stackPane = popOver.getRoot();
|
||||
stackPane.setPickOnBounds(false);
|
||||
stackPane.getStyleClass().add("popover"); //$NON-NLS-1$
|
||||
|
||||
Bindings.bindContent(stackPane.getStyleClass(), popOver.getStyleClass());
|
||||
|
||||
/*
|
||||
* The min width and height equal 2 * corner radius + 2 * arrow indent +
|
||||
|
@ -110,26 +115,22 @@ public class PopOverSkin implements Skin<PopOver> {
|
|||
|
||||
stackPane.minHeightProperty().bind(stackPane.minWidthProperty());
|
||||
|
||||
title = new Label();
|
||||
title.textProperty().bind(popOver.detachedTitleProperty());
|
||||
Label title = new Label();
|
||||
title.textProperty().bind(popOver.titleProperty());
|
||||
title.setMaxSize(MAX_VALUE, MAX_VALUE);
|
||||
title.setAlignment(Pos.CENTER);
|
||||
title.getStyleClass().add("text"); //$NON-NLS-1$
|
||||
|
||||
closeIcon = new Label();
|
||||
Label closeIcon = new Label();
|
||||
closeIcon.setGraphic(createCloseIcon());
|
||||
closeIcon.setMaxSize(MAX_VALUE, MAX_VALUE);
|
||||
closeIcon.setContentDisplay(GRAPHIC_ONLY);
|
||||
closeIcon.visibleProperty().bind(popOver.detachedProperty());
|
||||
closeIcon.visibleProperty().bind(
|
||||
popOver.closeButtonEnabledProperty().and(
|
||||
popOver.detachedProperty().or(popOver.headerAlwaysVisibleProperty())));
|
||||
closeIcon.getStyleClass().add("icon"); //$NON-NLS-1$
|
||||
closeIcon.setAlignment(CENTER_LEFT);
|
||||
closeIcon.getGraphic().setOnMouseClicked(
|
||||
new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent evt) {
|
||||
popOver.hide();
|
||||
}
|
||||
});
|
||||
closeIcon.getGraphic().setOnMouseClicked(evt -> popOver.hide());
|
||||
|
||||
titlePane = new StackPane();
|
||||
titlePane.getChildren().add(title);
|
||||
|
@ -140,61 +141,85 @@ public class PopOverSkin implements Skin<PopOver> {
|
|||
content.setCenter(popOver.getContentNode());
|
||||
content.getStyleClass().add("content"); //$NON-NLS-1$
|
||||
|
||||
if (popOver.isDetached()) {
|
||||
if (popOver.isDetached() || popOver.isHeaderAlwaysVisible()) {
|
||||
content.setTop(titlePane);
|
||||
}
|
||||
|
||||
if (popOver.isDetached()) {
|
||||
popOver.getStyleClass().add(DETACHED_STYLE_CLASS);
|
||||
content.getStyleClass().add(DETACHED_STYLE_CLASS);
|
||||
}
|
||||
|
||||
InvalidationListener updatePathListener = new InvalidationListener() {
|
||||
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
updatePath();
|
||||
}
|
||||
};
|
||||
|
||||
getPopupWindow().xProperty().addListener(updatePathListener);
|
||||
getPopupWindow().yProperty().addListener(updatePathListener);
|
||||
|
||||
popOver.arrowLocationProperty().addListener(updatePathListener);
|
||||
|
||||
popOver.contentNodeProperty().addListener(new ChangeListener<Node>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Node> value,
|
||||
Node oldContent, Node newContent) {
|
||||
content.setCenter(newContent);
|
||||
popOver.headerAlwaysVisibleProperty().addListener((o, oV, isVisible) -> {
|
||||
if (isVisible) {
|
||||
content.setTop(titlePane);
|
||||
} else if (!popOver.isDetached()) {
|
||||
content.setTop(null);
|
||||
}
|
||||
});
|
||||
|
||||
popOver.detachedProperty().addListener(new ChangeListener<Boolean>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> value,
|
||||
Boolean oldDetached, Boolean newDetached) {
|
||||
|
||||
updatePath();
|
||||
InvalidationListener updatePathListener = observable -> updatePath();
|
||||
getPopupWindow().xProperty().addListener(updatePathListener);
|
||||
getPopupWindow().yProperty().addListener(updatePathListener);
|
||||
popOver.arrowLocationProperty().addListener(updatePathListener);
|
||||
popOver.contentNodeProperty().addListener(
|
||||
(value, oldContent, newContent) -> content
|
||||
.setCenter(newContent));
|
||||
popOver.detachedProperty()
|
||||
.addListener((value, oldDetached, newDetached) -> {
|
||||
|
||||
if (newDetached) {
|
||||
popOver.getStyleClass().add(DETACHED_STYLE_CLASS);
|
||||
content.getStyleClass().add(DETACHED_STYLE_CLASS);
|
||||
content.setTop(titlePane);
|
||||
|
||||
switch (getSkinnable().getArrowLocation()) {
|
||||
case LEFT_TOP:
|
||||
case LEFT_CENTER:
|
||||
case LEFT_BOTTOM:
|
||||
popOver.setAnchorX(
|
||||
popOver.getAnchorX() + popOver.getArrowSize());
|
||||
break;
|
||||
case TOP_LEFT:
|
||||
case TOP_CENTER:
|
||||
case TOP_RIGHT:
|
||||
popOver.setAnchorY(
|
||||
popOver.getAnchorY() + popOver.getArrowSize());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
popOver.getStyleClass().remove(DETACHED_STYLE_CLASS);
|
||||
content.getStyleClass().remove(DETACHED_STYLE_CLASS);
|
||||
|
||||
if (!popOver.isHeaderAlwaysVisible()) {
|
||||
content.setTop(null);
|
||||
}
|
||||
}
|
||||
|
||||
popOver.sizeToScene();
|
||||
|
||||
updatePath();
|
||||
});
|
||||
|
||||
path = new Path();
|
||||
path.getStyleClass().add("border"); //$NON-NLS-1$
|
||||
path.setManaged(false);
|
||||
|
||||
clip = new Path();
|
||||
|
||||
/*
|
||||
* The clip is a path and the path has to be filled with a color.
|
||||
* Otherwise clipping will not work.
|
||||
*/
|
||||
clip.setFill(YELLOW);
|
||||
|
||||
createPathElements();
|
||||
updatePath();
|
||||
|
||||
final EventHandler<MouseEvent> mousePressedHandler = new EventHandler<MouseEvent>() {
|
||||
public void handle(MouseEvent evt) {
|
||||
final EventHandler<MouseEvent> mousePressedHandler = evt -> {
|
||||
log.info("mousePressed:" + popOver.isDetachable() + "," + popOver.isDetached());
|
||||
if (popOver.isDetachable() || popOver.isDetached()) {
|
||||
tornOff = false;
|
||||
|
||||
|
@ -204,20 +229,17 @@ public class PopOverSkin implements Skin<PopOver> {
|
|||
dragStartLocation = new Point2D(xOffset, yOffset);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
final EventHandler<MouseEvent> mouseReleasedHandler = new EventHandler<MouseEvent>() {
|
||||
public void handle(MouseEvent evt) {
|
||||
final EventHandler<MouseEvent> mouseReleasedHandler = evt -> {
|
||||
log.info("mouseReleased:tornOff" + tornOff + ", " + !getSkinnable().isDetached());
|
||||
if (tornOff && !getSkinnable().isDetached()) {
|
||||
tornOff = false;
|
||||
getSkinnable().detach();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
final EventHandler<MouseEvent> mouseDragHandler = new EventHandler<MouseEvent>() {
|
||||
|
||||
public void handle(MouseEvent evt) {
|
||||
final EventHandler<MouseEvent> mouseDragHandler = evt -> {
|
||||
log.info("mouseDrag:" + popOver.isDetachable() + "," + popOver.isDetached());
|
||||
if (popOver.isDetachable() || popOver.isDetached()) {
|
||||
double deltaX = evt.getScreenX() - xOffset;
|
||||
double deltaY = evt.getScreenY() - yOffset;
|
||||
|
@ -239,7 +261,6 @@ public class PopOverSkin implements Skin<PopOver> {
|
|||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
stackPane.setOnMousePressed(mousePressedHandler);
|
||||
stackPane.setOnMouseDragged(mouseDragHandler);
|
||||
|
@ -247,6 +268,8 @@ public class PopOverSkin implements Skin<PopOver> {
|
|||
|
||||
stackPane.getChildren().add(path);
|
||||
stackPane.getChildren().add(content);
|
||||
|
||||
content.setClip(clip);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -689,5 +712,6 @@ public class PopOverSkin implements Skin<PopOver> {
|
|||
elements.add(topCurveTo);
|
||||
|
||||
path.getElements().setAll(elements);
|
||||
clip.getElements().setAll(elements);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue