#include "stdafx.h" #include "ItemEraser.h" #include "SigmaDoc.h" #include "SigmaView.h" #include "ActionListItem.h" #include "ActionComboItem.h" #include "ActionAddItem.h" #include "ActionDeleteItem.h" #include "clipper2/clipper.h" #undef min #undef max CItemEraser::CItemEraser(CSigmaDoc* ppDoc) : CItem(ppDoc) { m_nodeDrawer = std::make_unique(ppDoc); m_token = ppDoc->GetView()->GetViewChangedListener().AddListener([this] { isViewChanged = true; }); } CItemEraser::~CItemEraser() { GetDoc()->GetView()->GetViewChangedListener().RemoveListener(m_token); } void CItemEraser::OnLButtonDblClk(UINT nFlags, CPoint point) { } void CItemEraser::DrawAssistant(CDC* pDC, int mouseX, int mouseY) { if (isViewChanged) { DrawCurveNode(pDC); isViewChanged = false; } DrawEraser(pDC, mouseX, mouseY, m_eraseRadius); } void CItemEraser::OnLButtonDown(CDC *pDC, UINT nFlags, CPoint point, int vk) { m_start = true; m_path.clear(); } void CItemEraser::OnLButtonUp(CDC* pDC, UINT nFlags, CPoint point, int vk) { // 根据需求,用户单击的时候要删除与当前框相交的部分,单击的时候 m_path 为空,我们这时只需要将当前点存到 m_path 中即可 if (m_path.empty()) { CPoint2D realPoint = GetDC()->GetReal(point); m_path.push_back(realPoint); } Erase(); DrawCurveNode(pDC); DrawEraser(pDC, point.x, point.y, m_eraseRadius); m_start = false; } void CItemEraser::OnRButtonDown(UINT nFlags, CPoint point) { } BOOL CItemEraser::OnRButtonUp(UINT nFlags, CPoint point) { return TRUE; } void CItemEraser::OnMouseWheel(uint32_t button, int32_t clicks, int32_t x, int32_t y, int32_t delta) { ResetLast(-1, -1, -1); } int CItemEraser::OnMouseMove(CDC* pDC, UINT nFlags, CPoint point) { auto realPoint = GetDC()->GetReal(point); if (m_start) { m_path.push_back(realPoint); // 如果是 Relink 模式,我们等用户松开鼠标才开始删除 if (m_mode != EraserMode::Relink && m_path.size() > 5) { Erase(); // 光标移动其实都是跳得移动的,这里如果不把最后一个点双重新放回去,可能会导致有一小段直接被跳过 CPoint2D last = m_path[m_path.size() - 1]; m_path.clear(); m_path.push_back(last); } } DrawAssistant(pDC, point.x, point.y); return 0; } BOOL CItemEraser::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { return FALSE; } BOOL CItemEraser::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { switch (nChar) { case VK_OEM_COMMA: ZoomEraseRadius(-ZOOM_VALUE); ResetLast(-1, -1, -1); Redraw(); break; case VK_OEM_PERIOD: ZoomEraseRadius(ZOOM_VALUE); ResetLast(-1, -1, -1); Redraw(); break; } return FALSE; } void CItemEraser::OnDraw(CXyDC* pXyDC, CDC* pDC) { } void CItemEraser::OnCancel(void) { } EraserMode CItemEraser::GetEraserMode() const { return m_mode; } void CItemEraser::SetEraserMode(EraserMode mode) { m_mode = mode; } int CItemEraser::GetEraserRadius() const { return m_eraseRadius; } void CItemEraser::SetEraserRadius(int radius) { if (radius <= 0) { return; } m_eraseRadius = radius; } void CItemEraser::Erase() { CSigmaDoc* pDoc = GetDoc(); CXy* pXy = pDoc->GetDraw(); std::unique_ptr pCurve = GetEraserPath(); if (pCurve == nullptr) { return; } Eraser eraser; eraser.SetMode(m_mode); eraser.SetXY(pXy); eraser.Cut(*pCurve); SetAction(eraser.m_plAdd, eraser.m_plDel); Redraw(); } void CItemEraser::SetAction(const CPositionList& plAdd, const CPositionList& plDel) { CSigmaDoc* pDoc = GetDoc(); auto pAction = std::make_unique(pDoc, 0); if (plDel.GetCount() > 0) { auto pDelAction = std::make_unique(pDoc, IDS_STRING_ACTION_DELETE, plDel); pDelAction->Do(); pAction->AddAction(pDelAction.release()); } if (plAdd.GetCount() > 0) { auto pAddAction = std::make_unique(pDoc, IDS_STRING_ACTION_ADD, plAdd); pAddAction->Do(); pAction->AddAction(pAddAction.release()); } if (pAction->GetCount() > 0) { pDoc->SetActionItem(pAction.release()); } } void CItemEraser::Redraw() { GetView()->Notify(redraw); } void CItemEraser::DrawCircle(CDC* pDC, int centerX, int centerY, int radius) { CRect rect; rect.left = centerX - radius; rect.top = centerY - radius; rect.right = centerX + radius; rect.bottom = centerY + radius; CPen pen(PS_SOLID, 3, m_penColor); CPen* pOldPen = pDC->SelectObject(&pen); pDC->Rectangle(rect); pDC->SelectObject(pOldPen); } void CItemEraser::DrawEraser(CDC* pDC, int centerX, int centerY, int radius) { int nOldROP2 = pDC->SetROP2(R2_NOTXORPEN); if (m_lastX > 0 && m_lastY > 0) { DrawCircle(pDC, m_lastX, m_lastY, m_lastRadius); ResetLast(-1, -1, -1); } DrawCircle(pDC, centerX, centerY, radius); ResetLast(centerX, centerY, radius); pDC->SetROP2(nOldROP2); } void CItemEraser::DrawCurveNode(CDC* pDC) { CXyElementFilter filter; filter.addType(DOUBLEFOX_CURVE); CXy* pXy = GetDoc()->m_pXy; CPositionList select; pXy->GetElement(filter, select); m_nodeDrawer->Draw(pDC, select); } void CItemEraser::ResetLast(int x, int y, int radius) { m_lastX = x; m_lastY = y; m_lastRadius = radius; } RTree* CItemEraser::GetSpatialTree() { if (m_rtree == nullptr) { CXyElementFilter filter;; filter.addType(DOUBLEFOX_CURVE); m_rtree = BuildRTree(*GetDoc()->m_pXy, filter); } return m_rtree.get(); } std::unique_ptr CItemEraser::GetEraserPath() const { using Clipper2Lib::PathD; using Clipper2Lib::PathsD; using Clipper2Lib::JoinType; using Clipper2Lib::EndType; using Clipper2Lib::InflatePaths; using Clipper2Lib::MakePathD; using Clipper2Lib::SimplifyPaths; if (m_path.empty()) { return nullptr; } PathD originalPath; for (const auto& point : m_path) { originalPath.emplace_back(point.x0, point.y0); } PathsD subject{ std::move(originalPath) }; PathsD simplified = SimplifyPaths(subject, 1.5); // 计算膨胀参数,我们使用画笔宽度对应的真实宽度 double delta = GetDC()->GetRealWidth(static_cast(m_eraseRadius)); //膨胀 PathsD solution = InflatePaths(simplified, delta, JoinType::Miter, EndType::Square); if (solution.empty()) { return nullptr; } // 只使用结果中的第一条线 PathD& expandedPath = solution[0]; if (expandedPath.empty()) { return nullptr; } auto pCurve = std::make_unique(); pCurve->Create((int)expandedPath.size() + 1); size_t i = 0; for (; i < expandedPath.size(); i++) { pCurve->x[i] = expandedPath[i].x; pCurve->y[i] = expandedPath[i].y; } pCurve->x[i] = expandedPath[0].x; pCurve->y[i] = expandedPath[0].y; pCurve->GetLocation(); return pCurve; } std::vector CItemEraser::GetScreenPoints(const std::vector& points) const { std::vector screenPoints; screenPoints.reserve(points.size()); CXyDC *pXyDC = GetDC(); for (CPoint2D point : points) { screenPoints.emplace_back(pXyDC->GetScreen(point)); } return screenPoints; } void CItemEraser::ZoomEraseRadius(int radius) { m_eraseRadius = std::clamp(m_eraseRadius + radius, MIN_ERASE_RADIUS, MAX_ERASE_RADIUS); }