273 lines
11 KiB
C++
273 lines
11 KiB
C++
#pragma once
|
||
|
||
#include "core/domain/Project.h"
|
||
|
||
#include <QByteArray>
|
||
#include <QJsonArray>
|
||
#include <QPixmap>
|
||
#include <QPointF>
|
||
#include <QImage>
|
||
#include <QPainterPath>
|
||
#include <QVector>
|
||
#include <QWidget>
|
||
#include <QElapsedTimer>
|
||
#include <QTimer>
|
||
|
||
class QKeyEvent;
|
||
class QDragEnterEvent;
|
||
class QDragMoveEvent;
|
||
class QDropEvent;
|
||
|
||
class EditorCanvas final : public QWidget {
|
||
Q_OBJECT
|
||
public:
|
||
enum class Tool { Move, Zoom, CreateEntity };
|
||
Q_ENUM(Tool)
|
||
|
||
explicit EditorCanvas(QWidget* parent = nullptr);
|
||
|
||
void setBackgroundImagePath(const QString& absolutePath);
|
||
QString backgroundImagePath() const { return m_bgAbsPath; }
|
||
void setBackgroundVisible(bool on);
|
||
bool backgroundVisible() const { return m_backgroundVisible; }
|
||
|
||
void setDepthMapPath(const QString& absolutePath);
|
||
void setDepthOverlayEnabled(bool on);
|
||
bool depthOverlayEnabled() const { return m_depthOverlayEnabled; }
|
||
|
||
void setTool(Tool tool);
|
||
Tool tool() const { return m_tool; }
|
||
|
||
/// 创建实体的分割方式(在「创」工具被选中后再次单击弹出面板选择)。
|
||
enum class EntityCreateSegmentMode { Manual, Snap, Sam };
|
||
Q_ENUM(EntityCreateSegmentMode)
|
||
void setEntityCreateSegmentMode(EntityCreateSegmentMode m);
|
||
EntityCreateSegmentMode entityCreateSegmentMode() const { return m_entityCreateSegmentMode; }
|
||
|
||
/// 进入/更新「待确认」实体多边形(允许用户在画布上微调)。
|
||
void setPendingEntityPolygonWorld(const QVector<QPointF>& polyWorld);
|
||
void clearPendingEntityPolygon();
|
||
bool hasPendingEntityPolygon() const { return m_pendingPolyWorld.size() >= 3; }
|
||
|
||
void resetView();
|
||
void zoomToFit();
|
||
|
||
void setWorldAxesVisible(bool on);
|
||
bool worldAxesVisible() const { return m_worldAxesVisible; }
|
||
|
||
void setAxisLabelsVisible(bool on);
|
||
bool axisLabelsVisible() const { return m_axisLabelsVisible; }
|
||
|
||
void setGizmoLabelsVisible(bool on);
|
||
bool gizmoLabelsVisible() const { return m_gizmoLabelsVisible; }
|
||
|
||
void setGridVisible(bool on);
|
||
bool gridVisible() const { return m_gridVisible; }
|
||
|
||
void setCheckerboardVisible(bool on);
|
||
bool checkerboardVisible() const { return m_checkerboardVisible; }
|
||
|
||
// 预览呈现:完整背景 + 全部实体(忽略显隐开关),隐藏编辑辅助元素,仅可平移/缩放查看
|
||
void setPresentationPreviewMode(bool on);
|
||
bool presentationPreviewMode() const { return m_presentationPreviewMode; }
|
||
/// 退出「点击实体放大」状态并平滑回到进入前的视图(预览模式)
|
||
void clearPresentationEntityFocus();
|
||
|
||
void setEntities(const QVector<core::Project::Entity>& entities,
|
||
const QVector<double>& opacities01,
|
||
const QString& projectDirAbs);
|
||
void setTools(const QVector<core::Project::Tool>& tools, const QVector<double>& opacities01);
|
||
void setTempHiddenIds(const QSet<QString>& entityIds, const QSet<QString>& toolIds);
|
||
void setCurrentFrame(int frame);
|
||
int currentFrame() const { return m_currentFrame; }
|
||
|
||
bool isDraggingEntity() const { return m_draggingEntity; }
|
||
|
||
void selectEntityById(const QString& id);
|
||
void clearEntitySelection();
|
||
|
||
// 与动画求值一致的原点/缩放(用于 K 帧与自动关键帧)
|
||
QPointF selectedAnimatedOriginWorld() const;
|
||
double selectedDepthScale01() const;
|
||
QPointF selectedEntityCentroidWorld() const;
|
||
double selectedDistanceScaleMultiplier() const;
|
||
double selectedUserScale() const;
|
||
double selectedCombinedScale() const;
|
||
|
||
enum class DragMode { None, Free, AxisX, AxisY };
|
||
|
||
signals:
|
||
void hoveredWorldPosChanged(const QPointF& worldPos);
|
||
void hoveredWorldPosDepthChanged(const QPointF& worldPos, int depthZ);
|
||
void selectedEntityChanged(bool hasSelection, const QString& id, int depth, const QPointF& originWorld);
|
||
void selectedToolChanged(bool hasSelection, const QString& id, const QPointF& originWorld);
|
||
void requestAddEntity(const core::Project::Entity& entity, const QImage& image);
|
||
void requestAddTool(const core::Project::Tool& tool);
|
||
/// 创建实体:将裁剪 RGB、标记叠加层与 SAM 提示发往模型服务,由主窗口收 JSON 轮廓后再 addEntity。
|
||
void requestSamSegment(
|
||
const QByteArray& cropRgbPng,
|
||
const QByteArray& overlayPng,
|
||
const QPointF& cropTopLeftWorld,
|
||
const QJsonArray& pointCoords,
|
||
const QJsonArray& pointLabels,
|
||
const QJsonArray& boxXyxy);
|
||
/// 待确认实体:用户按回车/点击空白处确认后触发(由主窗口弹窗命名并落盘)。
|
||
void requestFinalizePendingEntity(const QVector<QPointF>& polyWorld);
|
||
void requestMoveEntity(const QString& id, const QPointF& delta);
|
||
void requestMoveTool(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;
|
||
void resizeEvent(QResizeEvent* e) override;
|
||
void mousePressEvent(QMouseEvent* e) override;
|
||
void mouseMoveEvent(QMouseEvent* e) override;
|
||
void mouseReleaseEvent(QMouseEvent* e) override;
|
||
void wheelEvent(QWheelEvent* e) override;
|
||
void keyPressEvent(QKeyEvent* e) override;
|
||
void dragEnterEvent(QDragEnterEvent* e) override;
|
||
void dragMoveEvent(QDragMoveEvent* e) override;
|
||
void dropEvent(QDropEvent* e) override;
|
||
|
||
private:
|
||
void ensurePixmapLoaded() const;
|
||
void invalidatePixmap();
|
||
void updateCursor();
|
||
|
||
QPointF viewToWorld(const QPointF& v) const;
|
||
QPointF worldToView(const QPointF& w) const;
|
||
QRectF worldRectOfBackground() const;
|
||
bool isPointNearPendingVertex(const QPointF& worldPos, int* outIndex) const;
|
||
bool pendingPolygonContains(const QPointF& worldPos) 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;
|
||
QRectF rect; // world 坐标(用于拖拽与约束)
|
||
QVector<QPointF> polygonWorld; // 非空则使用 polygon
|
||
QPainterPath pathWorld; // polygonWorld 对应的 world 路径(缓存,避免每帧重建)
|
||
QVector<QPointF> cutoutPolygonWorld;
|
||
QColor color;
|
||
|
||
// 实体独立信息:
|
||
int depth = 0; // 0..255,来自划分区域平均深度
|
||
QImage image; // 抠图后的实体图像(带透明)
|
||
QPointF imageTopLeft; // image 对应的 world 左上角
|
||
double visualScale = 1.0; // 实体在 world 坐标下的缩放(用于贴图绘制)
|
||
double userScale = 1.0; // 与深度距离缩放相乘
|
||
double distanceScaleCalibMult = 0.0; // 与 Project::Entity 一致;0=未校准
|
||
bool ignoreDistanceScale = false;
|
||
QPointF animatedOriginWorld;
|
||
double animatedDepthScale01 = 0.5;
|
||
double opacity = 1.0; // 0..1(由可见性轨道求值)
|
||
};
|
||
int hitTestEntity(const QPointF& worldPos) const;
|
||
|
||
private:
|
||
struct ToolView {
|
||
core::Project::Tool tool;
|
||
double opacity = 1.0; // 0..1
|
||
};
|
||
|
||
private:
|
||
QString m_bgAbsPath;
|
||
bool m_backgroundVisible = true;
|
||
mutable QPixmap m_bgPixmap;
|
||
mutable bool m_pixmapDirty = true;
|
||
mutable QImage m_bgImage; // 原背景(用于抠图/填充)
|
||
mutable QImage m_bgImageCutout; // 抠图后的背景(实体区域填黑)
|
||
mutable bool m_bgImageDirty = true;
|
||
mutable bool m_bgCutoutDirty = true;
|
||
|
||
QString m_depthAbsPath;
|
||
mutable QImage m_depthImage8;
|
||
mutable bool m_depthDirty = true;
|
||
bool m_depthOverlayEnabled = false;
|
||
int m_depthOverlayAlpha = 110;
|
||
bool m_worldAxesVisible = true;
|
||
bool m_axisLabelsVisible = true;
|
||
bool m_gizmoLabelsVisible = true;
|
||
bool m_gridVisible = true;
|
||
bool m_checkerboardVisible = true;
|
||
bool m_presentationPreviewMode = false;
|
||
|
||
Tool m_tool = Tool::Move;
|
||
EntityCreateSegmentMode m_entityCreateSegmentMode = EntityCreateSegmentMode::Manual;
|
||
qreal m_scale = 1.0;
|
||
QPointF m_pan; // world 原点对应的 view 坐标偏移(view = world*scale + pan)
|
||
|
||
bool m_dragging = false;
|
||
bool m_draggingEntity = false;
|
||
bool m_drawingEntity = false;
|
||
QPointF m_lastMouseView;
|
||
// 拖动以“实体原点 animatedOriginWorld”为基准,避免因缩放导致 rect/topLeft 抖动
|
||
QPointF m_entityDragOffsetOriginWorld;
|
||
QPointF m_entityDragStartAnimatedOrigin;
|
||
// 拖动性能优化:拖动过程中不逐点修改 polygonWorld,而是保留基准形状+增量参数,在 paint 时做变换预览
|
||
bool m_dragPreviewActive = false;
|
||
QVector<QPointF> m_dragPolyBase;
|
||
QPainterPath m_dragPathBase;
|
||
QPointF m_dragImageTopLeftBase;
|
||
QRectF m_dragRectBase;
|
||
QPointF m_dragOriginBase;
|
||
QPointF m_dragDelta; // 纯平移
|
||
QPointF m_dragCentroidBase;
|
||
double m_dragScaleBase = 1.0; // 拖动开始时的 visualScale
|
||
double m_dragScaleRatio = 1.0; // 相对 m_dragScaleBase 的缩放比(由深度重算驱动)
|
||
QElapsedTimer m_previewEmitTimer;
|
||
qint64 m_lastPreviewEmitMs = 0;
|
||
qint64 m_lastDepthScaleRecalcMs = 0;
|
||
int m_selectedEntity = -1;
|
||
int m_selectedTool = -1;
|
||
bool m_draggingTool = false;
|
||
QPointF m_toolDragOffsetOriginWorld;
|
||
QPointF m_toolDragStartOriginWorld;
|
||
|
||
DragMode m_dragMode = DragMode::None;
|
||
QPointF m_dragStartMouseWorld;
|
||
|
||
QVector<Entity> m_entities;
|
||
QVector<ToolView> m_tools;
|
||
QSet<QString> m_tempHiddenEntityIds;
|
||
QSet<QString> m_tempHiddenToolIds;
|
||
QVector<QPointF> m_strokeWorld;
|
||
|
||
int m_currentFrame = 0;
|
||
|
||
// —— 创建实体:待确认多边形(可微调)——
|
||
QVector<QPointF> m_pendingPolyWorld;
|
||
bool m_pendingDragging = false;
|
||
bool m_pendingDragWhole = false;
|
||
int m_pendingDragVertex = -1;
|
||
QPointF m_pendingLastMouseWorld;
|
||
|
||
// —— 预览展示:实体悬停动效、点击聚焦缩放 ——
|
||
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;
|
||
};
|
||
|