增加预览页介绍信息显示

This commit is contained in:
2026-04-08 09:56:25 +08:00
parent f53fee8e5a
commit 028ed1b18d
17 changed files with 1059 additions and 23 deletions

View File

@@ -0,0 +1,176 @@
#include "dialogs/EntityIntroPopup.h"
#include <QDir>
#include <QFileInfo>
#include <QGuiApplication>
#include <QLabel>
#include <QScreen>
#include <QPainter>
#include <QPaintEvent>
#include <QPixmap>
#include <QStyleOption>
#include <QTextEdit>
#include <QVBoxLayout>
namespace gui {
namespace {
constexpr int kMaxThumb = 200;
QString elideLine(const QString& s, int maxChars) {
if (s.size() <= maxChars) {
return s;
}
return s.left(maxChars - 1) + QChar(0x2026);
}
} // namespace
EntityIntroPopup::EntityIntroPopup(QWidget* parent)
: QWidget(parent) {
setAttribute(Qt::WA_TranslucentBackground, false);
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
auto* root = new QVBoxLayout(this);
root->setContentsMargins(14, 12, 14, 12);
root->setSpacing(8);
m_title = new QLabel(this);
QFont tf = m_title->font();
tf.setBold(true);
tf.setPointSizeF(tf.pointSizeF() + 1.5);
m_title->setFont(tf);
m_title->setWordWrap(true);
root->addWidget(m_title);
m_body = new QTextEdit(this);
m_body->setReadOnly(true);
m_body->setFrameShape(QFrame::NoFrame);
m_body->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_body->setMaximumHeight(180);
m_body->setPlaceholderText(QStringLiteral("(无)"));
root->addWidget(m_body);
m_imagesHost = new QWidget(this);
m_imagesLayout = new QVBoxLayout(m_imagesHost);
m_imagesLayout->setContentsMargins(0, 0, 0, 0);
m_imagesLayout->setSpacing(6);
root->addWidget(m_imagesHost);
m_videoHint = new QLabel(this);
m_videoHint->setWordWrap(true);
m_videoHint->setStyleSheet(QStringLiteral("QLabel { color: palette(mid); font-size: 11px; }"));
root->addWidget(m_videoHint);
setMinimumWidth(280);
setMaximumWidth(420);
hide();
}
void EntityIntroPopup::setProjectDir(const QString& absoluteProjectDir) {
m_projectDirAbs = absoluteProjectDir;
}
void EntityIntroPopup::setContent(const core::EntityIntroContent& content) {
const QString t = content.title.trimmed().isEmpty()
? QStringLiteral("介绍")
: content.title.trimmed();
m_title->setText(elideLine(t, 200));
m_body->setPlainText(content.bodyText);
while (QLayoutItem* it = m_imagesLayout->takeAt(0)) {
if (QWidget* w = it->widget()) {
w->deleteLater();
}
delete it;
}
for (const QString& rel : content.imagePathsRelative) {
if (rel.isEmpty()) {
continue;
}
const QString abs =
m_projectDirAbs.isEmpty() ? QString() : QDir(m_projectDirAbs).filePath(rel);
auto* lab = new QLabel(m_imagesHost);
lab->setAlignment(Qt::AlignCenter);
if (!abs.isEmpty() && QFileInfo::exists(abs)) {
QPixmap pm(abs);
if (!pm.isNull()) {
if (pm.width() > kMaxThumb || pm.height() > kMaxThumb) {
pm = pm.scaled(kMaxThumb, kMaxThumb, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
lab->setPixmap(pm);
} else {
lab->setText(QStringLiteral("(无法加载图片)"));
}
} else {
lab->setText(QStringLiteral("(缺少文件:%1").arg(rel));
}
lab->setWordWrap(true);
m_imagesLayout->addWidget(lab);
}
if (content.videoPathRelative.trimmed().isEmpty()) {
m_videoHint->setText(QStringLiteral("视频介绍"));
} else {
m_videoHint->setText(QStringLiteral("播放功能预留").arg(content.videoPathRelative));
}
adjustSize();
}
void EntityIntroPopup::showNearCanvasPoint(const QPoint& viewPos, QWidget* canvasWidget) {
if (!canvasWidget) {
return;
}
adjustSize();
const QPoint globalAnchor = canvasWidget->mapToGlobal(viewPos);
QScreen* scr = QGuiApplication::screenAt(globalAnchor);
if (!scr) {
scr = canvasWidget->screen();
}
const QRect screen = scr ? scr->availableGeometry() : QRect(0, 0, 1920, 1080);
int x = globalAnchor.x() + 18;
int y = globalAnchor.y() - height() / 3;
if (x + width() > screen.right()) {
x = globalAnchor.x() - width() - 18;
}
if (x < screen.left()) {
x = screen.left() + 8;
}
if (y + height() > screen.bottom()) {
y = screen.bottom() - height() - 8;
}
if (y < screen.top()) {
y = screen.top() + 8;
}
move(x, y);
show();
raise();
}
void EntityIntroPopup::clearAndHide() {
hide();
m_title->clear();
m_body->clear();
while (QLayoutItem* it = m_imagesLayout->takeAt(0)) {
if (QWidget* w = it->widget()) {
w->deleteLater();
}
delete it;
}
m_videoHint->clear();
}
void EntityIntroPopup::paintEvent(QPaintEvent* e) {
Q_UNUSED(e);
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, true);
const QRect r = rect().adjusted(1, 1, -1, -1);
p.fillRect(r, palette().base());
p.setPen(QPen(palette().mid().color(), 1));
p.drawRoundedRect(r, 8, 8);
}
} // namespace gui

View File

@@ -0,0 +1,37 @@
#pragma once
#include "core/domain/EntityIntro.h"
#include <QWidget>
class QLabel;
class QTextEdit;
class QVBoxLayout;
namespace gui {
/// 预览模式下在实体附近显示的介绍浮层(非模态,可随内容伸缩)
class EntityIntroPopup final : public QWidget {
Q_OBJECT
public:
explicit EntityIntroPopup(QWidget* parent = nullptr);
void setProjectDir(const QString& absoluteProjectDir);
void setContent(const core::EntityIntroContent& content);
void showNearCanvasPoint(const QPoint& viewPos, QWidget* canvasWidget);
void clearAndHide();
protected:
void paintEvent(QPaintEvent* e) override;
private:
QString m_projectDirAbs;
QLabel* m_title = nullptr;
QTextEdit* m_body = nullptr;
QWidget* m_imagesHost = nullptr;
QVBoxLayout* m_imagesLayout = nullptr;
QLabel* m_videoHint = nullptr;
};
} // namespace gui