Skip to content
Open
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
15 changes: 13 additions & 2 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,21 @@ private String getSelectedRunningDirectory(
}

public Stream<Version> getDisplayVersions() {
Map<String, Integer> customOrder = new HashMap<>();
List<String> orderedIds = settings().getInstanceSortOrder(gameDirectory.getId());
for (int i = 0; i < orderedIds.size(); i++) {
customOrder.putIfAbsent(orderedIds.get(i), i);
}

return getVersions().stream()
.filter(v -> !v.isHidden())
.sorted(Comparator.comparing((Version v) -> Lang.requireNonNullElse(v.getReleaseTime(), Instant.EPOCH))
.thenComparing(v -> VersionNumber.asVersion(v.getId())));
.sorted(Comparator.comparing((Version v) -> customOrder.getOrDefault(v.getId(), Integer.MAX_VALUE))
.thenComparing(v -> Lang.requireNonNullElse(v.getReleaseTime(), Instant.EPOCH))
.thenComparing(v -> VersionNumber.asVersion(getGameVersion(v).orElse(v.getId()))));
}

public void setInstanceSortOrder(List<String> instanceIds) {
settings().setInstanceSortOrder(gameDirectory.getId(), instanceIds);
}

@Override
Expand Down
29 changes: 28 additions & 1 deletion HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,22 @@ private static void addAccountIDs(List<AccountID> accountIDs, List<JsonObject> m
}
}

private static void sortAccountsByCustomOrder() {
Map<AccountID, Integer> customOrder = new HashMap<>();
List<AccountID> orderedAccountIDs = settings().getAccountSortOrder();
for (int i = 0; i < orderedAccountIDs.size(); i++) {
customOrder.putIfAbsent(orderedAccountIDs.get(i), i);
}

accounts.sort(Comparator.comparing(account -> customOrder.getOrDefault(account.getAccountID(), Integer.MAX_VALUE)));
}

private static void updateAccountSortOrder() {
settings().setAccountSortOrder(accounts.stream()
.map(Account::getAccountID)
.toList());
}

private static void updateAccountMetadataRecords() {
// don't update the underlying account records before data loading is completed
// otherwise it might cause data loss
Expand Down Expand Up @@ -334,6 +350,8 @@ public static void init() {
}
}

sortAccountsByCustomOrder();

AccountID selectedAccountID = settings().selectedAccountProperty().get();
if (selected == null && selectedAccountID != null) {
for (Account account : accounts) {
Expand Down Expand Up @@ -408,7 +426,10 @@ public void onChanged(Change<? extends Account> change) {
settings().selectedAccountProperty().set(null);
}));
accounts.addListener(listener);
accounts.addListener(onInvalidating(Accounts::updateAccountMetadataRecords));
accounts.addListener(onInvalidating(() -> {
Accounts.updateAccountMetadataRecords();
Accounts.updateAccountSortOrder();
}));

initialized = true;

Expand Down Expand Up @@ -442,6 +463,12 @@ public static ObservableList<Account> getAccounts() {
return accounts;
}

public static void setAccountSortOrder(Collection<Account> order) {
settings().setAccountSortOrder(order.stream()
.map(Account::getAccountID)
.toList());
}

public static Account getSelectedAccount() {
return selectedAccount.get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ public final class LauncherSettings extends ObservableSetting implements JsonSch
/// The JSON property name for selected instance IDs keyed by game directory ID.
static final String PROPERTY_SELECTED_INSTANCE = "selectedInstance";

/// The JSON property name for custom instance sort order keyed by game directory ID.
static final String PROPERTY_INSTANCE_SORT_ORDER = "instanceSortOrder";

/// The JSON property name for custom account sort order.
static final String PROPERTY_ACCOUNT_SORT_ORDER = "accountSortOrder";

/// Default launcher theme used when no stored theme reference is available.
public static final ThemeReference DEFAULT_THEME_REFERENCE = new ThemeReference("hmcl.default", null);

Expand Down Expand Up @@ -654,6 +660,48 @@ public void setSelectedInstance(@Nullable GameDirectoryID gameDirectoryId, @Null
}
}

/// Custom instance sort order keyed by game directory ID.
@SerializedName(PROPERTY_INSTANCE_SORT_ORDER)
private final ObservableMap<GameDirectoryID, ObservableList<String>> instanceSortOrder = FXCollections.observableHashMap();

/// Returns custom instance sort order keyed by game directory ID.
public ObservableMap<GameDirectoryID, ObservableList<String>> getInstanceSortOrder() {
return instanceSortOrder;
}

/// Returns the custom instance sort order for the given game directory ID.
public List<String> getInstanceSortOrder(@Nullable GameDirectoryID gameDirectoryId) {
ObservableList<String> order = gameDirectoryId != null ? instanceSortOrder.get(gameDirectoryId) : null;
return order == null ? List.of() : List.copyOf(order);
}

/// Sets the custom instance sort order for the given game directory ID.
public void setInstanceSortOrder(@Nullable GameDirectoryID gameDirectoryId, Collection<String> order) {
if (gameDirectoryId == null) {
return;
}

if (order.isEmpty()) {
this.instanceSortOrder.remove(gameDirectoryId);
} else {
this.instanceSortOrder.put(gameDirectoryId, FXCollections.observableArrayList(order));
}
}

/// Custom account sort order.
@SerializedName(PROPERTY_ACCOUNT_SORT_ORDER)
private final ObservableList<AccountID> accountSortOrder = FXCollections.observableArrayList();

/// Returns custom account sort order.
public List<AccountID> getAccountSortOrder() {
return List.copyOf(accountSortOrder);
}

/// Sets custom account sort order.
public void setAccountSortOrder(Collection<AccountID> order) {
this.accountSortOrder.setAll(order);
}

// Accounts

/// The preferred login type to use when the user wants to add an account.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@
public class AccountListItem extends RadioButton {

private final Account account;
private final DropHandler dropHandler;
private final StringProperty title = new SimpleStringProperty();
private final StringProperty subtitle = new SimpleStringProperty();

public AccountListItem(Account account) {
public AccountListItem(Account account, DropHandler dropHandler) {
this.account = account;
this.dropHandler = dropHandler;
getStyleClass().clear();
setUserData(account);

Expand Down Expand Up @@ -198,6 +200,15 @@ public Account getAccount() {
return account;
}

public DropHandler getDropHandler() {
return dropHandler;
}

@FunctionalInterface
public interface DropHandler {
void onDrop(AccountListItem draggedItem, AccountListItem targetItem, boolean afterTarget);
}

public String getTitle() {
return title.get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tooltip;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseButton;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
Expand All @@ -45,16 +50,37 @@
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.util.javafx.BindingMapping;

import java.util.Objects;

import static org.jackhuang.hmcl.util.i18n.I18n.i18n;

public final class AccountListItemSkin extends SkinBase<AccountListItem> {
private static final DataFormat ACCOUNT_LIST_ITEM = new DataFormat("application/x-hmcl-account-list-item");

private static AccountListItem draggingItem;

private boolean dragging;

public AccountListItemSkin(AccountListItem skinnable) {
super(skinnable);

BorderPane root = new BorderPane();
root.setCursor(Cursor.HAND);
FXUtils.onClicked(root, skinnable::fire);
if (Controllers.getDecorator() != null) {
Controllers.getDecorator().getDecorator().forbidDraggingWindow(root);
}
root.setOnMouseClicked(e -> {
if (dragging) {
dragging = false;
e.consume();
return;
}

if (e.getButton() == MouseButton.PRIMARY) {
skinnable.fire();
e.consume();
}
});

JFXRadioButton chkSelected = new JFXRadioButton();
chkSelected.setMouseTransparent(true);
Expand Down Expand Up @@ -175,9 +201,54 @@ public AccountListItemSkin(AccountListItem skinnable) {
root.setStyle("-fx-padding: 8 8 8 0;");
JFXDepthManager.setDepth(root, 1);

root.setOnDragDetected(e -> {
if (e.getButton() != MouseButton.PRIMARY) {
return;
}

dragging = true;
draggingItem = skinnable;

Dragboard dragboard = root.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
content.put(ACCOUNT_LIST_ITEM, skinnable.getAccount().getAccountID().toString());
dragboard.setContent(content);
e.consume();
});

root.setOnDragOver(e -> {
if (canAcceptDrop(e.getDragboard(), skinnable)) {
e.acceptTransferModes(TransferMode.MOVE);
e.consume();
}
});

root.setOnDragDropped(e -> {
boolean success = false;
if (canAcceptDrop(e.getDragboard(), skinnable)) {
skinnable.getDropHandler().onDrop(draggingItem, skinnable, e.getY() > root.getHeight() / 2);
success = true;
}
e.setDropCompleted(success);
e.consume();
});

root.setOnDragDone(e -> {
draggingItem = null;
e.consume();
});

getChildren().setAll(root);
}

private static boolean canAcceptDrop(Dragboard dragboard, AccountListItem targetItem) {
return draggingItem != null
&& targetItem != null
&& draggingItem != targetItem
&& dragboard.hasContent(ACCOUNT_LIST_ITEM)
&& Objects.equals(dragboard.getContent(ACCOUNT_LIST_ITEM), draggingItem.getAccount().getAccountID().toString());
}

/// Moves the account between local and user account files.
private static void moveAccount(AccountListItem skinnable) {
Account account = skinnable.getAccount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;

import java.util.ArrayList;
import java.util.Locale;

import static org.jackhuang.hmcl.setting.SettingsManager.userSettings;
Expand Down Expand Up @@ -87,10 +88,35 @@ public void changed(ObservableValue<? extends Boolean> o, Boolean oldValue, Bool
private final ObjectProperty<Account> selectedAccount;

public AccountListPage() {
items = MappedObservableList.create(accounts, AccountListItem::new);
items = MappedObservableList.create(accounts, account -> new AccountListItem(account, this::moveItem));
selectedAccount = createSelectedItemPropertyFor(items, Account.class);
}

private void moveItem(AccountListItem draggedItem, AccountListItem targetItem, boolean afterTarget) {
if (draggedItem == null || targetItem == null || draggedItem == targetItem) {
return;
}

ObservableList<Account> accountList = Accounts.getAccounts();
ArrayList<Account> reorderedAccounts = new ArrayList<>(accountList);
int draggedIndex = reorderedAccounts.indexOf(draggedItem.getAccount());
int targetIndex = reorderedAccounts.indexOf(targetItem.getAccount());
if (draggedIndex < 0 || targetIndex < 0 || draggedIndex == targetIndex) {
return;
}

Account draggedAccount = reorderedAccounts.remove(draggedIndex);
if (draggedIndex < targetIndex) {
targetIndex--;
}
if (afterTarget) {
targetIndex++;
}
reorderedAccounts.add(targetIndex, draggedAccount);
accountList.setAll(reorderedAccounts);
Accounts.setAccountSortOrder(reorderedAccounts);
}

public ObjectProperty<Account> selectedAccountProperty() {
return selectedAccount;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ public void capableDraggingWindow(Node node) {
}

public void forbidDraggingWindow(Node node) {
node.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> allowMove.set(false));
node.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> allowMove.set(false));
node.addEventHandler(MouseEvent.MOUSE_MOVED, e -> {
allowMove.set(false);
e.consume();
Expand Down
9 changes: 1 addition & 8 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,9 @@
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.*;
import org.jackhuang.hmcl.util.versioning.VersionNumber;

import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -124,11 +121,7 @@ public MainPage getMainPage() {

GameDirectoryManager.registerVersionsListener(repository -> {
GameDirectory gameDirectory = repository.getGameDirectory();
List<Version> children = repository.getVersions().parallelStream()
.filter(version -> !version.isHidden())
.sorted(Comparator
.comparing((Version version) -> Lang.requireNonNullElse(version.getReleaseTime(), Instant.EPOCH))
.thenComparing(version -> VersionNumber.asVersion(repository.getGameVersion(version).orElse(version.getId()))))
List<Version> children = repository.getDisplayVersions()
.collect(Collectors.toList());
runInFX(() -> {
if (gameDirectory == GameDirectoryManager.getSelectedGameDirectory())
Expand Down
Loading