在 Label 文本溢出时安装 Tooltip (#4467)

This commit is contained in:
Glavo 2025-09-13 14:30:27 +08:00 committed by GitHub
parent 60ff34a587
commit 6b6276e6e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 66 additions and 10 deletions

View File

@ -80,6 +80,7 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.ref.WeakReference;
@ -113,6 +114,8 @@ public final class FXUtils {
public static final @Nullable ObservableBooleanValue DARK_MODE;
public static final @Nullable Boolean REDUCED_MOTION;
public static final @Nullable MethodHandle TEXT_TRUNCATED_PROPERTY;
static {
String jfxVersion = System.getProperty("javafx.version");
int majorVersion = -1;
@ -157,6 +160,20 @@ public final class FXUtils {
PREFERENCES = preferences;
DARK_MODE = darkMode;
REDUCED_MOTION = reducedMotion;
MethodHandle textTruncatedProperty = null;
if (JAVAFX_MAJOR_VERSION >= 23) {
try {
textTruncatedProperty = MethodHandles.publicLookup().findVirtual(
Labeled.class,
"textTruncatedProperty",
MethodType.methodType(ReadOnlyBooleanProperty.class)
);
} catch (Throwable e) {
LOG.warning("Failed to lookup textTruncatedProperty", e);
}
}
TEXT_TRUNCATED_PROPERTY = textTruncatedProperty;
}
public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace";
@ -356,6 +373,22 @@ public final class FXUtils {
ScrollUtils.addSmoothScrolling(scrollPane);
}
/// If the current environment is JavaFX 23 or higher, this method returns [Labeled#textTruncatedProperty()];
/// Otherwise, it returns `null`.
public static @Nullable ReadOnlyBooleanProperty textTruncatedProperty(Labeled labeled) {
if (TEXT_TRUNCATED_PROPERTY != null) {
try {
return (ReadOnlyBooleanProperty) TEXT_TRUNCATED_PROPERTY.invokeExact(labeled);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
private static final Duration TOOLTIP_FAST_SHOW_DELAY = Duration.millis(50);
private static final Duration TOOLTIP_SLOW_SHOW_DELAY = Duration.millis(500);
private static final Duration TOOLTIP_SHOW_DURATION = Duration.millis(5000);
@ -1170,17 +1203,39 @@ public final class FXUtils {
return button;
}
public static Label truncatedLabel(String text, int limit) {
Label label = new Label();
if (text.length() <= limit) {
label.setText(text);
} else {
label.setText(StringUtils.truncate(text, limit));
installFastTooltip(label, text);
}
public static Label newSafeTruncatedLabel(String text) {
Label label = new Label(text);
label.setTextOverrun(OverrunStyle.CENTER_WORD_ELLIPSIS);
showTooltipWhenTruncated(label);
return label;
}
private static final String LABEL_FULL_TEXT_PROP_KEY = FXUtils.class.getName() + ".LABEL_FULL_TEXT";
public static void showTooltipWhenTruncated(Labeled labeled) {
ReadOnlyBooleanProperty textTruncatedProperty = textTruncatedProperty(labeled);
if (textTruncatedProperty != null) {
ChangeListener<Boolean> listener = (observable, oldValue, newValue) -> {
var label = (Labeled) ((ReadOnlyProperty<?>) observable).getBean();
var tooltip = (Tooltip) label.getProperties().get(LABEL_FULL_TEXT_PROP_KEY);
if (newValue) {
if (tooltip == null) {
tooltip = new Tooltip();
tooltip.textProperty().bind(label.textProperty());
label.getProperties().put(LABEL_FULL_TEXT_PROP_KEY, tooltip);
}
FXUtils.installFastTooltip(label, tooltip);
} else if (tooltip != null) {
Tooltip.uninstall(label, tooltip);
}
};
listener.changed(textTruncatedProperty, false, textTruncatedProperty.get());
textTruncatedProperty.addListener(listener);
}
}
public static void applyDragListener(Node node, FileFilter filter, Consumer<List<File>> callback) {
applyDragListener(node, filter, callback, null);
}
@ -1387,7 +1442,7 @@ public final class FXUtils {
* Supports multi-monitor setups by detecting the current screen where the component is located.
* Now handles first-time popup display by forcing layout measurement.
*
* @param root the root node to calculate position relative to
* @param root the root node to calculate position relative to
* @param popupInstance the popup instance to position
* @return the optimal vertical position for the popup menu
*/

View File

@ -180,8 +180,9 @@ public final class JavaInstallPage extends WizardSinglePage {
pane.setLeft(new Label(name));
Label valueLabel = FXUtils.truncatedLabel(value, 60);
Label valueLabel = FXUtils.newSafeTruncatedLabel(value);
FXUtils.copyOnDoubleClick(valueLabel);
BorderPane.setMargin(valueLabel, new Insets(0, 0, 0, 16));
BorderPane.setAlignment(valueLabel, Pos.CENTER_RIGHT);
pane.setCenter(valueLabel);