#pragma once #include "core/domain/Project.h" #include #include #include #include #include #include #include #include #include #include class QKeyEvent; 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& 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& entities, const QString& projectDirAbs); 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 requestAddEntity(const core::Project::Entity& entity, const QImage& image); /// 创建实体:将裁剪 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& polyWorld); 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; 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; 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 polygonWorld; // 非空则使用 polygon QPainterPath pathWorld; // polygonWorld 对应的 world 路径(缓存,避免每帧重建) QVector 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=未校准 QPointF animatedOriginWorld; double animatedDepthScale01 = 0.5; // 编辑模式下实体被设为隐藏时:不响应点选且不绘制,除非当前选中(便于树选隐藏实体) bool hiddenInEditMode = false; }; int hitTestEntity(const QPointF& worldPos) const; 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 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; DragMode m_dragMode = DragMode::None; QPointF m_dragStartMouseWorld; QVector m_entities; QVector m_strokeWorld; int m_currentFrame = 0; // —— 创建实体:待确认多边形(可微调)—— QVector 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; };