Qt,C++开发炫酷圆形扇形菜单面板

一如既往,先上图

动画效果参考下面的是视频

20220316-182247

摘要描述:

1、支持嵌入面板和弹出按钮两种,目前仅完成嵌入面板,后期会实现弹出实现

2、支持非常多的自定义功能,有关自定义功能定义参考下面的风格数据结构体代码,结构体中的功能均支持自定义

风格数据结构体代码如下:

/// 圆形菜单控件风格样式数据结构
  ///
  typedef struct LNCFROUNDMENU_STYLE_{
    QList<DGRADIENTCOLOR_DATA> vMenuCtlBkgClr;        //菜单渐变背景颜色集合列表
    QColor    cMenuCtlBkgClr = QColor(88,88,88,44);   //菜单控件背景颜色
    QColor    cMenuBtnBkgClr = QColor(77,77,77,44);   //菜单按钮默认背景色
    QColor    cMenuBtnHovers = QColor(192,192,192,144);//菜单按钮悬停背景色
    QColor    cMenuBtnTxtClr = QColor(202,202,202);   //菜单按钮文字颜色
    QColor    cMenuTextHover = QColor(255,255,255);   //菜单按钮文字悬停颜色
    QColor    cSwicthBnColor = QColor(144,133,227);   //切换按钮默认颜色
    QColor    cSwicthExpands = QColor(250,142,85);    //切换按钮菜单展开时背景颜色
    QColor    cSwicthBnHover = QColor(250,85,101);    //切换按钮鼠标悬停颜色
    QColor    cBkgBorderClrs = QColor(11,11,21,99);   //背景边框颜色
    QColor    cItemBorderClr = QColor(11,11,21,99);   //菜单项边框颜色

    qreal     dFrameOutScale = 0.75;                  //菜单外边框距离窗口边界比例
    qreal     dFrameInnScale = 0.40;                  //菜单内边框距离窗口边界比例
    qreal     dCenterBnScale = 1.5;                   //菜单中心按钮大小比例,相对于菜单窗口大小的缩小比例
    qreal     dItemsBtnScale = 2.0;                   //菜单按钮图标大小比例,相对于菜单按钮扇形区域大小缩放比例
    qreal     dItemsBkgScale = 1.25;                  //菜单按钮圆形背景大小比例,相对于菜单按钮扇形区域大小缩放比例
    qreal     dItemBorderWid = 1;                     //菜单按钮边框宽度
    qreal     dBnRoundRadius = 0.5;                   //菜单按钮圆角比例,0-0.5之间
    qreal     dBkgBordersWid = 1;                     //菜单背景宽度

    qreal     dStartDrawAngle = 180.0;                //起始绘制角度

    uint      uCenterBtnSize = 64;                    //菜单中心按钮绝对大小尺寸
    uint      uAnimatedTimer = 400;                   //菜单展开和关闭动画周期时长

    bool      bShowCtrlBkgnd = false;                 //是否显示控件背景
    bool      bShowItemsLine = false;                 //是否绘制分割线
    bool      bRoundMenuItem = true;                  //是否绘制圆形菜单项,默认是,否则显示扇形
    bool      bDrawCenterBtn = true;                  //绘制中心按钮
    bool      bMenuBtnsScale = true;                  //中心按钮大小比例类型,true表示绝对比例,false绝对大小
    bool      bHasRandomClrs = false;                 //是否使用随机按钮背景颜色
    bool      bRotateAnimate = true;                  //是否启用旋转动画,如果使用旋转动画至少将菜单展开和关闭周期设置到400或以上
    bool      bShowBkgBorder = false;                 //是否显示背景边框
    bool      bHasItemBorder = false;                 //是否显示菜单按钮边框
    bool      bNoIcoFullName = true;                  //无图标按钮是否显示菜单全名

    bool operator == (const LNCFROUNDMENU_STYLE_& rhs) // == 操作运算符重载
    {
      if(vMenuCtlBkgClr.count()!= rhs.vMenuCtlBkgClr.count())
        return false;

      if(vMenuCtlBkgClr.count()== rhs.vMenuCtlBkgClr.count()&&rhs.vMenuCtlBkgClr.count()>0){
          for(int i=0;i<rhs.vMenuCtlBkgClr.count();i++)
            {
              if(QString("%1").arg(vMenuCtlBkgClr[i].fBkgndAngleVal) !=QString("%1").arg(rhs.vMenuCtlBkgClr[i].fBkgndAngleVal))
                return false;
              if(vMenuCtlBkgClr[i].cBkgndAngleClr != rhs.vMenuCtlBkgClr[i].cBkgndAngleClr)
                return false;
            }
        }

      return (cMenuCtlBkgClr == rhs.cMenuCtlBkgClr)
          && (cMenuBtnBkgClr == rhs.cMenuBtnBkgClr)
          && (cMenuBtnHovers == rhs.cMenuBtnHovers)
          && (cMenuBtnTxtClr == rhs.cMenuBtnTxtClr)
          && (bHasRandomClrs == rhs.bHasRandomClrs)
          && (cMenuTextHover == rhs.cMenuTextHover)
          && (cSwicthBnColor == rhs.cSwicthBnColor)
          && (cSwicthExpands == rhs.cSwicthExpands)
          && (cSwicthBnHover == rhs.cSwicthBnHover)
          && (QString("%1").arg(dFrameOutScale) == QString("%1").arg(rhs.dFrameOutScale))
          && (QString("%1").arg(dFrameInnScale) == QString("%1").arg(rhs.dFrameInnScale))
          && (QString("%1").arg(dCenterBnScale) == QString("%1").arg(rhs.dCenterBnScale))
          && (QString("%1").arg(dItemsBtnScale) == QString("%1").arg(rhs.dItemsBtnScale))
          && (QString("%1").arg(dItemsBkgScale) == QString("%1").arg(rhs.dItemsBkgScale))
          && (QString("%1").arg(dStartDrawAngle) == QString("%1").arg(rhs.dStartDrawAngle))
          && (uCenterBtnSize == rhs.uCenterBtnSize)
          && (uAnimatedTimer == rhs.uAnimatedTimer)
          && (bShowCtrlBkgnd == rhs.bShowCtrlBkgnd)
          && (bShowItemsLine == rhs.bShowItemsLine)
          && (bRoundMenuItem == rhs.bRoundMenuItem)
          && (bDrawCenterBtn == rhs.bDrawCenterBtn)
          && (bMenuBtnsScale == rhs.bMenuBtnsScale)
          && (bHasRandomClrs == rhs.bHasRandomClrs)
          && (bRotateAnimate == rhs.bRotateAnimate)
          && (cBkgBorderClrs == rhs.cBkgBorderClrs)
          && (bShowBkgBorder == rhs.bShowBkgBorder)
          && (cItemBorderClr == rhs.cItemBorderClr)
          && (QString("%1").arg(dItemBorderWid) == QString("%1").arg(rhs.dItemBorderWid))
          && (bHasItemBorder == rhs.bHasItemBorder)
          && (dBnRoundRadius == rhs.dBnRoundRadius)
          && (QString("%1").arg(dBkgBordersWid) == QString("%1").arg(rhs.dBkgBordersWid))
          && (bNoIcoFullName == rhs.bNoIcoFullName);                  //无图标按钮是否显示菜单全名
    }

    bool operator != (const LNCFROUNDMENU_STYLE_& rhs) // != 操作运算符重载
    {
      return !(*this == rhs);
    }
  }LNCFROUNDMENU_STYLE,*PLNCFROUNDMENU_STYLE;

除了风格自定义还支持自定义图标等功能,参考下面的变量

  ///菜单展开图标路径
    std::u16string sExpandImgPath;

    ///菜单闭合图标路径
    std::u16string sClosedImgPath;

头文件主要代码

 //默认构造函数
    explicit Lncf_QRoundMenuCtl(QWidget *parent = nullptr);

    /// 标准构造函数
    /// \brief Lncf_QRoundMenuCtl
    /// \param bPopup     :是否为popup模式
    /// \param uBtnSize   :中心按钮尺寸
    /// \param uItemType  :菜单项类型,0圆角矩形,1扇形
    /// \param parent     :父窗口句柄
    ///
    explicit Lncf_QRoundMenuCtl(bool bPopup,uint uBtnSize,uint uItemType = 0,QWidget *parent = nullptr);

    //标准析构
    ~Lncf_QRoundMenuCtl();
  private:
    /// 初始化圆形菜单控件
    /// \brief InitRoundMenuCtl
    ///
    void InitRoundMenuCtl();
  protected:
    // 重写系统事件
    bool event(QEvent *ev) override;
    // 重写系统绘制事件
    void paintEvent(QPaintEvent *event) override;

    /// 绘制控件圆形背景
    /// \brief DrawBackgroundCtl
    /// \param painter
    ///
    void DrawBackgroundCtl(QPainter *painter);

    /// 绘制菜单项圆形外边框
    /// \brief DrawItemOutBorder
    /// \param painter
    ///
    void DrawItemOutBorder(QPainter *painter);

    /// 绘制菜单项圆形内边框
    /// \brief DrawItemsInBorder
    /// \param painter
    ///
    void DrawItemsInBorder(QPainter *painter);

    /// 绘制菜单中心按钮
    /// \brief DrawSwitchsButton
    /// \param painter
    ///
    void DrawSwitchsButton(QPainter *painter);

    /// 绘制菜单项按钮
    /// \brief DrawMenuCtlButton
    /// \param painter
    ///
    void DrawMenuCtlButton(QPainter *painter);

    // 重写系统鼠标松开事件
    void mouseReleaseEvent(QMouseEvent *event) override;
private:
    /// 初始化菜单动画
    /// \brief InitMenuAnimation
    ///
    void  InitMenuAnimation();

    /// 获取菜单项外圆半径
    /// \brief GetItemsOutRadius
    /// \return
    ///
    qreal GetItemsOutRadius();

    /// 获取菜单项内圆半径
    /// \brief GetItemsInnRadius
    /// \return
    ///
    qreal GetItemsInnRadius();

    /// 获取菜单项目绘制开始角度
    /// \brief GetItemStartAngle
    /// \return
    ///
    qreal GetItemStartAngle();

    /// 获取菜单控件绘制弧度
    /// \brief GetFrmStartRadian
    /// \return
    ///
    qreal GetFrmStartRadian();

    /// 获取菜单切换按钮菜单绘图路径
    /// \brief GetSwitchBtnsPath
    /// \return
    ///
    QPainterPath GetSwitchBtnsPath();

    /// 获取菜单按钮绘图路径
    /// \brief GetMenuButtonPath
    /// \param index
    /// \param hover
    /// \return
    ///
    QPainterPath GetMenuButtonPath(int index, bool hover = false);
  private slots:
    /// 切换菜单按钮单击
    /// \brief MenuSwitchClicked
    ///
    void MenuSwitchClicked();

    /// 菜单项展开或收起
    /// \brief MenuItemBtnExpand
    /// \param bExpand
    ///
    void MenuItemBtnExpand(const bool& bExpand);

    /// 菜单项单击槽函数
    /// \brief MenuItemsBtnClick
    /// \param uIndex
    ///
    void MenuItemsBtnClick(const uint& uIndex);
  public:
    /// 添加菜单按钮项
    /// \brief AddMenuButtonItem
    /// \param tMenuItem
    /// 注意在插入和添加菜单时图标和title必须提供一个
    ///
    void AddMenuButtonItem(const LQROUNDMENUBTN_ITEM& tMenuItem);

    void AddMenuButtonItem(const QPixmap &icon, const QString &title, const QString &tooltip);

    void AddMenuButtonItem(const QPixmap &icon, const QString &title, const QString &tooltip,
                           const QColor &background);

    void AddMenuButtonItem(const QPixmap &icon, const std::u16string &title, const std::u16string &tooltip,
                           const QColor &background);

    /// 插入一个菜单按钮
    /// \brief InsertMenuBtnItem
    /// \param index
    /// \param tMenuItem
    ///
    void InsertMenuBtnItem(int index,LQROUNDMENUBTN_ITEM tMenuItem);

    /// 插入一个按钮项目
    /// \brief InsertMenuBtnItem
    /// \param index
    /// \param icon
    /// \param title
    /// \param tooltip
    /// \param background
    ///
    void InsertMenuBtnItem(int index, const QPixmap &icon, const QString &title, const QString &tooltip,
                           const QColor &background = QColor());

    void InsertMenuBtnItem(int index, const QPixmap &icon, const std::u16string &title, const std::u16string &tooltip,
                           const QColor &background = QColor());

    /// 获取菜单展开图标路径
    /// \brief GetExpandImgPath
    /// \return
    ///
    std::u16string GetExpandImgPath() const;

    /// 获取菜单闭合图标路径
    /// \brief GetClosedImgPath
    /// \return
    ///
    std::u16string GetClosedImgPath() const;

    /// 设置菜单展开图标路径
    /// \brief SetExpandImgPath
    /// \param sImage
    ///
    void SetExpandImgPath(const std::u16string& sImage);

    /// 设置菜单闭合图标路径
    /// \brief SetClosedImgPath
    /// \param sImage
    ///
    void SetClosedImgPath(const std::u16string& sImage);

核心绘图代码如下

// 重写系统绘制事件
  void Lncf_QRoundMenuCtl::paintEvent(QPaintEvent *event)
  {
    QWidget::paintEvent(event);

    QPainter painter(this);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);

    //绘制背景
    if(this->tMenuCtrlStyles.bShowCtrlBkgnd)
      DrawBackgroundCtl(&painter);

    //绘制菜单按钮
    DrawMenuCtlButton(&painter);

    //绘制中心按钮
    if(this->tMenuCtrlStyles.bDrawCenterBtn)
      DrawSwitchsButton(&painter);
  }

  /// 绘制控件圆形背景
  /// \brief DrawBackgroundCtl
  /// \param painter
  ///
  void Lncf_QRoundMenuCtl::DrawBackgroundCtl(QPainter *painter)
  {
    painter->setPen(this->tMenuCtrlStyles.bShowBkgBorder?QPen(tMenuCtrlStyles.cBkgBorderClrs,tMenuCtrlStyles.dBkgBordersWid):Qt::NoPen);
    if(this->tMenuCtrlStyles.vMenuCtlBkgClr.count()>0){
        QLinearGradient gradient(QPoint(0, 0), QPoint(width(), height()));
        for(int i=0;i<tMenuCtrlStyles.vMenuCtlBkgClr.count();i++)
          gradient.setColorAt(tMenuCtrlStyles.vMenuCtlBkgClr[i].fBkgndAngleVal, tMenuCtrlStyles.vMenuCtlBkgClr[i].cBkgndAngleClr);
        painter->setBrush(gradient);
      }
    else{
        painter->setBrush(this->tMenuCtrlStyles.cMenuCtlBkgClr);
      }
    QRectF rect = this->rect();
    uint uRectMinVal = qMin(this->width(),this->height());
    rect.setSize(QSize(uRectMinVal*tMenuCtrlStyles.dFrameOutScale,uRectMinVal*tMenuCtrlStyles.dFrameOutScale));
    rect.moveCenter(QPointF(width() / 2.0, height() / 2.0));
    painter->drawEllipse(rect);
  }

  /// 绘制菜单项圆形外边框
  /// \brief DrawItemOutBorder
  /// \param painter
  ///
  void Lncf_QRoundMenuCtl::DrawItemOutBorder(QPainter *painter)
  {
    QRectF rect(0.0, 0.0, 2.0 * GetItemsOutRadius(), 2.0 * GetItemsOutRadius());
    rect.moveCenter(QPointF(width() / 2.0, height() / 2.0));

    painter->save();
    painter->setPen(QPen(QBrush(this->tMenuCtrlStyles.cItemBorderClr), tMenuCtrlStyles.dItemBorderWid));
    painter->drawEllipse(rect);
    painter->restore();
  }

  /// 绘制菜单项圆形内边框
  /// \brief DrawItemsInBorder
  /// \param painter
  ///
  void Lncf_QRoundMenuCtl::DrawItemsInBorder(QPainter *painter)
  {
    QRectF rect(0.0, 0.0, 2.0 * GetItemsInnRadius(), 2.0 * GetItemsInnRadius());
    rect.moveCenter(QPointF(width() / 2.0, height() / 2.0));

    painter->save();
    painter->setPen(QPen(QBrush(this->tMenuCtrlStyles.cItemBorderClr), tMenuCtrlStyles.dItemBorderWid));
    painter->drawEllipse(rect);
    painter->restore();
  }

  int imageRotate =0;
  /// 绘制菜单中心按钮
  /// \brief DrawSwitchsButton
  /// \param painter
  ///
  void Lncf_QRoundMenuCtl::DrawSwitchsButton(QPainter *painter)
  {
    auto path = GetSwitchBtnsPath();

    painter->setBrush(bSwitchBnHover?tMenuCtrlStyles.cSwicthBnHover:(bHasExpandMenu ? tMenuCtrlStyles.cSwicthExpands : tMenuCtrlStyles.cSwicthBnColor));
    painter->setPen(tMenuCtrlStyles.bHasItemBorder?QPen(tMenuCtrlStyles.cItemBorderClr,tMenuCtrlStyles.dItemBorderWid):Qt::NoPen);

    painter->drawPath(path);

    auto pixmap = bHasExpandMenu ? QPixmap(QString::fromStdU16String(sClosedImgPath)) :
                                   QPixmap(QString::fromStdU16String(sExpandImgPath));
    auto rect = path.boundingRect();

    if(!pixmap.isNull()){
        rect = rect.marginsRemoved(QMargins(rect.width()  / 4.0,
                                            rect.height() / 4.0,
                                            rect.width()  / 4.0,
                                            rect.height() / 4.0));

        painter->drawPixmap(rect, pixmap, pixmap.rect());
      }
    else{
        rect = rect.marginsRemoved(QMargins(rect.width()  / 4.0,
                                            rect.height() / 4.0,
                                            rect.width()  / 4.0,
                                            rect.height() / 4.0));

        std::u16string sSwicthText = bHasExpandMenu ? u"MENU":
                                                      u"CLOSE";

        QFont fontObj = this->font();
        fontObj.setPointSizeF(rect.height()/4.0);
        painter->setFont(fontObj);

        painter->setPen(bSwitchBnHover?tMenuCtrlStyles.cMenuBtnTxtClr:tMenuCtrlStyles.cMenuTextHover);

        painter->drawText(rect, Qt::AlignCenter|Qt::TextWordWrap,QString::fromStdU16String(sSwicthText));
      }
  }

  /// 绘制菜单项按钮
  /// \brief DrawMenuCtlButton
  /// \param painter
  ///
  void Lncf_QRoundMenuCtl::DrawMenuCtlButton(QPainter *painter)
  {
    if(pMenuAnimeGroup->state() != QAbstractAnimation::Running){
        if(!bHasExpandMenu||vMenuButtonList.count()<=0){
            return;
          }
      }

    if(this->tMenuCtrlStyles.bShowItemsLine&&!this->tMenuCtrlStyles.bRoundMenuItem){
        DrawItemOutBorder(painter);
        DrawItemsInBorder(painter);
      }

    auto count = vMenuButtonList.count();
    if(count == 0) return;

    auto radian = 2 * PI / count;
    auto outer_radius = GetItemsOutRadius();
    auto inner_radius = GetItemsInnRadius();

    QFont fontObj = this->font();

    for (int i = 0 ; i< count; ++i)
      {
        auto path = GetMenuButtonPath(i);

        //绘制背景
        auto bgColor = this->tMenuCtrlStyles.bHasRandomClrs?QColor::fromHsl(rand()%360,rand()%256,rand()%200):vMenuButtonList.at(i)->cCustomBkgClr;
        if(!bgColor.isValid()){
            bgColor = this->tMenuCtrlStyles.cMenuBtnBkgClr;
          }

        auto iconRectWidth = outer_radius - inner_radius;

        if(!this->tMenuCtrlStyles.bRoundMenuItem){
            painter->fillPath(path,vMenuButtonList.at(i)->bIsHoverState?this->tMenuCtrlStyles.cMenuBtnHovers:bgColor);
          }
        else{
            QRectF rectBtnBkg(0, 0, iconRectWidth / this->tMenuCtrlStyles.dItemsBkgScale, iconRectWidth / this->tMenuCtrlStyles.dItemsBkgScale);
            rectBtnBkg.moveCenter(path.boundingRect().center());

            painter->setBrush(vMenuButtonList.at(i)->bIsHoverState?this->tMenuCtrlStyles.cMenuBtnHovers:bgColor);
            painter->setPen(tMenuCtrlStyles.bHasItemBorder?QPen(tMenuCtrlStyles.cItemBorderClr,tMenuCtrlStyles.dItemBorderWid):Qt::NoPen);
            painter->drawRoundedRect(rectBtnBkg,rectBtnBkg.width()*tMenuCtrlStyles.dBnRoundRadius,rectBtnBkg.height()*tMenuCtrlStyles.dBnRoundRadius);
          }

        auto pixmapIcon = vMenuButtonList.at(i)->pMenuItemIcon;
        if(!pixmapIcon.isNull()){
            QRectF rectIcon(0, 0, iconRectWidth / this->tMenuCtrlStyles.dItemsBtnScale, iconRectWidth / this->tMenuCtrlStyles.dItemsBtnScale);
            rectIcon.moveCenter(path.boundingRect().center());

            painter->drawPixmap(rectIcon, pixmapIcon, pixmapIcon.rect());
          }
        else{
            QRectF rectText(0, 0, iconRectWidth / this->tMenuCtrlStyles.dItemsBtnScale*0.95, iconRectWidth / this->tMenuCtrlStyles.dItemsBtnScale*0.95);
            rectText.moveCenter(path.boundingRect().center());
            fontObj.setPointSizeF(rectText.height()/4.0);
            painter->setFont(fontObj);
            painter->setPen(vMenuButtonList.at(i)->bIsHoverState?tMenuCtrlStyles.cMenuTextHover:tMenuCtrlStyles.cMenuBtnTxtClr);
            painter->drawText(rectText, Qt::AlignCenter|Qt::TextWordWrap, QString::fromStdU16String(vMenuButtonList.at(i)->uItemTitleVal));
          }

        /***************************
           * draw seperator
           * */
        if(this->tMenuCtrlStyles.bShowItemsLine&&!this->tMenuCtrlStyles.bRoundMenuItem){
            QPointF point1(width() /  2.0 + outer_radius * std::cos(radian * i + GetFrmStartRadian()),
                           height() / 2.0 - outer_radius * std::sin(radian * i + GetFrmStartRadian()));
            QPointF point2(width() /  2.0 + inner_radius * std::cos(radian * i + GetFrmStartRadian()),
                           height() / 2.0 - inner_radius * std::sin(radian * i + GetFrmStartRadian()));

            painter->save();
            painter->setPen(QPen(QBrush(tMenuCtrlStyles.cItemBorderClr), tMenuCtrlStyles.dBkgBordersWid));
            painter->drawLine(point1, point2);
            painter->restore();
          }
      }
  }

大半夜刚封装完,困的实在类,先写到这。

有关详细信息和技术交流,移步QQ群:717743458。

  • 9
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值