增加预览页介绍信息显示
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include <QPaintEvent>
|
||||
#include <QPainterPath>
|
||||
#include <QWheelEvent>
|
||||
#include <QKeyEvent>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -163,6 +164,16 @@ double depthToScale01(int depthZ) {
|
||||
return static_cast<double>(d) / 255.0;
|
||||
}
|
||||
|
||||
// depth01 0..1 -> 原始距离乘子 0.5..1.5;calibMult>0 时除以创建时记录的基准,使「原位置」为 1.0
|
||||
double distanceScaleFromDepth01(double depth01, double calibMult) {
|
||||
const double d = std::clamp(depth01, 0.0, 1.0);
|
||||
const double raw = 0.5 + d * 1.0;
|
||||
if (calibMult > 0.0) {
|
||||
return raw / std::max(calibMult, 1e-6);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
QImage extractEntityImage(const QImage& bg, const QVector<QPointF>& polyWorld, QPointF& outTopLeftWorld) {
|
||||
if (bg.isNull() || polyWorld.size() < 3) {
|
||||
outTopLeftWorld = {};
|
||||
@@ -246,6 +257,15 @@ EditorCanvas::EditorCanvas(QWidget* parent)
|
||||
setMouseTracking(true);
|
||||
|
||||
m_previewEmitTimer.start();
|
||||
|
||||
m_presZoomTimer = new QTimer(this);
|
||||
m_presZoomTimer->setInterval(16);
|
||||
connect(m_presZoomTimer, &QTimer::timeout, this, &EditorCanvas::tickPresentationZoomAnimation);
|
||||
|
||||
m_presHoverTimer = new QTimer(this);
|
||||
m_presHoverTimer->setInterval(40);
|
||||
connect(m_presHoverTimer, &QTimer::timeout, this, &EditorCanvas::tickPresentationHoverAnimation);
|
||||
|
||||
updateCursor();
|
||||
}
|
||||
|
||||
@@ -254,6 +274,19 @@ void EditorCanvas::setPresentationPreviewMode(bool on) {
|
||||
return;
|
||||
}
|
||||
m_presentationPreviewMode = on;
|
||||
if (m_presZoomTimer) {
|
||||
m_presZoomTimer->stop();
|
||||
}
|
||||
if (m_presHoverTimer) {
|
||||
m_presHoverTimer->stop();
|
||||
}
|
||||
m_presHoverEntityIndex = -1;
|
||||
m_presFocusedEntityIndex = -1;
|
||||
m_presHoverPhase = 0.0;
|
||||
m_presZoomAnimT = 0.0;
|
||||
m_presZoomFinishingRestore = false;
|
||||
m_presBgPanSession = false;
|
||||
m_presBgDragDist = 0.0;
|
||||
if (on) {
|
||||
m_tool = Tool::Move;
|
||||
m_selectedEntity = -1;
|
||||
@@ -292,6 +325,7 @@ void EditorCanvas::setEntities(const QVector<core::Project::Entity>& entities, c
|
||||
core::sampleLocation(e.locationKeys, m_currentFrame, e.originWorld, KeyInterpolation::Linear);
|
||||
v.animatedOriginWorld = originWorld;
|
||||
v.cutoutPolygonWorld = e.cutoutPolygonWorld;
|
||||
v.distanceScaleCalibMult = e.distanceScaleCalibMult;
|
||||
|
||||
// 逐帧自动算 z:使用实体多边形质心作为锚点采样深度(O(1)),避免卡顿
|
||||
QVector<QPointF> polyTmp;
|
||||
@@ -308,7 +342,7 @@ void EditorCanvas::setEntities(const QVector<core::Project::Entity>& entities, c
|
||||
const double userScaleAnimated =
|
||||
core::sampleUserScale(e.userScaleKeys, m_currentFrame, e.userScale, core::KeyInterpolation::Linear);
|
||||
v.userScale = std::max(1e-6, userScaleAnimated);
|
||||
const double distScale = 0.5 + ds01 * 1.0; // 0..1 -> 0.5..1.5(深度距离)
|
||||
const double distScale = distanceScaleFromDepth01(ds01, e.distanceScaleCalibMult);
|
||||
const double scale = distScale * v.userScale;
|
||||
v.visualScale = scale;
|
||||
|
||||
@@ -412,8 +446,8 @@ double EditorCanvas::selectedDistanceScaleMultiplier() const {
|
||||
if (m_selectedEntity < 0 || m_selectedEntity >= m_entities.size()) {
|
||||
return 1.0;
|
||||
}
|
||||
const double ds01 = std::clamp(m_entities[m_selectedEntity].animatedDepthScale01, 0.0, 1.0);
|
||||
return 0.5 + ds01 * 1.0;
|
||||
const auto& ent = m_entities[m_selectedEntity];
|
||||
return distanceScaleFromDepth01(ent.animatedDepthScale01, ent.distanceScaleCalibMult);
|
||||
}
|
||||
|
||||
double EditorCanvas::selectedUserScale() const {
|
||||
@@ -427,6 +461,95 @@ double EditorCanvas::selectedCombinedScale() const {
|
||||
return selectedDistanceScaleMultiplier() * selectedUserScale();
|
||||
}
|
||||
|
||||
void EditorCanvas::tickPresentationHoverAnimation() {
|
||||
if (!m_presentationPreviewMode) {
|
||||
return;
|
||||
}
|
||||
m_presHoverPhase += 0.35;
|
||||
if (m_presHoverPhase > 6.28318530718) {
|
||||
m_presHoverPhase -= 6.28318530718;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void EditorCanvas::tickPresentationZoomAnimation() {
|
||||
m_presZoomAnimT += 0.16;
|
||||
qreal u = std::min(1.0, static_cast<qreal>(m_presZoomAnimT));
|
||||
u = 1.0 - std::pow(1.0 - u, 3.0);
|
||||
m_pan = m_presZoomFromPan + (m_presZoomToPan - m_presZoomFromPan) * u;
|
||||
m_scale = m_presZoomFromScale + (m_presZoomToScale - m_presZoomFromScale) * u;
|
||||
if (m_presZoomAnimT >= 1.0) {
|
||||
m_presZoomTimer->stop();
|
||||
m_pan = m_presZoomToPan;
|
||||
m_scale = m_presZoomToScale;
|
||||
if (m_presZoomFinishingRestore) {
|
||||
m_presFocusedEntityIndex = -1;
|
||||
m_presZoomFinishingRestore = false;
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void EditorCanvas::presentationComputeZoomTarget(int entityIndex, QPointF* outPan, qreal* outScale) const {
|
||||
if (!outPan || !outScale || entityIndex < 0 || entityIndex >= m_entities.size()) {
|
||||
return;
|
||||
}
|
||||
const Entity& ent = m_entities[entityIndex];
|
||||
QRectF bb;
|
||||
if (!ent.image.isNull()) {
|
||||
const QSizeF sz(ent.image.width() * ent.visualScale, ent.image.height() * ent.visualScale);
|
||||
bb = QRectF(ent.imageTopLeft, sz);
|
||||
} else if (!ent.polygonWorld.isEmpty()) {
|
||||
bb = pathFromWorldPolygon(ent.polygonWorld).boundingRect();
|
||||
} else {
|
||||
bb = ent.rect;
|
||||
}
|
||||
const QPointF c = bb.center();
|
||||
const qreal rw = std::max(1.0, bb.width());
|
||||
const qreal rh = std::max(1.0, bb.height());
|
||||
qreal s = std::min(static_cast<qreal>(width()) / (rw * 1.28), static_cast<qreal>(height()) / (rh * 1.28));
|
||||
s = std::clamp(s, 0.12, 14.0);
|
||||
*outScale = s;
|
||||
*outPan = QPointF(width() / 2.0, height() / 2.0) - c * s;
|
||||
}
|
||||
|
||||
void EditorCanvas::beginPresentationZoomTowardEntity(int entityIndex) {
|
||||
if (entityIndex < 0 || entityIndex >= m_entities.size()) {
|
||||
return;
|
||||
}
|
||||
if (m_presFocusedEntityIndex < 0) {
|
||||
m_presRestorePan = m_pan;
|
||||
m_presRestoreScale = m_scale;
|
||||
}
|
||||
m_presFocusedEntityIndex = entityIndex;
|
||||
m_presZoomFromPan = m_pan;
|
||||
m_presZoomFromScale = m_scale;
|
||||
presentationComputeZoomTarget(entityIndex, &m_presZoomToPan, &m_presZoomToScale);
|
||||
m_presZoomAnimT = 0.0;
|
||||
m_presZoomFinishingRestore = false;
|
||||
m_presZoomTimer->start();
|
||||
}
|
||||
|
||||
void EditorCanvas::beginPresentationZoomRestore() {
|
||||
m_presZoomFromPan = m_pan;
|
||||
m_presZoomFromScale = m_scale;
|
||||
m_presZoomToPan = m_presRestorePan;
|
||||
m_presZoomToScale = m_presRestoreScale;
|
||||
m_presZoomAnimT = 0.0;
|
||||
m_presZoomFinishingRestore = true;
|
||||
m_presZoomTimer->start();
|
||||
}
|
||||
|
||||
void EditorCanvas::clearPresentationEntityFocus() {
|
||||
emit presentationInteractionDismissed();
|
||||
if (m_presZoomFinishingRestore) {
|
||||
return;
|
||||
}
|
||||
if (m_presFocusedEntityIndex >= 0) {
|
||||
beginPresentationZoomRestore();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorCanvas::clearEntitySelection() {
|
||||
if (m_selectedEntity < 0) {
|
||||
return;
|
||||
@@ -598,7 +721,11 @@ void EditorCanvas::ensurePixmapLoaded() const {
|
||||
|
||||
void EditorCanvas::updateCursor() {
|
||||
if (m_presentationPreviewMode) {
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
if (m_presHoverEntityIndex >= 0) {
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
} else {
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (m_tool) {
|
||||
@@ -784,8 +911,18 @@ void EditorCanvas::paintEvent(QPaintEvent* e) {
|
||||
p.drawImage(target, ent.image);
|
||||
p.restore();
|
||||
} else {
|
||||
const QSizeF sz(ent.image.width() * ent.visualScale, ent.image.height() * ent.visualScale);
|
||||
const QRectF target(ent.imageTopLeft, sz);
|
||||
const qreal pop =
|
||||
(m_presentationPreviewMode && i == m_presFocusedEntityIndex) ? 1.1 : 1.0;
|
||||
const QSizeF sz0(ent.image.width() * ent.visualScale, ent.image.height() * ent.visualScale);
|
||||
QRectF target;
|
||||
if (pop > 1.001) {
|
||||
const QRectF orig(ent.imageTopLeft, sz0);
|
||||
const QPointF cen = orig.center();
|
||||
const QSizeF sz = orig.size() * pop;
|
||||
target = QRectF(QPointF(cen.x() - sz.width() * 0.5, cen.y() - sz.height() * 0.5), sz);
|
||||
} else {
|
||||
target = QRectF(ent.imageTopLeft, sz0);
|
||||
}
|
||||
p.drawImage(target, ent.image);
|
||||
}
|
||||
} else {
|
||||
@@ -853,6 +990,33 @@ void EditorCanvas::paintEvent(QPaintEvent* e) {
|
||||
p.drawRect(ent.rect.adjusted(-2, -2, 2, 2));
|
||||
}
|
||||
}
|
||||
if (m_presentationPreviewMode && !ent.hiddenInEditMode) {
|
||||
const bool showHover = (i == m_presHoverEntityIndex);
|
||||
const bool showFocus = (i == m_presFocusedEntityIndex);
|
||||
if (showHover || showFocus) {
|
||||
p.setBrush(Qt::NoBrush);
|
||||
if (showHover) {
|
||||
const qreal pulse = 0.45 + 0.55 * std::sin(static_cast<double>(m_presHoverPhase));
|
||||
const qreal lw =
|
||||
(2.0 + 2.8 * pulse) / std::max(static_cast<qreal>(m_scale), static_cast<qreal>(0.001));
|
||||
p.setPen(QPen(QColor(255, 210, 80, static_cast<int>(65 + 110 * pulse)), lw));
|
||||
if (!ent.pathWorld.isEmpty()) {
|
||||
p.drawPath(ent.pathWorld);
|
||||
} else {
|
||||
p.drawRect(ent.rect);
|
||||
}
|
||||
}
|
||||
if (showFocus) {
|
||||
const qreal lw = 2.8 / std::max(static_cast<qreal>(m_scale), static_cast<qreal>(0.001));
|
||||
p.setPen(QPen(QColor(255, 120, 40, 230), lw));
|
||||
if (!ent.pathWorld.isEmpty()) {
|
||||
p.drawPath(ent.pathWorld);
|
||||
} else {
|
||||
p.drawRect(ent.rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建实体:手绘轨迹预览(world 坐标)
|
||||
@@ -986,8 +1150,27 @@ void EditorCanvas::mousePressEvent(QMouseEvent* e) {
|
||||
emit hoveredWorldPosDepthChanged(wp0, z0);
|
||||
|
||||
if (m_presentationPreviewMode) {
|
||||
if (e->button() == Qt::LeftButton || e->button() == Qt::MiddleButton) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
const int hit = hitTestEntity(wp0);
|
||||
if (hit >= 0) {
|
||||
const auto& ent = m_entities[hit];
|
||||
const QPointF cWorld =
|
||||
ent.polygonWorld.isEmpty() ? ent.rect.center() : polygonCentroid(ent.polygonWorld);
|
||||
const QPointF anchorView = worldToView(cWorld);
|
||||
beginPresentationZoomTowardEntity(hit);
|
||||
emit presentationEntityIntroRequested(ent.id, anchorView);
|
||||
return;
|
||||
}
|
||||
m_dragging = true;
|
||||
m_presBgPanSession = true;
|
||||
m_presBgDragDist = 0.0;
|
||||
m_lastMouseView = e->position();
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
return;
|
||||
}
|
||||
if (e->button() == Qt::MiddleButton) {
|
||||
m_dragging = true;
|
||||
m_presBgPanSession = false;
|
||||
m_lastMouseView = e->position();
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
}
|
||||
@@ -1117,6 +1300,23 @@ void EditorCanvas::mouseMoveEvent(QMouseEvent* e) {
|
||||
}
|
||||
emit hoveredWorldPosDepthChanged(wp, z);
|
||||
|
||||
if (m_presentationPreviewMode) {
|
||||
const int h = hitTestEntity(wp);
|
||||
if (h != m_presHoverEntityIndex) {
|
||||
m_presHoverEntityIndex = h;
|
||||
updateCursor();
|
||||
update();
|
||||
}
|
||||
if (h >= 0) {
|
||||
if (m_presHoverTimer && !m_presHoverTimer->isActive()) {
|
||||
m_presHoverTimer->start();
|
||||
}
|
||||
} else if (m_presHoverTimer) {
|
||||
m_presHoverTimer->stop();
|
||||
m_presHoverPhase = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_dragging) {
|
||||
QWidget::mouseMoveEvent(e);
|
||||
return;
|
||||
@@ -1198,7 +1398,8 @@ void EditorCanvas::mouseMoveEvent(QMouseEvent* e) {
|
||||
|
||||
const double ds01 = depthToScale01(depthZ);
|
||||
ent.animatedDepthScale01 = ds01;
|
||||
const double newScale = (0.5 + ds01 * 1.0) * ent.userScale;
|
||||
const double newScale =
|
||||
distanceScaleFromDepth01(ds01, ent.distanceScaleCalibMult) * ent.userScale;
|
||||
ent.visualScale = newScale;
|
||||
if (m_dragPreviewActive) {
|
||||
m_dragScaleRatio = std::clamp(newScale / std::max(1e-6, m_dragScaleBase), 0.02, 50.0);
|
||||
@@ -1218,6 +1419,9 @@ void EditorCanvas::mouseMoveEvent(QMouseEvent* e) {
|
||||
// 平移画布
|
||||
if (m_tool == Tool::Move || (e->buttons() & Qt::MiddleButton) ||
|
||||
(m_presentationPreviewMode && (e->buttons() & Qt::LeftButton))) {
|
||||
if (m_presentationPreviewMode && m_presBgPanSession) {
|
||||
m_presBgDragDist += std::abs(deltaView.x()) + std::abs(deltaView.y());
|
||||
}
|
||||
m_pan += deltaView;
|
||||
update();
|
||||
return;
|
||||
@@ -1226,6 +1430,13 @@ void EditorCanvas::mouseMoveEvent(QMouseEvent* e) {
|
||||
|
||||
void EditorCanvas::mouseReleaseEvent(QMouseEvent* e) {
|
||||
if (e->button() == Qt::LeftButton || e->button() == Qt::MiddleButton) {
|
||||
if (m_presentationPreviewMode && e->button() == Qt::LeftButton) {
|
||||
if (m_presBgPanSession && m_presBgDragDist < 8.0) {
|
||||
clearPresentationEntityFocus();
|
||||
}
|
||||
m_presBgPanSession = false;
|
||||
m_presBgDragDist = 0.0;
|
||||
}
|
||||
if (m_tool == Tool::CreateEntity && e->button() == Qt::LeftButton && m_drawingEntity) {
|
||||
m_dragging = false;
|
||||
m_drawingEntity = false;
|
||||
@@ -1252,6 +1463,10 @@ void EditorCanvas::mouseReleaseEvent(QMouseEvent* e) {
|
||||
}
|
||||
const QPointF c = polygonCentroid(ent.cutoutPolygonWorld);
|
||||
ent.depth = m_depthImage8.isNull() ? 0 : sampleDepthAtPoint(m_depthImage8, c);
|
||||
{
|
||||
const double ds01 = depthToScale01(ent.depth);
|
||||
ent.distanceScaleCalibMult = 0.5 + ds01 * 1.0;
|
||||
}
|
||||
|
||||
// 抠实体:从原背景抠出,背景对应区域填黑
|
||||
QImage cutout;
|
||||
@@ -1325,3 +1540,12 @@ void EditorCanvas::wheelEvent(QWheelEvent* e) {
|
||||
e->accept();
|
||||
}
|
||||
|
||||
void EditorCanvas::keyPressEvent(QKeyEvent* e) {
|
||||
if (m_presentationPreviewMode && e->key() == Qt::Key_Escape) {
|
||||
clearPresentationEntityFocus();
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
QWidget::keyPressEvent(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
#include <QElapsedTimer>
|
||||
#include <QTimer>
|
||||
|
||||
class QKeyEvent;
|
||||
|
||||
class EditorCanvas final : public QWidget {
|
||||
Q_OBJECT
|
||||
@@ -51,6 +54,8 @@ public:
|
||||
// 预览呈现:完整背景 + 全部实体(忽略显隐开关),隐藏编辑辅助元素,仅可平移/缩放查看
|
||||
void setPresentationPreviewMode(bool on);
|
||||
bool presentationPreviewMode() const { return m_presentationPreviewMode; }
|
||||
/// 退出「点击实体放大」状态并平滑回到进入前的视图(预览模式)
|
||||
void clearPresentationEntityFocus();
|
||||
|
||||
void setEntities(const QVector<core::Project::Entity>& entities, const QString& projectDirAbs);
|
||||
void setCurrentFrame(int frame);
|
||||
@@ -79,6 +84,10 @@ signals:
|
||||
void requestMoveEntity(const QString& id, const QPointF& delta);
|
||||
void entityDragActiveChanged(bool on);
|
||||
void selectedEntityPreviewChanged(const QString& id, int depth, const QPointF& originWorld);
|
||||
/// 预览模式下点击实体:anchorView 为实体质心在视图中的位置,用于摆放介绍浮层
|
||||
void presentationEntityIntroRequested(const QString& entityId, QPointF anchorViewPoint);
|
||||
/// 预览模式下应关闭介绍层(空白处轻点、Esc、开始还原缩放时由画布侧触发)
|
||||
void presentationInteractionDismissed();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* e) override;
|
||||
@@ -87,6 +96,7 @@ protected:
|
||||
void mouseMoveEvent(QMouseEvent* e) override;
|
||||
void mouseReleaseEvent(QMouseEvent* e) override;
|
||||
void wheelEvent(QWheelEvent* e) override;
|
||||
void keyPressEvent(QKeyEvent* e) override;
|
||||
|
||||
private:
|
||||
void ensurePixmapLoaded() const;
|
||||
@@ -97,6 +107,12 @@ private:
|
||||
QPointF worldToView(const QPointF& w) const;
|
||||
QRectF worldRectOfBackground() const;
|
||||
|
||||
void tickPresentationZoomAnimation();
|
||||
void tickPresentationHoverAnimation();
|
||||
void beginPresentationZoomTowardEntity(int entityIndex);
|
||||
void beginPresentationZoomRestore();
|
||||
void presentationComputeZoomTarget(int entityIndex, QPointF* outPan, qreal* outScale) const;
|
||||
|
||||
private:
|
||||
struct Entity {
|
||||
QString id;
|
||||
@@ -112,6 +128,7 @@ private:
|
||||
QPointF imageTopLeft; // image 对应的 world 左上角
|
||||
double visualScale = 1.0; // 实体在 world 坐标下的缩放(用于贴图绘制)
|
||||
double userScale = 1.0; // 与深度距离缩放相乘
|
||||
double distanceScaleCalibMult = 0.0; // 与 Project::Entity 一致;0=未校准
|
||||
QPointF animatedOriginWorld;
|
||||
double animatedDepthScale01 = 0.5;
|
||||
// 编辑模式下实体被设为隐藏时:不响应点选且不绘制,除非当前选中(便于树选隐藏实体)
|
||||
@@ -175,5 +192,22 @@ private:
|
||||
QVector<QPointF> m_strokeWorld;
|
||||
|
||||
int m_currentFrame = 0;
|
||||
|
||||
// —— 预览展示:实体悬停动效、点击聚焦缩放 ——
|
||||
QTimer* m_presZoomTimer = nullptr;
|
||||
QTimer* m_presHoverTimer = nullptr;
|
||||
int m_presHoverEntityIndex = -1;
|
||||
int m_presFocusedEntityIndex = -1;
|
||||
qreal m_presHoverPhase = 0.0;
|
||||
QPointF m_presRestorePan;
|
||||
qreal m_presRestoreScale = 1.0;
|
||||
QPointF m_presZoomFromPan;
|
||||
QPointF m_presZoomToPan;
|
||||
qreal m_presZoomFromScale = 1.0;
|
||||
qreal m_presZoomToScale = 1.0;
|
||||
qreal m_presZoomAnimT = 0.0;
|
||||
bool m_presZoomFinishingRestore = false;
|
||||
bool m_presBgPanSession = false;
|
||||
qreal m_presBgDragDist = 0.0;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user