Skip to content

Commit

Permalink
Prevent tooltip popover flicker upon mouseover
Browse files Browse the repository at this point in the history
Provide a wrapper for PopOver components used as tooltips, to debounce
the 'MouseEntered' and 'MouseExited' events used to show/hide it, in
order to prevent it flickering open/closed in a loop. This fixes #3016.

To this end, use a ..->HIDDEN->SHOWING->SHOWN->HIDING->.. state field,
together with a target visibility boolean field, where the transition
between HIDDEN and SHOWN incurs a small fixed delay.
  • Loading branch information
stejbac committed Sep 5, 2019
1 parent af95fcc commit a2c1244
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package bisq.desktop.components;

import bisq.common.UserThread;

import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;

Expand All @@ -31,14 +29,10 @@

import javafx.geometry.Insets;

import java.util.concurrent.TimeUnit;

public class AutoTooltipTableColumn<S, T> extends TableColumn<S, T> {

private Label helpIcon;
private Boolean hidePopover;
private PopOver infoPopover;

private PopOverWrapper popoverWrapper = new PopOverWrapper();

public AutoTooltipTableColumn(String text) {
super();
Expand All @@ -56,47 +50,37 @@ public void setTitle(String title) {
}

public void setTitleWithHelpText(String title, String help) {

final AutoTooltipLabel label = new AutoTooltipLabel(title);

helpIcon = new Label();
AwesomeDude.setIcon(helpIcon, AwesomeIcon.QUESTION_SIGN, "1em");
helpIcon.setOpacity(0.4);
helpIcon.setOnMouseEntered(e -> {
hidePopover = false;
final Label helpLabel = new Label(help);
helpLabel.setMaxWidth(300);
helpLabel.setWrapText(true);
helpLabel.setPadding(new Insets(10));
showInfoPopOver(helpLabel);
});
helpIcon.setOnMouseExited(e -> {
if (infoPopover != null)
infoPopover.hide();
hidePopover = true;
UserThread.runAfter(() -> {
if (hidePopover) {
infoPopover.hide();
hidePopover = false;
}
}, 250, TimeUnit.MILLISECONDS);
});
helpIcon.setOnMouseEntered(e -> popoverWrapper.showPopOver(() -> createInfoPopOver(help)));
helpIcon.setOnMouseExited(e -> popoverWrapper.hidePopOver());

final AutoTooltipLabel label = new AutoTooltipLabel(title);
final HBox hBox = new HBox(label, helpIcon);
hBox.setStyle("-fx-alignment: center-left");
hBox.setSpacing(4);
setGraphic(hBox);
}

private void showInfoPopOver(Node node) {
private PopOver createInfoPopOver(String help) {
Label helpLabel = new Label(help);
helpLabel.setMaxWidth(300);
helpLabel.setWrapText(true);
helpLabel.setPadding(new Insets(10));
return createInfoPopOver(helpLabel);
}

private PopOver createInfoPopOver(Node node) {
node.getStyleClass().add("default-text");

infoPopover = new PopOver(node);
PopOver infoPopover = new PopOver(node);
if (helpIcon.getScene() != null) {
infoPopover.setDetachable(false);
infoPopover.setArrowLocation(PopOver.ArrowLocation.LEFT_CENTER);

infoPopover.show(helpIcon, -10);
}
return infoPopover;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package bisq.desktop.components;

import bisq.common.UserThread;

import de.jensd.fx.fontawesome.AwesomeIcon;
import de.jensd.fx.glyphs.GlyphIcons;

Expand All @@ -30,15 +28,12 @@

import javafx.geometry.Insets;

import java.util.concurrent.TimeUnit;

import static bisq.desktop.util.FormBuilder.getIcon;

public class InfoAutoTooltipLabel extends AutoTooltipLabel {

private Node textIcon;
private Boolean hidePopover;
private PopOver infoPopover;
private PopOverWrapper popoverWrapper = new PopOverWrapper();

public InfoAutoTooltipLabel(String text, GlyphIcons icon, ContentDisplay contentDisplay, String info) {
super(text);
Expand All @@ -56,42 +51,31 @@ public InfoAutoTooltipLabel(String text, AwesomeIcon icon, ContentDisplay conten

private void addIcon(ContentDisplay contentDisplay, String info, double width) {
textIcon.setOpacity(0.4);

textIcon.setOnMouseEntered(e -> {
hidePopover = false;
final Label helpLabel = new Label(info);
helpLabel.setMaxWidth(width);
helpLabel.setWrapText(true);
helpLabel.setPadding(new Insets(10));
showInfoPopOver(helpLabel);
});

textIcon.setOnMouseExited(e -> {
if (infoPopover != null)
infoPopover.hide();
hidePopover = true;
UserThread.runAfter(() -> {
if (hidePopover) {
infoPopover.hide();
hidePopover = false;
}
}, 250, TimeUnit.MILLISECONDS);
});
textIcon.setOnMouseEntered(e -> popoverWrapper.showPopOver(() -> createInfoPopOver(info, width)));
textIcon.setOnMouseExited(e -> popoverWrapper.hidePopOver());

setGraphic(textIcon);
setContentDisplay(contentDisplay);
}

private PopOver createInfoPopOver(String info, double width) {
Label helpLabel = new Label(info);
helpLabel.setMaxWidth(width);
helpLabel.setWrapText(true);
helpLabel.setPadding(new Insets(10));
return createInfoPopOver(helpLabel);
}

private void showInfoPopOver(Node node) {
private PopOver createInfoPopOver(Node node) {
node.getStyleClass().add("default-text");

infoPopover = new PopOver(node);
PopOver infoPopover = new PopOver(node);
if (textIcon.getScene() != null) {
infoPopover.setDetachable(false);
infoPopover.setArrowLocation(PopOver.ArrowLocation.LEFT_CENTER);

infoPopover.show(textIcon, -10);
}
return infoPopover;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package bisq.desktop.components;

import bisq.common.UserThread;

import de.jensd.fx.fontawesome.AwesomeIcon;

import org.controlsfx.control.PopOver;
Expand All @@ -30,8 +28,6 @@
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

import java.util.concurrent.TimeUnit;

import lombok.Getter;

import static bisq.desktop.util.FormBuilder.getIcon;
Expand All @@ -50,8 +46,7 @@ public class InfoInputTextField extends AnchorPane {
private final Label privacyIcon;

private Label currentIcon;
private PopOver popover;
private boolean hidePopover;
private PopOverWrapper popoverWrapper = new PopOverWrapper();

public InfoInputTextField() {
this(0);
Expand Down Expand Up @@ -162,34 +157,22 @@ private void setActionHandlers(Node node) {
currentIcon.setVisible(true);

// As we don't use binding here we need to recreate it on mouse over to reflect the current state
currentIcon.setOnMouseEntered(e -> {
hidePopover = false;
showPopOver(node);
});
currentIcon.setOnMouseExited(e -> {
if (popover != null)
popover.hide();
hidePopover = true;
UserThread.runAfter(() -> {
if (hidePopover) {
popover.hide();
hidePopover = false;
}
}, 250, TimeUnit.MILLISECONDS);
});
currentIcon.setOnMouseEntered(e -> popoverWrapper.showPopOver(() -> createPopOver(node)));
currentIcon.setOnMouseExited(e -> popoverWrapper.hidePopOver());
}
}

private void showPopOver(Node node) {
private PopOver createPopOver(Node node) {
node.getStyleClass().add("default-text");

popover = new PopOver(node);
PopOver popover = new PopOver(node);
if (currentIcon.getScene() != null) {
popover.setDetachable(false);
popover.setArrowLocation(PopOver.ArrowLocation.LEFT_TOP);
popover.setArrowIndent(5);

popover.show(currentIcon, -17);
}
return popover;
}
}
31 changes: 7 additions & 24 deletions desktop/src/main/java/bisq/desktop/components/InfoTextField.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package bisq.desktop.components;

import bisq.common.UserThread;

import de.jensd.fx.fontawesome.AwesomeIcon;

import com.jfoenix.controls.JFXTextField;
Expand All @@ -32,8 +30,6 @@
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -51,13 +47,12 @@ public class InfoTextField extends AnchorPane {
protected final Label infoIcon;
protected final Label privacyIcon;
private Label currentIcon;
private Boolean hidePopover;
private PopOver popover;
private PopOverWrapper popoverWrapper = new PopOverWrapper();
private PopOver.ArrowLocation arrowLocation;

public InfoTextField() {

arrowLocation = PopOver.ArrowLocation.RIGHT_TOP;;
arrowLocation = PopOver.ArrowLocation.RIGHT_TOP;
textField = new BisqTextField();
textField.setLabelFloat(true);
textField.setEditable(false);
Expand Down Expand Up @@ -130,34 +125,22 @@ private void setActionHandlers(Node node) {
currentIcon.setVisible(true);

// As we don't use binding here we need to recreate it on mouse over to reflect the current state
currentIcon.setOnMouseEntered(e -> {
hidePopover = false;
showPopOver(node);
});
currentIcon.setOnMouseExited(e -> {
if (popover != null)
popover.hide();
hidePopover = true;
UserThread.runAfter(() -> {
if (hidePopover) {
popover.hide();
hidePopover = false;
}
}, 250, TimeUnit.MILLISECONDS);
});
currentIcon.setOnMouseEntered(e -> popoverWrapper.showPopOver(() -> createPopOver(node)));
currentIcon.setOnMouseExited(e -> popoverWrapper.hidePopOver());
}

private void showPopOver(Node node) {
private PopOver createPopOver(Node node) {
node.getStyleClass().add("default-text");

popover = new PopOver(node);
PopOver popover = new PopOver(node);
if (currentIcon.getScene() != null) {
popover.setDetachable(false);
popover.setArrowLocation(arrowLocation);
popover.setArrowIndent(5);

popover.show(currentIcon, -17);
}
return popover;
}

///////////////////////////////////////////////////////////////////////////////////////////
Expand Down
73 changes: 73 additions & 0 deletions desktop/src/main/java/bisq/desktop/components/PopOverWrapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.desktop.components;

import bisq.common.UserThread;

import org.controlsfx.control.PopOver;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

public class PopOverWrapper {

private PopOver popover;
private Supplier<PopOver> popoverSupplier;
private boolean hidePopover;
private PopOverState state = PopOverState.HIDDEN;

enum PopOverState {
HIDDEN, SHOWING, SHOWN, HIDING
}

public void showPopOver(Supplier<PopOver> popoverSupplier) {
this.popoverSupplier = popoverSupplier;
hidePopover = false;

if (state == PopOverState.HIDDEN) {
state = PopOverState.SHOWING;
popover = popoverSupplier.get();

UserThread.runAfter(() -> {
state = PopOverState.SHOWN;
if (hidePopover) {
// For some reason, this can result in a brief flicker when invoked
// from a 'runAfter' callback, rather than directly. So make the delay
// very short (25ms) so that we don't reach here often:
hidePopOver();
}
}, 25, TimeUnit.MILLISECONDS);
}
}

public void hidePopOver() {
hidePopover = true;

if (state == PopOverState.SHOWN) {
state = PopOverState.HIDING;
popover.hide();

UserThread.runAfter(() -> {
state = PopOverState.HIDDEN;
if (!hidePopover) {
showPopOver(popoverSupplier);
}
}, 250, TimeUnit.MILLISECONDS);
}
}
}

0 comments on commit a2c1244

Please sign in to comment.