Files
hfut-bishe/client/gui/props/EntityPropertySection.cpp
T
2026-05-14 13:30:06 +08:00

431 lines
17 KiB
C++
Raw 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 "props/EntityPropertySection.h"
#include "params/ParamControls.h"
#include <QDoubleSpinBox>
#include "widgets/CompactNumericSpinBox.h"
#include "widgets/PropertyPanelControls.h"
#include <QFormLayout>
#include <QFrame>
#include <QHBoxLayout>
#include <QCheckBox>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QPushButton>
#include <QTextEdit>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>
namespace gui {
EntityPropertySection::EntityPropertySection(QWidget* parent)
: PropertySectionWidget(parent) {
auto* lay = new QVBoxLayout(this);
polishPropertyVBoxLayout(lay);
auto* form = new QFormLayout();
polishPropertyFormLayout(form);
m_name = new PropertyPanelLineEdit(this);
m_name->setPlaceholderText(QStringLiteral("显示名称"));
m_name->setToolTip({});
polishPropertyStretchyTextField(m_name);
form->addRow(QStringLiteral("名称"), m_name);
m_depth = new QLabel(QStringLiteral("-"), this);
m_distScale = new QLabel(QStringLiteral("-"), this);
for (QLabel* lab : {m_depth, m_distScale}) {
lab->setTextInteractionFlags(Qt::TextSelectableByMouse);
lab->setWordWrap(true);
}
form->addRow(QStringLiteral("深度"), m_depth);
form->addRow(QStringLiteral("距离缩放"), m_distScale);
m_centerLabel = new QLabel(QStringLiteral("中心"), this);
m_centerPosition = new Vec2DoubleParamControl(this);
m_centerPosition->setToolTip(
QStringLiteral("无父级:形心世界坐标。有父级:形心相对父级形心的偏移。"));
form->addRow(m_centerLabel, m_centerPosition);
m_relativeLabel = new QLabel(QStringLiteral("相对位置"), this);
m_relativeToOwnCenter = new Vec2DoubleParamControl(this);
m_relativeToOwnCenter->setToolTip(QStringLiteral("变换原点相对自身形心的偏移"));
form->addRow(m_relativeLabel, m_relativeToOwnCenter);
m_priority = new CompactIntSpinBox(this, 72);
m_priority->setRange(-1000000, 1000000);
m_priority->setSingleStep(1);
m_priority->setAccelerated(true);
m_priority->setToolTip(QStringLiteral("优先级越高越靠上。背景为 0,实体默认 1;同优先级按距离缩放/深度决定覆盖关系。"));
form->addRow(QStringLiteral("优先级"), m_priority);
m_userScale = new CompactDoubleSpinBox(this, 56);
m_userScale->setRange(0.05, 20.0);
m_userScale->setDecimals(3);
m_userScale->setSingleStep(0.05);
m_userScale->setValue(1.0);
m_userScale->setToolTip({});
form->addRow(QStringLiteral("整体缩放"), m_userScale);
m_ignoreDistanceScale = new PropertyPanelCheckBox(QStringLiteral("不受距离缩放影响"), this);
m_ignoreDistanceScale->setToolTip({});
form->addRow(QStringLiteral("距离缩放"), m_ignoreDistanceScale);
m_visible = new PropertyPanelCheckBox(QString(), this);
m_visible->setChecked(true);
m_visible->setToolTip({});
form->addRow(QStringLiteral("可见性"), m_visible);
m_spritePreview = new QLabel(this);
// 贴图预览必须稳定占位:若用 setPixmap(pm.scaled(label->size()))QLabel 的 sizeHint 会被 pixmap 反向撑大,
// 再触发布局扩张,形成“拖动刷新属性 → 预览越来越大”的正反馈。
// 因此固定预览区域尺寸,pixmap 仅按该尺寸缩放。
constexpr int kSpritePreviewW = 200;
constexpr int kSpritePreviewH = 120;
m_spritePreview->setFixedSize(kSpritePreviewW, kSpritePreviewH);
m_spritePreview->setFrameShape(QFrame::StyledPanel);
m_spritePreview->setAlignment(Qt::AlignCenter);
m_spritePreview->setText(QStringLiteral("无图像"));
form->addRow(QStringLiteral("贴图"), m_spritePreview);
m_btnEditSprite = new PropertyPanelPushButton(QStringLiteral("编辑"), this);
m_btnEditSprite->setToolTip(QStringLiteral("编辑当前帧贴图/逐帧动画"));
form->addRow(QString(), m_btnEditSprite);
lay->addLayout(form);
auto* sep = new QFrame(this);
sep->setObjectName(QStringLiteral("PropertySectionSeparator"));
sep->setFrameShape(QFrame::HLine);
lay->addWidget(sep);
m_introHeader = new QWidget(this);
polishPropertyStretchyTextField(m_introHeader);
auto* headLay = new QHBoxLayout(m_introHeader);
headLay->setContentsMargins(0, 6, 0, 2);
headLay->setSpacing(4);
m_introToggle = new QToolButton(m_introHeader);
m_introToggle->setAutoRaise(true);
m_introToggle->setToolButtonStyle(Qt::ToolButtonTextOnly);
m_introToggle->setCursor(Qt::PointingHandCursor);
m_introToggle->setToolTip(QStringLiteral("展开/折叠介绍"));
m_introToggle->setText(QStringLiteral("▶"));
m_introToggle->setFixedWidth(22);
m_introToggle->setFixedHeight(22);
auto* introTitleLab = new QLabel(QStringLiteral("介绍"), m_introHeader);
introTitleLab->setObjectName(QStringLiteral("PropertyPanelHeading"));
headLay->addWidget(m_introToggle, 0, Qt::AlignVCenter);
headLay->addWidget(introTitleLab, 1, Qt::AlignVCenter);
m_introContent = new QWidget(this);
polishPropertyStretchyTextField(m_introContent);
auto* introLay = new QVBoxLayout(m_introContent);
polishPropertyVBoxLayout(introLay);
introLay->setContentsMargins(0, 4, 0, 0);
m_introTitle = new PropertyPanelLineEdit(m_introContent);
m_introTitle->setPlaceholderText(QStringLiteral("标题"));
introLay->addWidget(new QLabel(QStringLiteral("标题"), m_introContent));
introLay->addWidget(m_introTitle);
m_introBody = new PropertyPanelTextEdit(m_introContent);
m_introBody->setPlaceholderText(QStringLiteral("正文"));
m_introBody->setMaximumHeight(80);
introLay->addWidget(new QLabel(QStringLiteral("正文"), m_introContent));
introLay->addWidget(m_introBody);
m_introImages = new QListWidget(m_introContent);
m_introImages->setObjectName(QStringLiteral("PropertyPanelListWidget"));
m_introImages->setMinimumHeight(56);
m_introImages->setToolTip({});
m_introImages->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_introImages->setWordWrap(true);
introLay->addWidget(new QLabel(QStringLiteral("配图"), m_introContent));
introLay->addWidget(m_introImages);
m_btnIntroAddImage = new PropertyPanelPushButton(QStringLiteral("添加"), m_introContent);
m_btnIntroRemoveImage = new PropertyPanelPushButton(QStringLiteral("删除"), m_introContent);
m_btnIntroAddImage->setFixedWidth(52);
m_btnIntroRemoveImage->setFixedWidth(52);
m_btnIntroAddImage->setToolTip(QStringLiteral("添加配图"));
m_btnIntroRemoveImage->setToolTip(QStringLiteral("删除当前选中的配图"));
auto* imgRow = new QHBoxLayout();
imgRow->setContentsMargins(0, 0, 0, 0);
imgRow->setSpacing(8);
imgRow->addWidget(m_btnIntroAddImage, 0);
imgRow->addWidget(m_btnIntroRemoveImage, 0);
imgRow->addStretch(1);
introLay->addLayout(imgRow);
m_introVideo = new PropertyPanelLineEdit(m_introContent);
m_introVideo->setPlaceholderText(QStringLiteral("可选:视频相对路径(如 assets/entities/xxx.mp4"));
introLay->addWidget(new QLabel(QStringLiteral("视频路径(预留)"), m_introContent));
introLay->addWidget(m_introVideo);
lay->addWidget(m_introHeader);
lay->addWidget(m_introContent);
lay->addStretch(1);
m_introSaveTimer = new QTimer(this);
m_introSaveTimer->setSingleShot(true);
connect(m_introSaveTimer, &QTimer::timeout, this, [this]() {
if (!m_introBulkUpdate) {
emit introContentEdited();
}
});
connect(m_introToggle, &QToolButton::clicked, this, [this]() {
setIntroSectionExpanded(!m_introContent->isVisible());
});
setIntroSectionExpanded(true);
connect(m_name, &QLineEdit::editingFinished, this, [this]() {
if (m_name) {
emit displayNameCommitted(m_name->text());
}
});
connect(m_centerPosition, &Vec2DoubleParamControl::valueChanged, this, &EntityPropertySection::centerPositionEdited);
connect(m_relativeToOwnCenter, &Vec2DoubleParamControl::valueChanged, this,
&EntityPropertySection::relativeToOwnCenterEdited);
connect(m_priority, qOverload<int>(&QSpinBox::valueChanged), this, &EntityPropertySection::priorityEdited);
connect(m_userScale, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &EntityPropertySection::userScaleEdited);
connect(m_ignoreDistanceScale, &QCheckBox::toggled, this, &EntityPropertySection::ignoreDistanceScaleToggled);
connect(m_visible, &QCheckBox::toggled, this, &EntityPropertySection::visibleToggled);
connect(m_btnEditSprite, &QPushButton::clicked, this, &EntityPropertySection::spriteEditRequested);
connect(m_introTitle, &QLineEdit::textChanged, this, [this](const QString&) { scheduleIntroPersist(); });
connect(m_introBody, &QTextEdit::textChanged, this, [this]() { scheduleIntroPersist(); });
connect(m_introVideo, &QLineEdit::textChanged, this, [this](const QString&) { scheduleIntroPersist(); });
connect(m_btnIntroAddImage, &QPushButton::clicked, this, &EntityPropertySection::introAddImageRequested);
connect(m_btnIntroRemoveImage, &QPushButton::clicked, this, [this]() {
if (!m_introImages || !m_introImages->currentItem()) {
return;
}
delete m_introImages->takeItem(m_introImages->currentRow());
scheduleIntroPersist();
});
}
void EntityPropertySection::setIntroSectionExpanded(bool expanded) {
if (!m_introContent || !m_introToggle) {
return;
}
m_introContent->setVisible(expanded);
m_introToggle->setText(expanded ? QStringLiteral("▼") : QStringLiteral("▶"));
}
void EntityPropertySection::scheduleIntroPersist() {
if (m_introBulkUpdate || !m_introSaveTimer) {
return;
}
m_introSaveTimer->start(400);
}
void EntityPropertySection::clearDisconnected() {
if (m_introSaveTimer) {
m_introSaveTimer->stop();
}
m_introBulkUpdate = true;
setEditingEnabled(false);
if (m_name) {
m_name->blockSignals(true);
m_name->clear();
m_name->blockSignals(false);
}
if (m_depth) m_depth->setText(QStringLiteral("-"));
if (m_distScale) m_distScale->setText(QStringLiteral("-"));
if (m_centerPosition) m_centerPosition->setValue(0, 0);
if (m_relativeToOwnCenter) m_relativeToOwnCenter->setValue(0, 0);
if (m_priority) {
m_priority->blockSignals(true);
m_priority->setValue(1);
m_priority->blockSignals(false);
}
if (m_centerLabel) m_centerLabel->setText(QStringLiteral("中心"));
if (m_relativeLabel) m_relativeLabel->setText(QStringLiteral("相对位置"));
if (m_userScale) {
m_userScale->blockSignals(true);
m_userScale->setValue(1.0);
m_userScale->blockSignals(false);
}
if (m_ignoreDistanceScale) {
m_ignoreDistanceScale->blockSignals(true);
m_ignoreDistanceScale->setChecked(false);
m_ignoreDistanceScale->blockSignals(false);
}
if (m_visible) {
m_visible->blockSignals(true);
m_visible->setChecked(true);
m_visible->blockSignals(false);
}
if (m_introTitle) {
m_introTitle->blockSignals(true);
m_introTitle->clear();
m_introTitle->blockSignals(false);
}
if (m_introBody) {
m_introBody->blockSignals(true);
m_introBody->clear();
m_introBody->blockSignals(false);
}
if (m_introVideo) {
m_introVideo->blockSignals(true);
m_introVideo->clear();
m_introVideo->blockSignals(false);
}
if (m_introImages) m_introImages->clear();
if (m_spritePreview) {
m_spritePreview->setPixmap(QPixmap());
m_spritePreview->setText(QStringLiteral("无图像"));
}
setIntroSectionExpanded(true);
m_introBulkUpdate = false;
}
void EntityPropertySection::applyState(const EntityPropertyUiState& s) {
if (m_introSaveTimer) {
m_introSaveTimer->stop();
}
m_introBulkUpdate = true;
setEditingEnabled(true);
if (m_name) {
m_name->blockSignals(true);
m_name->setText(s.displayName);
m_name->blockSignals(false);
}
if (m_depth) m_depth->setText(QString::number(s.depthZ));
if (m_distScale) m_distScale->setText(s.distanceScaleText);
if (m_centerLabel) {
m_centerLabel->setText(s.hasParent ? QStringLiteral("相对中心") : QStringLiteral("世界中心"));
}
if (m_relativeLabel) {
m_relativeLabel->setText(QStringLiteral("相对位置"));
}
if (m_centerPosition && !m_centerPosition->isActivelyEditing()) {
m_centerPosition->setValue(s.centerPosition.x(), s.centerPosition.y());
}
if (m_relativeToOwnCenter && !m_relativeToOwnCenter->isActivelyEditing()) {
m_relativeToOwnCenter->setValue(s.relativeToOwnCenter.x(), s.relativeToOwnCenter.y());
}
if (m_priority) {
m_priority->blockSignals(true);
m_priority->setValue(s.priority);
m_priority->blockSignals(false);
}
if (m_userScale) {
m_userScale->blockSignals(true);
m_userScale->setValue(s.userScale);
m_userScale->blockSignals(false);
}
if (m_ignoreDistanceScale) {
m_ignoreDistanceScale->blockSignals(true);
m_ignoreDistanceScale->setChecked(s.ignoreDistanceScale);
m_ignoreDistanceScale->blockSignals(false);
}
if (m_visible) {
m_visible->blockSignals(true);
m_visible->setChecked(s.visible);
m_visible->blockSignals(false);
}
if (m_spritePreview && !s.spritePreview.isNull()) {
const QSize target = m_spritePreview->contentsRect().size().isValid()
? m_spritePreview->contentsRect().size()
: m_spritePreview->size();
const quint64 key = static_cast<quint64>(s.spritePreview.cacheKey());
if (m_spritePreviewCacheKey != key || m_spritePreviewLastTargetSize != target) {
const QPixmap pm = QPixmap::fromImage(s.spritePreview);
m_spritePreview->setPixmap(
pm.scaled(target, Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_spritePreviewCacheKey = key;
m_spritePreviewLastTargetSize = target;
}
} else if (m_spritePreview) {
m_spritePreview->setPixmap(QPixmap());
m_spritePreview->setText(QStringLiteral("无图像"));
m_spritePreviewCacheKey = 0;
m_spritePreviewLastTargetSize = {};
}
if (m_introTitle) {
m_introTitle->blockSignals(true);
m_introTitle->setText(s.intro.title);
m_introTitle->blockSignals(false);
}
if (m_introBody) {
m_introBody->blockSignals(true);
m_introBody->setPlainText(s.intro.bodyText);
m_introBody->blockSignals(false);
}
if (m_introVideo) {
m_introVideo->blockSignals(true);
m_introVideo->setText(s.intro.videoPathRelative);
m_introVideo->blockSignals(false);
}
if (m_introImages) {
m_introImages->clear();
for (const QString& p : s.intro.imagePathsRelative) {
if (!p.isEmpty()) {
auto* it = new QListWidgetItem(p, m_introImages);
it->setData(Qt::UserRole, p);
}
}
}
m_introBulkUpdate = false;
}
void EntityPropertySection::appendIntroImagePath(const QString& relativePath) {
if (!m_introImages || relativePath.isEmpty()) {
return;
}
auto* it = new QListWidgetItem(relativePath, m_introImages);
it->setData(Qt::UserRole, relativePath);
scheduleIntroPersist();
}
core::EntityIntroContent EntityPropertySection::introSnapshot() const {
core::EntityIntroContent out;
if (m_introTitle) {
out.title = m_introTitle->text();
}
if (m_introBody) {
out.bodyText = m_introBody->toPlainText();
}
if (m_introVideo) {
out.videoPathRelative = m_introVideo->text().trimmed();
}
if (m_introImages) {
for (int i = 0; i < m_introImages->count(); ++i) {
const QString p = m_introImages->item(i)->data(Qt::UserRole).toString();
if (!p.isEmpty()) {
out.imagePathsRelative.push_back(p);
}
}
}
return out;
}
void EntityPropertySection::setEditingEnabled(bool on) {
if (m_name) m_name->setEnabled(on);
if (m_centerPosition) m_centerPosition->setEnabled(on);
if (m_relativeToOwnCenter) m_relativeToOwnCenter->setEnabled(on);
if (m_priority) m_priority->setEnabled(on);
if (m_userScale) m_userScale->setEnabled(on);
if (m_ignoreDistanceScale) m_ignoreDistanceScale->setEnabled(on);
if (m_visible) m_visible->setEnabled(on);
if (m_btnEditSprite) m_btnEditSprite->setEnabled(on);
if (m_introHeader) m_introHeader->setEnabled(on);
if (m_introToggle) m_introToggle->setEnabled(on);
if (m_introTitle) m_introTitle->setEnabled(on);
if (m_introBody) m_introBody->setEnabled(on);
if (m_introVideo) m_introVideo->setEnabled(on);
if (m_introImages) m_introImages->setEnabled(on);
if (m_btnIntroAddImage) m_btnIntroAddImage->setEnabled(on);
if (m_btnIntroRemoveImage) m_btnIntroRemoveImage->setEnabled(on);
}
} // namespace gui