diff --git a/desktop/src/main/java/bisq/desktop/components/controlsfx/control/PopOver.java b/desktop/src/main/java/bisq/desktop/components/controlsfx/control/PopOver.java
index 9593cbc430..0b2c8790bd 100644
--- a/desktop/src/main/java/bisq/desktop/components/controlsfx/control/PopOver.java
+++ b/desktop/src/main/java/bisq/desktop/components/controlsfx/control/PopOver.java
@@ -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.
- *
+ *
* 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.
*
- *
+ *
* The following image shows a popover with an accordion content node. PopOver
* controls are automatically resizing themselves when the content node changes
* its size.
*
- *
+ *
+ * For styling apply stylesheets to the root pane of the PopOver.
+ *
+ * Example:
+ *
+ *
+ * PopOver popOver = new PopOver();
+ * popOver.getRoot().getStylesheets().add(...);
+ *
+ *
*/
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 fadeInDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION);
+ private final ObjectProperty 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,30 +120,24 @@ 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() {
- @Override
- public void handle(WindowEvent evt) {
- setDetached(false);
- }
- });
+ setOnHiding(evt -> setDetached(false));
/*
* Create some initial content.
*/
- Label label = new Label(""); //$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 repositionListener = new ChangeListener() {
- @Override
- public void changed(ObservableValue extends Object> value,
- Object oldObject, Object newObject) {
- if (isShowing() && !isDetached()) {
- show(getOwnerNode(), targetX, targetY);
- adjustWindowLocation();
- }
+ InvalidationListener repositionListener = observable -> {
+ if (isShowing() && !isDetached()) {
+ show(getOwnerNode(), targetX, targetY);
+ adjustWindowLocation();
}
};
@@ -123,12 +145,21 @@ public class PopOver extends PopupControl {
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.
+ *
+ * Example:
+ *
+ *
+ * PopOver popOver = new PopOver();
+ * popOver.getRoot().getStylesheets().add(...);
+ *
+ *
+ * @return the root pane
+ */
+ public final StackPane getRoot() {
+ return root;
+ }
+
// Content support.
- private final ObjectProperty contentNode = new SimpleObjectProperty(
+ private final ObjectProperty 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) {
- if (!isDetached()) {
- hide(Duration.ZERO);
- }
+ private final InvalidationListener hideListener = observable -> {
+ if (!isDetached()) {
+ hide(Duration.ZERO);
}
};
- private WeakInvalidationListener weakHideListener = new WeakInvalidationListener(
+ private final WeakInvalidationListener weakHideListener = new WeakInvalidationListener(
hideListener);
- private ChangeListener xListener = new ChangeListener() {
- @Override
- public void changed(ObservableValue extends Number> value,
- Number oldX, Number newX) {
- setX(getX() + (newX.doubleValue() - oldX.doubleValue()));
+ private final ChangeListener xListener = (value, oldX, newX) -> {
+ if (!isDetached()) {
+ setAnchorX(getAnchorX() + (newX.doubleValue() - oldX.doubleValue()));
}
};
- private WeakChangeListener weakXListener = new WeakChangeListener<>(
+ private final WeakChangeListener weakXListener = new WeakChangeListener<>(
xListener);
- private ChangeListener yListener = new ChangeListener() {
- @Override
- public void changed(ObservableValue extends Number> value,
- Number oldY, Number newY) {
- setY(getY() + (newY.doubleValue() - oldY.doubleValue()));
+ private final ChangeListener yListener = (value, oldY, newY) -> {
+ if (!isDetached()) {
+ setAnchorY(getAnchorY() + (newY.doubleValue() - oldY.doubleValue()));
}
};
- private WeakChangeListener weakYListener = new WeakChangeListener<>(
+ private final WeakChangeListener weakYListener = new WeakChangeListener<>(
yListener);
private Window ownerWindow;
+ private final EventHandler closePopOverOnOwnerWindowCloseLambda = event -> ownerWindowClosing();
+ private final WeakEventHandler 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,18 +438,15 @@ 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() {
- public void handle(MouseEvent evt) {
- if (evt.getTarget().equals(getScene().getRoot())) {
- if (!isDetached()) {
- hide();
- }
- }
- };
- });
+ getScene().addEventHandler(MOUSE_CLICKED, mouseEvent -> {
+ if (mouseEvent.getTarget().equals(getScene().getRoot())) {
+ if (!isDetached()) {
+ hide();
+ }
+ }
+ });
/*
* Move the window so that the arrow will end up pointing at the
@@ -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()) {
- // Fade Out
- Node skinNode = getSkin().getNode();
- skinNode.setOpacity(0);
+ if (isAnimated()) {
+ // Fade Out
+ Node skinNode = getSkin().getNode();
- FadeTransition fadeOut = new FadeTransition(fadeOutDuration,
- skinNode);
- fadeOut.setFromValue(1);
- fadeOut.setToValue(0);
- fadeOut.setOnFinished(evt -> super.hide());
- fadeOut.play();
+ FadeTransition fadeOut = new FadeTransition(fadeOutDuration,
+ skinNode);
+ 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 = new SimpleObjectProperty(
+ private final ObjectProperty 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 fadeInDurationProperty() {
+ return fadeInDuration;
+ }
+
+ /**
+ * Stores the fade-out duration.
+ *
+ * @return the fade-out duration property
+ */
+ public final ObjectProperty 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);
}
}
diff --git a/desktop/src/main/java/bisq/desktop/components/controlsfx/control/popover.css b/desktop/src/main/java/bisq/desktop/components/controlsfx/control/popover.css
index ccc6d5e42f..79f946fe6e 100644
--- a/desktop/src/main/java/bisq/desktop/components/controlsfx/control/popover.css
+++ b/desktop/src/main/java/bisq/desktop/components/controlsfx/control/popover.css
@@ -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 {
@@ -15,7 +15,7 @@
.popover > .detached {
}
-.popover > .content > .title > .text {
+.popover > .content > .title > .text {
-fx-padding: 6.0 6.0 0.0 6.0;
-fx-text-fill: rgba(120, 120, 120, .8);
-fx-font-weight: bold;
@@ -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;
}
diff --git a/desktop/src/main/java/bisq/desktop/components/controlsfx/skin/PopOverSkin.java b/desktop/src/main/java/bisq/desktop/components/controlsfx/skin/PopOverSkin.java
index 3ebdc843bf..e449f847a1 100644
--- a/desktop/src/main/java/bisq/desktop/components/controlsfx/skin/PopOverSkin.java
+++ b/desktop/src/main/java/bisq/desktop/components/controlsfx/skin/PopOverSkin.java
@@ -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 {
@@ -74,27 +79,27 @@ public class PopOverSkin implements Skin {
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 {
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() {
- @Override
- public void handle(MouseEvent evt) {
- popOver.hide();
- }
- });
+ closeIcon.getGraphic().setOnMouseClicked(evt -> popOver.hide());
titlePane = new StackPane();
titlePane.getChildren().add(title);
@@ -140,105 +141,125 @@ public class PopOverSkin implements Skin {
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();
+ popOver.headerAlwaysVisibleProperty().addListener((o, oV, isVisible) -> {
+ if (isVisible) {
+ content.setTop(titlePane);
+ } else if (!popOver.isDetached()) {
+ content.setTop(null);
}
- };
+ });
+ 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) -> {
- popOver.contentNodeProperty().addListener(new ChangeListener() {
- @Override
- public void changed(ObservableValue extends Node> value,
- Node oldContent, Node newContent) {
- content.setCenter(newContent);
- }
- });
+ if (newDetached) {
+ popOver.getStyleClass().add(DETACHED_STYLE_CLASS);
+ content.getStyleClass().add(DETACHED_STYLE_CLASS);
+ content.setTop(titlePane);
- popOver.detachedProperty().addListener(new ChangeListener() {
- @Override
- public void changed(ObservableValue extends Boolean> value,
- Boolean oldDetached, Boolean newDetached) {
+ 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);
- updatePath();
+ if (!popOver.isHeaderAlwaysVisible()) {
+ content.setTop(null);
+ }
+ }
- if (newDetached) {
- popOver.getStyleClass().add(DETACHED_STYLE_CLASS);
- content.getStyleClass().add(DETACHED_STYLE_CLASS);
- content.setTop(titlePane);
- } else {
- popOver.getStyleClass().remove(DETACHED_STYLE_CLASS);
- content.getStyleClass().remove(DETACHED_STYLE_CLASS);
- 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 mousePressedHandler = new EventHandler() {
- public void handle(MouseEvent evt) {
- if (popOver.isDetachable() || popOver.isDetached()) {
- tornOff = false;
+ final EventHandler mousePressedHandler = evt -> {
+ log.info("mousePressed:" + popOver.isDetachable() + "," + popOver.isDetached());
+ if (popOver.isDetachable() || popOver.isDetached()) {
+ tornOff = false;
- xOffset = evt.getScreenX();
- yOffset = evt.getScreenY();
+ xOffset = evt.getScreenX();
+ yOffset = evt.getScreenY();
- dragStartLocation = new Point2D(xOffset, yOffset);
- }
- };
+ dragStartLocation = new Point2D(xOffset, yOffset);
+ }
};
- final EventHandler mouseReleasedHandler = new EventHandler() {
- public void handle(MouseEvent evt) {
- if (tornOff && !getSkinnable().isDetached()) {
- tornOff = false;
- getSkinnable().detach();
- }
- };
+ final EventHandler mouseReleasedHandler = evt -> {
+ log.info("mouseReleased:tornOff" + tornOff + ", " + !getSkinnable().isDetached());
+ if (tornOff && !getSkinnable().isDetached()) {
+ tornOff = false;
+ getSkinnable().detach();
+ }
};
- final EventHandler mouseDragHandler = new EventHandler() {
+ final EventHandler mouseDragHandler = evt -> {
+ log.info("mouseDrag:" + popOver.isDetachable() + "," + popOver.isDetached());
+ if (popOver.isDetachable() || popOver.isDetached()) {
+ double deltaX = evt.getScreenX() - xOffset;
+ double deltaY = evt.getScreenY() - yOffset;
- public void handle(MouseEvent evt) {
- if (popOver.isDetachable() || popOver.isDetached()) {
- double deltaX = evt.getScreenX() - xOffset;
- double deltaY = evt.getScreenY() - yOffset;
+ Window window = getSkinnable().getScene().getWindow();
- Window window = getSkinnable().getScene().getWindow();
+ window.setX(window.getX() + deltaX);
+ window.setY(window.getY() + deltaY);
- window.setX(window.getX() + deltaX);
- window.setY(window.getY() + deltaY);
+ xOffset = evt.getScreenX();
+ yOffset = evt.getScreenY();
- xOffset = evt.getScreenX();
- yOffset = evt.getScreenY();
-
- if (dragStartLocation.distance(xOffset, yOffset) > 20) {
- tornOff = true;
- updatePath();
- } else if (tornOff) {
- tornOff = false;
- updatePath();
- }
+ if (dragStartLocation.distance(xOffset, yOffset) > 20) {
+ tornOff = true;
+ updatePath();
+ } else if (tornOff) {
+ tornOff = false;
+ updatePath();
}
- };
+ }
};
stackPane.setOnMousePressed(mousePressedHandler);
@@ -247,6 +268,8 @@ public class PopOverSkin implements Skin {
stackPane.getChildren().add(path);
stackPane.getChildren().add(content);
+
+ content.setClip(clip);
}
@Override
@@ -689,5 +712,6 @@ public class PopOverSkin implements Skin {
elements.add(topCurveTo);
path.getElements().setAll(elements);
+ clip.getElements().setAll(elements);
}
}