Files
hfut-bishe/client/gui/library/ResourceLibraryDock.cpp

265 lines
9.0 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "library/ResourceLibraryDock.h"
#include "core/library/EntityJson.h"
#include "core/library/ToolJson.h"
#include <QApplication>
#include <QDrag>
#include <QJsonArray>
#include <QJsonDocument>
#include <QListWidget>
#include <QMimeData>
#include <QPainter>
#include <QVBoxLayout>
#include <QWidget>
#include <QScrollBar>
#include <algorithm>
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<core::library::LibraryResource>& 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<core::library::LibraryResource> 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<core::library::LibraryResource>& store,
bool isOnline) {
if (!p) {
store.clear();
return;
}
connect(p, &core::library::ResourceLibraryProvider::resourcesReady,
this, [this, &store, isOnline](const QVector<core::library::LibraryResource>& 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<ResourceListWidget*>(m_list);
list->clear();
QVector<core::library::LibraryResource> 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