Skip to content
Merged
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
26 changes: 22 additions & 4 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@
import java.util.List;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -1370,6 +1367,27 @@ public static void onClicked(Node node, Runnable action) {
});
}

public static <T> void onScroll(Node node, List<T> list,
ToIntFunction<List<T>> finder,
Consumer<T> updater
) {
Comment on lines +1370 to +1373
Copy link

Copilot AI Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The onScroll method lacks documentation. Add a JavaDoc comment explaining the purpose, parameters, and behavior of this utility method, especially the finder function's expected return value (-1 for not found) and how the circular navigation works.

Copilot uses AI. Check for mistakes.
node.addEventHandler(ScrollEvent.SCROLL, event -> {
double deltaY = event.getDeltaY();
if (deltaY == 0)
return;

int index = finder.applyAsInt(list);
if (index < 0) return;
if (deltaY > 0) // up
index--;
else // down
index++;

updater.accept(list.get((index + list.size()) % list.size()));
event.consume();
});
}

public static void copyOnDoubleClick(Labeled label) {
label.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Tooltip;
import org.jackhuang.hmcl.auth.Account;
Expand Down Expand Up @@ -77,25 +76,9 @@ public AccountAdvancedListItem() {

setActionButtonVisible(false);

setOnScroll(event -> {
double deltaY = event.getDeltaY();
if (deltaY == 0)
return;

Account current = account.get();
if (current == null) return;

ObservableList<Account> accounts = Accounts.getAccounts();
int currentIndex = accounts.indexOf(current);
if (currentIndex < 0) return;

if (deltaY > 0) // up
currentIndex--;
else // down
currentIndex++;

Accounts.setSelectedAccount(accounts.get((currentIndex + accounts.size()) % accounts.size()));
});
FXUtils.onScroll(this, Accounts.getAccounts(),
accounts -> accounts.indexOf(account.get()),
Accounts::setSelectedAccount);
}

public ObjectProperty<Account> accountProperty() {
Expand Down
25 changes: 12 additions & 13 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,20 +208,11 @@ public final class MainPage extends StackPane implements DecoratorPage {
launchPane.getStyleClass().add("launch-pane");
launchPane.setMaxWidth(230);
launchPane.setMaxHeight(55);
launchPane.setOnScroll(event -> {
double deltaY = event.getDeltaY();
if (deltaY == 0)
return;

FXUtils.onScroll(launchPane, versions, list -> {
String currentId = getCurrentGame();
int index = Lang.indexWhere(versions, instance -> instance.getId().equals(currentId));
if (index < 0) return;
if (deltaY > 0) // up
index--;
else // down
index++;
profile.setSelectedVersion(versions.get((index + versions.size()) % versions.size()).getId());
});
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
}, it -> profile.setSelectedVersion(it.getId()));

StackPane.setAlignment(launchPane, Pos.BOTTOM_RIGHT);
{
JFXButton launchButton = new JFXButton();
Expand Down Expand Up @@ -435,6 +426,10 @@ public ReadOnlyObjectWrapper<State> stateProperty() {
return state;
}

public Profile getProfile() {
return profile;
Copy link

Copilot AI Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The newly added getProfile() method exposes the internal profile object without any access control or immutability guarantees. Consider whether this direct exposure is necessary or if a more restricted API would be safer.

Suggested change
return profile;
// Return a defensive copy to prevent external modification
return profile == null ? null : new Profile(profile);

Copilot uses AI. Check for mistakes.
}

public String getCurrentGame() {
return currentGame.get();
}
Expand All @@ -447,6 +442,10 @@ public void setCurrentGame(String currentGame) {
this.currentGame.set(currentGame);
}

public ObservableList<Version> getVersions() {
return versions;
Copy link

Copilot AI Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getVersions() method returns the internal ObservableList directly, allowing external code to modify it. Consider returning an unmodifiable view using FXCollections.unmodifiableObservableList(versions) to prevent unintended modifications.

Suggested change
return versions;
return FXCollections.unmodifiableObservableList(versions);

Copilot uses AI. Check for mistakes.
}

public boolean isShowUpdate() {
return showUpdate.get();
}
Expand Down
7 changes: 5 additions & 2 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ protected Skin(RootPage control) {
Versions.modifyGameSettings(profile, version);
}
});
FXUtils.onScroll(gameListItem, getSkinnable().getMainPage().getVersions(), list -> {
String currentId = getSkinnable().getMainPage().getCurrentGame();
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
}, it -> getSkinnable().getMainPage().getProfile().setSelectedVersion(it.getId()));

// third item in left sidebar
AdvancedListItem gameItem = new AdvancedListItem();
Expand Down Expand Up @@ -194,8 +198,7 @@ protected Skin(RootPage control) {
.add(downloadItem)
.startCategory(i18n("settings.launcher.general").toUpperCase(Locale.ROOT))
.add(launcherSettingsItem)
.add(chatItem)
;
.add(chatItem);

// the root page, with the sidebar in left, navigator in center.
setLeft(sideBar);
Expand Down