Skip to content

Commit c0b755d

Browse files
committed
[图形系统交互重构与帧率控制优化]: 重构图形项交互逻辑,新增帧率限制器,提升系统性能和用户体验
-**新增帧率控制**: 实现FrameRateLimiter类,通过事件过滤器智能控制绘制帧率,避免过度渲染 -**统一交互状态管理**: 将MouseRegion重构为枚举类,明确区分无选择、锚点、边缘区域和整体形状状态 -**优化图形项事件处理**: 重构圆弧、圆形、线段、多边形等所有基本图形项的鼠标移动和悬停事件 -**改进边缘检测算法**: 为各图形项实现detectEdgeRegion方法,提供更精确的边缘悬停识别和光标反馈 -**增强预览功能**: 新增updateHoverPreview统一处理悬停时的图形预览显示 -**简化类型定义**: 移除冗余type()方法,直接在头文件中返回形状类型枚举 -**完善边界检查**: 在pointsChanged中增加场景矩形验证,防止图形项超出有效范围 -**提升绘制性能**: 移除临时图形对象,直接使用几何缓存绘制,减少内存分配 -**优化旋转矩形交互**: 实现角点拖动调整大小,改进旋转控制和边缘拉伸行为 -**重构工具函数**: 用isPointNearEdge替代boundingFromLine,提供更准确的点线距离计算 -**整理初始化流程**: 提取GraphicsView初始化代码到独立方法,提高可读性
1 parent 3703172 commit c0b755d

27 files changed

Lines changed: 612 additions & 561 deletions

examples/graphics/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ set(PROJECT_SOURCES
1515
drawscene.hpp
1616
drawwidget.cpp
1717
drawwidget.h
18+
frameratelimiter.cc
19+
frameratelimiter.hpp
1820
icoconverterwidget.cc
1921
icoconverterwidget.hpp
2022
imagecaptureview.cc

examples/graphics/customlineitem.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ void CustomLineItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
1919
setClickedPos(point);
2020

2121
switch (mouseRegion()) {
22-
case All: pts_tmp.translate(dp); break;
22+
case MouseRegion::EntireShape: pts_tmp.translate(dp); break;
2323
default: return;
2424
}
2525
pointsChanged(pts_tmp);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include "frameratelimiter.hpp"
2+
3+
#include <QDebug>
4+
#include <QElapsedTimer>
5+
#include <QEvent>
6+
#include <QWidget>
7+
8+
class FrameRateLimiter::FrameRateLimiterPrivate
9+
{
10+
public:
11+
explicit FrameRateLimiterPrivate(FrameRateLimiter *q)
12+
: q_ptr(q)
13+
{}
14+
15+
FrameRateLimiter *q_ptr;
16+
17+
qint64 targetFrameTime = 1000 / 60; // 毫秒
18+
QElapsedTimer frameTimer;
19+
};
20+
21+
FrameRateLimiter::FrameRateLimiter(QObject *parent)
22+
: QObject{parent}
23+
, d_ptr(new FrameRateLimiterPrivate(this))
24+
{}
25+
26+
FrameRateLimiter::~FrameRateLimiter() {}
27+
28+
void FrameRateLimiter::setTargetFPS(int fps)
29+
{
30+
d_ptr->targetFrameTime = 1000 / qMax(1, fps);
31+
}
32+
33+
bool FrameRateLimiter::eventFilter(QObject *watched, QEvent *event)
34+
{
35+
qDebug() << "FrameRateLimiter::eventFilter:" << event->type();
36+
switch (event->type()) {
37+
case QEvent::Paint:
38+
if (!d_ptr->frameTimer.isValid()) {
39+
d_ptr->frameTimer.start();
40+
} else if (!d_ptr->frameTimer.hasExpired(d_ptr->targetFrameTime)) {
41+
qDebug() << "Has not expired";
42+
auto *widget = qobject_cast<QWidget *>(watched);
43+
if (widget) {
44+
QMetaObject::invokeMethod(
45+
widget, [widget] { widget->update(); }, Qt::QueuedConnection);
46+
}
47+
return true;
48+
} else {
49+
d_ptr->frameTimer.restart();
50+
}
51+
break;
52+
default: break;
53+
}
54+
55+
return QObject::eventFilter(watched, event);
56+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include <QObject>
4+
5+
class FrameRateLimiter : public QObject
6+
{
7+
Q_OBJECT
8+
public:
9+
explicit FrameRateLimiter(QObject *parent = nullptr);
10+
~FrameRateLimiter() override;
11+
12+
void setTargetFPS(int fps);
13+
14+
protected:
15+
bool eventFilter(QObject *watched, QEvent *event) override;
16+
17+
private:
18+
class FrameRateLimiterPrivate;
19+
QScopedPointer<FrameRateLimiterPrivate> d_ptr;
20+
};

examples/graphics/graphics.pro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ SOURCES += \
2626
customlineitem.cc \
2727
drawscene.cc \
2828
drawwidget.cpp \
29+
frameratelimiter.cc \
2930
icoconverterwidget.cc \
3031
imagecaptureview.cc \
3132
imageviewer.cpp \
@@ -53,6 +54,7 @@ HEADERS += \
5354
customlineitem.hpp \
5455
drawscene.hpp \
5556
drawwidget.h \
57+
frameratelimiter.hpp \
5658
icoconverterwidget.hpp \
5759
imagecaptureview.hpp \
5860
imageviewer.h \

src/graphics/graphicsarcitem.cpp

Lines changed: 101 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ auto Arc::controlPoints() const -> QPolygonF
7777
class GraphicsArcItem::GraphicsArcItemPrivate
7878
{
7979
public:
80+
enum class MouseEdgeRegion : int {
81+
NoSelection,
82+
InnerEdge,
83+
OuterEdge,
84+
StartAngleSide,
85+
EndAngleSide
86+
};
87+
8088
explicit GraphicsArcItemPrivate(GraphicsArcItem *q)
8189
: q_ptr(q)
8290
{}
@@ -86,7 +94,7 @@ class GraphicsArcItem::GraphicsArcItemPrivate
8694
Arc arch;
8795
QPainterPath arcPath;
8896
QPainterPath cachePath;
89-
GraphicsArcItem::MouseRegion mouseRegion = GraphicsArcItem::None;
97+
MouseEdgeRegion mouseRegion = MouseEdgeRegion::NoSelection;
9098
};
9199

92100
GraphicsArcItem::GraphicsArcItem(QGraphicsItem *parent)
@@ -136,11 +144,6 @@ auto GraphicsArcItem::arch() const -> Arc
136144
return d_ptr->arch;
137145
}
138146

139-
auto GraphicsArcItem::type() const -> int
140-
{
141-
return GraphicsBasicItem::Shape::ARC;
142-
}
143-
144147
inline auto lineSetLength(const QPointF p1, const QPointF p2, const double len) -> QPointF
145148
{
146149
QLineF line(p1, p2);
@@ -150,41 +153,41 @@ inline auto lineSetLength(const QPointF p1, const QPointF p2, const double len)
150153

151154
void GraphicsArcItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
152155
{
153-
if (!isValid()) {
156+
if ((event->buttons() & Qt::LeftButton) == 0 || !isValid()) {
154157
return;
155158
}
156159
if (!isSelected()) {
157160
setSelected(true);
158161
}
162+
auto scenePos = event->scenePos();
163+
auto dp = scenePos - clickedPos();
164+
setClickedPos(scenePos);
159165

160-
QPointF point = event->scenePos();
161-
QPointF dp = point - clickedPos();
162-
setClickedPos(event->scenePos());
163-
auto pts_tmp = geometryCache()->controlPoints();
164-
double distance = Utils::distance(d_ptr->arch.center, point);
166+
auto controlPoints = geometryCache()->controlPoints();
167+
double distance = Utils::distance(d_ptr->arch.center, scenePos);
165168

166169
switch (mouseRegion()) {
167-
case GraphicsBasicItem::MouseRegion::All: pts_tmp.translate(dp); break;
168-
case GraphicsBasicItem::MouseRegion::None: {
170+
case GraphicsBasicItem::MouseRegion::EntireShape: controlPoints.translate(dp); break;
171+
case GraphicsBasicItem::MouseRegion::EdgeArea: {
169172
switch (d_ptr->mouseRegion) {
170-
case InEdge0:
171-
setMyCursor(d_ptr->arch.center, event->scenePos());
173+
case GraphicsArcItemPrivate::MouseEdgeRegion::InnerEdge:
174+
setMyCursor(d_ptr->arch.center, scenePos);
172175
for (int i = 0; i < 3; ++i) {
173-
pts_tmp[i] = lineSetLength(d_ptr->arch.center, pts_tmp[i], distance);
176+
controlPoints[i] = lineSetLength(d_ptr->arch.center, controlPoints[i], distance);
174177
}
175178
break;
176-
case InEdge1:
177-
setMyCursor(d_ptr->arch.center, event->scenePos());
178-
pts_tmp[3] = lineSetLength(d_ptr->arch.center, pts_tmp[3], distance);
179+
case GraphicsArcItemPrivate::MouseEdgeRegion::OuterEdge:
180+
setMyCursor(d_ptr->arch.center, scenePos);
181+
controlPoints[3] = lineSetLength(d_ptr->arch.center, controlPoints[3], distance);
179182
break;
180-
case InEdgeH:
181-
case InEdgeL: {
183+
case GraphicsArcItemPrivate::MouseEdgeRegion::EndAngleSide:
184+
case GraphicsArcItemPrivate::MouseEdgeRegion::StartAngleSide: {
182185
Arc arch = d_ptr->arch;
183-
if (d_ptr->mouseRegion == InEdgeL) {
184-
arch.startAngle = QLineF(arch.center, event->scenePos()).angle();
186+
if (d_ptr->mouseRegion == GraphicsArcItemPrivate::MouseEdgeRegion::StartAngleSide) {
187+
arch.startAngle = QLineF(arch.center, scenePos).angle();
185188
setCursor(Utils::cursorForDirection(arch.startAngle));
186189
} else {
187-
arch.endAngle = QLineF(arch.center, event->scenePos()).angle();
190+
arch.endAngle = QLineF(arch.center, scenePos).angle();
188191
setCursor(Utils::cursorForDirection(arch.endAngle));
189192
}
190193
while (arch.startAngle > arch.endAngle) {
@@ -193,63 +196,17 @@ void GraphicsArcItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
193196
if (arch.endAngle - arch.startAngle > 360) {
194197
arch.endAngle -= 360;
195198
}
196-
pts_tmp = arch.controlPoints();
199+
controlPoints = arch.controlPoints();
197200
} break;
198201
default: return;
199202
}
200203
break;
201204
}
202-
case GraphicsBasicItem::MouseRegion::DotRegion: pts_tmp[hoveredDotIndex()] += dp; break;
205+
case GraphicsBasicItem::MouseRegion::AnchorPoint: controlPoints[hoveredDotIndex()] += dp; break;
203206
default: return;
204207
}
205208

206-
pointsChanged(pts_tmp);
207-
}
208-
209-
void GraphicsArcItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
210-
{
211-
auto pts_tmp = geometryCache()->controlPoints();
212-
QPointF point = event->scenePos();
213-
if (pts_tmp.size() == 2 || pts_tmp.size() == 3) {
214-
pts_tmp.append(point);
215-
showHoverArc(pts_tmp);
216-
}
217-
if (!isValid()) {
218-
return;
219-
}
220-
GraphicsBasicItem::hoverMoveEvent(event);
221-
if (mouseRegion() == GraphicsBasicItem::MouseRegion::DotRegion) {
222-
return;
223-
}
224-
setMouseRegion(GraphicsBasicItem::MouseRegion::None);
225-
226-
QPointF p1 = Utils::pointFromCenter(d_ptr->arch.center,
227-
d_ptr->arch.maxRadius,
228-
d_ptr->arch.startAngle);
229-
QPointF p2 = Utils::pointFromCenter(d_ptr->arch.center,
230-
d_ptr->arch.maxRadius,
231-
d_ptr->arch.endAngle);
232-
QLineF line1(p1, pts_tmp.at(0));
233-
QLineF line2(p2, pts_tmp.at(1));
234-
if (qAbs(Utils::distance(point, d_ptr->arch.center) - d_ptr->arch.minRadius) < margin() / 3) {
235-
d_ptr->mouseRegion = InEdge0;
236-
setMyCursor(d_ptr->arch.center, point);
237-
} else if (qAbs(Utils::distance(point, d_ptr->arch.center) - d_ptr->arch.maxRadius)
238-
< margin() / 3) {
239-
d_ptr->mouseRegion = InEdge1;
240-
setMyCursor(d_ptr->arch.center, point);
241-
} else if (Utils::boundingFromLine(line1, margin() / 4).containsPoint(point, Qt::OddEvenFill)) {
242-
d_ptr->mouseRegion = InEdgeL;
243-
setCursor(Utils::cursorForDirection(line1.angle()));
244-
} else if (Utils::boundingFromLine(line2, margin() / 4).containsPoint(point, Qt::OddEvenFill)) {
245-
d_ptr->mouseRegion = InEdgeH;
246-
setCursor(Utils::cursorForDirection(line2.angle()));
247-
} else if (d_ptr->arcPath.contains(point)) {
248-
setMouseRegion(GraphicsBasicItem::MouseRegion::All);
249-
setCursor(Qt::SizeAllCursor);
250-
} else {
251-
unsetCursor();
252-
}
209+
pointsChanged(controlPoints);
253210
}
254211

255212
void GraphicsArcItem::drawContent(QPainter *painter)
@@ -259,18 +216,15 @@ void GraphicsArcItem::drawContent(QPainter *painter)
259216

260217
void GraphicsArcItem::pointsChanged(const QPolygonF &ply)
261218
{
262-
auto rect = scene()->sceneRect();
263-
if (!rect.contains(ply.last())) {
264-
return;
265-
}
266-
267219
if (ply.size() == 3) {
268220
double deltaAngle = QLineF(ply[0], ply[1]).angleTo(QLineF(ply[0], ply[2]));
269221
if (deltaAngle < 0.00001 || deltaAngle > 355.99999) {
270222
return;
271223
}
272224
}
273225

226+
auto sceneRect = scene()->sceneRect();
227+
274228
switch (ply.size()) {
275229
case 1:
276230
case 2: geometryCache()->setControlPoints(ply); break;
@@ -279,7 +233,7 @@ void GraphicsArcItem::pointsChanged(const QPolygonF &ply)
279233
return;
280234
}
281235
QPolygonF polygon = d_ptr->cachePath.toFillPolygon() + ply;
282-
if (!rect.contains(polygon.boundingRect())) {
236+
if (!sceneRect.contains(polygon.boundingRect())) {
283237
return;
284238
}
285239
geometryCache()->setControlPoints(ply);
@@ -295,21 +249,83 @@ void GraphicsArcItem::pointsChanged(const QPolygonF &ply)
295249
update();
296250
}
297251

298-
void GraphicsArcItem::showHoverArc(const QPolygonF &ply)
252+
void GraphicsArcItem::updateHoverPreview(const QPointF &scenePos)
299253
{
300-
switch (ply.size()) {
254+
auto controlPoints = geometryCache()->controlPoints();
255+
auto size = controlPoints.size();
256+
if (size < 2 || size > 3) {
257+
return;
258+
}
259+
260+
controlPoints.append(scenePos);
261+
size = controlPoints.size();
262+
263+
switch (size) {
301264
case 3:
302265
// QPainterPath::arcTo: Adding point with invalid coordinates, ignoring call
303-
if (Utils::distance(ply[1], ply[2]) < margin()) {
266+
if (Utils::distance(controlPoints[1], controlPoints[2]) < margin()) {
304267
return;
305268
}
306-
Utils::calculateHalfArc(ply, d_ptr->cachePath);
269+
Utils::calculateHalfArc(controlPoints, d_ptr->cachePath);
307270
break;
308-
case 4: Utils::calculateAllArc(ply, d_ptr->cachePath, margin()); break;
271+
case 4: Utils::calculateAllArc(controlPoints, d_ptr->cachePath, margin()); break;
309272
default: return;
310273
}
311274

312275
update();
313276
}
314277

278+
GraphicsBasicItem::MouseRegion GraphicsArcItem::detectEdgeRegion(const QPointF &scenePos)
279+
{
280+
const double edgeMargin = margin() / 2.0;
281+
const Arc &arc = d_ptr->arch;
282+
283+
// 计算点到圆心的距离
284+
const double distanceToCenter = Utils::distance(scenePos, arc.center);
285+
286+
// 分别检查内外圆弧边缘 - 更清晰的逻辑
287+
if (qAbs(distanceToCenter - arc.minRadius) < edgeMargin) {
288+
d_ptr->mouseRegion = GraphicsArcItemPrivate::MouseEdgeRegion::InnerEdge;
289+
setMyCursor(arc.center, scenePos);
290+
setMouseRegion(GraphicsBasicItem::MouseRegion::EdgeArea);
291+
return GraphicsBasicItem::MouseRegion::EdgeArea;
292+
}
293+
294+
if (qAbs(distanceToCenter - arc.maxRadius) < edgeMargin) {
295+
d_ptr->mouseRegion = GraphicsArcItemPrivate::MouseEdgeRegion::OuterEdge;
296+
setMyCursor(arc.center, scenePos);
297+
setMouseRegion(GraphicsBasicItem::MouseRegion::EdgeArea);
298+
return GraphicsBasicItem::MouseRegion::EdgeArea;
299+
}
300+
301+
// 计算侧边端点
302+
const QPointF innerStart = Utils::pointFromCenter(arc.center, arc.minRadius, arc.startAngle);
303+
const QPointF outerStart = Utils::pointFromCenter(arc.center, arc.maxRadius, arc.startAngle);
304+
305+
// 分别检查两个侧边
306+
const QLineF startEdge(innerStart, outerStart);
307+
if (Utils::isPointNearEdge(scenePos, startEdge, edgeMargin)) {
308+
d_ptr->mouseRegion = GraphicsArcItemPrivate::MouseEdgeRegion::StartAngleSide;
309+
setCursor(Utils::cursorForDirection(startEdge.angle()));
310+
setMouseRegion(GraphicsBasicItem::MouseRegion::EdgeArea);
311+
return GraphicsBasicItem::MouseRegion::EdgeArea;
312+
}
313+
314+
const QPointF innerEnd = Utils::pointFromCenter(arc.center, arc.minRadius, arc.endAngle);
315+
const QPointF outerEnd = Utils::pointFromCenter(arc.center, arc.maxRadius, arc.endAngle);
316+
317+
const QLineF endEdge(innerEnd, outerEnd);
318+
if (Utils::isPointNearEdge(scenePos, endEdge, edgeMargin)) {
319+
d_ptr->mouseRegion = GraphicsArcItemPrivate::MouseEdgeRegion::EndAngleSide;
320+
setCursor(Utils::cursorForDirection(endEdge.angle()));
321+
setMouseRegion(GraphicsBasicItem::MouseRegion::EdgeArea);
322+
return GraphicsBasicItem::MouseRegion::EdgeArea;
323+
}
324+
325+
// 不在任何区域
326+
d_ptr->mouseRegion = GraphicsArcItemPrivate::MouseEdgeRegion::NoSelection;
327+
setMouseRegion(GraphicsBasicItem::MouseRegion::NoSelection);
328+
return GraphicsBasicItem::MouseRegion::NoSelection;
329+
}
330+
315331
} // namespace Graphics

0 commit comments

Comments
 (0)