mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2026-06-29 01:54:20 +03:00
Merge 8d9b12d7bb into 62f537dd8d
This commit is contained in:
commit
1ec56b7f0b
4 changed files with 193 additions and 5 deletions
|
|
@ -145,6 +145,30 @@ QStringList InstanceList::getLinkedInstancesById(const QString& id) const
|
||||||
return linkedInstances;
|
return linkedInstances;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList InstanceList::groupOrder() const
|
||||||
|
{
|
||||||
|
return m_groupOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceList::moveGroup(const QString& group, int toIndex)
|
||||||
|
{
|
||||||
|
int fromIndex = m_groupOrder.indexOf(group);
|
||||||
|
if (fromIndex < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int size = m_groupOrder.size();
|
||||||
|
toIndex = qBound(0, toIndex, size);
|
||||||
|
if (fromIndex == toIndex)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString name = m_groupOrder.takeAt(fromIndex);
|
||||||
|
// After takeAt, insert position shifts if target was after the removed position
|
||||||
|
int insertAt = (fromIndex < toIndex) ? (toIndex - 1) : toIndex;
|
||||||
|
m_groupOrder.insert(insertAt, name);
|
||||||
|
emit groupOrderChanged();
|
||||||
|
saveGroupList();
|
||||||
|
}
|
||||||
|
|
||||||
int InstanceList::rowCount(const QModelIndex& parent) const
|
int InstanceList::rowCount(const QModelIndex& parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
|
|
@ -293,6 +317,8 @@ void InstanceList::deleteGroup(const GroupId& name)
|
||||||
}
|
}
|
||||||
if (removed)
|
if (removed)
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
|
m_groupOrder.removeAll(name);
|
||||||
|
emit groupOrderChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceList::renameGroup(const QString& src, const QString& dst)
|
void InstanceList::renameGroup(const QString& src, const QString& dst)
|
||||||
|
|
@ -318,6 +344,10 @@ void InstanceList::renameGroup(const QString& src, const QString& dst)
|
||||||
}
|
}
|
||||||
if (modified)
|
if (modified)
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
|
int idx = m_groupOrder.indexOf(src);
|
||||||
|
if (idx >= 0)
|
||||||
|
m_groupOrder[idx] = dst;
|
||||||
|
emit groupOrderChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InstanceList::isGroupCollapsed(const QString& group)
|
bool InstanceList::isGroupCollapsed(const QString& group)
|
||||||
|
|
@ -692,7 +722,11 @@ void InstanceList::increaseGroupCount(const QString& group)
|
||||||
if (group.isEmpty())
|
if (group.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
bool wasNew = !m_groupNameCache.contains(group);
|
||||||
++m_groupNameCache[group];
|
++m_groupNameCache[group];
|
||||||
|
if (wasNew && !m_groupOrder.contains(group)) {
|
||||||
|
m_groupOrder.append(group);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceList::decreaseGroupCount(const QString& group)
|
void InstanceList::decreaseGroupCount(const QString& group)
|
||||||
|
|
@ -703,6 +737,7 @@ void InstanceList::decreaseGroupCount(const QString& group)
|
||||||
if (--m_groupNameCache[group] < 1) {
|
if (--m_groupNameCache[group] < 1) {
|
||||||
m_groupNameCache.remove(group);
|
m_groupNameCache.remove(group);
|
||||||
m_collapsedGroups.remove(group);
|
m_collapsedGroups.remove(group);
|
||||||
|
m_groupOrder.removeAll(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -757,6 +792,16 @@ void InstanceList::saveGroupList()
|
||||||
ungrouped.insert("hidden", QJsonValue(true));
|
ungrouped.insert("hidden", QJsonValue(true));
|
||||||
toplevel.insert("ungrouped", ungrouped);
|
toplevel.insert("ungrouped", ungrouped);
|
||||||
}
|
}
|
||||||
|
// Save group order (deduplicate to prevent silent corruption)
|
||||||
|
QJsonArray orderArr;
|
||||||
|
QStringList savedOrder;
|
||||||
|
for (const auto& name : m_groupOrder) {
|
||||||
|
if (!savedOrder.contains(name) && (name.isEmpty() || m_groupNameCache.contains(name))) {
|
||||||
|
savedOrder.append(name);
|
||||||
|
orderArr.append(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toplevel.insert("groupOrder", orderArr);
|
||||||
QJsonDocument doc(toplevel);
|
QJsonDocument doc(toplevel);
|
||||||
try {
|
try {
|
||||||
FS::write(groupFileName, doc.toJson());
|
FS::write(groupFileName, doc.toJson());
|
||||||
|
|
@ -772,9 +817,13 @@ void InstanceList::loadGroupList()
|
||||||
|
|
||||||
QString groupFileName = m_instDir + "/instgroups.json";
|
QString groupFileName = m_instDir + "/instgroups.json";
|
||||||
|
|
||||||
// if there's no group file, fail
|
// if there's no group file, set up default state
|
||||||
if (!QFileInfo(groupFileName).exists())
|
if (!QFileInfo(groupFileName).exists()) {
|
||||||
|
m_groupOrder.clear();
|
||||||
|
m_groupOrder.append("");
|
||||||
|
m_groupsLoaded = true;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray jsonData;
|
QByteArray jsonData;
|
||||||
try {
|
try {
|
||||||
|
|
@ -815,6 +864,16 @@ void InstanceList::loadGroupList()
|
||||||
|
|
||||||
m_instanceGroupIndex.clear();
|
m_instanceGroupIndex.clear();
|
||||||
m_groupNameCache.clear();
|
m_groupNameCache.clear();
|
||||||
|
m_groupOrder.clear();
|
||||||
|
|
||||||
|
// Load saved group order FIRST, so increaseGroupCount won't re-add groups out of order
|
||||||
|
if (rootObj.value("groupOrder").isArray()) {
|
||||||
|
QJsonArray orderArr = rootObj.value("groupOrder").toArray();
|
||||||
|
for (const auto& val : orderArr) {
|
||||||
|
QString name = val.toString();
|
||||||
|
m_groupOrder.append(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate through all the groups.
|
// Iterate through all the groups.
|
||||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||||
|
|
@ -862,6 +921,23 @@ void InstanceList::loadGroupList()
|
||||||
// empty string represents ungrouped "group"
|
// empty string represents ungrouped "group"
|
||||||
m_collapsedGroups.insert("");
|
m_collapsedGroups.insert("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deduplicate group order (defense against previously-corrupted data)
|
||||||
|
QStringList uniqueOrder;
|
||||||
|
for (const auto& name : m_groupOrder) {
|
||||||
|
if (!uniqueOrder.contains(name))
|
||||||
|
uniqueOrder.append(name);
|
||||||
|
}
|
||||||
|
m_groupOrder = uniqueOrder;
|
||||||
|
|
||||||
|
// Treat ungrouped group (empty string) as a regular group for ordering
|
||||||
|
if (m_groupOrder.isEmpty()) {
|
||||||
|
m_groupOrder = m_groupNameCache.keys();
|
||||||
|
m_groupOrder.sort();
|
||||||
|
}
|
||||||
|
if (!m_groupOrder.contains(""))
|
||||||
|
m_groupOrder.append("");
|
||||||
|
|
||||||
m_groupsLoaded = true;
|
m_groupsLoaded = true;
|
||||||
qDebug() << "Group list loaded.";
|
qDebug() << "Group list loaded.";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,8 +158,11 @@ class InstanceList : public QAbstractListModel {
|
||||||
QMimeData* mimeData(const QModelIndexList& indexes) const override;
|
QMimeData* mimeData(const QModelIndexList& indexes) const override;
|
||||||
|
|
||||||
QStringList getLinkedInstancesById(const QString& id) const;
|
QStringList getLinkedInstancesById(const QString& id) const;
|
||||||
|
QStringList groupOrder() const;
|
||||||
|
void moveGroup(const QString& group, int toIndex);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void groupOrderChanged();
|
||||||
void dataIsInvalid();
|
void dataIsInvalid();
|
||||||
void instancesChanged();
|
void instancesChanged();
|
||||||
void instanceSelectRequest(QString instanceId);
|
void instanceSelectRequest(QString instanceId);
|
||||||
|
|
@ -195,6 +198,7 @@ class InstanceList : public QAbstractListModel {
|
||||||
std::vector<std::unique_ptr<BaseInstance>> m_instances;
|
std::vector<std::unique_ptr<BaseInstance>> m_instances;
|
||||||
// id -> refs
|
// id -> refs
|
||||||
QMap<QString, int> m_groupNameCache;
|
QMap<QString, int> m_groupNameCache;
|
||||||
|
QStringList m_groupOrder;
|
||||||
|
|
||||||
SettingsObject* m_globalSettings;
|
SettingsObject* m_globalSettings;
|
||||||
QString m_instDir;
|
QString m_instDir;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@
|
||||||
#include <QAccessible>
|
#include <QAccessible>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCache>
|
#include <QCache>
|
||||||
|
|
||||||
#include <QDrag>
|
#include <QDrag>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QListView>
|
#include <QListView>
|
||||||
|
|
@ -46,6 +47,7 @@
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPersistentModelIndex>
|
#include <QPersistentModelIndex>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
|
#include <QSet>
|
||||||
#include <QtMath>
|
#include <QtMath>
|
||||||
|
|
||||||
#include "VisualGroup.h"
|
#include "VisualGroup.h"
|
||||||
|
|
@ -202,7 +204,23 @@ void InstanceView::updateGeometries()
|
||||||
}
|
}
|
||||||
|
|
||||||
qDeleteAll(m_groups);
|
qDeleteAll(m_groups);
|
||||||
m_groups = cats.values();
|
m_groups.clear();
|
||||||
|
|
||||||
|
// Build ordered group list from custom group order
|
||||||
|
auto instanceList = APPLICATION->instances();
|
||||||
|
auto groupOrder = instanceList->groupOrder();
|
||||||
|
QSet<QString> placed;
|
||||||
|
for (const auto& groupName : groupOrder) {
|
||||||
|
if (cats.contains(groupName)) {
|
||||||
|
m_groups.append(cats.take(groupName));
|
||||||
|
placed.insert(groupName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Append remaining groups (not in order) alphabetically
|
||||||
|
auto remaining = cats.values();
|
||||||
|
std::sort(remaining.begin(), remaining.end(), [](VisualGroup* a, VisualGroup* b) { return a->text.localeAwareCompare(b->text) < 0; });
|
||||||
|
m_groups.append(remaining);
|
||||||
|
|
||||||
updateScrollbar();
|
updateScrollbar();
|
||||||
viewport()->update();
|
viewport()->update();
|
||||||
}
|
}
|
||||||
|
|
@ -283,12 +301,20 @@ void InstanceView::mousePressEvent(QMouseEvent* event)
|
||||||
m_pressedIndex = index;
|
m_pressedIndex = index;
|
||||||
m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex);
|
m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex);
|
||||||
m_pressedPosition = geometryPos;
|
m_pressedPosition = geometryPos;
|
||||||
|
m_draggingGroup = false;
|
||||||
|
m_draggedGroupName.clear();
|
||||||
|
m_groupDragTargetIndex = -1;
|
||||||
|
|
||||||
if (event->button() == Qt::LeftButton) {
|
if (event->button() == Qt::LeftButton) {
|
||||||
VisualGroup::HitResults hitResult;
|
VisualGroup::HitResults hitResult;
|
||||||
m_pressedCategory = categoryAt(geometryPos, hitResult);
|
m_pressedCategory = categoryAt(geometryPos, hitResult);
|
||||||
if (m_pressedCategory && hitResult & VisualGroup::CheckboxHit) {
|
if (m_pressedCategory && hitResult & VisualGroup::HeaderHit) {
|
||||||
setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState);
|
// Always allow drag from header, collapse also possible on click
|
||||||
|
m_draggingGroup = true;
|
||||||
|
m_draggedGroupName = m_pressedCategory->text;
|
||||||
|
if (hitResult & VisualGroup::CheckboxHit) {
|
||||||
|
setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState);
|
||||||
|
}
|
||||||
event->accept();
|
event->accept();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -325,6 +351,39 @@ void InstanceView::mouseMoveEvent(QMouseEvent* event)
|
||||||
QPoint visualPos = event->pos();
|
QPoint visualPos = event->pos();
|
||||||
QPoint geometryPos = event->pos() + offset();
|
QPoint geometryPos = event->pos() + offset();
|
||||||
|
|
||||||
|
if (m_draggingGroup) {
|
||||||
|
topLeft = m_pressedPosition - offset();
|
||||||
|
qreal dist = (topLeft - event->pos()).manhattanLength();
|
||||||
|
qreal threshold = QApplication::startDragDistance();
|
||||||
|
if (dist > threshold) {
|
||||||
|
// Drag preview: compute visual target index and show indicator
|
||||||
|
setState(NoState);
|
||||||
|
QPoint cursorPos = geometryPos;
|
||||||
|
m_groupDragTargetIndex = -1;
|
||||||
|
for (int i = 0; i < m_groups.size(); i++) {
|
||||||
|
if (m_groups[i]->text == m_draggedGroupName)
|
||||||
|
continue;
|
||||||
|
int gTop = m_groups[i]->verticalPosition();
|
||||||
|
int gBot = gTop + m_groups[i]->totalHeight();
|
||||||
|
int hMid = gTop + VisualGroup::headerHeight() / 2;
|
||||||
|
if (cursorPos.y() < hMid) {
|
||||||
|
m_groupDragTargetIndex = i;
|
||||||
|
break;
|
||||||
|
} else if (cursorPos.y() < gBot) {
|
||||||
|
m_groupDragTargetIndex = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_groupDragTargetIndex < 0)
|
||||||
|
m_groupDragTargetIndex = m_groups.size();
|
||||||
|
if (m_groupDragTargetIndex > m_groups.size())
|
||||||
|
m_groupDragTargetIndex = m_groups.size();
|
||||||
|
viewport()->update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (state() == ExpandingState || state() == CollapsingState) {
|
if (state() == ExpandingState || state() == CollapsingState) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -368,6 +427,35 @@ void InstanceView::mouseReleaseEvent(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
executeDelayedItemsLayout();
|
executeDelayedItemsLayout();
|
||||||
|
|
||||||
|
// Commit group drag reorder on release
|
||||||
|
if (m_draggingGroup && m_groupDragTargetIndex >= 0) {
|
||||||
|
auto groupOrder = APPLICATION->instances()->groupOrder();
|
||||||
|
int currentOrderIdx = groupOrder.indexOf(m_draggedGroupName);
|
||||||
|
if (currentOrderIdx >= 0) {
|
||||||
|
// Convert visual target index to groupOrder index
|
||||||
|
int orderTarget = 0;
|
||||||
|
for (int i = 0; i < m_groupDragTargetIndex && i < m_groups.size(); i++) {
|
||||||
|
if (m_groups[i]->text == m_draggedGroupName)
|
||||||
|
continue;
|
||||||
|
int orderPos = groupOrder.indexOf(m_groups[i]->text);
|
||||||
|
if (orderPos >= 0 && orderPos >= orderTarget)
|
||||||
|
orderTarget = orderPos + 1;
|
||||||
|
}
|
||||||
|
// Clamp to valid range
|
||||||
|
if (orderTarget > groupOrder.size())
|
||||||
|
orderTarget = groupOrder.size();
|
||||||
|
if (orderTarget != currentOrderIdx) {
|
||||||
|
APPLICATION->instances()->moveGroup(m_draggedGroupName, orderTarget);
|
||||||
|
updateGeometries();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_draggingGroup = false;
|
||||||
|
m_draggedGroupName.clear();
|
||||||
|
m_groupDragTargetIndex = -1;
|
||||||
|
|
||||||
QPoint visualPos = event->pos();
|
QPoint visualPos = event->pos();
|
||||||
QPoint geometryPos = event->pos() + offset();
|
QPoint geometryPos = event->pos() + offset();
|
||||||
QPersistentModelIndex index = indexAt(visualPos);
|
QPersistentModelIndex index = indexAt(visualPos);
|
||||||
|
|
@ -524,6 +612,21 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event)
|
||||||
option.rect = backup;
|
option.rect = backup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw group drag drop indicator
|
||||||
|
if (m_groupDragTargetIndex >= 0) {
|
||||||
|
int indicatorY = 0;
|
||||||
|
if (m_groupDragTargetIndex < m_groups.size()) {
|
||||||
|
indicatorY = m_groups[m_groupDragTargetIndex]->verticalPosition() - verticalOffset();
|
||||||
|
} else if (!m_groups.isEmpty()) {
|
||||||
|
auto* last = m_groups.last();
|
||||||
|
indicatorY = last->verticalPosition() + last->totalHeight() - verticalOffset();
|
||||||
|
}
|
||||||
|
painter.save();
|
||||||
|
painter.setPen(QPen(Qt::white, 1));
|
||||||
|
painter.drawLine(m_leftMargin, indicatorY, wpWidth - m_rightMargin, indicatorY);
|
||||||
|
painter.restore();
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < model()->rowCount(); ++i) {
|
for (int i = 0; i < model()->rowCount(); ++i) {
|
||||||
const QModelIndex index = model()->index(i, 0);
|
const QModelIndex index = model()->index(i, 0);
|
||||||
if (isIndexHidden(index)) {
|
if (isIndexHidden(index)) {
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,11 @@ class InstanceView : public QAbstractItemView {
|
||||||
QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag;
|
QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag;
|
||||||
QPoint m_lastDragPosition;
|
QPoint m_lastDragPosition;
|
||||||
|
|
||||||
|
// group drag-reorder state
|
||||||
|
bool m_draggingGroup = false;
|
||||||
|
QString m_draggedGroupName;
|
||||||
|
int m_groupDragTargetIndex = -1;
|
||||||
|
|
||||||
VisualGroup* category(const QModelIndex& index) const;
|
VisualGroup* category(const QModelIndex& index) const;
|
||||||
VisualGroup* category(const QString& cat) const;
|
VisualGroup* category(const QString& cat) const;
|
||||||
VisualGroup* categoryAt(const QPoint& pos, VisualGroup::HitResults& result) const;
|
VisualGroup* categoryAt(const QPoint& pos, VisualGroup::HitResults& result) const;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue