This commit is contained in:
2026-05-14 13:30:06 +08:00
parent 974946cee4
commit e43171521d
91 changed files with 10485 additions and 3254 deletions
+67 -5
View File
@@ -2,13 +2,17 @@
#include "core/domain/Project.h"
#include <atomic>
#include <QByteArray>
#include <QJsonArray>
#include <QPixmap>
#include <QPoint>
#include <QPointF>
#include <QHash>
#include <QImage>
#include <QPainterPath>
#include <QRect>
#include <QVector>
#include <QWidget>
#include <QElapsedTimer>
@@ -22,7 +26,7 @@ class QDropEvent;
class EditorCanvas final : public QWidget {
Q_OBJECT
public:
enum class Tool { Move, Zoom, CreateEntity };
enum class Tool { Move, Zoom, CreateEntity, AddHotspot, MoveHotspot };
Q_ENUM(Tool)
explicit EditorCanvas(QWidget* parent = nullptr);
@@ -81,6 +85,13 @@ public:
/// 退出「点击实体放大」状态并平滑回到进入前的视图(预览模式)
void clearPresentationEntityFocus();
void setPresentationHotspots(const QVector<core::Project::PresentationHotspot>& hotspots);
void setPresentationHotspotEditorActive(bool on);
void setPresentationHotspotPlaybackTargetEnabled(bool on);
void setSelectedPresentationHotspotId(const QString& id);
QString selectedPresentationHotspotId() const { return m_selectedPresentationHotspotId; }
void clearPresentationHotspotSelection();
void setEntities(const QVector<core::Project::Entity>& entities,
const QVector<double>& opacities01,
const QString& projectDirAbs);
@@ -105,9 +116,13 @@ public:
void cancelBlackholeCopyResolve();
/// 背景图片文件内容被外部写盘更新(路径未变)时,强制重新加载缓存
void notifyBackgroundContentChanged();
/// 黑洞修复覆盖层(独立 PNG)更新,不修改 background 原图
void notifyBlackholeOverlaysChanged();
// 与动画求值一致的原点/缩放(用于 K 帧与自动关键帧)
/// 当前帧求值后的变换原点(枢轴),与关键帧/父子偏移一致
QPointF selectedAnimatedOriginWorld() const;
/// 同 selectedAnimatedOriginWorld(语义名)
QPointF selectedEntityPivotWorld() const;
double selectedDepthScale01() const;
QPointF selectedEntityCentroidWorld() const;
double selectedDistanceScaleMultiplier() const;
@@ -145,6 +160,13 @@ signals:
void presentationEntityIntroRequested(const QString& entityId, QPointF anchorViewPoint);
/// 预览模式下应关闭介绍层(空白处轻点、Esc、开始还原缩放时由画布侧触发)
void presentationInteractionDismissed();
/// 首张背景已可绘制,或整图同步路径已就绪(用于打开工程时关闭加载对话框)
void initialBackgroundLoadFinished();
void requestAddPresentationHotspot(const QPointF& centerWorld);
void requestAddPresentationHotspotForAnimation(const QPointF& centerWorld, const QString& animationId);
void presentationHotspotMoved(const QString& id, const QPointF& centerWorld);
void selectedPresentationHotspotChanged(const QString& id);
void hotspotPlayRequested(const QString& hotspotId);
protected:
void paintEvent(QPaintEvent* e) override;
@@ -161,6 +183,14 @@ protected:
private:
void ensurePixmapLoaded() const;
void invalidatePixmap();
/// libvips + 视口分块:滚轮放大只拉高可见区域的解码分辨率(非整图降采样)
void invalidateViewportLod();
void ensureBackgroundViewport();
void tryEmitInitialBackgroundLoadFinished();
QRectF visibleWorldRectF() const;
int backgroundLogicalWidth() const;
int backgroundLogicalHeight() const;
void updateCursor();
QPointF viewToWorld(const QPointF& v) const;
@@ -169,6 +199,10 @@ private:
bool isPointNearPendingVertex(const QPointF& worldPos, int* outIndex) const;
bool pendingPolygonContains(const QPointF& worldPos) const;
int hitTestPresentationHotspot(const QPointF& worldPos) const;
/// 预览 none:热点内「播」触点命中则返回热点下标,否则 -1
int hitTestHotspotPlayControl(const QPointF& worldPos) const;
void tickPresentationZoomAnimation();
void tickPresentationHoverAnimation();
void beginPresentationZoomTowardEntity(int entityIndex);
@@ -192,10 +226,13 @@ private:
double userScale = 1.0; // 与深度距离缩放相乘
double distanceScaleCalibMult = 0.0; // 与 Project::Entity 一致;0=未校准
bool ignoreDistanceScale = false;
int priority = 1;
QPointF animatedOriginWorld;
double animatedDepthScale01 = 0.5;
double opacity = 1.0; // 0..1(由可见性轨道求值)
bool blackholeVisible = true;
QString blackholeOverlayPath;
QRect blackholeOverlayRect;
};
int hitTestEntity(const QPointF& worldPos) const;
@@ -203,11 +240,30 @@ private:
struct ToolView {
core::Project::Tool tool;
double opacity = 1.0; // 0..1
int depthZ = 0;
};
private:
QString m_projectDirAbs;
QHash<QString, QImage> m_blackholeOverlayImageCache;
QString m_bgAbsPath;
bool m_backgroundVisible = true;
mutable QSize m_bgLogicalSize; // 原图逻辑像素(与项目/实体世界坐标一致)
mutable bool m_useViewportLod = false;
mutable QImage m_bgViewportImage; // 仅覆盖 m_bgViewportCacheRect
mutable QRect m_bgViewportCacheRect;
mutable bool m_bgViewportDirty = true;
/// 记录上次视口纹理对应的「世界→屏幕」像素密度,滚轮缩放后需换更高分辨率纹理
mutable qreal m_vpCachedDensity = 0.0;
/// 递增以使进行中的后台解码结果作废(路径变更 / 缩放 / 平移需刷新纹理)
std::atomic<uint32_t> m_vpToken{0};
std::atomic<bool> m_vpDecodeInFlight{false};
/// 全图缩略解码成功后置位,下一帧强制区域解码以尽快变清晰
mutable bool m_vpPreferRegionDecode = false;
bool m_initialBgLoadFinishedEmitted = false;
QImage m_bhCopyPatch; // 黑洞「复制背景」预览:原图子区域
QPoint m_bhCopyPatchOrigin;
bool m_bhCopyPatchValid = false;
mutable QPixmap m_bgPixmap;
mutable bool m_pixmapDirty = true;
mutable QImage m_bgImage; // 原背景(用于抠图/填充)
@@ -226,6 +282,12 @@ private:
bool m_gridVisible = true;
bool m_checkerboardVisible = true;
bool m_presentationPreviewMode = false;
bool m_presentationHotspotEditorActive = false;
bool m_presentationHotspotPlaybackTargetEnabled = false;
QVector<core::Project::PresentationHotspot> m_presentationHotspots;
QString m_selectedPresentationHotspotId;
int m_draggingHotspotIndex = -1;
QPointF m_hotspotDragOffsetWorld;
Tool m_tool = Tool::Move;
EntityCreateSegmentMode m_entityCreateSegmentMode = EntityCreateSegmentMode::Manual;
@@ -236,9 +298,9 @@ private:
bool m_draggingEntity = false;
bool m_drawingEntity = false;
QPointF m_lastMouseView;
// 拖动以“实体原点 animatedOriginWorld”为基准,避免因缩放导致 rect/topLeft 抖动
QPointF m_entityDragOffsetOriginWorld;
QPointF m_entityDragStartAnimatedOrigin;
// 拖动锚点(局部坐标,相对形心):解决拖动过程中距离缩放变化导致的“抖动/闪烁”
QPointF m_entityDragAnchorLocal;
QPointF m_entityDragStartCentroidWorld;
// 拖动性能优化:拖动过程中不逐点修改 polygonWorld,而是保留基准形状+增量参数,在 paint 时做变换预览
bool m_dragPreviewActive = false;
QVector<QPointF> m_dragPolyBase;