当前位置: 首页 > news >正文

QGraphicsItem鼠标拖动旋转(五)

系列文章目录

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


文章目录

  • 系列文章目录
  • 前言
  • 一、效果演示
  • 二、过程解析
    • 1.鼠标悬浮
    • 2.旋转处理
    • 3.旋转后的图元,鼠标拉升悬浮的图标
    • 4.图元位移问题
  • 总结


前言

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


一、效果演示

请添加图片描述

二、过程解析

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);
}

总结

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

相关文章:

  • win7连接打印机0x0000011b错误的解决办法
  • 四、RocketMq本地集群搭建:多master-slaver异步
  • pcan二次开发文档 | PEAK-System Documentation
  • R语言数据分组聚合实战:使用aggregate函数对mtcars数据通过两个分类变量进行数据分组聚合、并计算分组的均值、使用na.rm删除异常值
  • Chapter15 : Artificial Intelligence in Compound Design
  • 前端HTML5 +CSS3 1. 基础认知
  • R语言替换字符串中指定字符的子串:sub函数查找字符串中第一个匹配到的子串并替换、如果要删除指定字符串子串则将替换的子符串设置为空字符串
  • java计算机毕业设计基于springboo大学生社团管理系统 vue+elementui
  • un9.2:创建springboot的两种方式。
  • dubbo的Failed to save registry store file问题
  • HTML期末作业 计算机毕业设计 html css javascript食品餐饮行业网站(10页)
  • 搭建和mybatis-plus官网一样主题的网站(cos+宝塔+vercel)
  • 股权重组是指什么
  • 剑指offer(C++)-JZ11:旋转数组的最小数字(算法-搜索算法)
  • 废水除氟技术,高盐废水除氟有什么好的方法?
  • ----------
  • HTTP请求重发
  • iBatis和MyBatis在使用ResultMap对应关系时的区别
  • IDEA常用插件整理
  • Java新版本的开发已正式进入轨道,版本号18.3
  • react-core-image-upload 一款轻量级图片上传裁剪插件
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • unity如何实现一个固定宽度的orthagraphic相机
  • Vue2.0 实现互斥
  • 测试开发系类之接口自动化测试
  • 日剧·日综资源集合(建议收藏)
  • 如何实现 font-size 的响应式
  • 深入 Nginx 之配置篇
  • 实战|智能家居行业移动应用性能分析
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 温故知新之javascript面向对象
  • 学习笔记:对象,原型和继承(1)
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 找一份好的前端工作,起点很重要
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • 移动端高清、多屏适配方案
  • ###C语言程序设计-----C语言学习(6)#
  • (arch)linux 转换文件编码格式
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (十)【Jmeter】线程(Threads(Users))之jp@gc - Stepping Thread Group (deprecated)
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (四)Linux Shell编程——输入输出重定向
  • (转)jdk与jre的区别
  • (转)mysql使用Navicat 导出和导入数据库
  • (转)机器学习的数学基础(1)--Dirichlet分布
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码
  • .NET CORE 第一节 创建基本的 asp.net core
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .Net FrameWork总结
  • .NET项目中存在多个web.config文件时的加载顺序
  • .NET中的Exception处理(C#)
  • @DependsOn:解析 Spring 中的依赖关系之艺术
  • [.net] 如何在mail的加入正文显示图片
  • [BPU部署教程] 教你搞定YOLOV5部署 (版本: 6.2)