Skip to content

Commit

Permalink
Enhance FXUtils (Make tooltip installer faster).
Browse files Browse the repository at this point in the history
  • Loading branch information
burningtnt committed Oct 27, 2023
1 parent 8eb7ef6 commit 0a49eb2
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 42 deletions.
59 changes: 17 additions & 42 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,6 @@
import java.awt.*;
import java.io.*;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -230,23 +228,22 @@ public static Node wrapMargin(Node node, Insets insets) {
return new StackPane(node);
}

public static void setValidateWhileTextChanged(Node field, boolean validate) {
if (field instanceof JFXTextField) {
if (validate) {
addListener(field, "FXUtils.validation", ((JFXTextField) field).textProperty(), o -> ((JFXTextField) field).validate());
} else {
removeListener(field, "FXUtils.validation");
}
((JFXTextField) field).validate();
} else if (field instanceof JFXPasswordField) {
if (validate) {
addListener(field, "FXUtils.validation", ((JFXPasswordField) field).textProperty(), o -> ((JFXPasswordField) field).validate());
} else {
removeListener(field, "FXUtils.validation");
}
((JFXPasswordField) field).validate();
} else
throw new IllegalArgumentException("Only JFXTextField and JFXPasswordField allowed");
public static void setValidateWhileTextChanged(JFXTextField field, boolean validate) {
if (validate) {
addListener(field, "FXUtils.validation", field.textProperty(), o -> field.validate());
} else {
removeListener(field, "FXUtils.validation");
}
field.validate();
}

public static void setValidateWhileTextChanged(JFXPasswordField field, boolean validate) {
if (validate) {
addListener(field, "FXUtils.validation", field.textProperty(), o -> field.validate());
} else {
removeListener(field, "FXUtils.validation");
}
field.validate();
}

public static boolean getValidateWhileTextChanged(Node field) {
Expand Down Expand Up @@ -318,29 +315,7 @@ public static void installSlowTooltip(Node node, String tooltip) {
}

public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
runInFX(() -> {
try {
// Java 8
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
behaviorConstructor.setAccessible(true);
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
installMethod.setAccessible(true);
installMethod.invoke(behavior, node, tooltip);
} catch (ReflectiveOperationException e) {
try {
// Java 9
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
} catch (ReflectiveOperationException e2) {
e.addSuppressed(e2);
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e);
}
Tooltip.install(node, tooltip);
}
});
runInFX(() -> TooltipInstaller.install(node, openDelay, visibleDelay, closeDelay, tooltip));
}

public static void playAnimation(Node node, String animationKey, Timeline timeline) {
Expand Down
130 changes: 130 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <[email protected]> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;

import javafx.scene.Node;
import javafx.scene.control.Tooltip;
import javafx.util.Duration;
import org.jackhuang.hmcl.util.Logging;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.logging.Level;

public abstract class TooltipInstaller {
// Fallback: Java8TooltipInstaller -> Java9TooltipInstaller -> NoDurationTooltipInstaller

private static final class Java8TooltipInstaller extends TooltipInstaller {
public static TooltipInstaller of() {
try {
// Java 8
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
behaviorConstructor.setAccessible(true);
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
installMethod.setAccessible(true);
return new Java8TooltipInstaller(behaviorConstructor, installMethod);
} catch (ReflectiveOperationException e) {
Logging.LOG.log(Level.WARNING, "Cannot use Java 8 Tooltip Installer, fallback to Java 9 Tooltip Installer.", e);
return Java9TooltipInstaller.of();
}
}

private final Constructor<?> behaviorConstructor;

private final Method installMethod;

public Java8TooltipInstaller(Constructor<?> behaviorConstructor, Method installMethod) {
this.behaviorConstructor = behaviorConstructor;
this.installMethod = installMethod;
}

@Override
protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
try {
installMethod.invoke(
behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false),
node,
tooltip
);

return this;
} catch (ReflectiveOperationException e) {
Logging.LOG.log(Level.WARNING, "Cannot use Java 8 Tooltip Installer, fallback to Java 9 Tooltip Installer.", e);
return Java9TooltipInstaller.of().installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip);
}
}
}

private static final class Java9TooltipInstaller extends TooltipInstaller {
public static TooltipInstaller of() {
try {
Method setShowDelay = Tooltip.class.getMethod("setShowDelay", Duration.class);
Method setShowDuration = Tooltip.class.getMethod("setShowDuration", Duration.class);
Method setHideDelay = Tooltip.class.getMethod("setHideDelay", Duration.class);
return new Java9TooltipInstaller(setShowDelay, setShowDuration, setHideDelay);
} catch (ReflectiveOperationException e) {
Logging.LOG.log(Level.WARNING, "Cannot use Java 9 Tooltip Installer, fallback to No Duration Tooltip Installer.", e);
return new NoDurationTooltipInstaller();
}
}

private final Method setShowDelayMethod;
private final Method setShowDurationMethod;
private final Method setHideDelayMethod;

private Java9TooltipInstaller(Method setShowDelayMethod, Method setShowDurationMethod, Method setHideDelayMethod) {
this.setShowDelayMethod = setShowDelayMethod;
this.setShowDurationMethod = setShowDurationMethod;
this.setHideDelayMethod = setHideDelayMethod;
}

@Override
protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
try {
setShowDelayMethod.invoke(tooltip, new Duration(openDelay));
setShowDurationMethod.invoke(tooltip, new Duration(visibleDelay));
setHideDelayMethod.invoke(tooltip, new Duration(closeDelay));
Tooltip.install(node, tooltip);
return this;
} catch (ReflectiveOperationException e) {
Logging.LOG.log(Level.WARNING, "Cannot use Java 9 Tooltip Installer, fallback to No Duration Tooltip Installer.", e);
return new NoDurationTooltipInstaller().installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip);
}
}
}

private static final class NoDurationTooltipInstaller extends TooltipInstaller {
@Override
protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
Tooltip.install(node, tooltip);
return this;
}
}

private static TooltipInstaller installer = null;

// FXThread
public static void install(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
FXUtils.checkFxUserThread();

installer = (installer == null ? Java8TooltipInstaller.of() : installer).installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip);
}

protected abstract TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip);
}

0 comments on commit 0a49eb2

Please sign in to comment.