#include "library/ResourceLibraryDock.h" #include "core/library/EntityJson.h" #include "core/library/ToolJson.h" #include #include #include #include #include #include #include #include #include #include #include namespace gui { namespace { constexpr const char* kMimeType = "application/x-hfut-resource+json"; QPixmap makePreviewPixmap(const core::library::LibraryResource& r) { const QSize sz = r.imageSize.isValid() ? r.imageSize : QSize(200, 200); QImage img(sz, QImage::Format_ARGB32_Premultiplied); img.fill(Qt::transparent); QPainter p(&img); p.setRenderHint(QPainter::Antialiasing, true); QRectF rr(QPointF(0, 0), QSizeF(sz)); rr = rr.adjusted(10, 10, -10, -10); p.setPen(QPen(QColor(0, 0, 0, 50), 2)); p.setBrush(QBrush(r.accent)); p.drawRoundedRect(rr, 18, 18); p.setPen(QColor(255, 255, 255, 240)); QFont f = p.font(); f.setPointSize(std::max(9, f.pointSize())); f.setBold(true); p.setFont(f); p.drawText(rr.adjusted(12, 12, -12, -12), Qt::AlignLeft | Qt::AlignTop, r.displayName); return QPixmap::fromImage(img); } class ResourceListWidget final : public QListWidget { public: explicit ResourceListWidget(QWidget* parent = nullptr) : QListWidget(parent) { setSelectionMode(QAbstractItemView::SingleSelection); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragOnly); setDefaultDropAction(Qt::CopyAction); setUniformItemSizes(false); setSpacing(6); setViewMode(QListView::IconMode); setResizeMode(QListView::Adjust); // 注意:Static 会让拖拽“完全没反应”(不会启动 drag)。 // 这里用 Free,仍然不会在列表内部重排(DragOnly),但允许拖出到画布。 setMovement(QListView::Free); setIconSize(QSize(96, 96)); setWordWrap(true); // “一行两个资源”:用网格尺寸 + 固定列宽近似实现 setGridSize(QSize(180, 140)); } void setResources(const QVector& r) { m_resources = r; } protected: void startDrag(Qt::DropActions supportedActions) override { Q_UNUSED(supportedActions); auto* item = currentItem(); if (!item) { return; } bool ok = false; const int idx = item->data(Qt::UserRole).toInt(&ok); if (!ok || idx < 0 || idx >= m_resources.size()) { return; } const auto& res = m_resources[idx]; QJsonObject payload; payload.insert(QStringLiteral("resourceId"), res.resourceId); payload.insert(QStringLiteral("displayName"), res.displayName); payload.insert(QStringLiteral("kind"), res.kind == core::library::LibraryResource::Kind::Tool ? QStringLiteral("tool") : QStringLiteral("entity")); if (res.kind == core::library::LibraryResource::Kind::Tool) { payload.insert(QStringLiteral("tool"), core::library::toolToJson(res.toolTemplate)); } else { payload.insert(QStringLiteral("entity"), core::library::entityToJson(res.entityTemplate)); } { QJsonArray a; a.append(res.imageSize.width()); a.append(res.imageSize.height()); payload.insert(QStringLiteral("imageSize"), a); } { QJsonArray a; a.append(res.accent.red()); a.append(res.accent.green()); a.append(res.accent.blue()); a.append(res.accent.alpha()); payload.insert(QStringLiteral("accent"), a); } { QJsonArray a; a.append(res.imageOffsetFromOrigin.x()); a.append(res.imageOffsetFromOrigin.y()); payload.insert(QStringLiteral("imageOffsetFromOrigin"), a); } const QByteArray bytes = QJsonDocument(payload).toJson(QJsonDocument::Compact); auto* mime = new QMimeData(); mime->setData(QString::fromUtf8(kMimeType), bytes); auto* drag = new QDrag(this); drag->setMimeData(mime); drag->setPixmap(item->icon().pixmap(iconSize())); drag->setHotSpot(QPoint(iconSize().width() / 2, iconSize().height() / 2)); drag->exec(Qt::CopyAction); } private: QVector m_resources; }; } // namespace ResourceLibraryDock::ResourceLibraryDock(QWidget* parent) : QDockWidget(QStringLiteral("资源库"), parent) { setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); auto* shell = new QWidget(this); auto* layout = new QVBoxLayout(shell); layout->setContentsMargins(8, 8, 8, 8); layout->setSpacing(6); m_list = new ResourceListWidget(shell); layout->addWidget(m_list, 1); if (m_list && m_list->verticalScrollBar()) { connect(m_list->verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int value) { if (!m_list || !m_list->verticalScrollBar()) return; if (!m_onlineProvider || !m_onlineBound || m_onlineLoading || m_onlineExhausted) return; const int maxV = m_list->verticalScrollBar()->maximum(); // 接近底部(阈值 80px):自动加载下一页 if (value >= maxV - 80) { fetchOnlineNextPage(); } }); } setWidget(shell); } void ResourceLibraryDock::setProviders(core::library::ResourceLibraryProvider* localProvider, core::library::ResourceLibraryProvider* onlineProvider) { if (m_localProvider == localProvider && m_onlineProvider == onlineProvider) { return; } if (m_localProvider) disconnect(m_localProvider, nullptr, this, nullptr); if (m_onlineProvider) disconnect(m_onlineProvider, nullptr, this, nullptr); m_localProvider = localProvider; m_onlineProvider = onlineProvider; m_onlineExhausted = false; auto bind = [&](core::library::ResourceLibraryProvider* p, QVector& store, bool isOnline) { if (!p) { store.clear(); return; } connect(p, &core::library::ResourceLibraryProvider::resourcesReady, this, [this, &store, isOnline](const QVector& res) { if (!isOnline) { store = res; } else { m_onlineLoading = false; if (res.isEmpty()) { m_onlineExhausted = true; return; } store += res; } rebuildCombinedList(); }); connect(p, &core::library::ResourceLibraryProvider::resourcesFailed, this, [this, isOnline](const QString& err) { Q_UNUSED(err); if (isOnline) { m_onlineLoading = false; // 在线错误默认不打扰 UI;下次滚动到底仍可重试(除非 provider 选择返回空 ready)。 } }); }; bind(m_localProvider, m_localResources, false); bind(m_onlineProvider, m_onlineResources, true); if (m_localProvider) { m_localProvider->fetchResourcesAsync(); } else { m_localResources.clear(); } rebuildCombinedList(); ensureOnlineLoaded(); } void ResourceLibraryDock::ensureOnlineLoaded() { if (m_onlineBound) { return; } m_onlineBound = true; if (!m_onlineProvider) { return; } fetchOnlineNextPage(); } void ResourceLibraryDock::fetchOnlineNextPage() { if (!m_onlineProvider) { return; } if (m_onlineLoading) { return; } if (m_onlineExhausted) { return; } m_onlineLoading = true; // provider 当前仍是预留实现;这里保持“分页”调用语义(多次调用 fetchResourcesAsync) m_onlineProvider->fetchResourcesAsync(); } void ResourceLibraryDock::rebuildCombinedList() { if (!m_list) return; auto* list = static_cast(m_list); list->clear(); QVector combined; combined.reserve(m_localResources.size() + m_onlineResources.size()); combined += m_localResources; combined += m_onlineResources; list->setResources(combined); for (int i = 0; i < combined.size(); ++i) { const auto& r = combined[i]; auto* it = new QListWidgetItem(); it->setFlags(it->flags() | Qt::ItemIsDragEnabled); it->setText(r.displayName); it->setIcon(QIcon(makePreviewPixmap(r))); it->setData(Qt::UserRole, i); it->setToolTip(r.resourceId); list->addItem(it); } } } // namespace gui