Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java
Original file line number Diff line number Diff line change
Expand Up @@ -1151,10 +1151,28 @@ public void handle(WindowEvent event) {
});
}
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS && MacOSNativeUtils.isSupported()) {
MacOSNativeUtils.setAppearance(darkModeProperty().get());
// On macOS, calling NSApplication.setAppearance() overrides the system
// appearance, which locks the JavaFX colorScheme property and prevents it
// from following the system theme. Therefore, only set the appearance when
// the user explicitly chooses "light" or "dark" mode. When in "auto" (follow
// system) mode, leave the appearance unset so the JavaFX colorScheme tracks
// the system automatically.
Runnable syncMacAppearance = () -> {
String mode = settings().themeBrightnessModeProperty().get();
if ("auto".equalsIgnoreCase(Objects.toString(mode, "").trim())) {
MacOSNativeUtils.resetAppearance();
} else {
MacOSNativeUtils.setAppearance(darkModeProperty().get());
}
};

ChangeListener<Boolean> listener = FXUtils.onWeakChange(Themes.darkModeProperty(), MacOSNativeUtils::setAppearance);
stage.getProperties().put("Themes.applyNativeDarkMode.listener", listener);
syncMacAppearance.run();

// Re-evaluate when the user switches between auto / explicit brightness modes.
ChangeListener<String> modeListener = FXUtils.onWeakChange(
settings().themeBrightnessModeProperty(),
mode -> syncMacAppearance.run());
stage.getProperties().put("Themes.applyNativeDarkMode.modeListener", modeListener);
}
}

Expand Down
2 changes: 2 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ private static final class InstallerItemSkin extends SkinBase<InstallerItem> {
Label statusLabel = new Label();
statusLabel.getStyleClass().add("installer-item-status");
statusLabel.setMouseTransparent(true);
statusLabel.setWrapText(true);
statusLabel.setMaxWidth(Double.MAX_VALUE);
pane.getChildren().add(statusLabel);
HBox.setHgrow(statusLabel, Priority.ALWAYS);
statusLabel.textProperty().bind(Bindings.createStringBinding(() -> {
Expand Down
16 changes: 16 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/MacOSNativeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,22 @@ public static void setAppearance(boolean dark, boolean highContrast) {
}
}

/// Resets the application appearance to `nil` so the system controls it.
/// Call this when switching to "follow system" mode so that the JavaFX
/// [colorScheme] property continues to track the system theme automatically.
public static void resetAppearance() {
if (nsApp == null) return;

try {
var objc = ObjectiveCRuntime.INSTANCE;

Pointer setSel = objc.sel_registerName("setAppearance:");
objc.objc_msgSend(nsApp, setSel, (Pointer) null);
} catch (Throwable t) {
LOG.warning("Failed to reset macOS appearance", t);
}
}

private MacOSNativeUtils() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public FloatScrollBarSkin(final ScrollBar scrollBar) {
this.scrollBar = scrollBar;
scrollBar.setPrefHeight(1e-18);
scrollBar.setPrefWidth(1e-18);
scrollBar.setFocusTraversable(false);

this.group = new Region() {
Point2D dragStart;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.OverrunStyle;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
Expand All @@ -45,6 +46,10 @@ public TwoLineListItem() {

lblTitle = new Label();
lblTitle.getStyleClass().add("title");
lblTitle.setTextOverrun(OverrunStyle.ELLIPSIS);
lblTitle.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(lblTitle, Priority.SOMETIMES);
lblTitle.setMinWidth(0);

this.firstLine = new HBox(lblTitle);
firstLine.getStyleClass().add("first-line");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ public Cell(ListView<GameItem> listView) {
StackPane rootPane = new StackPane();
rootPane.getStyleClass().add("advanced-list-item");
rootPane.getChildren().setAll(ripplerContainer);
rootPane.maxWidthProperty().bind(listView.widthProperty().subtract(5));
// Allow items to fill the full width; the right padding prevents
// overlap with the floating scrollbar (6px wide at x=-6).
rootPane.maxWidthProperty().bind(listView.widthProperty().subtract(8));
rootPane.setPadding(new Insets(0, 8, 0, 0));

FXUtils.onClicked(rootPane, () -> {
GameItem item = getItem();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ public static void copyWithHeader(Path from, Path to) throws IOException {
if (header.isPresent()) {
out.write(ByteBuffer.wrap(header.get()));
}
} else {
// Fallback: when the destination file has no recognizable suffix
// (e.g. the user removed the .sh extension), try to detect and
// preserve the header from the source file by checking all known
// suffixes. This prevents the update from turning an executable
// shell script into a plain .jar file.
for (String knownSuffix : suffix2header.keySet()) {
Optional<byte[]> header = readHeader(zip, knownSuffix);
if (header.isPresent()) {
out.write(ByteBuffer.wrap(header.get()));
break;
}
}
}

in.transferTo(detectHeaderLength(zip, in), Long.MAX_VALUE, out);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.addon.mod;

import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import org.jackhuang.hmcl.addon.LocalAddonFile;
Expand Down Expand Up @@ -75,14 +76,17 @@ protected void invalidated() {
if (isOld()) return;

Path path = LocalModFile.this.file.toAbsolutePath();
boolean newValue = get();

try {
if (get())
if (newValue)
LocalModFile.this.file = modManager.enableMod(path);
else
LocalModFile.this.file = modManager.disableMod(path);
} catch (IOException e) {
LOG.error("Unable to invert state of mod file " + path, e);
// Revert so the UI stays consistent with the actual file state.
Platform.runLater(() -> set(!newValue));
}
}
};
Expand Down