From bdf1bffaef77afa1e4d2458ff7fc6d4b83e5b772 Mon Sep 17 00:00:00 2001 From: CalbootOnceMore Date: Fri, 1 May 2026 21:57:15 +0800 Subject: [PATCH 1/6] account order Co-authored-by: A01-opika --- .../org/jackhuang/hmcl/setting/Accounts.java | 6 +- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 12 +- .../hmcl/ui/account/AccountListItem.java | 9 +- .../hmcl/ui/account/AccountListItemSkin.java | 36 ++--- .../hmcl/ui/account/AccountListPage.java | 141 ++++++++++++++++-- 5 files changed, 169 insertions(+), 35 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 2aee028f1fa..6db65799c53 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -76,6 +76,9 @@ private Accounts() { public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(OAUTH_CALLBACK)); public static final List> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR); + // FX-Thread + public static boolean skipSelectionCheckFlag = false; + // ==== login type / account factory mapping ==== private static final Map> type2factory = new HashMap<>(); private static final Map, String> factory2type = new HashMap<>(); @@ -262,7 +265,7 @@ static void init() { } if (!globalConfig().isEnableOfflineAccount()) - accounts.addListener(new ListChangeListener() { + accounts.addListener(new ListChangeListener<>() { @Override public void onChanged(Change change) { while (change.next()) { @@ -280,6 +283,7 @@ public void onChanged(Change change) { selectedAccount.set(selected); InvalidationListener listener = o -> { + if (skipSelectionCheckFlag) return; // this method first checks whether the current selection is valid // if it's valid, the underlying storage will be updated // otherwise, the first account will be selected as an alternative(or null if accounts is empty) 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 fa65e748c4f..48816d22f19 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -39,14 +39,12 @@ import javafx.event.EventType; import javafx.geometry.Bounds; import javafx.geometry.Rectangle2D; -import javafx.scene.Cursor; -import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.Scene; +import javafx.scene.*; import javafx.scene.control.*; import javafx.scene.control.skin.VirtualFlow; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.image.WritableImage; import javafx.scene.input.*; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.Priority; @@ -1679,4 +1677,10 @@ public static void useJFXContextMenu(TextInputControl control) { e.consume(); }); } + + public static WritableImage takeSnapshot(Region node) { + SnapshotParameters snapShotParams = new SnapshotParameters(); + snapShotParams.setFill(Color.TRANSPARENT); + return node.snapshot(snapShotParams, new WritableImage((int) node.getWidth() + 10, (int) node.getHeight() + 10)); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java index fa5de7480a4..c61692d150f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java @@ -61,11 +61,14 @@ public class AccountListItem extends RadioButton { private final Account account; + private final AccountListPage page; + private final StringProperty title = new SimpleStringProperty(); private final StringProperty subtitle = new SimpleStringProperty(); - public AccountListItem(Account account) { + public AccountListItem(Account account, AccountListPage page) { this.account = account; + this.page = page; getStyleClass().clear(); setUserData(account); @@ -188,6 +191,10 @@ public Account getAccount() { return account; } + public AccountListPage getPage() { + return page; + } + public String getTitle() { return title.get(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java index c2551eff5e3..78635d2dda7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java @@ -27,6 +27,9 @@ import javafx.scene.control.Label; import javafx.scene.control.SkinBase; import javafx.scene.control.Tooltip; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.Dragboard; +import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; @@ -95,24 +98,10 @@ public AccountListItemSkin(AccountListItem skinnable) { spinnerMove.getStyleClass().add("small-spinner-pane"); btnMove.setOnAction(e -> { Account account = skinnable.getAccount(); - Accounts.getAccounts().remove(account); - if (account.isPortable()) { - account.setPortable(false); - if (!Accounts.getAccounts().contains(account)) - Accounts.getAccounts().add(account); - } else { - account.setPortable(true); - if (!Accounts.getAccounts().contains(account)) { - int idx = 0; - for (int i = Accounts.getAccounts().size() - 1; i >= 0; i--) { - if (Accounts.getAccounts().get(i).isPortable()) { - idx = i + 1; - break; - } - } - Accounts.getAccounts().add(idx, account); - } - } + int index = Accounts.getAccounts().indexOf(account); + Accounts.getAccounts().removeAll(account); + account.setPortable(!account.isPortable()); + Accounts.getAccounts().add(index, account); }); btnMove.getStyleClass().add("toggle-icon4"); if (skinnable.getAccount().isPortable()) { @@ -184,6 +173,17 @@ public AccountListItemSkin(AccountListItem skinnable) { root.setStyle("-fx-padding: 8 8 8 0;"); JFXDepthManager.setDepth(root, 1); + // Enable drag detection for reordering + root.setOnDragDetected(event -> { + if (skinnable.getPage().isSearching().get()) return; + Dragboard db = root.startDragAndDrop(TransferMode.MOVE); + ClipboardContent content = new ClipboardContent(); + content.putString(skinnable.getAccount().getIdentifier()); + db.setContent(content); + db.setDragView(FXUtils.takeSnapshot(root)); + event.consume(); + }); + getChildren().setAll(root); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java index bdcaff5f0a9..9595497eca6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -17,14 +17,14 @@ */ package org.jackhuang.hmcl.ui.account; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXTextField; +import com.jfoenix.effects.JFXDepthManager; +import javafx.animation.PauseTransition; +import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ListProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleListProperty; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.*; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; @@ -33,10 +33,17 @@ import javafx.scene.control.ScrollPane; import javafx.scene.control.Skin; import javafx.scene.control.Tooltip; +import javafx.scene.input.Dragboard; +import javafx.scene.input.TransferMode; +import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import javafx.util.Duration; import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; +import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount; +import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; @@ -45,6 +52,7 @@ import org.jackhuang.hmcl.ui.construct.ClassTitle; import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.LocaleUtils; import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.javafx.BindingMapping; @@ -53,6 +61,8 @@ import java.util.Locale; import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; +import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -68,7 +78,7 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco || globalConfig().isEnableOfflineAccount()) RESTRICTED.set(false); else - globalConfig().enableOfflineAccountProperty().addListener(new ChangeListener() { + globalConfig().enableOfflineAccountProperty().addListener(new ChangeListener<>() { @Override public void changed(ObservableValue o, Boolean oldValue, Boolean newValue) { if (newValue) { @@ -80,14 +90,42 @@ public void changed(ObservableValue o, Boolean oldValue, Bool } private final ObservableList items; + private final ObservableList displayedItems; private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage"))); private final ListProperty accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList()); private final ListProperty authServers = new SimpleListProperty<>(this, "authServers", FXCollections.observableArrayList()); private final ObjectProperty selectedAccount; + private final StringProperty searchingText = new SimpleStringProperty(this, "searchingText", ""); + private final BooleanBinding isSearching = Bindings.createBooleanBinding(() -> StringUtils.isNotBlank(searchingText.get()), searchingText); + public AccountListPage() { - items = MappedObservableList.create(accounts, AccountListItem::new); + items = MappedObservableList.create(accounts, (account) -> new AccountListItem(account, this)); + displayedItems = FXCollections.observableArrayList(items); selectedAccount = createSelectedItemPropertyFor(items, Account.class); + + InvalidationListener listener = (observable) -> { + String text = searchingText.get().toLowerCase(Locale.ROOT); + if (StringUtils.isBlank(text)) { + displayedItems.setAll(items); + return; + } + displayedItems.setAll( + items.stream().filter(item -> { + Account account = item.getAccount(); + String type = ""; + if (account instanceof MicrosoftAccount) type = "microsoft"; + else if (account instanceof OfflineAccount) type = "offline"; + else if (account instanceof AuthlibInjectorAccount) type = ((AuthlibInjectorAccount) account).getServer().getUrl().toLowerCase(Locale.ROOT); + return account.getCharacter().toLowerCase(Locale.ROOT).contains(text) + || account.getUsername().toLowerCase(Locale.ROOT).contains(text) + || account.getUUID().toString().contains(text) + || type.contains(text); + }).toList() + ); + }; + items.addListener(listener); + searchingText.addListener(listener); } public ObjectProperty selectedAccountProperty() { @@ -107,6 +145,10 @@ public ListProperty authServersProperty() { return authServers; } + public BooleanBinding isSearching() { + return isSearching; + } + @Override protected Skin createDefaultSkin() { return new AccountListPageSkin(this); @@ -204,6 +246,27 @@ public AccountListPageSkin(AccountListPage skinnable) { setLeft(scrollPane, addAuthServerItem); } + HBox searchBar = new HBox(); + { + JFXTextField searchField = new JFXTextField(); + searchField.setPromptText(i18n("search")); + HBox.setHgrow(searchField, Priority.ALWAYS); + PauseTransition pause = new PauseTransition(Duration.millis(100)); + pause.setOnFinished(e -> skinnable.searchingText.set(searchField.getText())); + searchField.textProperty().addListener((observable, oldValue, newValue) -> { + pause.setRate(1); + pause.playFromStart(); + }); + JFXButton btnClearSearch = createToolbarButton2(null, SVG.CLOSE, searchField::clear); + onEscPressed(searchField, btnClearSearch::fire); + + searchBar.getChildren().setAll(searchField, btnClearSearch); + searchBar.getStyleClass().add("card"); + searchBar.setSpacing(1); + VBox.setMargin(searchBar, new Insets(10, 10, 5, 10)); + JFXDepthManager.setDepth(searchBar, 1); + } + ScrollPane scrollPane = new ScrollPane(); VBox list = new VBox(); { @@ -213,13 +276,69 @@ public AccountListPageSkin(AccountListPage skinnable) { list.setSpacing(10); list.getStyleClass().add("card-list"); - Bindings.bindContent(list.getChildren(), skinnable.items); + list.setOnDragOver((event) -> { + if (event.getGestureSource() != list && event.getDragboard().hasString()) { + event.acceptTransferModes(TransferMode.MOVE); + } + event.consume(); + }); + + list.setOnDragDropped((event) -> { + Dragboard db = event.getDragboard(); + boolean success = false; + if (db.hasString()) { + String accountId = db.getString(); + int targetIndex = getTargetIndex(list, event.getY()); + + // Find the account in the original list + Account draggedAccount = null; + int sourceIndex = -1; + for (int i = 0; i < Accounts.getAccounts().size(); i++) { + if (Accounts.getAccounts().get(i).getIdentifier().equals(accountId)) { + draggedAccount = Accounts.getAccounts().get(i); + sourceIndex = i; + break; + } + } + + boolean selected = skinnable.selectedAccountProperty().get() == draggedAccount; + if (draggedAccount != null && sourceIndex != targetIndex) { + // Remove from old position + Accounts.skipSelectionCheckFlag = true; + Accounts.getAccounts().remove(sourceIndex); + // Insert at new position + int newIndex = targetIndex > sourceIndex ? targetIndex - 1 : targetIndex; + if (newIndex < 0) newIndex = 0; + if (newIndex > Accounts.getAccounts().size()) newIndex = Accounts.getAccounts().size(); + Accounts.getAccounts().add(newIndex, draggedAccount); + if (selected) skinnable.selectedAccountProperty().set(draggedAccount); + Accounts.skipSelectionCheckFlag = false; + success = true; + } + } + event.setDropCompleted(success); + event.consume(); + }); + + Bindings.bindContent(list.getChildren(), skinnable.displayedItems); scrollPane.setContent(list); FXUtils.smoothScrolling(scrollPane); + } + + setCenter(new VBox(searchBar, scrollPane)); + } - setCenter(scrollPane); + private int getTargetIndex(VBox list, double y) { + int index = 0; + for (int i = 0; i < list.getChildren().size(); i++) { + javafx.scene.Node child = list.getChildren().get(i); + if (child.getLayoutY() + child.getBoundsInParent().getHeight() / 2 > y) { + return i; + } + index = i + 1; } + return index; } } } From bcfc7e7627f1eaf72ec7497d2ad62a7c623a2725 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 31 May 2026 18:51:31 +0800 Subject: [PATCH 2/6] update --- .../hmcl/ui/account/AccountListPage.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java index 9595497eca6..4c373bfbc51 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -305,14 +305,17 @@ public AccountListPageSkin(AccountListPage skinnable) { if (draggedAccount != null && sourceIndex != targetIndex) { // Remove from old position Accounts.skipSelectionCheckFlag = true; - Accounts.getAccounts().remove(sourceIndex); - // Insert at new position - int newIndex = targetIndex > sourceIndex ? targetIndex - 1 : targetIndex; - if (newIndex < 0) newIndex = 0; - if (newIndex > Accounts.getAccounts().size()) newIndex = Accounts.getAccounts().size(); - Accounts.getAccounts().add(newIndex, draggedAccount); - if (selected) skinnable.selectedAccountProperty().set(draggedAccount); - Accounts.skipSelectionCheckFlag = false; + try { + Accounts.getAccounts().remove(sourceIndex); + // Insert at new position + int newIndex = targetIndex > sourceIndex ? targetIndex - 1 : targetIndex; + if (newIndex < 0) newIndex = 0; + if (newIndex > Accounts.getAccounts().size()) newIndex = Accounts.getAccounts().size(); + Accounts.getAccounts().add(newIndex, draggedAccount); + if (selected) skinnable.selectedAccountProperty().set(draggedAccount); + } finally { + Accounts.skipSelectionCheckFlag = false; + } success = true; } } From d0338f481c6450c22664d6287977dd93b576c1a2 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 31 May 2026 18:57:13 +0800 Subject: [PATCH 3/6] update --- .../org/jackhuang/hmcl/ui/account/AccountListItemSkin.java | 3 ++- .../java/org/jackhuang/hmcl/ui/account/AccountListPage.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java index 78635d2dda7..6187dff0031 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java @@ -99,7 +99,8 @@ public AccountListItemSkin(AccountListItem skinnable) { btnMove.setOnAction(e -> { Account account = skinnable.getAccount(); int index = Accounts.getAccounts().indexOf(account); - Accounts.getAccounts().removeAll(account); + if (index < 0) return; + Accounts.getAccounts().remove(index); account.setPortable(!account.isPortable()); Accounts.getAccounts().add(index, account); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java index 4c373bfbc51..c81ca1f052a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -105,11 +105,11 @@ public AccountListPage() { selectedAccount = createSelectedItemPropertyFor(items, Account.class); InvalidationListener listener = (observable) -> { - String text = searchingText.get().toLowerCase(Locale.ROOT); - if (StringUtils.isBlank(text)) { + if (StringUtils.isBlank(searchingText.get())) { displayedItems.setAll(items); return; } + String text = searchingText.get().toLowerCase(Locale.ROOT); displayedItems.setAll( items.stream().filter(item -> { Account account = item.getAccount(); From fa2100db05e91c1370fc599b8115275df4380652 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 31 May 2026 20:03:37 +0800 Subject: [PATCH 4/6] update --- .../org/jackhuang/hmcl/setting/Accounts.java | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 6db65799c53..6c0ca9d8efc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -150,12 +150,15 @@ private static void updateAccountStorages() { ArrayList> global = new ArrayList<>(); ArrayList> portable = new ArrayList<>(); - for (Account account : accounts) { + for (int i = 0, accountsSize = accounts.size(); i < accountsSize; i++) { + Account account = accounts.get(i); Map storage = getAccountStorage(account); - if (account.isPortable()) + if (account.isPortable()) { + storage.put("index", i); portable.add(storage); - else + } else { global.add(storage); + } } if (!global.equals(globalAccountStorages)) @@ -213,18 +216,6 @@ static void init() { loadGlobalAccountStorages(); // load accounts - Account selected = null; - for (Map storage : config().getAccountStorages()) { - Account account = parseAccount(storage); - if (account != null) { - account.setPortable(true); - accounts.add(account); - if (Boolean.TRUE.equals(storage.get("selected"))) { - selected = account; - } - } - } - for (Map storage : globalAccountStorages) { Account account = parseAccount(storage); if (account != null) { @@ -232,6 +223,27 @@ static void init() { } } + List unordered = new ArrayList<>(); + Account[] s = new Account[1]; + config().getAccountStorages() + .stream() + .sorted(Comparator.comparingInt(storage -> storage.get("index") instanceof Number n ? n.intValue() : -1)) + .forEachOrdered(storage -> { + Account account = parseAccount(storage); + if (account == null) return; + account.setPortable(true); + if (storage.get("index") instanceof Number n) { + accounts.add(n.intValue(), account); + } else { + unordered.add(account); + } + if (Boolean.TRUE.equals(storage.get("selected"))) { + s[0] = account; + } + }); + accounts.addAll(0, unordered); + Account selected = s[0]; + String selectedAccountIdentifier = config().getSelectedAccount(); if (selected == null && selectedAccountIdentifier != null) { boolean portable = true; From 39e760af4bb895774445e73eb6e9074669a85d76 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Mon, 1 Jun 2026 22:00:22 +0800 Subject: [PATCH 5/6] update --- .../java/org/jackhuang/hmcl/setting/Accounts.java | 15 ++++++++++++--- .../hmcl/ui/account/AccountListItemSkin.java | 13 ++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 6c0ca9d8efc..a02091b1ca7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -196,6 +196,14 @@ private static Account parseAccount(Map storage) { } } + private static int getAccountIndex(Map storage) { + if (storage.get("index") instanceof Number n) { + int i = n.intValue(); + return Math.max(i, -1); + } + return -1; + } + /** * Called when it's ready to load accounts from {@link ConfigHolder#config()}. */ @@ -227,13 +235,14 @@ static void init() { Account[] s = new Account[1]; config().getAccountStorages() .stream() - .sorted(Comparator.comparingInt(storage -> storage.get("index") instanceof Number n ? n.intValue() : -1)) + .sorted(Comparator.comparingInt(Accounts::getAccountIndex)) .forEachOrdered(storage -> { Account account = parseAccount(storage); if (account == null) return; account.setPortable(true); - if (storage.get("index") instanceof Number n) { - accounts.add(n.intValue(), account); + int i = getAccountIndex(storage); + if (i >= 0) { + accounts.add(Math.max(i, accounts.size()), account); } else { unordered.add(account); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java index 6187dff0031..92198df80cb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java @@ -100,9 +100,16 @@ public AccountListItemSkin(AccountListItem skinnable) { Account account = skinnable.getAccount(); int index = Accounts.getAccounts().indexOf(account); if (index < 0) return; - Accounts.getAccounts().remove(index); - account.setPortable(!account.isPortable()); - Accounts.getAccounts().add(index, account); + boolean selected = Accounts.getSelectedAccount() == account; + Accounts.skipSelectionCheckFlag = true; + try { + Accounts.getAccounts().remove(index); + account.setPortable(!account.isPortable()); + Accounts.getAccounts().add(index, account); + if (selected) Accounts.setSelectedAccount(account); + } finally { + Accounts.skipSelectionCheckFlag = false; + } }); btnMove.getStyleClass().add("toggle-icon4"); if (skinnable.getAccount().isPortable()) { From 1bf1a0b95358f135a736bed2dd98d19cbc8fbcf2 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Tue, 2 Jun 2026 22:27:54 +0800 Subject: [PATCH 6/6] update --- .../src/main/java/org/jackhuang/hmcl/setting/Accounts.java | 2 +- .../org/jackhuang/hmcl/ui/account/AccountListPage.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index a02091b1ca7..92d25b430a2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -242,7 +242,7 @@ static void init() { account.setPortable(true); int i = getAccountIndex(storage); if (i >= 0) { - accounts.add(Math.max(i, accounts.size()), account); + accounts.add(Math.min(i, accounts.size()), account); } else { unordered.add(account); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java index c81ca1f052a..7d3d80204cb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -277,6 +277,7 @@ public AccountListPageSkin(AccountListPage skinnable) { list.getStyleClass().add("card-list"); list.setOnDragOver((event) -> { + if (skinnable.isSearching().get()) return; if (event.getGestureSource() != list && event.getDragboard().hasString()) { event.acceptTransferModes(TransferMode.MOVE); } @@ -284,6 +285,7 @@ public AccountListPageSkin(AccountListPage skinnable) { }); list.setOnDragDropped((event) -> { + if (skinnable.isSearching().get()) return; Dragboard db = event.getDragboard(); boolean success = false; if (db.hasString()) { @@ -294,7 +296,7 @@ public AccountListPageSkin(AccountListPage skinnable) { Account draggedAccount = null; int sourceIndex = -1; for (int i = 0; i < Accounts.getAccounts().size(); i++) { - if (Accounts.getAccounts().get(i).getIdentifier().equals(accountId)) { + if (accountId.equals(Accounts.getAccounts().get(i).getIdentifier())) { draggedAccount = Accounts.getAccounts().get(i); sourceIndex = i; break; @@ -336,7 +338,8 @@ private int getTargetIndex(VBox list, double y) { int index = 0; for (int i = 0; i < list.getChildren().size(); i++) { javafx.scene.Node child = list.getChildren().get(i); - if (child.getLayoutY() + child.getBoundsInParent().getHeight() / 2 > y) { + var bounds = child.getBoundsInParent(); + if (bounds.getMinY() + bounds.getHeight() / 2 > y) { return i; } index = i + 1;