新增根据帧数控制可见性

This commit is contained in:
2026-04-09 17:30:55 +08:00
parent e116a9ec79
commit d67d7dc0c5
29 changed files with 4387 additions and 408 deletions

View File

@@ -0,0 +1,264 @@
#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

View File

@@ -0,0 +1,38 @@
#pragma once
#include "core/library/ResourceLibraryProvider.h"
#include <QDockWidget>
class QListWidget;
namespace gui {
class ResourceLibraryDock final : public QDockWidget {
Q_OBJECT
public:
explicit ResourceLibraryDock(QWidget* parent = nullptr);
~ResourceLibraryDock() override = default;
void setProviders(core::library::ResourceLibraryProvider* localProvider,
core::library::ResourceLibraryProvider* onlineProvider);
private:
void rebuildCombinedList();
void ensureOnlineLoaded();
void fetchOnlineNextPage();
private:
core::library::ResourceLibraryProvider* m_localProvider = nullptr;
core::library::ResourceLibraryProvider* m_onlineProvider = nullptr;
QVector<core::library::LibraryResource> m_localResources;
QVector<core::library::LibraryResource> m_onlineResources;
QListWidget* m_list = nullptr;
bool m_onlineBound = false;
bool m_onlineLoading = false;
bool m_onlineExhausted = false;
};
} // namespace gui