QGraphicsItem鼠标拖动旋转(五)

系列文章目录

QGraphicsItem图元的简单使用(一)
QGraphicsItem图元拖动绘制(二)
QGraphicsItem图元旋转缩放和自定义图元(三)
QGraphicsItem鼠标拖动图元进行缩放拉伸(四)



前言

接上篇,该篇主要讲解一下如何通过鼠标拖动图元进行旋转


一、效果演示

请添加图片描述

二、过程解析

1.鼠标悬浮

当我选中图元时,鼠标悬浮进入到图元的-旋转圆附近时,需要显示对应的鼠标ico样式,但是鼠标的悬浮事件触发是由以下几个部分组成的:
1)图元设置接收鼠标悬浮事件

    // 接收鼠标悬浮事件
    this->setAcceptHoverEvents(true);

2)图元的鼠标悬浮进入和离开事件,是由boundingRect区域决定的,所以需要修改,我们实际显示绘制的还是m_rect,只是包含旋转的连接虚线和旋转圆的区域,方便触发鼠标悬浮事件(有更好方案的大佬可以评论区留言)

QRectF RectItem::boundingRect() const
{
    // 因为图元的鼠标悬浮进入事件触发是在该区域内,但是鼠标选中时,通过虚线延长的旋转按钮是无法触发鼠标悬浮进入事件的,所以需要特殊处理
    // 但是实际绘制显示,还是以图元大小为准

    // 设置图元绘制边界距离图元dAdjust个像素
    return  QRectF(m_rect.x(), m_rect.y() - (m_dLineLen + 2.0 * m_dCircleRadius),
                   m_rect.width(), m_rect.height() + (m_dLineLen + 2.0 * m_dCircleRadius)).
            adjusted(-m_dAdjust, -m_dAdjust, m_dAdjust, m_dAdjust);
}

3)绘制矩形图元,以及选中区域

void RectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    // 重绘函数,绘制矩形
    Q_UNUSED(widget);

    // 空矩形就不绘制
    if(m_rect.isEmpty())
    {
        return;
    }

    // 设置画笔和画刷
    painter->setPen(QPen(Qt::black, 1));
    painter->setBrush(Qt::green);
    // 绘制矩形
    painter->drawRect(m_rect);

    // 绘制选中时的虚框
    if (option->state & QStyle::State_Selected)
    {
        // 获取图元绘制区域
        QRectF rect = getRect();// this->boundingRect();

        // 绘制虚线框
        painter->setPen(QPen(option->palette.windowText(), 0, Qt::DashLine));
        painter->setBrush(Qt::NoBrush);

        // 设置虚线框距离绘图区域的间距,因为画笔有宽度
        const qreal pad = painter->pen().widthF() / 2 + m_dAdjust;

        painter->drawRect(rect.adjusted(-pad, -pad, pad, pad));

        // 加一条虚线,连接一个圆,用来做旋转处理
        // 绘制连线
        painter->drawLine(rect.center().x(), rect.top() - m_dLineLen, rect.center().x(), rect.top());
        // 绘制圆
        painter->drawEllipse(rect.center().x() - m_dCircleRadius, rect.top() - m_dLineLen - 2.0 * m_dCircleRadius,
                             2.0 * m_dCircleRadius, 2.0 * m_dCircleRadius);
    }
}

2.旋转处理

代码如下(示例):

void RectItem::RotateRect(const QPointF &mousePos)
{
    // 设置中心点为原点
    QPointF originPos = this->getRect().center();
    // 从原点延伸出去两条线,鼠标按下时的点和当前鼠标位置所在点的连线
    QLineF p1 = QLineF(originPos, m_pressPos);
    QLineF p2 = QLineF(originPos, mousePos);
    // 旋转角度
    qreal dRotateAngle = p2.angleTo(p1);

    // 设置旋转中心
    this->setTransformOriginPoint(originPos);

	// 计算当前旋转的角度
    qreal dCurAngle = this->rotation() + dRotateAngle;
    while (dCurAngle > 360.0) {
        dCurAngle -= 360.0;
    }

    // 设置旋转角度
    this->setRotation(dCurAngle);

    // 刷新显示
    this->update();
}

3.旋转后的图元,鼠标拉升悬浮的图标

图元在旋转之后,再进行拉升,那么在鼠标悬浮进入拉升区域时,鼠标的图标肯定会有所变化,我这边偷懒写了一个垃圾算法,想做的体验更好的朋友,可以设置鼠标图标为图片,通过旋转图片来达到更好的效果

void RectItem::RotateCursor(qreal dAngle, Qt::CursorShape eCursor)
{
    // 实际显示的鼠标图标
    Qt::CursorShape eRealCursor = eCursor;

    // 旋转角度是[0,360°)之间,只需要考虑[0,180)之间的变化就行

    while (dAngle > 180.0) {
        dAngle -= 180.0;
    }
    // 【0,30°)不需要变化

    if((0.0 <= dAngle && dAngle < 30.0) ||  (150.0 <= dAngle && dAngle < 180.0))
    {   // 1、如果是[0,30)或[150,180),则不做处理
        eRealCursor = eCursor;
    }
    else if((30.0 <= dAngle && dAngle < 60.0))
    {   // 2、如果是[30,60),则【左下右上】->【左右】,【左上右下】->【上下】...以此类推
        if(eCursor == Qt::SizeBDiagCursor)
        {
            eRealCursor = Qt::SizeHorCursor;
        }
        else if(eCursor == Qt::SizeBDiagCursor)
        {
            eRealCursor = Qt::SizeHorCursor;
        }
        else if(eCursor == Qt::SizeVerCursor)
        {
            eRealCursor = Qt::SizeBDiagCursor;
        }
        else if(eCursor == Qt::SizeHorCursor)
        {
            eRealCursor = Qt::SizeFDiagCursor;
        }
    }
    else if((60.0 <= dAngle && dAngle < 120.0))
    {   // 3、如果是[60,120),则【左下右上】->【左上右下】,【左右】->【上下】...以此类推
        if(eCursor == Qt::SizeBDiagCursor)
        {
            eRealCursor = Qt::SizeFDiagCursor;
        }
        else if(eCursor == Qt::SizeFDiagCursor)
        {
            eRealCursor = Qt::SizeBDiagCursor;
        }
        else if(eCursor == Qt::SizeVerCursor)
        {
            eRealCursor = Qt::SizeHorCursor;
        }
        else if(eCursor == Qt::SizeHorCursor)
        {
            eRealCursor = Qt::SizeVerCursor;
        }
    }
    else if((120.0 <= dAngle && dAngle < 150.0))
    {   // 3、如果是[120,150),则【左下右上】->【上下】,【左上右下】->【左右】...以此类推
        if(eCursor == Qt::SizeBDiagCursor)
        {
            eRealCursor = Qt::SizeVerCursor;
        }
        else if(eCursor == Qt::SizeFDiagCursor)
        {
            eRealCursor = Qt::SizeHorCursor;
        }
        else if(eCursor == Qt::SizeVerCursor)
        {
            eRealCursor = Qt::SizeFDiagCursor;
        }
        else if(eCursor == Qt::SizeHorCursor)
        {
            eRealCursor = Qt::SizeBDiagCursor;
        }
    }

    this->setCursor(eRealCursor);
}

4.图元位移问题

有旋转角度的图元,在拉伸之后,再次旋转图元,会发生位移,具体原理可以参考另外一个大佬的这篇文章:QGraphicsItem旋转后,坐标变化机制解析,写的很详细,想看现象的直接不做处理就行,我们需要在鼠标拉升图元后,添加如下代码处理

void RectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    // 防止鼠标右键弹出菜单后,左击移动
    if (event->button() == Qt::LeftButton && 	// 左键按下
            m_eMouseHandle != Mouse_None)  	// 只处理拖动拉升
    {
        // 解决有旋转角度的矩形,拉伸之后,再次旋转,旋转中心该仍然为之前坐标,手动设置为中心,会产生漂移的问题
        auto rr = this->getRect();
        auto angle = qDegreesToRadians(this->rotation());

        auto p1 = rr.center();
        auto origin = this->transformOriginPoint();
        QPointF p2 = QPointF(0, 0);

        p2.setX(origin.x() + qCos(angle)*(p1.x() - origin.x()) - qSin(angle)*(p1.y() - origin.y()));
        p2.setY(origin.y() + qSin(angle)*(p1.x() - origin.x()) + qCos(angle)*(p1.y() - origin.y()));

        auto diff = p1 - p2;

        this->setRect(rr.adjusted(-diff.x(), -diff.y(), -diff.x(), -diff.y()));
        this->setTransformOriginPoint(this->getRect().center());

        this->update();

        // 拖动结束后恢复选中
        this->setSelected(true);
    }

    return QGraphicsItem::mouseReleaseEvent(event);
}

总结

感觉要总结的东西有很多,下章将详细讲解一下图元的坐标以及相对场景的坐标!

  • 1
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Li_Zhi_Yao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值