From 6b6276e6e2ad8790fd5eae325a22967b1d75ffa3 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 13 Sep 2025 14:30:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=A8=20Label=20=E6=96=87=E6=9C=AC=E6=BA=A2?= =?UTF-8?q?=E5=87=BA=E6=97=B6=E5=AE=89=E8=A3=85=20Tooltip=20(#4467)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 73 ++++++++++++++++--- .../hmcl/ui/main/JavaInstallPage.java | 3 +- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 71d987205..3be6033f2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -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 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> 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 */ diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaInstallPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaInstallPage.java index 5bdbcc577..01c3da8af 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaInstallPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaInstallPage.java @@ -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);