├── .gitignore ├── InteractiveButtons.pro ├── LICENSE ├── README.md ├── interactive_buttons ├── appendbutton.cpp ├── appendbutton.h ├── generalbuttoninterface.cpp ├── generalbuttoninterface.h ├── infobutton.cpp ├── infobutton.h ├── interactivebuttonbase.cpp ├── interactivebuttonbase.h ├── pointmenubutton.cpp ├── pointmenubutton.h ├── threedimenbutton.cpp ├── threedimenbutton.h ├── watercirclebutton.cpp ├── watercirclebutton.h ├── waterfallbuttongroup.cpp ├── waterfallbuttongroup.h ├── waterfloatbutton.cpp ├── waterfloatbutton.h ├── waterzoombutton.cpp ├── waterzoombutton.h ├── winclosebutton.cpp ├── winclosebutton.h ├── winmaxbutton.cpp ├── winmaxbutton.h ├── winmenubutton.cpp ├── winmenubutton.h ├── winminbutton.cpp ├── winminbutton.h ├── winrestorebutton.cpp ├── winrestorebutton.h ├── winsidebarbutton.cpp └── winsidebarbutton.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── pictures ├── picture.gif └── picture2.gif ├── resources.qrc └── resources └── icons └── point_menu.png /.gitignore: -------------------------------------------------------------------------------- 1 | Run 2 | RunAndroid 3 | RunAndroidx86 4 | .tags 5 | .tags_sorted_by_file 6 | *.user 7 | *.user.* 8 | *.[oa] 9 | debug/ 10 | release/ 11 | run/ 12 | *.keystore 13 | *.key 14 | keys/ 15 | .gradle/ 16 | build/ 17 | *.dex 18 | *.apk 19 | *.ap 20 | *.class 21 | .idea/ 22 | local.properties 23 | .DS_Store 24 | capture/ 25 | bin/ 26 | qen/ 27 | Thumbs.db 28 | *.o 29 | *.so 30 | *.dmp 31 | .navigation/ 32 | proguard/ 33 | .vs/ 34 | *.VC.db 35 | .vs/ 36 | .vscode/ 37 | *.pdb 38 | GPATH 39 | GRTAGS 40 | GTAGS 41 | .qmake.stash 42 | *resource.rc 43 | *.Debug 44 | *.Release 45 | Makefile 46 | *.opt 47 | .history/ 48 | *.user -------------------------------------------------------------------------------- /InteractiveButtons.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2019-07-05T09:54:58 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = WindowsButtons 12 | TEMPLATE = app 13 | 14 | # The following define makes your compiler emit warnings if you use 15 | # any feature of Qt which has been marked as deprecated (the exact warnings 16 | # depend on your compiler). Please consult the documentation of the 17 | # deprecated API in order to know how to port your code away from it. 18 | DEFINES += QT_DEPRECATED_WARNINGS 19 | 20 | # You can also make your code fail to compile if you use deprecated APIs. 21 | # In order to do so, uncomment the following line. 22 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 24 | 25 | CONFIG += c++11 26 | 27 | INCLUDEPATH += interactive_buttons\ 28 | 29 | SOURCES += \ 30 | interactive_buttons/appendbutton.cpp \ 31 | interactive_buttons/infobutton.cpp \ 32 | main.cpp \ 33 | mainwindow.cpp \ 34 | interactive_buttons/interactivebuttonbase.cpp \ 35 | interactive_buttons/pointmenubutton.cpp \ 36 | interactive_buttons/threedimenbutton.cpp \ 37 | interactive_buttons/waterzoombutton.cpp \ 38 | interactive_buttons/winminbutton.cpp \ 39 | interactive_buttons/winmaxbutton.cpp \ 40 | interactive_buttons/winrestorebutton.cpp \ 41 | interactive_buttons/winclosebutton.cpp \ 42 | interactive_buttons/winmenubutton.cpp \ 43 | interactive_buttons/watercirclebutton.cpp \ 44 | interactive_buttons/waterfloatbutton.cpp 45 | 46 | HEADERS += \ 47 | interactive_buttons/appendbutton.h \ 48 | interactive_buttons/infobutton.h \ 49 | mainwindow.h \ 50 | interactive_buttons/interactivebuttonbase.h \ 51 | interactive_buttons/pointmenubutton.h \ 52 | interactive_buttons/threedimenbutton.h \ 53 | interactive_buttons/waterzoombutton.h \ 54 | interactive_buttons/winminbutton.h \ 55 | interactive_buttons/winmaxbutton.h \ 56 | interactive_buttons/winrestorebutton.h \ 57 | interactive_buttons/winclosebutton.h \ 58 | interactive_buttons/winmenubutton.h \ 59 | interactive_buttons/watercirclebutton.h \ 60 | interactive_buttons/waterfloatbutton.h 61 | 62 | # Default rules for deployment. 63 | qnx: target.path = /tmp/$${TARGET}/bin 64 | else: unix:!android: target.path = /opt/$${TARGET}/bin 65 | !isEmpty(target.path): INSTALLS += target 66 | 67 | RESOURCES += \ 68 | resources.qrc 69 | 70 | DISTFILES += \ 71 | README.md 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 可交互的创意动态按钮 2 | === 3 | 4 | ## 特点 5 | 6 | - 所有颜色自定义 7 | - 鼠标悬浮渐变 8 | - 两种点击效果:鼠标点击渐变 / 水波纹动画(可多层波纹叠加) 9 | - 额外鼠标移入/移出/按下/弹起的实时/延迟共8种事件 10 | - 鼠标悬浮图标位置主动变化 11 | - 鼠标拖动图标抖动反弹效果 12 | - 鼠标进入父控件时开启出现效果,或启动时出现 13 | - 延迟出现的动画效果(多个按钮连续) 14 | - 记录开关状态 15 | - 直接设置 x、y 的圆角显示 16 | - 边框颜色设置 17 | - 禁用时半透明+点击穿透效果 18 | - 添加额外的边缘角标 19 | - 三种前景模式:图标、文字、带遮罩的图标(任意变色) 20 | - 支持QSS直接设置部分属性 21 | - 与父类 QPushButton 兼容 22 | - 时间准确性:根据时间戳计算动画进度,即使在低性能机器上也可准时完成动画 23 | - 稳定性:一按钮多功能,完美兼容多种情况下焦点事件 24 | - 极其强大的可扩展性,继承该按钮后可任意修改显示效果和动画效果 25 | 26 | 27 | 28 | ## 截图 29 | 30 | ![截图](pictures/picture.gif) 31 | 32 | 33 | 34 | ## 用法 35 | 36 | ```C++ 37 | // 方框按钮 38 | InteractiveButtonBase* btn = new InteractiveButtonBase("text", this); 39 | btn->setGeometry(300, 300, 100, 100); 40 | ``` 41 | 42 | ```C++ 43 | // 圆形按钮带图标,以及鼠标靠近(父控件悬浮)出现动画 44 | icon_btn = new WaterCircleButton(QIcon(":/icons/point_menu"), this); 45 | icon_btn->setGeometry(100, 50, 32, 32); 46 | icon_btn->setShowAni(true); // 调用showForeground2时出现动画 47 | ``` 48 | 49 | ```C++ 50 | // 自定义颜色按钮(遮罩效果) 51 | InteractiveButtonBase* pixmap_btn = new InteractiveButtonBase(QPixmap(":/icons/point_menu"), this); 52 | pixmap_btn->setGeometry(132, 50, 32, 32); 53 | ``` 54 | 55 | ```C++ 56 | // 椭圆形按钮 57 | InteractiveButtonBase* float_btn = new WaterFloatButton("text", this); 58 | float_btn->setGeometry(164, 50, 100, 32); 59 | float_btn->setBgColor(QColor(102,51,204,192), QColor(102,51,204,255)); 60 | ``` 61 | 62 | ```C++ 63 | // 自定义按钮(win最小化按钮) 64 | WinMenuButton* menu_btn = new WinMenuButton(this); 65 | menu_btn->setGeometry(368, 10, 32, 32); 66 | ``` 67 | 68 | 69 | 70 | ## API 71 | 72 | 只说明一下常用的方法,具体可参考 `interactivebuttonbase.h`: 73 | 74 | ```C++ 75 | // 设置文本(同QPushButton) 76 | void setText(QString text); 77 | 78 | // 设置Icon 79 | void setIcon(QIcon icon); 80 | 81 | // 设置Pixmap,会根据 icon_color 自动变色 82 | void setPixmap(QPixmap pixmap); 83 | 84 | // 设置额外扩展的角标,比如右边扩展小箭头、右上角标签、左边图标等 85 | virtual void setPaintAddin(QPixmap pixmap, Qt::Alignment align = Qt::AlignRight, QSize size = QSize(0, 0)); 86 | 87 | // 调整各种动画时长 88 | void setHoverAniDuration(int d); 89 | void setPressAniDuration(int d); 90 | void setClickAniDuration(int d); 91 | void setWaterAniDuration(int press, int release, int finish); 92 | // 是否启用水波纹,false则改为渐变 93 | void setWaterRipple(bool enable = true); 94 | void setJitterAni(bool enable = true); 95 | 96 | // 设置无交互时的背景颜色 97 | void setBgColor(QColor bg); 98 | // 设置hover和press两种状态的背景颜色,与背景叠加 99 | // press会和hover以及默认背景再叠加 100 | void setBgColor(QColor hover, QColor press); 101 | // 单独设置各种颜色 102 | void setNormalColor(QColor color); 103 | void setBorderColor(QColor color); 104 | void setHoverColor(QColor color); 105 | void setPressColor(QColor color); 106 | 107 | // 设置前景颜色 108 | void setIconColor(QColor color = QColor(0, 0, 0)); 109 | void setTextColor(QColor color = QColor(0, 0, 0)); 110 | void setFocusBg(QColor color); 111 | void setFocusBorder(QColor color); 112 | 113 | // 设置字体大小 114 | // 如果已设置 setTextDynamicSize(true),那么会有改变动画 115 | void setFontSize(int f); 116 | 117 | // 前景对齐方式 118 | void setAlign(Qt::Alignment a); 119 | 120 | // 设置圆角 121 | void setRadius(int r); 122 | void setRadius(int rx, int ry); 123 | 124 | // 设置边界宽度(默认0,透明) 125 | void setBorderWidth(int x); 126 | 127 | // 分别设置四周空白 128 | void setPaddings(int l, int r, int t, int b); 129 | // 设置水平、垂直空白 130 | void setPaddings(int h, int v); 131 | // 设置四周相同的空白 132 | void setPaddings(int x); 133 | // 设置图标边缘空白比例,0~0.5,越大越空 134 | void setIconPaddingProper(double x); 135 | 136 | // 设置鼠标交互时前景(文字/图标)会不会动 137 | void setFixedForePos(bool f = true); 138 | // 设置固定大小;并且会自动选取合适的最小值(adjustMinimumSize()) 139 | void setFixedForeSize(bool f = true, int paddings = 0); 140 | // 设置为正方形(以高度为准) 141 | void setSquareSize(); 142 | 143 | // 修改字体时是否显示动画 144 | void setTextDynamicSize(bool d = true); 145 | 146 | // 设置鼠标单击松开后是否当做移开 147 | // 点击出现菜单的按钮必须设置 148 | void setLeaveAfterClick(bool l = true); 149 | 150 | // 启用双击,会响应双击事件,但是单击会有延迟 151 | void setDoubleClicked(bool e = true); 152 | 153 | // 动画时是否自动设置文字的颜色,针对派生类的设置,不一定有效 154 | void setAutoTextColor(bool a = true); 155 | 156 | // 临时屏蔽hover事件,需要手动关闭(set true->操作->set false) 157 | // 比如出现动画,如果一开始鼠标在按钮上,按钮移开,但是hover效果还在,直到鼠标动 158 | void setBlockHover(bool b = true); 159 | 160 | // 鼠标进入按钮区域,前景图标从对面方向缩放出现 161 | void setShowAni(bool enable = true); 162 | // 前景从中心点出现的缩放动画 163 | void showForeground(); 164 | // 前景从该点对面出现的动画 165 | void showForeground2(QPoint point = QPoint(0, 0)); 166 | // 隐藏前景,使用 showForeground() 恢复 167 | void hideForeground(); 168 | // 延迟的 showForeground2() 169 | void delayShowed(int msecond, QPoint point = QPoint(0, 0)); 170 | 171 | // 调整最小大小,很常用 172 | void adjustMinimumSize(); 173 | 174 | void setState(bool s = true); 175 | bool getState(); 176 | 177 | // 模拟鼠标点击效果,与鼠标点击作用一样 178 | // 会触发 clicked 信号 179 | virtual void simulateStatePress(bool s = true, bool a = false); 180 | 181 | // 该点是否在按钮的范围内 182 | virtual bool inArea(QPoint point); 183 | ``` 184 | 185 | 信号: 186 | 187 | ```C++ 188 | void showAniFinished(); 189 | void hideAniFinished(); 190 | void pressAppearAniFinished(); 191 | void pressDisappearAniFinished(); 192 | void jitterAniFinished(); 193 | 194 | // 点击事件 195 | void clicked(); 196 | void doubleClicked(); 197 | void rightClicked(); 198 | 199 | // 焦点 200 | void signalFocusIn(); 201 | void signalFocusOut(); 202 | 203 | // 其余鼠标事件 204 | void signalMouseEnter(); 205 | // 进入后延迟信号(以渐变动画完成为准,相当于可手动设置) 206 | void signalMouseEnterLater(); 207 | 208 | void signalMouseLeave(); 209 | // 离开后延迟的信号(直至渐变动画完成(要是划过一下子离开,这个也会变快)) 210 | void signalMouseLeaveLater(); 211 | 212 | void signalMousePress(QMouseEvent *event); 213 | void signalMousePressLater(QMouseEvent *event); 214 | 215 | void signalMouseRelease(QMouseEvent *event); 216 | void signalMouseReleaseLater(QMouseEvent *event); 217 | ``` 218 | 219 | -------------------------------------------------------------------------------- /interactive_buttons/appendbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "appendbutton.h" 2 | 3 | AppendButton::AppendButton(QWidget *parent) : InteractiveButtonBase(parent) 4 | { 5 | setUnifyGeomerey(true); 6 | } 7 | 8 | void AppendButton::paintEvent(QPaintEvent *event) 9 | { 10 | InteractiveButtonBase::paintEvent(event); 11 | 12 | if (!show_foreground) return ; // 不显示前景 13 | 14 | double w = _w, h = _h; 15 | double l = _l+w/4+offset_pos.y(), 16 | t = _t+h/4-offset_pos.x(), 17 | r = w*3/4+offset_pos.y(), 18 | b = h*3/4-offset_pos.x(); 19 | double mx = _l+w/2, my = _t+h/2; 20 | 21 | if (click_ani_appearing || click_ani_disappearing) 22 | { 23 | double pro = click_ani_progress / 100.0; 24 | l -= l * pro; 25 | t -= t * pro; 26 | r += (w-r) * pro; 27 | b += (h-b) * pro; 28 | } 29 | 30 | QPainter painter(this); 31 | painter.setPen(icon_color); 32 | painter.setRenderHint(QPainter::Antialiasing, true); 33 | 34 | painter.translate(mx, my); 35 | painter.rotate((90+add_angle) * hover_progress / 100); 36 | painter.translate(-mx, -my); 37 | 38 | painter.drawLine(QPointF(l, (t+b)/2), QPointF(r, (t+b)/2)); 39 | painter.drawLine(QPointF((l+r)/2, t), QPointF((l+r)/2, b)); 40 | } 41 | 42 | void AppendButton::enterEvent(QEvent *event) 43 | { 44 | add_angle = 0; 45 | rotate_speed = 2; 46 | InteractiveButtonBase::enterEvent(event); 47 | } 48 | 49 | void AppendButton::mouseReleaseEvent(QMouseEvent *event) 50 | { 51 | rotate_speed = 2; 52 | InteractiveButtonBase::mouseReleaseEvent(event); 53 | } 54 | 55 | void AppendButton::anchorTimeOut() 56 | { 57 | if (press_progress) 58 | { 59 | add_angle += rotate_speed; 60 | if (add_angle > 360) 61 | { 62 | add_angle -= 360; 63 | if (rotate_speed < 30) // 动态旋转速度 64 | { 65 | rotate_speed++; 66 | } 67 | } 68 | } 69 | 70 | InteractiveButtonBase::anchorTimeOut(); 71 | } 72 | -------------------------------------------------------------------------------- /interactive_buttons/appendbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef APPENDBUTTON_H 2 | #define APPENDBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class AppendButton : public InteractiveButtonBase 7 | { 8 | public: 9 | AppendButton(QWidget *parent = nullptr); 10 | 11 | protected: 12 | void paintEvent(QPaintEvent *event) override; 13 | void enterEvent(QEvent *event) override; 14 | void mouseReleaseEvent(QMouseEvent *event) override; 15 | 16 | public slots: 17 | virtual void anchorTimeOut() override; 18 | 19 | private: 20 | int add_angle = 0; // 旋转角度 21 | int rotate_speed = 2; // 旋转的速度 22 | }; 23 | 24 | #endif // APPENDBUTTON_H 25 | -------------------------------------------------------------------------------- /interactive_buttons/generalbuttoninterface.cpp: -------------------------------------------------------------------------------- 1 | #include "generalbuttoninterface.h" 2 | 3 | GeneralButtonInterface::GeneralButtonInterface() : QPushButton () 4 | { 5 | init(); 6 | } 7 | 8 | GeneralButtonInterface::GeneralButtonInterface(QWidget *parent) : QPushButton (parent) 9 | { 10 | init(); 11 | } 12 | 13 | GeneralButtonInterface::GeneralButtonInterface(QString icon, QWidget *parent) : QPushButton (QIcon(icon), "", parent) 14 | { 15 | init(); 16 | } 17 | 18 | GeneralButtonInterface::GeneralButtonInterface(QIcon icon, QWidget *parent) : QPushButton (icon, "", parent) 19 | { 20 | init(); 21 | } 22 | 23 | GeneralButtonInterface::GeneralButtonInterface(QIcon icon, QString text, QWidget *parent) : QPushButton (icon, text, parent) 24 | { 25 | init(); 26 | } 27 | 28 | GeneralButtonInterface::GeneralButtonInterface(QString icon, QString text, QWidget *parent) : QPushButton (QIcon(icon), text, parent) 29 | { 30 | init(); 31 | } 32 | 33 | void GeneralButtonInterface::init() 34 | { 35 | static_fixed = true; 36 | store_direct = DIRECT_NONE; 37 | // connect(thm, SIGNAL(windowChanged()), this, SLOT(updateUI())); 38 | } 39 | 40 | void GeneralButtonInterface::setStore(DirectType type) 41 | { 42 | this->store_direct = type; 43 | } 44 | -------------------------------------------------------------------------------- /interactive_buttons/generalbuttoninterface.h: -------------------------------------------------------------------------------- 1 | #ifndef GENERALBUTTON_H 2 | #define GENERALBUTTON_H 3 | 4 | #include 5 | #include 6 | #include 7 | //#include "defines.h" 8 | //#include "globalvar.h" 9 | 10 | class GeneralButtonInterface : public QPushButton 11 | { 12 | Q_OBJECT 13 | public: 14 | enum DirectType { 15 | DIRECT_NONE, 16 | DIRECT_TOP, 17 | DIRECT_LEFT, 18 | DIRECT_RIGHT, 19 | DIRECT_BOTTOM 20 | }; 21 | 22 | GeneralButtonInterface(); 23 | GeneralButtonInterface(QWidget* parent); 24 | GeneralButtonInterface(QString icon, QWidget* parent); 25 | GeneralButtonInterface(QIcon icon, QWidget* parent); 26 | GeneralButtonInterface(QIcon icon, QString text, QWidget* parent); 27 | GeneralButtonInterface(QString icon, QString text, QWidget* parent); 28 | 29 | void init(); 30 | 31 | virtual void setStore(DirectType type); 32 | virtual void showFore(){} 33 | virtual void hideFore(){} 34 | virtual void showBack(){} 35 | virtual void hideBack(){} 36 | 37 | virtual void disableFixed(){ this->static_fixed=false; } 38 | virtual void setFixed(){this->static_fixed=true;} 39 | virtual bool isFixed(){return static_fixed;} 40 | 41 | public slots: 42 | virtual void updateUI(){} 43 | 44 | 45 | protected: 46 | bool static_fixed; 47 | DirectType store_direct; 48 | }; 49 | 50 | #endif // GENERALBUTTON_H 51 | -------------------------------------------------------------------------------- /interactive_buttons/infobutton.cpp: -------------------------------------------------------------------------------- 1 | #include "infobutton.h" 2 | 3 | InfoButton::InfoButton(QWidget *parent) : InteractiveButtonBase(parent) 4 | { 5 | setUnifyGeomerey(true); 6 | // hover_speed = 1; 7 | } 8 | 9 | void InfoButton::paintEvent(QPaintEvent *event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | QPainter painter(this); 16 | painter.setRenderHint(QPainter::Antialiasing, true); 17 | painter.setPen(icon_color); 18 | QPainterPath path; 19 | 20 | double l = _l, t = _t, w = _w, h = _h; 21 | 22 | double cu = 2; // 点的粗细 23 | if (!hover_progress) // 显示感叹号 24 | { 25 | // 画点 26 | path.addEllipse(QRectF(l+w/2-cu, t+h/4, cu*2, cu*2)); 27 | 28 | // 画线 29 | path.addRoundedRect(QRectF(l+w/2-cu/2, t+h*3/8+cu, cu, h/2 - cu*2), cu/2, cu/2); 30 | } 31 | else if (hover_progress < 100) // 转换动画 32 | { 33 | double prop = hover_progress / 100.0; 34 | 35 | // 眼睛出现 36 | double ra = cu * prop * 1.2; 37 | path.addEllipse(l+w/4-ra, t+h/4-ra, ra*2, ra*2); 38 | path.addEllipse(l+w*3/4-ra, t+h/4-ra, ra*2, ra*2); 39 | 40 | // 鼻子下降 41 | double top = t+h/4 + (h/4) * prop; 42 | path.addEllipse(QRectF(l+w/2-cu, top, cu*2, cu*2)); 43 | 44 | double h_mv = w / 4 * prop; // 胡子端点 45 | top += cu*2 + h/8*(1-prop); // +点的高度 +点和线距离 46 | 47 | // 左胡子移动 48 | QPainterPath pathl; 49 | pathl.moveTo(l+w/2, top); // 竖线顶端 50 | pathl.cubicTo(QPointF(l+w*8/16, t+h*7/8), QPointF(l+w*5/16+w*3/16*(1-prop), t+h*6/8), QPointF(l+w/2-h_mv, t+h*11/16+h*3/16*(1-prop))); 51 | painter.drawPath(pathl); 52 | 53 | // 右胡子移动 54 | QPainterPath pathr; 55 | pathr.moveTo(l+w/2, top); 56 | pathr.cubicTo(QPointF(l+w*8/16, t+h*7/8), QPointF(l+w*11/16-w*3/16*(1-prop), t+h*6/8), QPointF(l+w/2+h_mv, t+h*11/16+h*3/16*(1-prop))); 57 | painter.drawPath(pathr); 58 | } 59 | else // 显示Hover静态猫头 60 | { 61 | // 眼睛 62 | if (pressing) // 横线 63 | { 64 | if (offset_pos.x() * offset_pos.x() + offset_pos.y() + offset_pos.y() > this->width() * this->height() / 4) // 显示 x_x 65 | { 66 | painter.drawLine(QPointF(l+w/4-cu, t+h/4-cu), QPointF(l+w/4+cu, t+h/4+cu)); 67 | painter.drawLine(QPointF(l+w/4-cu, t+h/4+cu+0.5), QPointF(l+w/4+cu, t+h/4-cu)); 68 | painter.drawLine(QPointF(l+w*3/4-cu, t+h/4-cu), QPointF(l+w*3/4+cu, t+h/4+cu)); 69 | painter.drawLine(QPointF(l+w*3/4-cu, t+h/4+cu+0.5), QPointF(l+w*3/4+cu, t+h/4-cu)); 70 | } 71 | else // 显示 -_- 72 | { 73 | painter.drawLine(QPointF(l+w/4-cu*2, t+h/4), QPointF(l+w/4+cu*2, t+h/4)); 74 | painter.drawLine(QPointF(l+w*3/4-cu*2, t+h/4), QPointF(l+w*3/4+cu*2, t+h/4)); 75 | } 76 | 77 | if (tongue) 78 | { 79 | double prop = press_progress / 100.0; 80 | // TODO: 添加吐舌头……太麻烦,懒得写了 81 | } 82 | } 83 | else // 点 84 | { 85 | double ra = cu * 1.2; 86 | path.addEllipse(l+w/4-ra, t+h/4-ra, ra*2, ra*2); 87 | path.addEllipse(l+w*3/4-ra, t+h/4-ra, ra*2, cu*2); 88 | } 89 | 90 | // 鼻子点 91 | path.addEllipse(l+w/2-cu, t+h/2-cu/2, cu*2, cu*2); 92 | 93 | // 左胡子 94 | QPainterPath pathl; 95 | pathl.moveTo(l+w/2, t+h*4/8); // 中心顶点 96 | pathl.cubicTo(QPointF(l+w*8/16, t+h*7/8), QPointF(l+w*5/16, t+h*6/8), QPointF(l+w/4, t+h*11/16)); 97 | painter.drawPath(pathl); 98 | 99 | // 右胡子 100 | QPainterPath pathr; 101 | pathr.moveTo(l+w/2, t+h*4/8); 102 | pathr.cubicTo(QPointF(l+w*8/16, t+h*7/8), QPointF(l+w*11/16, t+h*6/8), QPointF(l+w*3/4, t+h*11/16)); 103 | painter.drawPath(pathr); 104 | } 105 | painter.fillPath(path, icon_color); 106 | } 107 | 108 | void InfoButton::mousePressEvent(QMouseEvent *event) 109 | { 110 | if (event->button() == Qt::LeftButton) 111 | { 112 | // 随机吐舌头 113 | tongue = qrand() % 10 == 0; 114 | } 115 | 116 | return InteractiveButtonBase::mousePressEvent(event); 117 | } 118 | -------------------------------------------------------------------------------- /interactive_buttons/infobutton.h: -------------------------------------------------------------------------------- 1 | #ifndef INFOBUTTON_H 2 | #define INFOBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class InfoButton : public InteractiveButtonBase 7 | { 8 | public: 9 | InfoButton(QWidget* parent = nullptr); 10 | 11 | protected: 12 | void paintEvent(QPaintEvent *event) override; 13 | void mousePressEvent(QMouseEvent *event) override; 14 | 15 | private: 16 | bool tongue = false; 17 | }; 18 | 19 | #endif // INFOBUTTON_H 20 | -------------------------------------------------------------------------------- /interactive_buttons/interactivebuttonbase.cpp: -------------------------------------------------------------------------------- 1 | #include "interactivebuttonbase.h" 2 | 3 | /** 4 | * 所有内容的初始化 5 | * 如果要自定义,可以在这里调整所有的默认值 6 | */ 7 | InteractiveButtonBase::InteractiveButtonBase(QWidget *parent) 8 | : QPushButton(parent), icon(nullptr), text(""), paint_addin(), 9 | fore_paddings(4, 4, 4, 4), 10 | self_enabled(true), parent_enabled(false), fore_enabled(true), 11 | show_animation(false), show_foreground(true), show_ani_appearing(false), show_ani_disappearing(false), 12 | show_duration(300), show_timestamp(0), hide_timestamp(0), show_ani_progress(0), show_ani_point(0, 0), 13 | enter_pos(-1, -1), press_pos(-1, -1), release_pos(-1, -1), mouse_pos(-1, -1), anchor_pos(-1, -1), 14 | offset_pos(0, 0), effect_pos(-1, -1), release_offset(0, 0), 15 | hovering(false), pressing(false), 16 | hover_timestamp(0), leave_timestamp(0), press_timestamp(0), release_timestamp(0), 17 | hover_bg_duration(300), press_bg_duration(300), click_ani_duration(300), 18 | move_speed(5), 19 | icon_color(0, 0, 0), text_color(0, 0, 0), 20 | normal_bg(0xF2, 0xF2, 0xF2, 0), hover_bg(128, 128, 128, 32), press_bg(128, 128, 128, 64), border_bg(0, 0, 0, 0), 21 | focus_bg(0, 0, 0, 0), focus_border(0, 0, 0, 0), 22 | hover_speed(5), press_start(40), press_speed(5), 23 | hover_progress(0), press_progress(0), icon_padding_proper(0.2), icon_text_padding(4), icon_text_size(16), 24 | border_width(1), radius_x(0), radius_y(0), 25 | font_size(0), fixed_fore_pos(false), fixed_fore_size(false), text_dynamic_size(false), auto_text_color(true), focusing(false), 26 | click_ani_appearing(false), click_ani_disappearing(false), click_ani_progress(0), 27 | mouse_press_event(nullptr), mouse_release_event(nullptr), 28 | unified_geometry(false), _l(0), _t(0), _w(32), _h(32), 29 | jitter_animation(true), elastic_coefficient(1.2), jitter_duration(300), 30 | water_animation(true), water_press_duration(800), water_release_duration(400), water_finish_duration(300), 31 | align(Qt::AlignCenter), _state(false), leave_after_clicked(false), _block_hover(false), 32 | double_clicked(false), double_timer(nullptr), double_prevent(false) 33 | { 34 | setMouseTracking(true); // 鼠标没有按下时也能捕获移动事件 35 | 36 | model = PaintModel::None; 37 | 38 | anchor_timer = new QTimer(this); 39 | anchor_timer->setInterval(10); 40 | connect(anchor_timer, SIGNAL(timeout()), this, SLOT(anchorTimeOut())); 41 | 42 | setWaterRipple(); 43 | 44 | connect(this, SIGNAL(clicked()), this, SLOT(slotClicked())); 45 | 46 | setFocusPolicy(Qt::NoFocus); // 避免一个按钮还获取Tab键焦点 47 | } 48 | 49 | /** 50 | * 文字类型的按钮 51 | */ 52 | InteractiveButtonBase::InteractiveButtonBase(QString text, QWidget *parent) 53 | : InteractiveButtonBase(parent) 54 | { 55 | setText(text); 56 | } 57 | 58 | /** 59 | * 图标类型的按钮 60 | */ 61 | InteractiveButtonBase::InteractiveButtonBase(QIcon icon, QWidget *parent) 62 | : InteractiveButtonBase(parent) 63 | { 64 | setIcon(icon); 65 | } 66 | 67 | /** 68 | * 变色图标类型的按钮 69 | */ 70 | InteractiveButtonBase::InteractiveButtonBase(QPixmap pixmap, QWidget *parent) 71 | : InteractiveButtonBase(parent) 72 | { 73 | setPixmap(pixmap); 74 | } 75 | 76 | InteractiveButtonBase::InteractiveButtonBase(QIcon icon, QString text, QWidget *parent) 77 | : InteractiveButtonBase(parent) 78 | { 79 | setIcon(icon); 80 | setText(text); 81 | } 82 | 83 | InteractiveButtonBase::InteractiveButtonBase(QPixmap pixmap, QString text, QWidget *parent) 84 | : InteractiveButtonBase(parent) 85 | { 86 | setPixmap(pixmap); 87 | setText(text); 88 | } 89 | 90 | /** 91 | * 设置按钮文字 92 | * @param text 按钮文字 93 | */ 94 | void InteractiveButtonBase::setText(QString text) 95 | { 96 | this->text = text; 97 | if (model == PaintModel::None) 98 | { 99 | model = PaintModel::Text; 100 | setAlign(Qt::AlignCenter); 101 | } 102 | else if (model == PaintModel::PixmapMask) 103 | { 104 | if (pixmap.isNull()) 105 | model = PaintModel::Text; 106 | else 107 | model = PaintModel::PixmapText; 108 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 109 | QFontMetrics fm(this->font()); 110 | icon_text_size = fm.lineSpacing(); 111 | } 112 | else if (model == PaintModel::Icon) 113 | { 114 | if (text.isEmpty()) 115 | { 116 | model = PaintModel::Icon; 117 | setAlign(Qt::AlignCenter); 118 | } 119 | else 120 | { 121 | if (icon.isNull()) 122 | model = PaintModel::Text; 123 | else 124 | model = PaintModel::IconText; 125 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 126 | QFontMetrics fm(this->font()); 127 | icon_text_size = fm.lineSpacing(); 128 | } 129 | } 130 | 131 | if (parent_enabled) 132 | QPushButton::setText(text); 133 | 134 | // 根据字体调整大小 135 | if (text_dynamic_size) 136 | { 137 | adjustMinimumSize(); 138 | } 139 | update(); 140 | } 141 | 142 | /** 143 | * 设置 icon 图标 144 | * @param path 图标路径文本 145 | */ 146 | void InteractiveButtonBase::setIconPath(QString path) 147 | { 148 | setIcon(QIcon(path)); 149 | } 150 | 151 | /** 152 | * 设置 pixmap 图标 153 | * @param path 图标路径文本 154 | */ 155 | void InteractiveButtonBase::setPixmapPath(QString path) 156 | { 157 | setPixmap(QPixmap(path)); 158 | } 159 | 160 | /** 161 | * 设置 icon 162 | * @param icon 图标 163 | */ 164 | void InteractiveButtonBase::setIcon(QIcon icon) 165 | { 166 | if (model == PaintModel::None) 167 | { 168 | model = PaintModel::Icon; 169 | setAlign(Qt::AlignCenter); 170 | } 171 | else if (model == PaintModel::Text) 172 | { 173 | if (text.isEmpty()) 174 | model = PaintModel::Icon; 175 | else 176 | model = PaintModel::IconText; 177 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 178 | QFontMetrics fm(this->font()); 179 | icon_text_size = fm.lineSpacing(); 180 | } 181 | else if (model == PaintModel::PixmapMask) 182 | { 183 | pixmap = QPixmap(); 184 | model = PaintModel::Icon; 185 | } 186 | else if (model == PaintModel::PixmapText) 187 | { 188 | pixmap = QPixmap(); 189 | if (text.isEmpty()) 190 | model = PaintModel::Icon; 191 | else 192 | model = PaintModel::IconText; 193 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 194 | QFontMetrics fm(this->font()); 195 | icon_text_size = fm.lineSpacing(); 196 | } 197 | else if (model == PaintModel::IconText && text.isEmpty()) 198 | { 199 | model = PaintModel::Icon; 200 | setAlign(Qt::AlignCenter); 201 | } 202 | this->icon = icon; 203 | if (parent_enabled) 204 | QPushButton::setIcon(icon); 205 | update(); 206 | } 207 | 208 | /** 209 | * 设置 Pixmap 210 | * @param pixmap [description] 211 | */ 212 | void InteractiveButtonBase::setPixmap(QPixmap pixmap) 213 | { 214 | if (model == PaintModel::None) 215 | model = PaintModel::PixmapMask; 216 | else if (model == PaintModel::Text) 217 | { 218 | if (text.isEmpty()) 219 | model = PaintModel::PixmapMask; 220 | else 221 | model = PaintModel::PixmapText; 222 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 223 | QFontMetrics fm(this->font()); 224 | icon_text_size = fm.lineSpacing(); 225 | } 226 | else if (model == PaintModel::Icon) 227 | { 228 | icon = QIcon(); 229 | model = PaintModel::PixmapMask; 230 | } 231 | else if (model == PaintModel::IconText) 232 | { 233 | icon = QIcon(); 234 | if (text.isEmpty()) 235 | model = PaintModel::PixmapMask; 236 | else 237 | model = PaintModel::PixmapText; 238 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 239 | QFontMetrics fm(this->font()); 240 | icon_text_size = fm.lineSpacing(); 241 | } 242 | this->pixmap = getColoredPixmap(pixmap, isEnabled() ? icon_color : getOpacityColor(icon_color)); 243 | if (parent_enabled) 244 | QPushButton::setIcon(QIcon(pixmap)); 245 | update(); 246 | } 247 | 248 | /** 249 | * 设置额外的图标,例如角标 250 | * @param pixmap 图标 251 | * @param align 对齐方式 252 | * @param size 图标尺寸 253 | */ 254 | void InteractiveButtonBase::setPaintAddin(QPixmap pixmap, Qt::Alignment align, QSize size) 255 | { 256 | QBitmap mask = pixmap.mask(); 257 | pixmap.fill(icon_color); 258 | pixmap.setMask(mask); 259 | paint_addin = PaintAddin(pixmap, align, size); 260 | update(); 261 | } 262 | 263 | /** 264 | * 设置子类功能是否开启 265 | * 如果关闭,则相当于默认的 QPushButton 266 | * @param e 开关 267 | */ 268 | void InteractiveButtonBase::setSelfEnabled(bool e) 269 | { 270 | self_enabled = e; 271 | } 272 | 273 | /** 274 | * 设置父类(QPushButton)功能是否开启 275 | * 如果开启,则绘制父类背景、父类前景 276 | * @param e 开关 277 | */ 278 | void InteractiveButtonBase::setParentEnabled(bool e) 279 | { 280 | parent_enabled = e; 281 | 282 | // 传递子类内容到父类去,避免子类关掉后不显示 283 | if (model == PaintModel::Text || model == PaintModel::IconText || model == PaintModel::PixmapText) 284 | QPushButton::setText(text); 285 | if (model == PaintModel::Icon || model == PaintModel::IconText) 286 | QPushButton::setIcon(icon); 287 | if (model == PaintModel::PixmapMask || model == PaintModel::PixmapText) 288 | QPushButton::setIcon(QIcon(pixmap)); 289 | } 290 | 291 | /** 292 | * 设置是否绘制前景图标/文字 293 | * 关闭后则只绘制背景 294 | * @param e 开启 295 | */ 296 | void InteractiveButtonBase::setForeEnabled(bool e) 297 | { 298 | fore_enabled = e; 299 | } 300 | 301 | /** 302 | * 设置鼠标悬浮背景渐变的动画时长 303 | * @param d 动画时长(毫秒) 304 | */ 305 | void InteractiveButtonBase::setHoverAniDuration(int d) 306 | { 307 | this->hover_bg_duration = d; 308 | // hover_progress = 0; // 重置hover效果 309 | } 310 | 311 | /** 312 | * 设置鼠标按下渐变效果的动画时长 313 | * @param d 动画时长(毫秒) 314 | */ 315 | void InteractiveButtonBase::setPressAniDuration(int d) 316 | { 317 | this->press_bg_duration = d; 318 | } 319 | 320 | /** 321 | * 设置单击效果的动画时长 322 | * @param d 动画时长(毫秒) 323 | */ 324 | void InteractiveButtonBase::setClickAniDuration(int d) 325 | { 326 | this->click_ani_duration = d; 327 | } 328 | 329 | /** 330 | * 设置水波纹动画时长 331 | * @param press 按住时时长(时长毫秒) 332 | * @param release 松开后速度(时长毫秒) 333 | * @param finish 渐变消失速度(时长毫秒) 334 | */ 335 | void InteractiveButtonBase::setWaterAniDuration(int press, int release, int finish) 336 | { 337 | this->water_press_duration = press; 338 | this->water_release_duration = release; 339 | this->water_finish_duration = finish; 340 | } 341 | 342 | /** 343 | * 各种状态改变 344 | * 主要是监控 可用 状态,不可用时设置为半透明 345 | */ 346 | void InteractiveButtonBase::changeEvent(QEvent *event) 347 | { 348 | QPushButton::changeEvent(event); 349 | 350 | if (event->type() == QEvent::EnabledChange && model == PixmapMask) // 可用状态改变了 351 | { 352 | if (isEnabled()) // 恢复可用:透明度变回去 353 | { 354 | QColor color = icon_color; 355 | color.setAlpha(color.alpha() * 2); 356 | setIconColor(color); 357 | } 358 | else // 变成不可用:透明度减半 359 | { 360 | QColor color = icon_color; 361 | color.setAlpha(color.alpha() / 2); 362 | setIconColor(color); 363 | } 364 | } 365 | } 366 | 367 | /** 368 | * 设置水波纹动画是否开启 369 | * 关闭时,将使用渐变动画 370 | * @param enable 开关 371 | */ 372 | void InteractiveButtonBase::setWaterRipple(bool enable) 373 | { 374 | if (water_animation == enable) 375 | return; 376 | water_animation = enable; 377 | } 378 | 379 | /** 380 | * 设置抖动效果是否开启 381 | * 鼠标拖拽移动的距离越长,抖动距离越长、次数越多 382 | * @param enable 开关 383 | */ 384 | void InteractiveButtonBase::setJitterAni(bool enable) 385 | { 386 | jitter_animation = enable; 387 | } 388 | 389 | /** 390 | * 设置是否使用统一图标绘制区域 391 | * 监听图标尺寸大小变化、中心点偏移,计算新的中心坐标位置 392 | * @param enable 开关 393 | */ 394 | void InteractiveButtonBase::setUnifyGeomerey(bool enable) 395 | { 396 | unified_geometry = enable; 397 | _l = _t = 0; 398 | _w = width(); 399 | _h = height(); 400 | } 401 | 402 | /** 403 | * 设置背景颜色 404 | * @param bg 背景颜色 405 | */ 406 | void InteractiveButtonBase::setBgColor(QColor bg) 407 | { 408 | setNormalColor(bg); 409 | update(); 410 | } 411 | 412 | /** 413 | * 设置事件背景颜色 414 | * @param hover 鼠标悬浮时的背景颜色 415 | * @param press 鼠标按下时的背景颜色 416 | */ 417 | void InteractiveButtonBase::setBgColor(QColor hover, QColor press) 418 | { 419 | if (hover != Qt::black) 420 | setHoverColor(hover); 421 | if (press != Qt::black) 422 | setPressColor(press); 423 | update(); 424 | } 425 | 426 | /** 427 | * 设置按钮背景颜色 428 | * @param color 背景颜色 429 | */ 430 | void InteractiveButtonBase::setNormalColor(QColor color) 431 | { 432 | normal_bg = color; 433 | update(); 434 | } 435 | 436 | /** 437 | * 设置边框线条颜色 438 | * @param color 边框颜色 439 | */ 440 | void InteractiveButtonBase::setBorderColor(QColor color) 441 | { 442 | border_bg = color; 443 | } 444 | 445 | /** 446 | * 设置鼠标悬浮时的背景颜色 447 | * @param color 背景颜色 448 | */ 449 | void InteractiveButtonBase::setHoverColor(QColor color) 450 | { 451 | hover_bg = color; 452 | } 453 | 454 | /** 455 | * 设置鼠标按住时的背景颜色 456 | * @param color 背景颜色 457 | */ 458 | void InteractiveButtonBase::setPressColor(QColor color) 459 | { 460 | press_bg = color; 461 | } 462 | 463 | /** 464 | * 设置图标颜色(仅针对可变色的 pixmap 图标) 465 | * @param color 图标颜色 466 | */ 467 | void InteractiveButtonBase::setIconColor(QColor color) 468 | { 469 | icon_color = color; 470 | 471 | // 绘制图标(如果有) 472 | if (model == PaintModel::PixmapMask || model == PaintModel::PixmapText) 473 | { 474 | pixmap = getColoredPixmap(pixmap, isEnabled() ? icon_color : getOpacityColor(icon_color)); 475 | } 476 | 477 | // 绘制额外角标(如果有的话) 478 | if (paint_addin.enable) 479 | { 480 | paint_addin.pixmap = getColoredPixmap(paint_addin.pixmap, isEnabled() ? icon_color : getOpacityColor(icon_color)); 481 | } 482 | 483 | update(); 484 | } 485 | 486 | /** 487 | * 设置前景文字颜色 488 | * @param color 文字颜色 489 | */ 490 | void InteractiveButtonBase::setTextColor(QColor color) 491 | { 492 | text_color = color; 493 | update(); 494 | } 495 | 496 | /** 497 | * 设置获取焦点时的背景颜色(默认关闭) 498 | * @param color 背景颜色 499 | */ 500 | void InteractiveButtonBase::setFocusBg(QColor color) 501 | { 502 | setFocusPolicy(Qt::StrongFocus); 503 | focus_bg = color; 504 | } 505 | 506 | /** 507 | * 设置获取焦点时的边框颜色(默认关闭) 508 | * @param color 边框颜色 509 | */ 510 | void InteractiveButtonBase::setFocusBorder(QColor color) 511 | { 512 | setFocusPolicy(Qt::StrongFocus); 513 | focus_border = color; 514 | } 515 | 516 | /** 517 | * 设置文字大小(PointSize,覆盖 font() 字体大小) 518 | * @param f 文字大小 519 | */ 520 | void InteractiveButtonBase::setFontSize(int f) 521 | { 522 | if (!font_size) // 第一次设置字体大小,直接设置 523 | { 524 | font_size = f; 525 | QFont font(this->font()); 526 | font.setPointSize(f); 527 | setFont(font); 528 | update(); 529 | } 530 | else // 改变字体大小,使用字体缩放动画 531 | { 532 | QPropertyAnimation *ani = new QPropertyAnimation(this, "font_size"); 533 | ani->setStartValue(font_size); 534 | ani->setEndValue(f); 535 | ani->setDuration(click_ani_duration); 536 | connect(ani, &QPropertyAnimation::finished, [=] { 537 | QFontMetrics fm(this->font()); 538 | icon_text_size = fm.lineSpacing(); 539 | ani->deleteLater(); 540 | }); 541 | ani->start(); 542 | } 543 | // 修改字体大小时调整按钮的最小尺寸,避免文字显示不全 544 | if (text_dynamic_size) 545 | { 546 | QFont font; 547 | font.setPointSize(f); 548 | QFontMetrics fms(font); 549 | setMinimumSize(fms.horizontalAdvance(text) + fore_paddings.left + fore_paddings.right, fms.lineSpacing() + fore_paddings.top + fore_paddings.bottom); 550 | } 551 | if (model != PaintModel::Text) 552 | { 553 | QFontMetrics fm(this->font()); 554 | icon_text_size = fm.lineSpacing(); 555 | } 556 | } 557 | 558 | /** 559 | * 获取字体大小 560 | * 用来作为字体动画的属性参数 561 | * @return 临时字体大小 562 | */ 563 | int InteractiveButtonBase::getFontSizeT() 564 | { 565 | return font_size; 566 | } 567 | 568 | /** 569 | * 设置动画中的临时字体大小 570 | * 用来作为字体动画的属性参数 571 | * @param f 临时字体大小 572 | */ 573 | void InteractiveButtonBase::setFontSizeT(int f) 574 | { 575 | this->font_size = f; 576 | QFont font(this->font()); 577 | font.setPointSize(f); 578 | setFont(font); 579 | update(); 580 | } 581 | 582 | /** 583 | * 如果点击失去焦点的话,即使鼠标移到上面,也不会出现背景 584 | * 可以用这个方法继续保持悬浮状态 585 | */ 586 | void InteractiveButtonBase::setHover() 587 | { 588 | if (!hovering && inArea(mapFromGlobal(QCursor::pos()))) 589 | InteractiveButtonBase::enterEvent(new QEvent(QEvent::Type::None)); 590 | } 591 | 592 | /** 593 | * 设置对齐方式 594 | * @param a 对齐方式 595 | */ 596 | void InteractiveButtonBase::setAlign(Qt::Alignment a) 597 | { 598 | align = a; 599 | update(); 600 | } 601 | 602 | /** 603 | * 设置四个角的半径 604 | * @param r 半径 605 | */ 606 | void InteractiveButtonBase::setRadius(int r) 607 | { 608 | radius_x = radius_y = r; 609 | } 610 | 611 | /** 612 | * 分开设置 X、Y 的半径 613 | * @param rx X半径 614 | * @param ry Y半径 615 | */ 616 | void InteractiveButtonBase::setRadius(int rx, int ry) 617 | { 618 | radius_x = rx; 619 | radius_y = ry; 620 | } 621 | 622 | /** 623 | * 设置边框线条的粗细 624 | * @param x 线条粗细 625 | */ 626 | void InteractiveButtonBase::setBorderWidth(int x) 627 | { 628 | border_width = x; 629 | } 630 | 631 | /** 632 | * 设置不可用情况(默认为假) 633 | * 区别于 setEnabled(bool),两个相反的,并不是覆盖方法 634 | * @param dis 不可用 635 | */ 636 | void InteractiveButtonBase::setDisabled(bool dis) 637 | { 638 | if (dis == !isEnabled()) // 相同的 639 | return; 640 | 641 | setEnabled(!dis); 642 | 643 | if (parentWidget() != nullptr) 644 | { 645 | setAttribute(Qt::WA_TransparentForMouseEvents, dis); // 点击穿透 646 | } 647 | 648 | if (model == PixmapMask || model == PixmapText) 649 | { 650 | pixmap = getColoredPixmap(pixmap, dis ? getOpacityColor(icon_color) : icon_color); 651 | } 652 | 653 | update(); // 修改透明度 654 | } 655 | 656 | /** 657 | * 设置前景和四条边的 paddings 658 | * @param l 左边空白 659 | * @param r 右边空白 660 | * @param t 顶边空白 661 | * @param b 底边空白 662 | */ 663 | void InteractiveButtonBase::setPaddings(int l, int r, int t, int b) 664 | { 665 | fore_paddings.left = l; 666 | fore_paddings.right = r; 667 | fore_paddings.top = t; 668 | fore_paddings.bottom = b; 669 | setFixedForeSize(); 670 | } 671 | 672 | /** 673 | * 统一设置方向的 paddings 674 | * @param h 横向 675 | * @param v 纵向 676 | */ 677 | void InteractiveButtonBase::setPaddings(int h, int v) 678 | { 679 | fore_paddings.left = fore_paddings.right = (h + 1) / 2; 680 | fore_paddings.top = fore_paddings.bottom = (v + 1) / 2; 681 | setFixedForeSize(); 682 | } 683 | 684 | /** 685 | * 统一设置前景和四条边的 paddings 686 | * @param x 一样大小的四边留白 687 | */ 688 | void InteractiveButtonBase::setPaddings(int x) 689 | { 690 | fore_paddings.left = x; 691 | fore_paddings.right = x; 692 | fore_paddings.top = x; 693 | fore_paddings.bottom = x; 694 | setFixedForeSize(); 695 | } 696 | 697 | /** 698 | * 设置Icon模式旁边空多少 699 | * @param x 0~1.0,越大越空 700 | */ 701 | void InteractiveButtonBase::setIconPaddingProper(double x) 702 | { 703 | icon_padding_proper = x; 704 | int short_side = min(width(), height()); // 短边 705 | // 非固定的情况,尺寸大小变了之后所有 padding 都要变 706 | int padding = int(short_side * icon_padding_proper); //static_cast(short_side * (1 - GOLDEN_RATIO) / 2); 707 | fore_paddings.left = fore_paddings.top = fore_paddings.right = fore_paddings.bottom = padding; 708 | update(); 709 | } 710 | 711 | /** 712 | * 设置字体大小时是否同步修改按钮的最小尺寸(避免按钮显示不全) 713 | * @param d 开关 714 | */ 715 | void InteractiveButtonBase::setTextDynamicSize(bool d) 716 | { 717 | text_dynamic_size = d; 718 | } 719 | 720 | /** 721 | * 见 setFixedForePos(bool f) 722 | */ 723 | void InteractiveButtonBase::setFixedTextPos(bool f) 724 | { 725 | fixed_fore_pos = f; 726 | } 727 | 728 | /** 729 | * 设置前景是否固定,而不移动 730 | * 将去除鼠标移入靠近、抖动效果,统一图标区域大小不变 731 | * 只包括:鼠标进入/点击,均表现为缩放效果(默认) 732 | * 不影响任何其他功能 733 | * @param f [description] 734 | */ 735 | void InteractiveButtonBase::setFixedForePos(bool f) 736 | { 737 | fixed_fore_pos = f; 738 | } 739 | 740 | /** 741 | * 固定按钮(最小值)为适当尺寸,并且固定四周留白 742 | * 前景应为文字/图标对应尺寸的最小尺寸 743 | * @param f 是否固定前景 744 | * @param addin 留白的像素大小 745 | */ 746 | void InteractiveButtonBase::setFixedForeSize(bool f, int addin) 747 | { 748 | fixed_fore_size = f; 749 | 750 | if (!f) 751 | return; 752 | if (model == PaintModel::Text || model == PaintModel::IconText || model == PaintModel::PixmapText) 753 | { 754 | int icon_width = (model != PaintModel::Text && icon.isNull()) ? 0 : icon_text_size; 755 | QFont font = this->font(); 756 | if (font_size > 0) 757 | font.setPointSize(font_size); 758 | QFontMetrics fm(font); 759 | int w = fm.horizontalAdvance(text); 760 | w = icon_width + w + quick_sqrt(w / 2) + fore_paddings.left + fore_paddings.right; 761 | setMinimumSize( 762 | w + addin, 763 | fm.lineSpacing() + fore_paddings.top + fore_paddings.bottom + addin); 764 | } 765 | else if (model == PaintModel::Icon || model == PaintModel::PixmapMask) 766 | { 767 | int size = height(); 768 | setMinimumSize(size + addin, size + addin); 769 | } 770 | } 771 | 772 | void InteractiveButtonBase::setSquareSize() 773 | { 774 | setFixedWidth(height()); 775 | setMinimumWidth(height()); 776 | setMaximumWidth(height()); 777 | } 778 | 779 | /** 780 | * 设置鼠标单击松开后是否当做移开 781 | * 避免菜单、弹窗出现后,由于鼠标仍然留在按钮上面,导致依旧显示 hover 背景 782 | * @param l 开关 783 | */ 784 | void InteractiveButtonBase::setLeaveAfterClick(bool l) 785 | { 786 | leave_after_clicked = l; 787 | } 788 | 789 | /** 790 | * 响应双击事件 791 | * 注意:会先触发单击事件、再触发双击事件(其实就是懒得做) 792 | * 建议在 QListWidget 等地方使用! 793 | * @param e 开关 794 | */ 795 | void InteractiveButtonBase::setDoubleClicked(bool e) 796 | { 797 | double_clicked = e; 798 | 799 | if (double_timer == nullptr) 800 | { 801 | double_timer = new QTimer(this); 802 | double_timer->setInterval(DOUBLE_PRESS_INTERVAL); 803 | connect(double_timer, &QTimer::timeout, [=] { 804 | double_timer->stop(); 805 | emit clicked(); // 手动触发单击事件 806 | }); 807 | } 808 | } 809 | 810 | /** 811 | * 动画时是否自动设置文字的颜色 812 | */ 813 | void InteractiveButtonBase::setAutoTextColor(bool a) 814 | { 815 | this->auto_text_color = a; 816 | } 817 | 818 | /** 819 | * 一开始没有聚焦时,假装获取焦点 820 | * 通过信号槽使其他控件(例如QLineEdit)按下enter键触发此按钮事件 821 | * 直到触发了焦点改变事件,此控件失去焦点(需要手动改变) 822 | */ 823 | void InteractiveButtonBase::setPretendFocus(bool f) 824 | { 825 | focusing = f; 826 | update(); 827 | } 828 | 829 | /** 830 | * 如果按钮被做成一个组合,在显示的时候开启动画 831 | * 一开始鼠标下的按钮一直在hover状态,移开也不会变 832 | * 开启后临时屏蔽,记得在动画结束后关闭 833 | */ 834 | void InteractiveButtonBase::setBlockHover(bool b) 835 | { 836 | _block_hover = b; 837 | if (b && hovering) 838 | leaveEvent(nullptr); 839 | } 840 | 841 | /** 842 | * 是否开启出现动画 843 | * 鼠标进入按钮区域,前景图标从对面方向缩放出现 844 | * @param enable 开关 845 | */ 846 | void InteractiveButtonBase::setShowAni(bool enable) 847 | { 848 | show_animation = enable; 849 | 850 | if (!show_animation) // 关闭隐藏前景 851 | { 852 | show_foreground = true; 853 | } 854 | else if (show_animation) // 开启隐藏前景 855 | { 856 | if (!hovering && !pressing) // 应该是隐藏状态 857 | { 858 | show_ani_appearing = show_ani_disappearing = show_foreground = false; 859 | show_ani_progress = 0; 860 | } 861 | else // 应该是显示状态 862 | { 863 | show_foreground = true; 864 | show_ani_appearing = show_ani_disappearing = false; 865 | show_ani_progress = 100; 866 | } 867 | } 868 | } 869 | 870 | /** 871 | * 按钮前景出现动画 872 | * 从中心点出现的缩放动画 873 | */ 874 | void InteractiveButtonBase::showForeground() 875 | { 876 | if (!show_animation) 877 | return; 878 | waters.clear(); 879 | if (!anchor_timer->isActive()) 880 | anchor_timer->start(); 881 | if (show_ani_disappearing) 882 | show_ani_disappearing = false; 883 | show_ani_appearing = true; 884 | show_timestamp = getTimestamp(); 885 | show_foreground = true; 886 | show_ani_point = QPoint(0, 0); 887 | } 888 | 889 | /** 890 | * 按钮前景出现动画2 891 | * 指定方向(笛卡尔坐标),从反方向至中心点 892 | * @param point 最开始出现的方向(大小不影响,只按 x、y 比例来) 893 | */ 894 | void InteractiveButtonBase::showForeground2(QPoint point) 895 | { 896 | showForeground(); 897 | if (point == QPoint(0, 0)) 898 | point = mapFromGlobal(QCursor::pos()) - QPoint(width() / 2, height() / 2); // 相对于按钮中心 899 | show_ani_point = point; 900 | 901 | if (unified_geometry) // 统一出现动画 902 | updateUnifiedGeometry(); 903 | } 904 | 905 | /** 906 | * 隐藏前景 907 | * 为下一次的出现动画做准备 908 | */ 909 | void InteractiveButtonBase::hideForeground() 910 | { 911 | if (!show_animation) 912 | return; 913 | if (!anchor_timer->isActive()) 914 | anchor_timer->start(); 915 | if (show_ani_appearing) 916 | show_ani_appearing = false; 917 | show_ani_disappearing = true; 918 | hide_timestamp = getTimestamp(); 919 | } 920 | 921 | /** 922 | * 延迟出现前景 923 | * 适用于多个按钮连续出现的一套效果 924 | * @param time 延迟时长(毫秒) 925 | * @param point 出现方向 926 | */ 927 | void InteractiveButtonBase::delayShowed(int time, QPoint point) 928 | { 929 | setShowAni(true); 930 | QTimer::singleShot(time, [=] { 931 | showForeground2(point); 932 | connect(this, &InteractiveButtonBase::showAniFinished, [=] { 933 | setShowAni(false); 934 | disconnect(this, SIGNAL(showAniFinished()), nullptr, nullptr); 935 | }); 936 | }); 937 | } 938 | 939 | /** 940 | * 获取文字 941 | */ 942 | QString InteractiveButtonBase::getText() 943 | { 944 | return text; 945 | } 946 | 947 | /** 948 | * 设置菜单 949 | * 并解决菜单无法监听到 release 的问题 950 | * @param menu 菜单对象 951 | */ 952 | void InteractiveButtonBase::setMenu(QMenu *menu) 953 | { 954 | // 默认设置了不获取焦点事件,所以如果设置了菜单的话,就不会有Release事件,水波纹动画会一直飘荡 955 | // 在 focusOut 事件中,模拟了 release 事件, 956 | this->setFocusPolicy(Qt::FocusPolicy::ClickFocus); 957 | 958 | QPushButton::setMenu(menu); 959 | } 960 | 961 | /** 962 | * 根据内容,调整最小尺寸(不影响最大值) 963 | */ 964 | void InteractiveButtonBase::adjustMinimumSize() 965 | { 966 | int icon_width = (model == PaintModel::Text || model == PaintModel::None) || icon.isNull() ? 0 : icon_text_size; 967 | if (icon_width && !text.isEmpty()) 968 | icon_width += icon_text_padding; 969 | int w = 0, h = 0; 970 | if (font_size <= 0) 971 | { 972 | QFontMetrics fm(font()); 973 | w = fm.horizontalAdvance(text); 974 | w = icon_width + w + quick_sqrt(w / 2) + fore_paddings.left + fore_paddings.right; 975 | h = fm.lineSpacing() + fore_paddings.top + fore_paddings.bottom; 976 | } 977 | else 978 | { 979 | QFont font; 980 | font.setPointSize(font_size); 981 | QFontMetrics fm(font); 982 | w = fm.horizontalAdvance(text); 983 | w = icon_width + w + quick_sqrt(w / 2) + fore_paddings.left + fore_paddings.right; 984 | h = fm.lineSpacing() + fore_paddings.top + fore_paddings.bottom; 985 | } 986 | setMinimumSize(w, h); 987 | } 988 | 989 | /** 990 | * 设置状态 991 | * 一个用来作为开关效果的属性 992 | * @param s 状态 993 | */ 994 | void InteractiveButtonBase::setState(bool s) 995 | { 996 | _state = s; 997 | update(); 998 | } 999 | 1000 | /** 1001 | * 获取状态 1002 | * @return 状态 1003 | */ 1004 | bool InteractiveButtonBase::getState() 1005 | { 1006 | return _state; 1007 | } 1008 | 1009 | /** 1010 | * 模拟按下开关的效果,并改变状态 1011 | * 如果不使用状态,则出现点击动画 1012 | * @param s 目标状态(默认为false) 1013 | * @param a 鼠标在区域内则点击无效(恐怕再次点击) 1014 | */ 1015 | void InteractiveButtonBase::simulateStatePress(bool s, bool a) 1016 | { 1017 | if (getState() == s) 1018 | return; 1019 | 1020 | // 鼠标悬浮在上方,有两种情况: 1021 | // 1、点击按钮后触发,重复了 1022 | // 2、需要假装触发,例如 Popup 类型,尽管悬浮在上面,但是无法点击到 1023 | if (a && inArea(mapFromGlobal(QCursor::pos()))) // 点击当前按钮,不需要再模拟了 1024 | return; 1025 | 1026 | mousePressEvent(new QMouseEvent(QMouseEvent::Type::None, QPoint(width() / 2, height() / 2), Qt::LeftButton, Qt::NoButton, Qt::NoModifier)); 1027 | 1028 | mouseReleaseEvent(new QMouseEvent(QMouseEvent::Type::None, QPoint(width() / 2, height() / 2), Qt::LeftButton, Qt::NoButton, Qt::NoModifier)); 1029 | 1030 | // if (!inArea(mapFromGlobal(QCursor::pos()))) // 针对模拟release 后面 // 必定成立 1031 | hovering = false; 1032 | } 1033 | 1034 | /** 1035 | * 模拟鼠标悬浮的效果 1036 | * 适用于键盘操作时,模拟鼠标hover状态 1037 | * 用 discardHoverPress 取消状态 1038 | */ 1039 | void InteractiveButtonBase::simulateHover() 1040 | { 1041 | if (!hovering) 1042 | { 1043 | if (_block_hover) 1044 | setBlockHover(false); // 可能已经临时屏蔽掉鼠标 enter 事件,强制hover 1045 | enterEvent(nullptr); 1046 | } 1047 | } 1048 | 1049 | /** 1050 | * 强制丢弃hover、press状态 1051 | * 适用于悬浮/点击后,弹出模态浮窗 1052 | * 浮窗关闭后调用此方法 1053 | * @param force 如果鼠标仍在此按钮内,是否强制取消hover/press状态 1054 | */ 1055 | void InteractiveButtonBase::discardHoverPress(bool force) 1056 | { 1057 | if (!force && inArea(mapFromGlobal(QCursor::pos()))) // 鼠标还在这范围内 1058 | return; 1059 | 1060 | if (hovering) 1061 | { 1062 | leaveEvent(nullptr); 1063 | } 1064 | 1065 | if (pressing) 1066 | { 1067 | mouseReleaseEvent(new QMouseEvent(QMouseEvent::Type::None, QPoint(width() / 2, height() / 2), Qt::LeftButton, Qt::NoButton, Qt::NoModifier)); 1068 | } 1069 | } 1070 | 1071 | /** 1072 | * 鼠标移入事件,触发 hover 时间戳 1073 | */ 1074 | void InteractiveButtonBase::enterEvent(QEvent *event) 1075 | { 1076 | if (_block_hover) // 临时屏蔽hover事件 1077 | { 1078 | if (event) 1079 | event->accept(); 1080 | return; 1081 | } 1082 | 1083 | if (!anchor_timer->isActive()) 1084 | { 1085 | anchor_timer->start(); 1086 | } 1087 | hovering = true; 1088 | hover_timestamp = getTimestamp(); 1089 | leave_timestamp = 0; 1090 | if (mouse_pos == QPoint(-1, -1)) 1091 | mouse_pos = mapFromGlobal(QCursor::pos()); 1092 | emit signalMouseEnter(); 1093 | 1094 | return QPushButton::enterEvent(event); 1095 | } 1096 | 1097 | /** 1098 | * 鼠标移开事件,触发 leave 时间戳 1099 | */ 1100 | void InteractiveButtonBase::leaveEvent(QEvent *event) 1101 | { 1102 | hovering = false; 1103 | if (!pressing) 1104 | mouse_pos = QPoint(width() / 2, height() / 2); 1105 | emit signalMouseLeave(); 1106 | 1107 | return QPushButton::leaveEvent(event); 1108 | } 1109 | 1110 | /** 1111 | * 鼠标按下事件,触发 press 时间戳 1112 | * 添加水波纹动画 waters 队列 1113 | */ 1114 | void InteractiveButtonBase::mousePressEvent(QMouseEvent *event) 1115 | { 1116 | mouse_pos = event->pos(); 1117 | 1118 | if (event->button() == Qt::LeftButton) 1119 | { 1120 | if (!hovering) 1121 | InteractiveButtonBase::enterEvent(new QEvent(QEvent::Type::None)); 1122 | 1123 | pressing = true; 1124 | press_pos = mouse_pos; 1125 | // 判断双击事件 1126 | if (double_clicked) 1127 | { 1128 | qint64 last_press_timestamp = press_timestamp; 1129 | press_timestamp = getTimestamp(); 1130 | if (release_timestamp + DOUBLE_PRESS_INTERVAL >= press_timestamp && last_press_timestamp + SINGLE_PRESS_INTERVAL > release_timestamp && release_pos == press_pos) // 是双击(判断两次单击的间隔) 1131 | { 1132 | double_prevent = true; // 阻止本次的release识别为单击 1133 | press_timestamp = 0; // 避免很可能出现的三击、四击... 1134 | double_timer->stop(); // 取消延迟一小会儿的单击信号 1135 | emit doubleClicked(); 1136 | return; 1137 | } 1138 | else 1139 | { 1140 | double_prevent = false; // 避免有额外的 bug 1141 | } 1142 | } 1143 | else 1144 | { 1145 | press_timestamp = getTimestamp(); 1146 | } 1147 | 1148 | if (water_animation) 1149 | { 1150 | if (waters.size() && waters.last().release_timestamp == 0) // 避免两个按键同时按下 1151 | waters.last().release_timestamp = getTimestamp(); 1152 | waters << Water(press_pos, press_timestamp); 1153 | } 1154 | else // 透明渐变 1155 | { 1156 | if (press_progress < press_start) 1157 | press_progress = press_start; // 直接设置为按下效果初始值(避免按下反应慢) 1158 | } 1159 | } 1160 | mouse_press_event = event; 1161 | emit signalMousePress(event); 1162 | 1163 | return QPushButton::mousePressEvent(event); 1164 | } 1165 | 1166 | /** 1167 | * 鼠标松开事件,触发 release 时间戳 1168 | * 添加抖动动画 jitters 队列 1169 | */ 1170 | void InteractiveButtonBase::mouseReleaseEvent(QMouseEvent *event) 1171 | { 1172 | if (pressing && event->button() == Qt::LeftButton) 1173 | { 1174 | if (!inArea(event->pos()) || leave_after_clicked) 1175 | { 1176 | hovering = false; 1177 | } 1178 | pressing = false; 1179 | release_pos = event->pos(); 1180 | release_timestamp = getTimestamp(); 1181 | 1182 | // 添加抖动效果 1183 | if (jitter_animation) 1184 | { 1185 | setJitter(); 1186 | } 1187 | 1188 | if (water_animation && waters.size()) 1189 | { 1190 | waters.last().release_timestamp = release_timestamp; 1191 | } 1192 | 1193 | if (double_clicked) 1194 | { 1195 | if (double_prevent) // 双击的当次release,不参与单击计算 1196 | { 1197 | double_prevent = false; 1198 | return; 1199 | } 1200 | 1201 | // 应该不是双击的操作 1202 | if (release_pos != press_pos || release_timestamp - press_timestamp >= SINGLE_PRESS_INTERVAL) 1203 | { 1204 | } 1205 | else // 可能是双击,准备 1206 | { 1207 | double_timer->start(); 1208 | return; // 禁止单击事件 1209 | } 1210 | } 1211 | } 1212 | else if (leave_after_clicked && !pressing && double_clicked && double_prevent) // 双击,失去焦点了,pressing 丢失 1213 | { 1214 | return; 1215 | } 1216 | else if (event->button() == Qt::RightButton && event->buttons() == Qt::NoButton) 1217 | { 1218 | if ((release_pos - press_pos).manhattanLength() < QApplication::startDragDistance()) 1219 | emit rightClicked(); 1220 | } 1221 | mouse_release_event = event; 1222 | emit signalMouseRelease(event); 1223 | 1224 | return QPushButton::mouseReleaseEvent(event); 1225 | } 1226 | 1227 | /** 1228 | * 鼠标移动事件 1229 | */ 1230 | void InteractiveButtonBase::mouseMoveEvent(QMouseEvent *event) 1231 | { 1232 | if (_block_hover) // 临时屏蔽hover事件 1233 | { 1234 | if (event) 1235 | event->accept(); 1236 | return; 1237 | } 1238 | if (hovering == false) // 失去焦点又回来了 1239 | { 1240 | enterEvent(nullptr); 1241 | } 1242 | mouse_pos = mapFromGlobal(QCursor::pos()); 1243 | 1244 | return QPushButton::mouseMoveEvent(event); 1245 | } 1246 | 1247 | /** 1248 | * 尺寸大小改变事件 1249 | * 同步调整和尺寸有关的所有属性 1250 | */ 1251 | void InteractiveButtonBase::resizeEvent(QResizeEvent *event) 1252 | { 1253 | if (!pressing && !hovering) 1254 | { 1255 | mouse_pos = QPoint(width() / 2, height() / 2); 1256 | anchor_pos = mouse_pos; 1257 | } 1258 | water_radius = static_cast(max(width(), height()) * 1.42); // 长边 1259 | // 非固定的情况,尺寸大小变了之后所有 padding 都要变 1260 | if (!fixed_fore_size && (model == PaintModel::Icon || model == PaintModel::PixmapMask)) 1261 | { 1262 | int short_side = min(width(), height()); // 短边 1263 | int padding = int(short_side * icon_padding_proper); //static_cast(short_side * (1 - GOLDEN_RATIO) / 2); 1264 | fore_paddings.left = fore_paddings.top = fore_paddings.right = fore_paddings.bottom = padding; 1265 | } 1266 | _l = _t = 0; 1267 | _w = width(); 1268 | _h = height(); 1269 | 1270 | return QPushButton::resizeEvent(event); 1271 | } 1272 | 1273 | /** 1274 | * 获得焦点事件 1275 | * 已经取消按钮获取焦点,focusIn和focusOut事件都不会触发 1276 | */ 1277 | void InteractiveButtonBase::focusInEvent(QFocusEvent *event) 1278 | { 1279 | if (!hovering && inArea(mapFromGlobal(QCursor::pos()))) 1280 | InteractiveButtonBase::enterEvent(new QEvent(QEvent::Type::None)); 1281 | 1282 | focusing = true; 1283 | emit signalFocusIn(); 1284 | 1285 | return QPushButton::focusInEvent(event); 1286 | } 1287 | 1288 | /** 1289 | * 失去焦点事件 1290 | * 兼容按住时突然失去焦点(例如弹出菜单、被其他窗口抢走了) 1291 | */ 1292 | void InteractiveButtonBase::focusOutEvent(QFocusEvent *event) 1293 | { 1294 | if (hovering) 1295 | { 1296 | hovering = false; 1297 | } 1298 | if (pressing) // 鼠标一直按住,可能在click事件中移动了焦点 1299 | { 1300 | pressing = false; 1301 | release_pos = mapFromGlobal(QCursor::pos()); 1302 | release_timestamp = getTimestamp(); 1303 | 1304 | if (water_animation && waters.size()) 1305 | { 1306 | waters.last().release_timestamp = release_timestamp; 1307 | } 1308 | } 1309 | 1310 | focusing = false; 1311 | emit signalFocusOut(); 1312 | 1313 | return QPushButton::focusOutEvent(event); 1314 | } 1315 | 1316 | /** 1317 | * 重绘事件 1318 | * 绘制所有内容:背景、动画、前景、角标 1319 | */ 1320 | void InteractiveButtonBase::paintEvent(QPaintEvent *event) 1321 | { 1322 | if (parent_enabled) // 绘制父类(以便使用父类的QSS和各项属性) 1323 | QPushButton::paintEvent(event); 1324 | if (!self_enabled) // 不绘制自己 1325 | return; 1326 | QPainter painter(this); 1327 | 1328 | // ==== 绘制背景 ==== 1329 | QPainterPath path_back = getBgPainterPath(); 1330 | painter.setRenderHint(QPainter::Antialiasing, true); 1331 | 1332 | if (normal_bg.alpha() != 0) // 默认背景 1333 | { 1334 | painter.fillPath(path_back, isEnabled() ? normal_bg : getOpacityColor(normal_bg)); 1335 | } 1336 | if (focusing && focus_bg.alpha() != 0) // 焦点背景 1337 | { 1338 | painter.fillPath(path_back, focus_bg); 1339 | } 1340 | 1341 | if ((border_bg.alpha() != 0 || (focusing && focus_border.alpha() != 0)) && border_width > 0) 1342 | { 1343 | painter.save(); 1344 | QPen pen; 1345 | pen.setColor((focusing && focus_border.alpha()) ? focus_border : border_bg); 1346 | pen.setWidth(border_width); 1347 | painter.setPen(pen); 1348 | painter.drawPath(path_back); 1349 | painter.restore(); 1350 | } 1351 | 1352 | if (hover_progress) // 悬浮背景 1353 | { 1354 | painter.fillPath(path_back, getOpacityColor(hover_bg, hover_progress / 100.0)); 1355 | } 1356 | 1357 | if (press_progress && !water_animation) // 按下渐变淡化消失 1358 | { 1359 | painter.fillPath(path_back, getOpacityColor(press_bg, press_progress / 100.0)); 1360 | } 1361 | else if (water_animation && waters.size()) // 水波纹,且至少有一个水波纹 1362 | { 1363 | paintWaterRipple(painter); 1364 | } 1365 | 1366 | // ==== 绘制前景 ==== 1367 | if (fore_enabled /*针对按钮设置*/ && show_foreground /*针对动画设置*/) 1368 | { 1369 | painter.setPen(isEnabled() ? icon_color : getOpacityColor(icon_color)); 1370 | 1371 | // 绘制额外内容(可能被前景覆盖) 1372 | if (paint_addin.enable) 1373 | { 1374 | int l = fore_paddings.left, t = fore_paddings.top, r = width() - fore_paddings.right, b = height() - fore_paddings.bottom; 1375 | int small_edge = min(height(), width()); 1376 | int pw = paint_addin.size.width() ? paint_addin.size.width() : small_edge * 4 / 5; 1377 | int ph = paint_addin.size.height() ? paint_addin.size.height() : small_edge * 4 / 5; 1378 | if (paint_addin.align & Qt::AlignLeft) 1379 | r = l + pw; 1380 | else if (paint_addin.align & Qt::AlignRight) 1381 | l = r - pw; 1382 | else if (paint_addin.align & Qt::AlignHCenter) 1383 | { 1384 | l = width() / 2 - pw / 2; 1385 | r = l + pw; 1386 | } 1387 | if (paint_addin.align & Qt::AlignTop) 1388 | b = t + ph; 1389 | else if (paint_addin.align & Qt::AlignBottom) 1390 | t = b - ph; 1391 | else if (paint_addin.align & Qt::AlignVCenter) 1392 | { 1393 | t = height() / 2 - ph / 2; 1394 | b = t + ph; 1395 | } 1396 | painter.drawPixmap(QRect(l, t, r - l, b - t), paint_addin.pixmap); 1397 | } 1398 | 1399 | QRectF &rect = paint_rect; 1400 | rect = QRectF(fore_paddings.left + (fixed_fore_pos ? 0 : offset_pos.x()), 1401 | fore_paddings.top + (fixed_fore_pos ? 0 : offset_pos.y()), // 原来的位置,不包含点击、出现效果 1402 | width() - fore_paddings.left - fore_paddings.right, 1403 | height() - fore_paddings.top - fore_paddings.bottom); 1404 | 1405 | // 抖动出现动画 1406 | if ((show_ani_appearing || show_ani_disappearing) && show_ani_point != QPoint(0, 0) && !fixed_fore_pos) 1407 | { 1408 | //int w = width(), h = height(); 1409 | int pro = getSpringBackProgress(show_ani_progress, 50); 1410 | 1411 | // show_ani_point 是鼠标进入的点,那么起始方向应该是相反的 1412 | double x = show_ani_point.x(), y = show_ani_point.y(); 1413 | int gen = quick_sqrt(long(x * x + y * y)); 1414 | x = water_radius * x / gen; // 动画起始中心点横坐标 反向 1415 | y = water_radius * y / gen; // 动画起始中心点纵坐标 反向 1416 | 1417 | rect = QRectF( 1418 | rect.left() - x * (100 - pro) / 100 + rect.width() * (100 - pro) / 100, 1419 | rect.top() - y * (100 - pro) / 100 + rect.height() * (100 - pro) / 100, 1420 | rect.width() * pro / 100, 1421 | rect.height() * pro / 100); 1422 | } 1423 | else if (align == Qt::AlignCenter && model != PaintModel::Text && !fixed_fore_size) // 默认的缩放动画 1424 | { 1425 | double delta_x = 0, delta_y = 0; 1426 | if (click_ani_progress != 0) // 图标缩放 1427 | { 1428 | delta_x = rect.width() * click_ani_progress / 400; 1429 | delta_y = rect.height() * click_ani_progress / 400; 1430 | } 1431 | else if (show_ani_appearing) 1432 | { 1433 | /*int pro; // 将动画进度转换为回弹动画进度 1434 | if (show_ani_progress <= 50) 1435 | pro = show_ani_progress * 2; 1436 | else if (show_ani_progress <= 75) 1437 | pro = (show_ani_progress-50)/2 + 100; 1438 | else 1439 | pro = 100 + (100-show_ani_progress)/2; 1440 | 1441 | delta_x = rect.width() * (100-pro) / 100; 1442 | delta_y = rect.height() * (100-pro) / 100;*/ 1443 | 1444 | double pro = getNolinearProg(show_ani_progress, SpringBack50); 1445 | delta_x = static_cast(rect.width() * (1 - pro)); 1446 | delta_y = static_cast(rect.height() * (1 - pro)); 1447 | } 1448 | else if (show_ani_disappearing) 1449 | { 1450 | double pro = 1 - getNolinearProg(show_ani_progress, SlowFaster); 1451 | delta_x = rect.width() * pro; // (100-show_ani_progress) / 100; 1452 | delta_y = rect.height() * pro; // (100-show_ani_progress) / 100; 1453 | } 1454 | if (int(delta_x+1e-6) || int(delta_y+1e-6)) 1455 | rect = QRectF(rect.left() + delta_x, rect.top() + delta_y, 1456 | rect.width() - delta_x * 2, rect.height() - delta_y * 2); 1457 | } 1458 | 1459 | /*if (this->isEnabled()) 1460 | { 1461 | QColor color = icon_color; 1462 | color.setAlpha(color.alpha() / 2); 1463 | painter.setPen(color); 1464 | }*/ 1465 | 1466 | if (model == None) 1467 | { 1468 | // 子类自己的绘制内容 1469 | } 1470 | else if (model == Text) 1471 | { 1472 | // 绘制文字教程: https://blog.csdn.net/temetnosce/article/details/78068464 1473 | painter.setPen(isEnabled() ? text_color : getOpacityColor(text_color)); 1474 | /*if (show_ani_appearing || show_ani_disappearing) 1475 | { 1476 | int pro = getSpringBackProgress(show_ani_progress, 50); 1477 | QFont font = painter.font(); 1478 | int ps = font.pointSize(); 1479 | ps = ps * show_ani_progress / 100; 1480 | font.setPointSize(ps); 1481 | painter.setFont(font); 1482 | }*/ 1483 | if (font_size > 0) 1484 | { 1485 | QFont font = painter.font(); 1486 | font.setPointSize(font_size); 1487 | painter.setFont(font); 1488 | } 1489 | painter.drawText(rect, static_cast(align), text); 1490 | } 1491 | else if (model == Icon) // 绘制图标 1492 | { 1493 | icon.paint(&painter, rect.toRect(), align, getIconMode()); 1494 | } 1495 | else if (model == PixmapMask) 1496 | { 1497 | painter.setRenderHint(QPainter::SmoothPixmapTransform, true); // 可以让边缘看起来平滑一些 1498 | painter.drawPixmap(rect.toRect(), pixmap); 1499 | } 1500 | else if (model == IconText || model == PixmapText) // 强制左对齐;左图标中文字 1501 | { 1502 | // 绘制图标 1503 | int &sz = icon_text_size; 1504 | QRectF icon_rect(rect.left(), rect.top() + rect.height() / 2 - sz / 2, sz, sz); 1505 | icon_rect.moveTo(icon_rect.left() - quick_sqrt(long(offset_pos.x())), icon_rect.top() - quick_sqrt(long(offset_pos.y()))); 1506 | drawIconBeforeText(painter, icon_rect.toRect()); 1507 | rect.setLeft(rect.left() + sz + icon_text_padding); 1508 | 1509 | // 绘制文字 1510 | // 扩展文字范围,确保文字可见 1511 | painter.setPen(isEnabled() ? text_color : getOpacityColor(text_color)); 1512 | rect.setWidth(rect.width() + sz + icon_text_padding); 1513 | if (font_size > 0) 1514 | { 1515 | QFont font = painter.font(); 1516 | font.setPointSize(font_size); 1517 | painter.setFont(font); 1518 | } 1519 | painter.drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, text); 1520 | } 1521 | } 1522 | 1523 | // ==== 绘制鼠标位置 ==== 1524 | // painter.drawEllipse(QRect(anchor_pos.x()-5, anchor_pos.y()-5, 10, 10)); // 移动锚点 1525 | // painter.drawEllipse(QRect(effect_pos.x()-2, effect_pos.y()-2, 4, 4)); // 影响位置锚点 1526 | 1527 | // return QPushButton::paintEvent(event); // 不绘制父类背景了 1528 | } 1529 | 1530 | /** 1531 | * IconText/PixmapText模式下,绘制图标 1532 | * 可扩展到绘制图标背景色(模仿menu选中、禁用情况)等 1533 | */ 1534 | void InteractiveButtonBase::drawIconBeforeText(QPainter &painter, QRect icon_rect) 1535 | { 1536 | if (model == IconText) 1537 | icon.paint(&painter, icon_rect, align, getIconMode()); 1538 | else if (model == PixmapText) 1539 | painter.drawPixmap(icon_rect, pixmap); 1540 | } 1541 | 1542 | /** 1543 | * 判断坐标是否在按钮区域内 1544 | * 避免失去了焦点,但是依旧需要 hover 效果(非菜单和弹窗抢走焦点) 1545 | * 为子类异形按钮区域判断提供支持 1546 | * @param point 当前鼠标 1547 | * @return 是否在区域内 1548 | */ 1549 | bool InteractiveButtonBase::inArea(QPoint point) 1550 | { 1551 | return !(point.x() < 0 || point.y() < 0 || point.x() > width() || point.y() > height()); 1552 | } 1553 | 1554 | bool InteractiveButtonBase::inArea(QPointF point) 1555 | { 1556 | return !(point.x() < 0 || point.y() < 0 || point.x() > width() || point.y() > height()); 1557 | } 1558 | 1559 | /** 1560 | * 获取按钮背景的绘制区域 1561 | * 为子类异形按钮提供支持 1562 | * @return [description] 1563 | */ 1564 | QPainterPath InteractiveButtonBase::getBgPainterPath() 1565 | { 1566 | QPainterPath path; 1567 | if (radius_x || radius_y) 1568 | path.addRoundedRect(QRect(0, 0, width(), height()), radius_x, radius_y); 1569 | else 1570 | path.addRect(QRect(0, 0, width(), height())); 1571 | return path; 1572 | } 1573 | 1574 | /** 1575 | * 获取水波纹绘制区域(圆形,但不规则区域) 1576 | * 圆形水面 & 按钮区域 1577 | * @param water 一面水波纹动画对象 1578 | * @return 绘制路径 1579 | */ 1580 | QPainterPath InteractiveButtonBase::getWaterPainterPath(InteractiveButtonBase::Water water) 1581 | { 1582 | double prog = getNolinearProg(water.progress, FastSlower); 1583 | double ra = water_radius * prog; 1584 | QRectF circle(water.point.x() - ra, 1585 | water.point.y() - ra, 1586 | ra * 2, 1587 | ra * 2); 1588 | /*QRect circle(water.point.x() - water_radius*water.progress/100, 1589 | water.point.y() - water_radius*water.progress/100, 1590 | water_radius*water.progress/50, 1591 | water_radius*water.progress/50);*/ 1592 | QPainterPath path; 1593 | path.addEllipse(circle); 1594 | if (radius_x || radius_y) 1595 | return path & getBgPainterPath(); 1596 | return path; 1597 | } 1598 | 1599 | /** 1600 | * 获取统一的尺寸大小(已废弃) 1601 | * 兼容圆形按钮出现动画,半径使用水波纹(对角线) 1602 | * 可直接使用 protected 对象 1603 | * @return 前景绘制区域 1604 | */ 1605 | QRectF InteractiveButtonBase::getUnifiedGeometry() 1606 | { 1607 | // 将动画进度转换为回弹动画进度 1608 | int pro = show_ani_appearing ? getSpringBackProgress(show_ani_progress, 50) : show_ani_progress; 1609 | double ul = 0, ut = 0, uw = width(), uh = height(); 1610 | 1611 | // show_ani_point 是鼠标进入的点,那么起始方向应该是相反的 1612 | double x = show_ani_point.x(), y = show_ani_point.y(); 1613 | int gen = quick_sqrt(long(x * x + y * y)); 1614 | x = -water_radius * x / gen; // 动画起始中心点横坐标 反向 1615 | y = -water_radius * y / gen; // 动画起始中心点纵坐标 反向 1616 | 1617 | ul = ul + x * (100 - pro) / 100 + uw * (100 - pro) / 200; 1618 | ut = ut + y * (100 - pro) / 100 + uh * (100 - pro) / 200; 1619 | uw = uw * pro / 100; 1620 | uh = uh * pro / 100; 1621 | 1622 | return QRectF(ul, ut, uw, uh); 1623 | } 1624 | 1625 | /** 1626 | * 更新统一绘制区域 1627 | * 内部的 _l, _t, _w, _h 可直接使用 1628 | */ 1629 | void InteractiveButtonBase::updateUnifiedGeometry() 1630 | { 1631 | _l = 0; 1632 | _t = 0; 1633 | _w = width(); 1634 | _h = height(); 1635 | if ((show_ani_appearing || show_ani_disappearing) && show_ani_point != QPoint(0, 0)) 1636 | { 1637 | int pro; // 将动画进度转换为回弹动画进度 1638 | pro = show_ani_appearing ? getSpringBackProgress(show_ani_progress, 50) : show_ani_progress; 1639 | 1640 | // show_ani_point 是鼠标进入的点,那么起始方向应该是相反的 1641 | double x = show_ani_point.x(), y = show_ani_point.y(); 1642 | int gen = quick_sqrt(long(x * x + y * y)); 1643 | x = -water_radius * x / gen; // 动画起始中心点横坐标 反向 1644 | y = -water_radius * y / gen; // 动画起始中心点纵坐标 反向 1645 | 1646 | _l = _l + x * (100 - pro) / 100 + _w * (100 - pro) / 200; 1647 | _t = _t + y * (100 - pro) / 100 + _h * (100 - pro) / 200; 1648 | _w = _w * pro / 100; 1649 | _h = _h * pro / 100; 1650 | } 1651 | } 1652 | 1653 | /** 1654 | * 绘制一个水波纹动画 1655 | * @param painter 绘制对象(即painter(this)对象) 1656 | */ 1657 | void InteractiveButtonBase::paintWaterRipple(QPainter &painter) 1658 | { 1659 | QColor water_finished_color(press_bg); 1660 | 1661 | for (int i = 0; i < waters.size(); i++) 1662 | { 1663 | Water water = waters.at(i); 1664 | if (water.finished) // 渐变消失 1665 | { 1666 | water_finished_color.setAlpha(press_bg.alpha() * water.progress / 100); 1667 | QPainterPath path_back = getBgPainterPath(); 1668 | // painter.setPen(water_finished_color); 1669 | painter.fillPath(path_back, QBrush(water_finished_color)); 1670 | } 1671 | else // 圆形出现 1672 | { 1673 | QPainterPath path = getWaterPainterPath(water); 1674 | painter.fillPath(path, QBrush(press_bg)); 1675 | } 1676 | } 1677 | } 1678 | 1679 | /** 1680 | * 鼠标松开的时候,计算所有抖动效果的路径和事件 1681 | * 在每次重绘界面的时候,依次遍历所有的路径 1682 | */ 1683 | void InteractiveButtonBase::setJitter() 1684 | { 1685 | jitters.clear(); 1686 | QPoint center_pos = geometry().center() - geometry().topLeft(); 1687 | double full_manh = (anchor_pos - center_pos).manhattanLength(); // 距离 1688 | // 是否达到需要抖动的距离 1689 | if (full_manh > (geometry().topLeft() - geometry().bottomRight()).manhattanLength()) // 距离超过外接圆半径,开启抖动 1690 | { 1691 | QPointF jitter_pos(effect_pos); 1692 | full_manh = (jitter_pos - center_pos).manhattanLength(); 1693 | double manh = full_manh; 1694 | int duration = jitter_duration; 1695 | qint64 timestamp = release_timestamp; 1696 | while (manh > elastic_coefficient) 1697 | { 1698 | jitters << Jitter(jitter_pos, timestamp); 1699 | jitter_pos = center_pos - (jitter_pos - center_pos) / elastic_coefficient; 1700 | duration = int(jitter_duration * manh / full_manh); 1701 | timestamp += duration; 1702 | manh = static_cast(manh / elastic_coefficient); 1703 | } 1704 | jitters << Jitter(center_pos, timestamp); 1705 | anchor_pos = mouse_pos = center_pos; 1706 | } 1707 | else if (!hovering) // 悬浮的时候依旧有效 1708 | { 1709 | // 未达到抖动距离,直接恢复 1710 | mouse_pos = center_pos; 1711 | } 1712 | } 1713 | 1714 | /** 1715 | * 速度极快的开方算法,效率未知,原理未知 1716 | * @param X 待开方的数字 1717 | * @return 平方根 1718 | */ 1719 | int InteractiveButtonBase::quick_sqrt(long X) const 1720 | { 1721 | bool fu = false; 1722 | if (X < 0) 1723 | { 1724 | fu = true; 1725 | X = -X; 1726 | } 1727 | #if !defined(Q_OS_WIN) 1728 | X = qSqrt(X); 1729 | return fu ? -X : X; 1730 | #endif 1731 | unsigned long M = static_cast(X); 1732 | unsigned int N, i; 1733 | unsigned long tmp, ttp; // 结果、循环计数 1734 | if (M == 0) // 被开方数,开方结果也为0 1735 | return 0; 1736 | N = 0; 1737 | tmp = (M >> 30); // 获取最高位:B[m-1] 1738 | M <<= 2; 1739 | if (tmp > 1) // 最高位为1 1740 | { 1741 | N++; // 结果当前位为1,否则为默认的0 1742 | tmp -= N; 1743 | } 1744 | for (i = 15; i > 0; i--) // 求剩余的15位 1745 | { 1746 | N <<= 1; // 左移一位 1747 | tmp <<= 2; 1748 | tmp += (M >> 30); // 假设 1749 | ttp = N; 1750 | ttp = (ttp << 1) + 1; 1751 | M <<= 2; 1752 | if (tmp >= ttp) // 假设成立 1753 | { 1754 | tmp -= ttp; 1755 | N++; 1756 | } 1757 | } 1758 | return (fu ? -1 : 1) * static_cast(N); // 不知道为什么计算出来的结果是反过来的 1759 | } 1760 | 1761 | /** 1762 | * 最大值 1763 | */ 1764 | int InteractiveButtonBase::max(int a, int b) const { return a > b ? a : b; } 1765 | 1766 | /** 1767 | * 最小值 1768 | */ 1769 | int InteractiveButtonBase::min(int a, int b) const { return a < b ? a : b; } 1770 | 1771 | /** 1772 | * 获取现行时间戳,13位,精确到毫秒 1773 | * @return 时间戳 1774 | */ 1775 | qint64 InteractiveButtonBase::getTimestamp() const 1776 | { 1777 | return QDateTime::currentDateTime().toMSecsSinceEpoch(); 1778 | } 1779 | 1780 | /** 1781 | * 是否为亮色颜色 1782 | * @param color 颜色 1783 | * @return 是否为亮色 1784 | */ 1785 | bool InteractiveButtonBase::isLightColor(QColor color) 1786 | { 1787 | return color.red() * 0.299 + color.green() * 0.578 + color.blue() * 0.114 >= 192; 1788 | } 1789 | 1790 | /** 1791 | * 获取非线性动画在某一时间比例的动画进度 1792 | * 仅适用于弹过头效果的动画 1793 | * @param x 实际相对完整100%的动画进度 1794 | * @param max 前半部分动画进度上限 1795 | * @return 应当显示的动画进度 1796 | */ 1797 | int InteractiveButtonBase::getSpringBackProgress(int x, int max) 1798 | { 1799 | if (x <= max) 1800 | return x * 100 / max; 1801 | if (x <= max + (100 - max) / 2) 1802 | return (x - max) / 2 + 100; 1803 | return 100 + (100 - x) / 2; 1804 | } 1805 | 1806 | /** 1807 | * 获取透明的颜色 1808 | * @param color 颜色 1809 | * @param level 比例 1810 | * @return 透明颜色 1811 | */ 1812 | QColor InteractiveButtonBase::getOpacityColor(QColor color, double level) 1813 | { 1814 | color.setAlpha(static_cast(color.alpha() * level)); 1815 | return color; 1816 | } 1817 | 1818 | /** 1819 | * 获取对应颜色的图标 pixmap 1820 | * @param p 图标 1821 | * @param c 颜色 1822 | * @return 对应颜色的图标 1823 | */ 1824 | QPixmap InteractiveButtonBase::getColoredPixmap(QPixmap p, QColor c) 1825 | { 1826 | // 创建一个新的 QPixmap 对象,用于修改颜色 1827 | QPixmap newPixmap(p.size()); 1828 | newPixmap.fill(Qt::transparent); // 设置背景为透明 1829 | 1830 | QPainter painter(&newPixmap); 1831 | painter.setCompositionMode(QPainter::CompositionMode_Source); 1832 | painter.drawPixmap(0, 0, p); // 将原始图片绘制到新的 QPixmap 上 1833 | 1834 | painter.setCompositionMode(QPainter::CompositionMode_SourceIn); 1835 | painter.fillRect(newPixmap.rect(), c); // 以指定颜色填充整个 QPixmap 1836 | 1837 | painter.setCompositionMode(QPainter::CompositionMode_DestinationOver); 1838 | painter.drawPixmap(0, 0, p); // 将原始图片再次绘制到新的 QPixmap 上,覆盖指定颜色 1839 | 1840 | painter.end(); 1841 | 1842 | return newPixmap; 1843 | } 1844 | 1845 | double InteractiveButtonBase::getNolinearProg(int p, InteractiveButtonBase::NolinearType type) 1846 | { 1847 | if (p <= 0) 1848 | return 0.0; 1849 | if (p >= 100) 1850 | return 1.0; 1851 | 1852 | switch (type) 1853 | { 1854 | case Linear: 1855 | return p / 100.0; 1856 | case SlowFaster: 1857 | return p * p / 10000.0; 1858 | case FastSlower: 1859 | return quick_sqrt(p * 100) / 100.0; 1860 | case SlowFastSlower: 1861 | if (p <= 50) 1862 | return p * p / 50.0; 1863 | else 1864 | return 0.5 + quick_sqrt(50 * (p - 50)) / 100.0; 1865 | case SpringBack20: 1866 | case SpringBack50: 1867 | if (p <= 50) 1868 | return p / 50.0; 1869 | else if (p < 75) 1870 | return 1.0 + (p - 50) / 200.0; 1871 | else 1872 | return 1.0 + (100 - p) / 200.0; 1873 | } 1874 | } 1875 | 1876 | QIcon::Mode InteractiveButtonBase::getIconMode() 1877 | { 1878 | return isEnabled() ? (getState() ? QIcon::Selected : (hovering || pressing ? QIcon::Active : QIcon::Normal)) : QIcon::Disabled; 1879 | } 1880 | 1881 | /** 1882 | * 锚点变成到鼠标位置的定时时钟 1883 | * 同步计算所有和时间或者帧数有关的动画和属性 1884 | */ 1885 | void InteractiveButtonBase::anchorTimeOut() 1886 | { 1887 | qint64 timestamp = getTimestamp(); 1888 | // ==== 背景色 ==== 1889 | /*if (hovering) // 在框内:加深 1890 | { 1891 | if (hover_progress < 100) // 先判断,再计算,可节约运算资源 1892 | hover_progress = min((timestamp - hover_timestamp) * 100 / press_bg_duration, 100); 1893 | } 1894 | else // 在框外:变浅 1895 | { 1896 | if (hover_progress > 0) 1897 | hover_progress = max((timestamp - leave_timestamp) * 100 / press_bg_duration, 0); 1898 | } 1899 | 1900 | if (pressing) 1901 | { 1902 | if (press_progress < 100) 1903 | press_progress = min(press_start + (timestamp - press_timestamp) * 100 / press_bg_duration, 100); 1904 | } 1905 | else 1906 | { 1907 | if (press_progress > 0) // 如果按下的效果还在,变浅 1908 | press_progress = max((timestamp - release_timestamp) * 100 / press_bg_duration, 0); 1909 | }*/ 1910 | 1911 | if (pressing) // 鼠标按下 1912 | { 1913 | if (press_progress < 100) // 透明渐变,且没有完成 1914 | { 1915 | press_progress += press_speed; 1916 | if (press_progress >= 100) 1917 | { 1918 | press_progress = 100; 1919 | if (mouse_press_event) 1920 | { 1921 | emit signalMousePressLater(mouse_press_event); 1922 | mouse_press_event = nullptr; 1923 | } 1924 | } 1925 | } 1926 | if (hovering && hover_progress < 100) 1927 | { 1928 | hover_progress += hover_speed; 1929 | if (hover_progress >= 100) 1930 | { 1931 | hover_progress = 100; 1932 | emit signalMouseEnterLater(); 1933 | } 1934 | } 1935 | } 1936 | else // 鼠标悬浮 1937 | { 1938 | if (press_progress > 0) // 如果按下的效果还在,变浅 1939 | { 1940 | press_progress -= press_speed; 1941 | if (press_progress <= 0) 1942 | { 1943 | press_progress = 0; 1944 | if (mouse_release_event) 1945 | { 1946 | emit signalMouseReleaseLater(mouse_release_event); 1947 | mouse_release_event = nullptr; 1948 | } 1949 | } 1950 | } 1951 | 1952 | if (hovering) // 在框内:加深 1953 | { 1954 | if (hover_progress < 100) 1955 | { 1956 | hover_progress += hover_speed; 1957 | if (hover_progress >= 100) 1958 | { 1959 | hover_progress = 100; 1960 | emit signalMouseEnterLater(); 1961 | } 1962 | } 1963 | } 1964 | else // 在框外:变浅 1965 | { 1966 | if (hover_progress > 0) 1967 | { 1968 | hover_progress -= hover_speed; 1969 | if (hover_progress <= 0) 1970 | { 1971 | hover_progress = 0; 1972 | emit signalMouseLeaveLater(); 1973 | } 1974 | } 1975 | } 1976 | } 1977 | 1978 | // ==== 按下背景水波纹动画 ==== 1979 | if (water_animation) 1980 | { 1981 | for (int i = 0; i < waters.size(); i++) 1982 | { 1983 | Water &water = waters[i]; 1984 | if (water.finished) // 结束状态 1985 | { 1986 | water.progress = static_cast(100 - 100 * (timestamp - water.finish_timestamp) / water_finish_duration); 1987 | if (water.progress <= 0) 1988 | { 1989 | waters.removeAt(i--); 1990 | if (mouse_release_event) // 还没有发送按下延迟信号 1991 | { 1992 | emit signalMouseReleaseLater(mouse_release_event); 1993 | mouse_release_event = nullptr; 1994 | } 1995 | } 1996 | } 1997 | else // 正在出现状态 1998 | { 1999 | if (water.progress >= 100) // 满了 2000 | { 2001 | water.progress = 100; 2002 | if (water.release_timestamp) // 鼠标已经松开了 2003 | { 2004 | water.finished = true; // 准备结束 2005 | water.finish_timestamp = timestamp; 2006 | } 2007 | } 2008 | else // 动画中的 2009 | { 2010 | if (water.release_timestamp) // 鼠标已经松开了 2011 | { 2012 | water.progress = static_cast(100 * (water.release_timestamp - water.press_timestamp) / water_press_duration + 100 * (timestamp - water.release_timestamp) / water_release_duration); 2013 | } 2014 | else // 鼠标一直按下 2015 | { 2016 | water.progress = static_cast(100 * (timestamp - water.press_timestamp) / water_press_duration); 2017 | } 2018 | if (water.progress >= 100) 2019 | { 2020 | water.progress = 100; 2021 | if (mouse_press_event) // 还没有发送按下延迟信号 2022 | { 2023 | emit signalMousePressLater(mouse_press_event); 2024 | mouse_press_event = nullptr; 2025 | } 2026 | } 2027 | } 2028 | } 2029 | } 2030 | } 2031 | 2032 | // ==== 出现动画 ==== 2033 | if (show_animation) 2034 | { 2035 | if (show_ani_appearing) // 出现 2036 | { 2037 | qint64 delta = getTimestamp() - show_timestamp; 2038 | if (show_ani_progress >= 100) // 出现结束 2039 | { 2040 | show_ani_appearing = false; 2041 | emit showAniFinished(); 2042 | } 2043 | else 2044 | { 2045 | show_ani_progress = static_cast(100 * delta / show_duration); 2046 | if (show_ani_progress > 100) 2047 | show_ani_progress = 100; 2048 | } 2049 | } 2050 | if (show_ani_disappearing) // 消失 2051 | { 2052 | qint64 delta = getTimestamp() - hide_timestamp; 2053 | if (show_ani_progress <= 0) // 消失结束 2054 | { 2055 | show_ani_disappearing = false; 2056 | show_foreground = false; 2057 | show_ani_point = QPoint(0, 0); 2058 | emit hideAniFinished(); 2059 | } 2060 | else 2061 | { 2062 | show_ani_progress = static_cast(100 - 100 * delta / show_duration); 2063 | if (show_ani_progress < 0) 2064 | show_ani_progress = 0; 2065 | } 2066 | } 2067 | } 2068 | 2069 | // ==== 按下动画 ==== 2070 | if (click_ani_disappearing) // 点击动画效果消失 2071 | { 2072 | qint64 delta = getTimestamp() - release_timestamp - click_ani_duration; 2073 | if (delta <= 0) 2074 | click_ani_progress = 100; 2075 | else 2076 | click_ani_progress = static_cast(100 - delta * 100 / click_ani_duration); 2077 | if (click_ani_progress < 0) 2078 | { 2079 | click_ani_progress = 0; 2080 | click_ani_disappearing = false; 2081 | emit pressAppearAniFinished(); 2082 | } 2083 | } 2084 | if (click_ani_appearing) // 点击动画效果 2085 | { 2086 | qint64 delta = getTimestamp() - release_timestamp; 2087 | if (delta <= 0) 2088 | click_ani_progress = 0; 2089 | else 2090 | click_ani_progress = static_cast(delta * 100 / click_ani_duration); 2091 | if (click_ani_progress > 100) 2092 | { 2093 | click_ani_progress = 100; // 保持100的状态,下次点击时回到0 2094 | click_ani_appearing = false; 2095 | click_ani_disappearing = true; 2096 | emit pressDisappearAniFinished(); 2097 | } 2098 | } 2099 | 2100 | // ==== 锚点移动 ==== 2101 | if (jitters.size() > 0) // 松开时的抖动效果 2102 | { 2103 | // 当前应该是处在最后一个点 2104 | Jitter cur = jitters.first(); 2105 | Jitter aim = jitters.at(1); 2106 | int del = static_cast(getTimestamp() - cur.timestamp); 2107 | int dur = static_cast(aim.timestamp - cur.timestamp); 2108 | effect_pos = cur.point + (aim.point - cur.point) * del / dur; 2109 | offset_pos = effect_pos - (geometry().center() - geometry().topLeft()); 2110 | 2111 | if (del >= dur) 2112 | jitters.removeFirst(); 2113 | 2114 | // 抖动结束 2115 | if (jitters.size() == 1) 2116 | { 2117 | jitters.clear(); 2118 | emit jitterAniFinished(); 2119 | } 2120 | } 2121 | else if (anchor_pos != mouse_pos) // 移动效果 2122 | { 2123 | double delta_x = anchor_pos.x() - mouse_pos.x(), 2124 | delta_y = anchor_pos.y() - mouse_pos.y(); 2125 | 2126 | anchor_pos.setX(anchor_pos.x() - quick_sqrt(int(delta_x))); 2127 | anchor_pos.setY(anchor_pos.y() - quick_sqrt(int(delta_y))); 2128 | 2129 | offset_pos.setX(quick_sqrt(long(anchor_pos.x() - (width() >> 1)))); 2130 | offset_pos.setY(quick_sqrt(long(anchor_pos.y() - (height() >> 1)))); 2131 | effect_pos.setX((width() >> 1) + offset_pos.x()); 2132 | effect_pos.setY((height() >> 1) + offset_pos.y()); 2133 | } 2134 | else if (!pressing && !hovering && !hover_progress && !press_progress && !click_ani_appearing && !click_ani_disappearing && !jitters.size() && !waters.size() && !show_ani_appearing && !show_ani_disappearing) // 没有需要加载的项,暂停(节约资源) 2135 | { 2136 | anchor_timer->stop(); 2137 | } 2138 | 2139 | // ==== 统一坐标的出现动画 ==== 2140 | if (unified_geometry) 2141 | { 2142 | updateUnifiedGeometry(); 2143 | } 2144 | 2145 | update(); 2146 | } 2147 | 2148 | /** 2149 | * 鼠标单击事件 2150 | * 实测按下后,在按钮区域弹起,不管移动多少距离都算是 clicked 2151 | */ 2152 | void InteractiveButtonBase::slotClicked() 2153 | { 2154 | click_ani_appearing = true; 2155 | click_ani_disappearing = false; 2156 | click_ani_progress = 0; 2157 | release_offset = offset_pos; 2158 | 2159 | jitters.clear(); // 清除抖动 2160 | } 2161 | 2162 | /** 2163 | * 强行关闭状态 2164 | * 以槽的形式,便与利用 2165 | */ 2166 | void InteractiveButtonBase::slotCloseState() 2167 | { 2168 | setState(false); 2169 | } 2170 | -------------------------------------------------------------------------------- /interactive_buttons/interactivebuttonbase.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERACTIVEBUTTONBASE_H 2 | #define INTERACTIVEBUTTONBASE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define PI 3.1415926 19 | #define GOLDEN_RATIO 0.618 20 | 21 | #define DOUBLE_PRESS_INTERVAL 300 // /* 300 */松开和按下的间隔。相等为双击 22 | #define SINGLE_PRESS_INTERVAL 200 // /* 150 */按下时间超过这个数就是单击。相等为单击 23 | 24 | /** 25 | * Copyright (c) 2019 命燃芯乂 All rights reserved. 26 | × 27 | * 邮箱:wxy@iwxyi.com 28 | * QQ号:482582886 29 | * 时间:2020.12.28 30 | * 31 | * 说明:灵性的自定义按钮,简单又有趣 32 | * 源码:https://github.com/MRXY001/Interactive-Windows-Buttons 33 | * 34 | * 本代码为本人编写方便自己使用,现在无私送给大家免费使用。 35 | * 程序版权归作者所有,只可使用不能出售,违反者本人有权追究责任。 36 | */ 37 | 38 | class InteractiveButtonBase : public QPushButton 39 | { 40 | Q_OBJECT 41 | Q_PROPERTY(bool self_enabled READ getSelfEnabled WRITE setSelfEnabled) // 是否启用自定义的按钮(true) 42 | Q_PROPERTY(bool parent_enabled READ getParentEnabled WRITE setParentEnabled) // 是否启用父类按钮(false) 43 | Q_PROPERTY(bool fore_enabled READ getForeEnabled WRITE setForeEnabled) // 是否绘制自定义按钮前景色(true) 44 | Q_PROPERTY(QString text READ getText WRITE setText) // 前景文字 45 | Q_PROPERTY(QString icon_path READ getIconPath WRITE setIconPath) // 前景图标 46 | Q_PROPERTY(QString pixmap_path READ getPixmapPath WRITE setPixmapPath) // 前景图标 47 | Q_PROPERTY(QColor icon_color READ getIconColor WRITE setIconColor) // 前景图标帅色 48 | Q_PROPERTY(QColor text_color READ getTextColor WRITE setTextColor) // 前景文字颜色 49 | Q_PROPERTY(QColor background_color READ getNormalColor WRITE setNormalColor) // 背景颜色 50 | Q_PROPERTY(QColor border_color READ getBorderColor WRITE setBorderColor) // 边界颜色 51 | Q_PROPERTY(QColor hover_color READ getHoverColor WRITE setHoverColor) // 鼠标悬浮背景颜色 52 | Q_PROPERTY(QColor press_color READ getPressColor WRITE setPressColor) // 鼠标按下背景颜色 53 | Q_PROPERTY(int hover_duration READ getHoverAniDuration WRITE setHoverAniDuration) // 鼠标悬浮动画周期 54 | Q_PROPERTY(int press_duration READ getPressAniDuration WRITE setPressAniDuration) // 鼠标按下动画周期 55 | Q_PROPERTY(int click_duration READ getClickAniDuration WRITE setClickAniDuration) // 鼠标点击动画周期 56 | Q_PROPERTY(double icon_padding_proper READ getIconPaddingProper WRITE setIconPaddingProper) // 图标四边空白处大小比例 57 | Q_PROPERTY(int radius READ getRadius WRITE setRadius) // 边框圆角半径 58 | Q_PROPERTY(int border_width READ getBorderWidth WRITE setBorderWidth) // 边框线条粗细 59 | Q_PROPERTY(bool fixed_fore_pos READ getFixedTextPos WRITE setFixedTextPos) // 是否固定前景位置(false) 60 | Q_PROPERTY(bool text_dynamic_size READ getTextDynamicSize WRITE setTextDynamicSize) // 修改字体大小时调整按钮最小尺寸(false) 61 | Q_PROPERTY(bool leave_after_clicked READ getLeaveAfterClick WRITE setLeaveAfterClick) // 鼠标单击松开后取消悬浮效果(针对菜单、弹窗) 62 | Q_PROPERTY(bool show_animation READ getShowAni WRITE setShowAni) // 是否启用出现动画(鼠标移开则消失)(false) 63 | Q_PROPERTY(bool water_animation READ getWaterRipple WRITE setWaterRipple) // 是否启用点击水波纹动画(否则使用渐变)(true) 64 | Q_PROPERTY(int font_size READ getFontSizeT WRITE setFontSizeT) // 动:按钮字体动画效果(自动,不应该设置) 65 | public: 66 | InteractiveButtonBase(QWidget *parent = nullptr); 67 | InteractiveButtonBase(QString text, QWidget *parent = nullptr); 68 | InteractiveButtonBase(QIcon icon, QWidget *parent = nullptr); 69 | InteractiveButtonBase(QPixmap pixmap, QWidget *parent = nullptr); 70 | InteractiveButtonBase(QIcon icon, QString text, QWidget *parent = nullptr); 71 | InteractiveButtonBase(QPixmap pixmap, QString text, QWidget *parent = nullptr); 72 | 73 | /** 74 | * 前景实体 75 | */ 76 | enum PaintModel 77 | { 78 | None, // 无前景,仅使用背景 79 | Text, // 纯文字(替代父类) 80 | Icon, // 纯图标 81 | PixmapMask, // 可变色图标(通过pixmap+遮罩实现),锯齿化明显 82 | IconText, // 图标+文字(强制左对齐) 83 | PixmapText // 变色图标+文字(强制左对齐) 84 | }; 85 | 86 | /** 87 | * 前景额外的图标(可以多个) 88 | * 可能是角标(比如展开箭头) 89 | * 可能是前缀(图例) 90 | */ 91 | struct PaintAddin 92 | { 93 | PaintAddin() : enable(false) {} 94 | PaintAddin(QPixmap p, Qt::Alignment a, QSize s) : enable(true), pixmap(p), align(a), size(s) {} 95 | bool enable; // 是否启用 96 | QPixmap pixmap; // 可变色图标 97 | Qt::Alignment align; // 对齐方式 98 | QSize size; // 固定大小 99 | }; 100 | 101 | /** 102 | * 鼠标松开时抖动动画 103 | * 松开的时候计算每一次抖动距离+时间,放入队列中 104 | * 定时调整抖动的队列实体索引 105 | */ 106 | struct Jitter 107 | { 108 | Jitter(QPointF p, qint64 t) : point(p), timestamp(t) {} 109 | QPointF point; // 要运动到的目标坐标 110 | qint64 timestamp; // 运动到目标坐标应该的时间戳,结束后删除本次抖动路径对象 111 | }; 112 | 113 | /** 114 | * 鼠标按下/弹起水波纹动画 115 | * 鼠标按下时动画速度慢(压住),松开后动画速度骤然加快 116 | * 同样用队列记录所有的水波纹动画实体 117 | */ 118 | struct Water 119 | { 120 | Water(QPointF p, qint64 t) : point(p), progress(0), press_timestamp(t), 121 | release_timestamp(0), finish_timestamp(0), finished(false) {} 122 | QPointF point; 123 | int progress; // 水波纹进度100%(已弃用,当前使用时间戳) 124 | qint64 press_timestamp; // 鼠标按下时间戳 125 | qint64 release_timestamp; // 鼠标松开时间戳。与按下时间戳、现行时间戳一起成为水波纹进度计算参数 126 | qint64 finish_timestamp; // 结束时间戳。与当前时间戳相减则为渐变消失经过的时间戳 127 | bool finished; // 是否结束。结束后改为渐变消失 128 | }; 129 | 130 | /** 131 | * 四周边界的padding 132 | * 调整按钮大小时:宽度+左右、高度+上下 133 | */ 134 | struct EdgeVal 135 | { 136 | EdgeVal() {} 137 | EdgeVal(int l, int t, int r, int b) : left(l), top(t), right(r), bottom(b) {} 138 | int left, top, right, bottom; // 四个边界的空白距离 139 | }; 140 | 141 | enum NolinearType 142 | { 143 | Linear, 144 | SlowFaster, 145 | FastSlower, 146 | SlowFastSlower, 147 | SpringBack20, 148 | SpringBack50 149 | }; 150 | 151 | virtual void setText(QString text); 152 | virtual void setIconPath(QString path); 153 | virtual void setIcon(QIcon icon); 154 | virtual void setPixmapPath(QString path); 155 | virtual void setPixmap(QPixmap pixmap); 156 | virtual void setPaintAddin(QPixmap pixmap, Qt::Alignment align = Qt::AlignRight, QSize size = QSize(0, 0)); 157 | 158 | void setSelfEnabled(bool e = true); 159 | void setParentEnabled(bool e = false); 160 | void setForeEnabled(bool e = true); 161 | 162 | void setHoverAniDuration(int d); 163 | void setPressAniDuration(int d); 164 | void setClickAniDuration(int d); 165 | void setWaterAniDuration(int press, int release, int finish); 166 | void setWaterRipple(bool enable = true); 167 | void setJitterAni(bool enable = true); 168 | void setUnifyGeomerey(bool enable = true); 169 | void setBgColor(QColor bg); 170 | void setBgColor(QColor hover, QColor press); 171 | void setNormalColor(QColor color); 172 | void setBorderColor(QColor color); 173 | void setHoverColor(QColor color); 174 | void setPressColor(QColor color); 175 | void setIconColor(QColor color = QColor(0, 0, 0)); 176 | void setTextColor(QColor color = QColor(0, 0, 0)); 177 | void setFocusBg(QColor color); 178 | void setFocusBorder(QColor color); 179 | void setFontSize(int f); 180 | void setHover(); 181 | void setAlign(Qt::Alignment a); 182 | void setRadius(int r); 183 | void setRadius(int rx, int ry); 184 | void setBorderWidth(int x); 185 | void setDisabled(bool dis = true); 186 | void setPaddings(int l, int r, int t, int b); 187 | void setPaddings(int h, int v); 188 | void setPaddings(int x); 189 | void setIconPaddingProper(double x); 190 | void setFixedForePos(bool f = true); 191 | void setFixedForeSize(bool f = true, int addin = 0); 192 | void setSquareSize(); 193 | void setTextDynamicSize(bool d = true); 194 | void setLeaveAfterClick(bool l = true); 195 | void setDoubleClicked(bool e = true); 196 | void setAutoTextColor(bool a = true); 197 | void setPretendFocus(bool f = true); 198 | void setBlockHover(bool b = true); 199 | 200 | void setShowAni(bool enable = true); 201 | void showForeground(); 202 | void showForeground2(QPoint point = QPoint(0, 0)); 203 | void hideForeground(); 204 | void delayShowed(int time, QPoint point = QPoint(0, 0)); 205 | 206 | QString getText(); 207 | void setMenu(QMenu *menu); 208 | void adjustMinimumSize(); 209 | void setState(bool s = true); 210 | bool getState(); 211 | virtual void simulateStatePress(bool s = true, bool a = false); 212 | bool isHovering() { return hovering; } 213 | bool isPressing() { return pressing; } 214 | void simulateHover(); 215 | void discardHoverPress(bool force = false); 216 | 217 | bool getSelfEnabled() { return self_enabled; } 218 | bool getParentEnabled() { return parent_enabled; } 219 | bool getForeEnabled() { return fore_enabled; } 220 | QColor getIconColor() { return icon_color; } 221 | QColor getTextColor() { return text_color; } 222 | QColor getNormalColor() { return normal_bg; } 223 | QColor getBorderColor() { return border_bg; } 224 | QColor getHoverColor() { return hover_bg; } 225 | QColor getPressColor() { return press_bg; } 226 | QString getIconPath() { return ""; } 227 | QString getPixmapPath() { return ""; } 228 | int getHoverAniDuration() { return hover_bg_duration; } 229 | int getPressAniDuration() { return press_bg_duration; } 230 | int getClickAniDuration() { return click_ani_duration; } 231 | double getIconPaddingProper() { return icon_padding_proper; } 232 | int getRadius() { return qMax(radius_x, radius_y); } 233 | int getBorderWidth() { return border_width; } 234 | bool getFixedTextPos() { return fixed_fore_pos; } 235 | bool getTextDynamicSize() { return text_dynamic_size; } 236 | bool getLeaveAfterClick() { return leave_after_clicked; } 237 | bool getShowAni() { return show_animation; } 238 | bool getWaterRipple() { return water_animation; } 239 | 240 | #if QT_DEPRECATED_SINCE(5, 11) 241 | QT_DEPRECATED_X("Use InteractiveButtonBase::setFixedForePos(bool fixed = true)") 242 | void setFixedTextPos(bool f = true); 243 | #endif 244 | 245 | virtual bool inArea(QPoint point); 246 | virtual bool inArea(QPointF point); 247 | 248 | protected: 249 | void enterEvent(QEvent *event) override; 250 | void leaveEvent(QEvent *event) override; 251 | void mousePressEvent(QMouseEvent *event) override; 252 | void mouseReleaseEvent(QMouseEvent *event) override; 253 | void mouseMoveEvent(QMouseEvent *event) override; 254 | void resizeEvent(QResizeEvent *event) override; 255 | void focusInEvent(QFocusEvent *event) override; 256 | void focusOutEvent(QFocusEvent *event) override; 257 | void changeEvent(QEvent *event) override; 258 | void paintEvent(QPaintEvent *event) override; 259 | 260 | virtual QPainterPath getBgPainterPath(); 261 | virtual QPainterPath getWaterPainterPath(Water water); 262 | virtual void drawIconBeforeText(QPainter &painter, QRect icon_rect); 263 | 264 | QRectF getUnifiedGeometry(); 265 | void updateUnifiedGeometry(); 266 | void paintWaterRipple(QPainter &painter); 267 | void setJitter(); 268 | 269 | int getFontSizeT(); 270 | void setFontSizeT(int f); 271 | 272 | int max(int a, int b) const; 273 | int min(int a, int b) const; 274 | int quick_sqrt(long X) const; 275 | qint64 getTimestamp() const; 276 | bool isLightColor(QColor color); 277 | int getSpringBackProgress(int x, int max); 278 | QColor getOpacityColor(QColor color, double level = 0.5); 279 | QPixmap getColoredPixmap(QPixmap p, QColor c); 280 | 281 | double getNolinearProg(int p, NolinearType type); 282 | QIcon::Mode getIconMode(); 283 | 284 | signals: 285 | void showAniFinished(); 286 | void hideAniFinished(); 287 | void pressAppearAniFinished(); 288 | void pressDisappearAniFinished(); 289 | void jitterAniFinished(); 290 | void doubleClicked(); 291 | void rightClicked(); 292 | void signalFocusIn(); 293 | void signalFocusOut(); 294 | 295 | void signalMouseEnter(); 296 | void signalMouseEnterLater(); // 进入后延迟信号(以渐变动画完成为准,相当于可手动设置) 297 | void signalMouseLeave(); 298 | void signalMouseLeaveLater(); // 离开后延迟的信号(直至渐变动画完成(要是划过一下子离开,这个也会变快)) 299 | void signalMousePress(QMouseEvent *event); 300 | void signalMousePressLater(QMouseEvent *event); 301 | void signalMouseRelease(QMouseEvent *event); 302 | void signalMouseReleaseLater(QMouseEvent *event); 303 | 304 | public slots: 305 | virtual void anchorTimeOut(); 306 | virtual void slotClicked(); 307 | void slotCloseState(); 308 | 309 | protected: 310 | PaintModel model; 311 | QIcon icon; 312 | QString text; 313 | QPixmap pixmap; 314 | PaintAddin paint_addin; 315 | EdgeVal fore_paddings; 316 | 317 | protected: 318 | // 总体开关 319 | bool self_enabled, parent_enabled, fore_enabled; // 是否启用子类、启动父类、绘制子类前景 320 | 321 | // 出现前景的动画 322 | bool show_animation, show_foreground; 323 | bool show_ani_appearing, show_ani_disappearing; 324 | int show_duration; 325 | qint64 show_timestamp, hide_timestamp; 326 | int show_ani_progress; 327 | QPointF show_ani_point; 328 | QRectF paint_rect; 329 | 330 | // 鼠标开始悬浮、按下、松开、离开的坐标和时间戳 331 | // 鼠标锚点、目标锚点、当前锚点的坐标;当前XY的偏移量 332 | QPointF enter_pos, press_pos, release_pos, mouse_pos, anchor_pos /*目标锚点渐渐靠近鼠标*/; 333 | QPointF offset_pos /*当前偏移量*/, effect_pos, release_offset; // 相对中心、相对左上角、弹起时的平方根偏移 334 | bool hovering, pressing; // 是否悬浮和按下的状态机 335 | qint64 hover_timestamp, leave_timestamp, press_timestamp, release_timestamp; // 各种事件的时间戳 336 | int hover_bg_duration, press_bg_duration, click_ani_duration; // 各种动画时长 337 | 338 | // 定时刷新界面(保证动画持续) 339 | QTimer *anchor_timer; 340 | int move_speed; 341 | 342 | // 背景与前景 343 | QColor icon_color, text_color; // 前景颜色 344 | QColor normal_bg, hover_bg, press_bg, border_bg; // 各种背景颜色 345 | QColor focus_bg, focus_border; // 有焦点的颜色 346 | int hover_speed, press_start, press_speed; // 颜色渐变速度 347 | int hover_progress, press_progress; // 颜色渐变进度 348 | double icon_padding_proper; // 图标的大小比例 349 | int icon_text_padding, icon_text_size; // 图标+文字模式共存时,两者间隔、图标大小 350 | int border_width; 351 | int radius_x, radius_y; 352 | int font_size; 353 | bool fixed_fore_pos; // 鼠标进入时是否固定文字位置 354 | bool fixed_fore_size; // 鼠标进入/点击时是否固定前景大小 355 | bool text_dynamic_size; // 设置字体时自动调整最小宽高 356 | bool auto_text_color; // 动画时是否自动调整文字颜色 357 | bool focusing; // 是否获得了焦点 358 | 359 | // 鼠标单击动画 360 | bool click_ani_appearing, click_ani_disappearing; // 是否正在按下的动画效果中 361 | int click_ani_progress; // 按下的进度(使用时间差计算) 362 | QMouseEvent *mouse_press_event, *mouse_release_event; 363 | 364 | // 统一绘制图标的区域(从整个按钮变为中心三分之二,并且根据偏移计算) 365 | bool unified_geometry; // 上面用不到的话,这个也用不到…… 366 | double _l, _t, _w, _h; 367 | 368 | // 鼠标拖拽弹起来回抖动效果 369 | bool jitter_animation; // 是否开启鼠标松开时的抖动效果 370 | double elastic_coefficient; // 弹性系数 371 | QList jitters; 372 | int jitter_duration; // 抖动一次,多次效果叠加 373 | 374 | // 鼠标按下水波纹动画效果 375 | bool water_animation; // 是否开启水波纹动画 376 | QList waters; 377 | int water_press_duration, water_release_duration, water_finish_duration; 378 | int water_radius; 379 | 380 | // 其他效果 381 | Qt::Alignment align; // 文字/图标对其方向 382 | bool _state; // 一个记录状态的变量,比如是否持续 383 | bool leave_after_clicked; // 鼠标单击松开后取消悬浮效果(针对菜单、弹窗),按钮必定失去焦点 384 | bool _block_hover; // 如果有出现动画,临时屏蔽hovering效果 385 | 386 | // 双击 387 | bool double_clicked; // 开启双击 388 | QTimer *double_timer; // 双击时钟 389 | bool double_prevent; // 双击阻止单击release的flag 390 | }; 391 | 392 | #endif // INTERACTIVEBUTTONBASE_H 393 | -------------------------------------------------------------------------------- /interactive_buttons/pointmenubutton.cpp: -------------------------------------------------------------------------------- 1 | #include "pointmenubutton.h" 2 | 3 | PointMenuButton::PointMenuButton(QWidget *parent) : InteractiveButtonBase(parent) 4 | { 5 | setUnifyGeomerey(true); 6 | radius = 1; 7 | setClickAniDuration(600); 8 | } 9 | 10 | void PointMenuButton::mousePressEvent(QMouseEvent *event) 11 | { 12 | InteractiveButtonBase::slotClicked(); 13 | return InteractiveButtonBase::mousePressEvent(event); 14 | } 15 | 16 | void PointMenuButton::paintEvent(QPaintEvent * event) 17 | { 18 | InteractiveButtonBase::paintEvent(event); 19 | 20 | if (!show_foreground) return ; // 不显示前景 21 | 22 | int w = _w, h = _h; 23 | int l = _l+w/3, t = _t+h/3, r = w*2/3, b = h*2/3; 24 | int mx = _l+w/2+offset_pos.x(), my = _t+h/2+offset_pos.y(); 25 | 26 | // 画笔 27 | QPainter painter(this); 28 | painter.setPen(QPen(icon_color)); 29 | painter.setRenderHint(QPainter::Antialiasing,true); 30 | 31 | if (click_ani_appearing) 32 | { 33 | int midx = (l+r) / 2; 34 | int move_radius = (b-t)*3/4/2; 35 | QPainterPath path; 36 | 37 | // 第一个点 38 | if (click_ani_progress <= ANI_STEP_3) // 画圈 39 | { 40 | double tp = click_ani_progress / (double)ANI_STEP_3; 41 | QPoint o(midx, t + move_radius); 42 | QPoint p(o.x() - move_radius * sin(PI * tp), o.y() - move_radius * cos(PI * tp)); 43 | path.addEllipse(p.x()-radius, p.y()-radius, radius<<1, radius<<1); 44 | } 45 | else if (click_ani_progress <= ANI_STEP_3*2) // 静止 46 | { 47 | QPoint o(midx, t + move_radius*2); 48 | path.addEllipse(o.x()-radius, o.y()-radius, radius<<1, radius<<1); 49 | } 50 | else // 下移 51 | { 52 | double tp = (click_ani_progress-ANI_STEP_3*2) / (100.0 - ANI_STEP_3*2); 53 | QPoint p(midx, b-(1-tp)*(b-t)/6); 54 | path.addEllipse(p.x()-radius, p.y()-radius, radius<<1, radius<<1); 55 | } 56 | 57 | // 第二个点 58 | if (click_ani_progress <= (100-ANI_STEP_3*2)) // 静止 59 | { 60 | QPoint o(midx, (t+b)/2); 61 | path.addEllipse(o.x()-radius, o.y()-radius, radius<<1, radius<<1); 62 | } 63 | else // 不动 64 | { 65 | QPoint o(midx, (t+b)/2); 66 | path.addEllipse(o.x()-radius, o.y()-radius, radius<<1, radius<<1); 67 | } 68 | 69 | // 第三个点 70 | if (click_ani_progress <= ANI_STEP_3) // 静止 71 | { 72 | QPoint o(midx, b); 73 | path.addEllipse(o.x()-radius, o.y()-radius, radius<<1, radius<<1); 74 | } 75 | else if (click_ani_progress > ANI_STEP_3 && click_ani_progress <= ANI_STEP_3*2) // 画圈 76 | { 77 | double tp = (click_ani_progress-ANI_STEP_3) / (double)ANI_STEP_3; 78 | QPoint o(midx, b - move_radius); 79 | QPoint p(o.x() + move_radius * sin(PI * tp), o.y() + move_radius * cos(PI * tp)); 80 | path.addEllipse(p.x()-radius, p.y()-radius, radius<<1, radius<<1); 81 | } 82 | else // 上移 83 | { 84 | double tp = (click_ani_progress-ANI_STEP_3*2) / (100-ANI_STEP_3*2); 85 | QPoint p(midx, t+(1-tp)*(b-t)/6); 86 | path.addEllipse(p.x()-radius, p.y()-radius, radius<<1, radius<<1); 87 | } 88 | 89 | painter.fillPath(path, QColor(icon_color)); 90 | } 91 | else 92 | { 93 | QPainterPath path; 94 | path.addEllipse((l+r)/2-radius, t-radius, radius<<1, radius<<1); 95 | path.addEllipse((l+r)/2-radius, (t+b)/2-radius, radius<<1, radius<<1); 96 | path.addEllipse((l+r)/2-radius, b-radius, radius<<1, radius<<1); 97 | painter.fillPath(path, QColor(icon_color)); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /interactive_buttons/pointmenubutton.h: -------------------------------------------------------------------------------- 1 | #ifndef POINTMENUBUTTON_H 2 | #define POINTMENUBUTTON_H 3 | 4 | #include 5 | #include "interactivebuttonbase.h" 6 | 7 | #define ANI_STEP_3 40 8 | 9 | class PointMenuButton : public InteractiveButtonBase 10 | { 11 | public: 12 | PointMenuButton(QWidget* parent = nullptr); 13 | 14 | protected: 15 | void mousePressEvent(QMouseEvent *event) override; 16 | void paintEvent(QPaintEvent*event) override; 17 | 18 | private: 19 | int radius; 20 | }; 21 | 22 | #endif // POINTMENUBUTTON_H 23 | -------------------------------------------------------------------------------- /interactive_buttons/threedimenbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "threedimenbutton.h" 2 | 3 | ThreeDimenButton::ThreeDimenButton(QWidget* parent) : InteractiveButtonBase (parent) 4 | { 5 | setMouseTracking(true); 6 | aop_w = width() / AOPER; 7 | aop_h = height() / AOPER; 8 | 9 | shadow_effect = new QGraphicsDropShadowEffect(this); 10 | shadow_effect->setOffset(0, 0); 11 | shadow_effect->setColor(QColor(0x88, 0x88, 0x88, 0x64)); 12 | shadow_effect->setBlurRadius(10); 13 | setGraphicsEffect(shadow_effect); 14 | 15 | setJitterAni(false); 16 | } 17 | 18 | void ThreeDimenButton::enterEvent(QEvent *event) 19 | { 20 | 21 | } 22 | 23 | void ThreeDimenButton::leaveEvent(QEvent *event) 24 | { 25 | if (in_rect && !pressing && !inArea(mapFromGlobal(QCursor::pos()))) 26 | { 27 | in_rect = false; 28 | InteractiveButtonBase::leaveEvent(nullptr); 29 | } 30 | 31 | // 不return,因为区域不一样 32 | } 33 | 34 | void ThreeDimenButton::mousePressEvent(QMouseEvent *event) 35 | { 36 | // 因为上面可能有控件,所以可能无法监听到 enter 事件 37 | if (!in_rect && inArea(event->pos())) // 鼠标移入 38 | { 39 | in_rect = true; 40 | InteractiveButtonBase::enterEvent(nullptr); 41 | } 42 | 43 | if (in_rect) 44 | return InteractiveButtonBase::mousePressEvent(event); 45 | } 46 | 47 | void ThreeDimenButton::mouseReleaseEvent(QMouseEvent *event) 48 | { 49 | if (pressing) 50 | { 51 | InteractiveButtonBase::mouseReleaseEvent(event); 52 | 53 | if (leave_after_clicked || (!inArea(event->pos()) && !pressing)) // 鼠标移出 54 | { 55 | in_rect = false; 56 | InteractiveButtonBase::leaveEvent(nullptr); 57 | } 58 | } 59 | } 60 | 61 | void ThreeDimenButton::mouseMoveEvent(QMouseEvent *event) 62 | { 63 | bool is_in = inArea(event->pos()); 64 | 65 | if (is_in && !in_rect)// 鼠标移入 66 | { 67 | in_rect = true; 68 | InteractiveButtonBase::enterEvent(nullptr); 69 | } 70 | else if (!is_in && in_rect && !pressing) // 鼠标移出 71 | { 72 | in_rect = false; 73 | InteractiveButtonBase::leaveEvent(nullptr); 74 | } 75 | 76 | if (in_rect) 77 | InteractiveButtonBase::mouseMoveEvent(event); 78 | } 79 | 80 | void ThreeDimenButton::resizeEvent(QResizeEvent *event) 81 | { 82 | aop_w = width() / AOPER; 83 | aop_h = height() / AOPER; 84 | return InteractiveButtonBase::resizeEvent(event); 85 | } 86 | 87 | void ThreeDimenButton::anchorTimeOut() 88 | { 89 | // 因为上面有控件挡住了,所以需要定时监控move情况 90 | mouse_pos = mapFromGlobal(QCursor::pos()); 91 | if (!pressing && !inArea(mouse_pos)) // 鼠标移出 92 | { 93 | InteractiveButtonBase::leaveEvent(nullptr); 94 | } 95 | 96 | InteractiveButtonBase::anchorTimeOut(); 97 | 98 | // 修改阴影的位置 99 | if (offset_pos == QPoint(0,0)) 100 | shadow_effect->setOffset(0, 0); 101 | else 102 | { 103 | if (offset_pos.manhattanLength() > SHADE) 104 | { 105 | double sx = -SHADE * offset_pos.x() / offset_pos.manhattanLength(); 106 | double sy = -SHADE * offset_pos.y() / offset_pos.manhattanLength(); 107 | shadow_effect->setOffset(sx*hover_progress/100, sy*hover_progress/100); 108 | } 109 | else 110 | { 111 | shadow_effect->setOffset(-offset_pos.x()*hover_progress/100, -offset_pos.y()*hover_progress/100); 112 | } 113 | } 114 | } 115 | 116 | QPainterPath ThreeDimenButton::getBgPainterPath() 117 | { 118 | QPainterPath path; 119 | if (hover_progress) // 鼠标悬浮效果 120 | { 121 | /** 122 | * 位置比例 = 悬浮比例 × 距离比例 123 | * 坐标位置 ≈ 鼠标方向偏移 124 | */ 125 | double hp = hover_progress / 100.0; 126 | QPoint o(width()/2, height()/2); // 中心点 127 | QPointF m = limitPointXY(mapFromGlobal(QCursor::pos())-o, width()/2, height()/2); // 当前鼠标的点 128 | QPointF f = limitPointXY(offset_pos, aop_w, aop_h); // 偏移点(压力中心) 129 | 130 | QPointF lt, lb, rb, rt; 131 | // 左上角 132 | { 133 | QPointF p = QPoint(aop_w, aop_h) - o; 134 | double prob = dian_cheng(m, p) / dian_cheng(p, p); 135 | lt = o + (p) * (1-prob*hp/AOPER); 136 | } 137 | // 右上角 138 | { 139 | QPointF p = QPoint(width() - aop_w, aop_h) - o; 140 | double prob = dian_cheng(m, p) / dian_cheng(p, p); 141 | rt = o + (p) * (1-prob*hp/AOPER); 142 | } 143 | // 左下角 144 | { 145 | QPointF p = QPoint(aop_w, height() - aop_h) - o; 146 | double prob = dian_cheng(m, p) / dian_cheng(p, p); 147 | lb = o + (p) * (1-prob*hp/AOPER); 148 | } 149 | // 右下角 150 | { 151 | QPointF p = QPoint(width() - aop_w, height() - aop_h) - o; 152 | double prob = dian_cheng(m, p) / dian_cheng(p, p); 153 | rb = o + (p) * (1-prob*hp/AOPER); 154 | } 155 | 156 | path.moveTo(lt); 157 | path.lineTo(lb); 158 | path.lineTo(rb); 159 | path.lineTo(rt); 160 | path.lineTo(lt); 161 | } 162 | else 163 | { 164 | // 简单的path,提升性能用 165 | path.addRect(aop_w, aop_h, width()-aop_w*2, height()-aop_h*2); 166 | } 167 | 168 | return path; 169 | } 170 | 171 | QPainterPath ThreeDimenButton::getWaterPainterPath(InteractiveButtonBase::Water water) 172 | { 173 | QRect circle(water.point.x() - water_radius*water.progress/100, 174 | water.point.y() - water_radius*water.progress/100, 175 | water_radius*water.progress/50, 176 | water_radius*water.progress/50); 177 | QPainterPath path; 178 | path.addEllipse(circle); 179 | return path & getBgPainterPath(); 180 | } 181 | 182 | void ThreeDimenButton::simulateStatePress(bool s, bool a) 183 | { 184 | in_rect = true; 185 | InteractiveButtonBase::simulateStatePress(s, a); 186 | in_rect = false; 187 | } 188 | 189 | bool ThreeDimenButton::inArea(QPointF point) 190 | { 191 | return !(point.x() < aop_w 192 | || point.x() > width()-aop_w 193 | || point.y() < aop_h 194 | || point.y() > height()-aop_h); 195 | } 196 | 197 | /** 198 | * 计算两个向量的叉积 199 | * 获取压力值 200 | */ 201 | double ThreeDimenButton::cha_cheng(QPointF a, QPointF b) 202 | { 203 | return a.x() * b.y() - b.x()* a.y(); 204 | } 205 | 206 | double ThreeDimenButton::dian_cheng(QPointF a, QPointF b) 207 | { 208 | return a.x() * b.x() + a.y() * b.y(); 209 | } 210 | 211 | QPointF ThreeDimenButton::limitPointXY(QPointF v, int w, int h) 212 | { 213 | // 注意:成立时,v.x != 0,否则除零错误 214 | if (v.x() < -w) 215 | { 216 | v.setY(v.y()*-w/v.x()); 217 | v.setX(-w); 218 | } 219 | 220 | if (v.x() > w) 221 | { 222 | v.setY(v.y()*w/v.x()); 223 | v.setX(w); 224 | } 225 | 226 | if (v.y() < -h) 227 | { 228 | v.setX(v.x()*-h/v.y()); 229 | v.setY(-h); 230 | } 231 | 232 | if (v.y() > h) 233 | { 234 | v.setX(v.x()*h/v.y()); 235 | v.setY(h); 236 | } 237 | 238 | return v; 239 | } 240 | -------------------------------------------------------------------------------- /interactive_buttons/threedimenbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef THREEDIMENBUTTON_H 2 | #define THREEDIMENBUTTON_H 3 | 4 | #include 5 | #include "interactivebuttonbase.h" 6 | 7 | class ThreeDimenButton : public InteractiveButtonBase 8 | { 9 | #define AOPER 10 10 | #define SHADE 10 11 | Q_OBJECT 12 | public: 13 | ThreeDimenButton(QWidget* parent = nullptr); 14 | 15 | protected: 16 | void enterEvent(QEvent* event) override; 17 | void leaveEvent(QEvent* event) override; 18 | void mousePressEvent(QMouseEvent* event) override; 19 | void mouseReleaseEvent(QMouseEvent* event) override; 20 | void mouseMoveEvent(QMouseEvent* event) override; 21 | void resizeEvent(QResizeEvent* event) override; 22 | 23 | void anchorTimeOut() override; 24 | 25 | QPainterPath getBgPainterPath() override; 26 | QPainterPath getWaterPainterPath(InteractiveButtonBase::Water water) override; 27 | 28 | void simulateStatePress(bool s = true, bool a = false) override; 29 | bool inArea(QPointF point) override; 30 | 31 | private: 32 | double cha_cheng(QPointF a, QPointF b); 33 | double dian_cheng(QPointF a, QPointF b); 34 | QPointF limitPointXY(QPointF v, int w, int h); 35 | 36 | protected: 37 | QGraphicsDropShadowEffect* shadow_effect; 38 | bool in_rect; 39 | int aop_w, aop_h; 40 | }; 41 | 42 | #endif // THREEDIMENBUTTON_H 43 | -------------------------------------------------------------------------------- /interactive_buttons/watercirclebutton.cpp: -------------------------------------------------------------------------------- 1 | #include "watercirclebutton.h" 2 | 3 | WaterCircleButton::WaterCircleButton(QWidget* parent) : InteractiveButtonBase (parent), in_circle(false), radius(16) 4 | { 5 | 6 | } 7 | 8 | WaterCircleButton::WaterCircleButton(QIcon icon, QWidget *parent) : InteractiveButtonBase (icon, parent), in_circle(false), radius(16) 9 | { 10 | 11 | } 12 | 13 | WaterCircleButton::WaterCircleButton(QPixmap pixmap, QWidget *parent) : InteractiveButtonBase (pixmap, parent), in_circle(false), radius(16) 14 | { 15 | 16 | } 17 | 18 | void WaterCircleButton::enterEvent(QEvent *event) 19 | { 20 | 21 | } 22 | 23 | void WaterCircleButton::leaveEvent(QEvent *event) 24 | { 25 | if (in_circle && !pressing && !inArea(mapFromGlobal(QCursor::pos()))) 26 | { 27 | in_circle = false; 28 | InteractiveButtonBase::leaveEvent(event); 29 | } 30 | } 31 | 32 | void WaterCircleButton::mousePressEvent(QMouseEvent *event) 33 | { 34 | if (in_circle || (!hovering && inArea(event->pos()))) 35 | return InteractiveButtonBase::mousePressEvent(event); 36 | } 37 | 38 | void WaterCircleButton::mouseReleaseEvent(QMouseEvent *event) 39 | { 40 | if (pressing) 41 | { 42 | InteractiveButtonBase::mouseReleaseEvent(event); 43 | 44 | if (leave_after_clicked || (!inArea(event->pos()) && !pressing)) // 鼠标移出 45 | { 46 | in_circle = false; 47 | InteractiveButtonBase::leaveEvent(nullptr); 48 | } 49 | } 50 | } 51 | 52 | void WaterCircleButton::mouseMoveEvent(QMouseEvent *event) 53 | { 54 | bool is_in = inArea(event->pos()); 55 | 56 | if (is_in && !in_circle)// 鼠标移入 57 | { 58 | in_circle = true; 59 | InteractiveButtonBase::enterEvent(nullptr); 60 | } 61 | else if (!is_in && in_circle && !pressing) // 鼠标移出 62 | { 63 | in_circle = false; 64 | InteractiveButtonBase::leaveEvent(nullptr); 65 | } 66 | 67 | if (in_circle) 68 | InteractiveButtonBase::mouseMoveEvent(event); 69 | } 70 | 71 | void WaterCircleButton::resizeEvent(QResizeEvent *event) 72 | { 73 | center_pos = geometry().center() - geometry().topLeft(); 74 | radius = min(size().width(), size().height())/ 2; 75 | 76 | return InteractiveButtonBase::resizeEvent(event); 77 | } 78 | 79 | QPainterPath WaterCircleButton::getBgPainterPath() 80 | { 81 | QPainterPath path; 82 | int w = size().width(), h = size().height(); 83 | QRect rect(w/2-radius, h/2-radius, radius*2, radius*2); 84 | path.addEllipse(rect); 85 | return path; 86 | } 87 | 88 | QPainterPath WaterCircleButton::getWaterPainterPath(InteractiveButtonBase::Water water) 89 | { 90 | QPainterPath path = InteractiveButtonBase::getWaterPainterPath(water) & getBgPainterPath(); 91 | return path; 92 | } 93 | 94 | void WaterCircleButton::simulateStatePress(bool s) 95 | { 96 | in_circle = true; 97 | InteractiveButtonBase::simulateStatePress(s); 98 | in_circle = false; 99 | } 100 | 101 | bool WaterCircleButton::inArea(QPoint point) 102 | { 103 | return (point - center_pos).manhattanLength() <= radius; 104 | } 105 | -------------------------------------------------------------------------------- /interactive_buttons/watercirclebutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERCIRCLEBUTTON_H 2 | #define WATERCIRCLEBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WaterCircleButton : public InteractiveButtonBase 7 | { 8 | public: 9 | WaterCircleButton(QWidget* parent = nullptr); 10 | WaterCircleButton(QIcon icon, QWidget* parent = nullptr); 11 | WaterCircleButton(QPixmap pixmap, QWidget* parent = nullptr); 12 | 13 | protected: 14 | void enterEvent(QEvent* event) override; 15 | void leaveEvent(QEvent* event) override; 16 | void mousePressEvent(QMouseEvent* event) override; 17 | void mouseReleaseEvent(QMouseEvent* event) override; 18 | void mouseMoveEvent(QMouseEvent* event) override; 19 | void resizeEvent(QResizeEvent* event) override; 20 | 21 | QPainterPath getBgPainterPath() override; 22 | QPainterPath getWaterPainterPath(Water water) override; 23 | 24 | void simulateStatePress(bool s = true); 25 | bool inArea(QPoint point) override; 26 | 27 | protected: 28 | QPoint center_pos; 29 | bool in_circle; 30 | int radius; 31 | }; 32 | 33 | #endif // WATERCIRCLEBUTTON_H 34 | -------------------------------------------------------------------------------- /interactive_buttons/waterfallbuttongroup.cpp: -------------------------------------------------------------------------------- 1 | #include "waterfallbuttongroup.h" 2 | 3 | WaterFallButtonGroup::WaterFallButtonGroup(QWidget *parent) 4 | : QWidget(parent), 5 | normal_bg(128,128,128,32), 6 | hover_bg(100,149,237,128), 7 | press_bg(100,149,237), 8 | selected_bg(100,149,237,128), 9 | normal_ft(0,0,0), 10 | selected_ft(255,255,255) 11 | { 12 | 13 | } 14 | 15 | void WaterFallButtonGroup::initStringList(QStringList list, QStringList selected) 16 | { 17 | clearButtons(); 18 | foreach (QString s, list) 19 | { 20 | if (s.trimmed().isEmpty()) 21 | continue; 22 | addButton(s, selected.contains(s)); 23 | } 24 | updateButtonPositions(); 25 | } 26 | 27 | void WaterFallButtonGroup::setSelects(QStringList list) 28 | { 29 | foreach (InteractiveButtonBase *btn, btns) 30 | { 31 | if (btn->getText().isEmpty()) 32 | continue; 33 | if (list.contains(btn->getText()) && !btn->getState()) 34 | selectBtn(btn); 35 | else if (!list.contains(btn->getText()) && btn->getState()) 36 | selectBtn(btn); 37 | } 38 | } 39 | 40 | void WaterFallButtonGroup::addButton(QString s, bool selected) 41 | { 42 | WaterFloatButton* btn = new WaterFloatButton(s, this); 43 | btn->setFixedForeSize(); 44 | btn->show(); 45 | setBtnColors(btn); 46 | btns.append(btn); 47 | 48 | if (selected) 49 | selectBtn(btn); 50 | 51 | btn->setAutoTextColor(false); 52 | connect(btn, &InteractiveButtonBase::clicked, this, [=]{ 53 | if (!selectable) 54 | return ; 55 | 56 | selectBtn(btn); 57 | if (btn->getState()) 58 | emit signalSelected(s); 59 | else 60 | emit signalUnselected(s); 61 | }); 62 | } 63 | 64 | void WaterFallButtonGroup::addButton(QString s, QColor c, bool selected) 65 | { 66 | WaterFloatButton* btn = new WaterFloatButton(s, this); 67 | btn->setFixedForeSize(); 68 | setBtnColors(btn); 69 | btns.append(btn); 70 | 71 | if (selected) 72 | selectBtn(btn); 73 | 74 | btn->setAutoTextColor(false); 75 | btn->setTextColor(c); 76 | connect(btn, &InteractiveButtonBase::clicked, this, [=]{ 77 | if (!selectable) 78 | return ; 79 | selectBtn(btn); 80 | if (btn->getState()) 81 | emit signalSelected(s); 82 | else 83 | emit signalUnselected(s); 84 | }); 85 | } 86 | 87 | void WaterFallButtonGroup::clearButtons() 88 | { 89 | foreach (InteractiveButtonBase* btn, btns) 90 | btn->deleteLater(); 91 | btns.clear(); 92 | } 93 | 94 | void WaterFallButtonGroup::setSelecteable(bool en) 95 | { 96 | this->selectable = en; 97 | } 98 | 99 | void WaterFallButtonGroup::setColors(QColor normal_bg, QColor hover_bg, QColor press_bg, QColor selected_bg, QColor normal_ft, QColor selected_ft) 100 | { 101 | this->normal_bg = normal_bg; 102 | this->hover_bg = hover_bg; 103 | this->press_bg = press_bg; 104 | this->selected_bg = selected_bg; 105 | this->normal_ft = normal_ft; 106 | if (selected_ft != Qt::transparent) 107 | this->selected_ft = selected_ft; 108 | else 109 | selected_ft = getReverseColor(selected_bg); 110 | 111 | foreach (InteractiveButtonBase* btn, btns) 112 | { 113 | btn->setTextColor(normal_ft); 114 | btn->setBgColor(normal_bg); 115 | btn->setBgColor(hover_bg, press_bg); 116 | } 117 | } 118 | 119 | void WaterFallButtonGroup::setMouseColor(QColor hover_bg, QColor press_bg) 120 | { 121 | this->hover_bg = hover_bg; 122 | this->press_bg = press_bg; 123 | foreach (InteractiveButtonBase* btn, btns) 124 | { 125 | btn->setBgColor(hover_bg, press_bg); 126 | } 127 | } 128 | 129 | void WaterFallButtonGroup::updateButtonPositions() 130 | { 131 | const int space_h = 3, space_v = 3; 132 | int total_w = this->width(), w = space_v; 133 | int total_h = 0; 134 | // 自动调整位置 135 | foreach (InteractiveButtonBase* btn, btns) 136 | { 137 | int btn_w = btn->width(); 138 | if (w == 0 || w + btn_w <= total_w) // 开头,或者同一行 139 | { 140 | btn->move(w, total_h); 141 | w += btn_w + space_h; 142 | } 143 | else // 另起一行 144 | { 145 | total_h += btn->height() + space_v; 146 | w = space_h; 147 | btn->move(w, total_h); 148 | w += btn_w + space_h; 149 | } 150 | } 151 | if (btns.size()) 152 | total_h += btns.last()->height(); 153 | this->setFixedHeight(total_h); 154 | } 155 | 156 | void WaterFallButtonGroup::resizeEvent(QResizeEvent *event) 157 | { 158 | updateButtonPositions(); 159 | 160 | QWidget::resizeEvent(event); 161 | } 162 | 163 | QColor WaterFallButtonGroup::getReverseColor(QColor color) 164 | { 165 | return QColor( 166 | getReverseChannel(color.red()), 167 | getReverseChannel(color.green()), 168 | getReverseChannel(color.blue()) 169 | ); 170 | } 171 | 172 | int WaterFallButtonGroup::getReverseChannel(int x) 173 | { 174 | if (x < 92 || x > 159) 175 | return 255 - x; 176 | else if (x < 128) 177 | return 255; 178 | else // if (x > 128) 179 | return 0; 180 | } 181 | 182 | void WaterFallButtonGroup::setBtnColors(InteractiveButtonBase *btn) 183 | { 184 | btn->setBgColor(normal_bg); 185 | btn->setBgColor(hover_bg, press_bg); 186 | } 187 | 188 | void WaterFallButtonGroup::selectBtn(InteractiveButtonBase *btn) 189 | { 190 | btn->setState(!btn->getState()); 191 | if (btn->getState()) // 选中 192 | { 193 | btn->setBgColor(selected_bg); 194 | // btn->setTextColor(selected_ft); 195 | } 196 | else 197 | { 198 | btn->setBgColor(normal_bg); 199 | // btn->setTextColor(normal_ft); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /interactive_buttons/waterfallbuttongroup.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERFALLBUTTONGROUP_H 2 | #define WATERFALLBUTTONGROUP_H 3 | 4 | #include "waterfloatbutton.h" 5 | 6 | class WaterFallButtonGroup : public QWidget 7 | { 8 | Q_OBJECT 9 | public: 10 | WaterFallButtonGroup(QWidget* parent = nullptr); 11 | 12 | void initStringList(QStringList list, QStringList selected = QStringList()); 13 | void setSelects(QStringList list); 14 | void addButton(QString s, bool selected = false); 15 | void addButton(QString s, QColor c, bool selected = false); 16 | void clearButtons(); 17 | 18 | void setSelecteable(bool en); 19 | void setColors(QColor normal_bg, QColor hover_bg, QColor press_bg, QColor selected_bg, QColor normal_ft, QColor selected_ft = Qt::transparent); 20 | void setMouseColor(QColor hover_bg, QColor press_bg); 21 | 22 | void updateButtonPositions(); 23 | 24 | protected: 25 | void resizeEvent(QResizeEvent *event) override; 26 | 27 | private: 28 | QColor getReverseColor(QColor color); 29 | int getReverseChannel(int x); 30 | void setBtnColors(InteractiveButtonBase* btn); 31 | void selectBtn(InteractiveButtonBase* btn); 32 | 33 | signals: 34 | void signalSelected(QString s); 35 | void signalUnselected(QString s); 36 | 37 | private: 38 | QListbtns; 39 | bool selectable = true; 40 | QColor normal_bg, hover_bg, press_bg, selected_bg, normal_ft, selected_ft; 41 | }; 42 | 43 | #endif // WATERFALLBUTTONGROUP_H 44 | -------------------------------------------------------------------------------- /interactive_buttons/waterfloatbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "waterfloatbutton.h" 2 | 3 | WaterFloatButton::WaterFloatButton(QWidget *parent) : InteractiveButtonBase(parent), 4 | in_area(false), mwidth(16), radius(8) 5 | { 6 | fore_enabled = false; 7 | fore_paddings.left = fore_paddings.right = radius; 8 | } 9 | 10 | WaterFloatButton::WaterFloatButton(QString s, QWidget *parent) : InteractiveButtonBase(s, parent), 11 | in_area(false), mwidth(16), radius(8) 12 | { 13 | fore_enabled = false; 14 | fore_paddings.left = fore_paddings.right = radius; 15 | } 16 | 17 | void WaterFloatButton::enterEvent(QEvent *event) 18 | { 19 | 20 | } 21 | 22 | void WaterFloatButton::leaveEvent(QEvent *event) 23 | { 24 | if (in_area && !pressing && !inArea(mapFromGlobal(QCursor::pos()))) 25 | { 26 | in_area = false; 27 | InteractiveButtonBase::leaveEvent(event); 28 | } 29 | } 30 | 31 | void WaterFloatButton::mousePressEvent(QMouseEvent *event) 32 | { 33 | if (in_area) 34 | return InteractiveButtonBase::mousePressEvent(event); 35 | } 36 | 37 | void WaterFloatButton::mouseReleaseEvent(QMouseEvent *event) 38 | { 39 | if (pressing) 40 | { 41 | InteractiveButtonBase::mouseReleaseEvent(event); 42 | 43 | if (!pressing && !inArea(event->pos())) 44 | { 45 | in_area = false; 46 | InteractiveButtonBase::leaveEvent(event); 47 | } 48 | } 49 | } 50 | 51 | void WaterFloatButton::mouseMoveEvent(QMouseEvent *event) 52 | { 53 | bool is_in = inArea(event->pos()); 54 | 55 | if (!in_area && is_in) // 鼠标移入 56 | { 57 | in_area = true; 58 | InteractiveButtonBase::enterEvent(nullptr); 59 | } 60 | else if (in_area && !is_in && !pressing) // 鼠标移出 61 | { 62 | in_area = false; 63 | InteractiveButtonBase::leaveEvent(nullptr); 64 | } 65 | 66 | if (in_area) 67 | InteractiveButtonBase::mouseMoveEvent(event); 68 | } 69 | 70 | void WaterFloatButton::resizeEvent(QResizeEvent *event) 71 | { 72 | int w = geometry().width(), h = geometry().height(); 73 | if (h >= w * 4) // 宽度为准 74 | radius = w / 4; 75 | else 76 | radius = h/2; 77 | mwidth = (w-radius*2); 78 | 79 | return InteractiveButtonBase::resizeEvent(event); 80 | } 81 | 82 | void WaterFloatButton::paintEvent(QPaintEvent *event) 83 | { 84 | InteractiveButtonBase::paintEvent(event); 85 | 86 | QPainter painter(this); 87 | // painter.setRenderHint(QPainter::SmoothPixmapTransform, true); 88 | painter.setRenderHint(QPainter::Antialiasing,true); 89 | 90 | // 鼠标悬浮进度 91 | QColor edge_color = hover_bg; 92 | int pro = 0; 93 | if (hover_progress > 0 || press_progress || waters.size()) 94 | { 95 | if (water_animation) 96 | { 97 | /** 不用判断 water 是出现还是消失状态 98 | * 如果一直悬浮的话,颜色不会变 99 | * 而如果是点一下立马移开,文字会出现一种“渐隐渐现”的效果 100 | */ 101 | if (waters.size()) 102 | pro = max(hover_progress, waters.last().progress); 103 | else 104 | pro = hover_progress; 105 | } 106 | else 107 | { 108 | max(hover_progress, press_progress); 109 | } 110 | edge_color.setAlpha(255 * (100 - pro) / 100); 111 | } 112 | 113 | // 画边框 114 | QPainterPath path; 115 | if (show_foreground) 116 | { 117 | path = getBgPainterPath(); // 整体背景 118 | 119 | // 出现动画 120 | if (show_ani_appearing && show_ani_progress != 100 && border_bg.alpha() != 0) 121 | { 122 | int pw = size().width() * show_ani_progress / 100; 123 | QRect rect(0, 0, pw, size().height()); 124 | QPainterPath rect_path; 125 | rect_path.addRect(rect); 126 | path &= rect_path; 127 | 128 | int x = show_ani_point.x(), y = show_ani_point.y(); 129 | int gen = quick_sqrt(x*x + y*y); 130 | x = - water_radius * x / gen; // 动画起始中心点横坐标 反向 131 | y = - water_radius * y / gen; // 动画起始中心点纵坐标 反向 132 | } 133 | if (border_bg.alpha() != 0) // 如果有背景,则不进行画背景线条 134 | { 135 | painter.setPen(border_bg); 136 | painter.drawPath(path); 137 | } 138 | } 139 | 140 | // 画文字 141 | if ((self_enabled || fore_enabled) && !text.isEmpty()) 142 | { 143 | QRect rect = QRect(QPoint(0,0), size()); 144 | QColor color; 145 | if (pro) 146 | { 147 | if (auto_text_color) 148 | { 149 | QColor aim_color = isLightColor(hover_bg) ? QColor(0, 0, 0) : QColor(255, 255, 255); 150 | color = QColor( 151 | text_color.red() + (aim_color.red() - text_color.red()) * pro / 100, 152 | text_color.green() + (aim_color.green() - text_color.green()) * pro / 100, 153 | text_color.blue() + (aim_color.blue() - text_color.blue()) * pro / 100, 154 | 255); 155 | } 156 | else 157 | { 158 | color = text_color; 159 | } 160 | painter.setPen(color); 161 | } 162 | else 163 | { 164 | color = text_color; 165 | color.setAlpha(255); 166 | } 167 | painter.setPen(color); 168 | if (font_size > 0) 169 | { 170 | QFont font = painter.font(); 171 | font.setPointSize(font_size); 172 | painter.setFont(font); 173 | } 174 | painter.drawText(rect, Qt::AlignCenter, text); 175 | } 176 | } 177 | 178 | QPainterPath WaterFloatButton::getBgPainterPath() 179 | { 180 | QPainterPath path1, path2, path3; 181 | int w = size().width(), h = size().height(); 182 | 183 | QRect mrect(w/2-mwidth/2, h/2-radius, mwidth, radius*2); 184 | path1.addRect(mrect); 185 | 186 | QPoint o1(w/2-mwidth/2, h/2); 187 | QPoint o2(w/2+mwidth/2, h/2); 188 | path2.addEllipse(o1.x()-radius, o1.y()-radius, radius*2, radius*2); 189 | path3.addEllipse(o2.x()-radius, o2.y()-radius, radius*2, radius*2); 190 | 191 | return path1 | path2 | path3; 192 | } 193 | 194 | QPainterPath WaterFloatButton::getWaterPainterPath(InteractiveButtonBase::Water water) 195 | { 196 | QPainterPath path = InteractiveButtonBase::getWaterPainterPath(water) & getBgPainterPath(); 197 | return path; 198 | } 199 | 200 | bool WaterFloatButton::inArea(QPoint point) 201 | { 202 | int w = size().width(), h = size().height(); 203 | QPoint o1(w/2-mwidth/2, h/2); 204 | QPoint o2(w/2+mwidth/2, h/2); 205 | QRect mrect(w/2-mwidth/2, h/2-radius, mwidth, radius*2); 206 | 207 | if (mrect.contains(point)) 208 | return true; 209 | if ((point-o1).manhattanLength() <= radius || 210 | (point-o2).manhattanLength() <= radius) 211 | return true; 212 | return false; 213 | } 214 | -------------------------------------------------------------------------------- /interactive_buttons/waterfloatbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERFLOATBUTTON_H 2 | #define WATERFLOATBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WaterFloatButton : public InteractiveButtonBase 7 | { 8 | public: 9 | WaterFloatButton(QWidget* parent = nullptr); 10 | WaterFloatButton(QString s, QWidget* parent = nullptr); 11 | 12 | protected: 13 | void enterEvent(QEvent* event) override; 14 | void leaveEvent(QEvent* event) override; 15 | void mousePressEvent(QMouseEvent* event) override; 16 | void mouseReleaseEvent(QMouseEvent* event) override; 17 | void mouseMoveEvent(QMouseEvent* event) override; 18 | void resizeEvent(QResizeEvent* event) override; 19 | void paintEvent(QPaintEvent *event) override; 20 | 21 | QPainterPath getBgPainterPath() override; 22 | QPainterPath getWaterPainterPath(Water water) override; 23 | 24 | bool inArea(QPoint point) override; 25 | 26 | protected: 27 | QPoint center_pos; 28 | bool in_area; 29 | int mwidth; 30 | int radius; 31 | }; 32 | 33 | #endif // WATERFLOATBUTTON_H 34 | -------------------------------------------------------------------------------- /interactive_buttons/waterzoombutton.cpp: -------------------------------------------------------------------------------- 1 | #include "waterzoombutton.h" 2 | 3 | WaterZoomButton::WaterZoomButton(QString text, QWidget *parent) : InteractiveButtonBase(text, parent) 4 | { 5 | choking = DEFAULT_CHOKING; 6 | radius_zoom = -1; 7 | choking_prop = 0; 8 | } 9 | 10 | WaterZoomButton::WaterZoomButton(QWidget *parent) : InteractiveButtonBase(parent) 11 | { 12 | choking = DEFAULT_CHOKING; 13 | radius_zoom = -1; 14 | choking_prop = 0; 15 | } 16 | 17 | void WaterZoomButton::setChoking(int c) 18 | { 19 | choking = c; 20 | } 21 | 22 | int WaterZoomButton::getChokingSpacing() 23 | { 24 | return choking * 2; 25 | } 26 | 27 | int WaterZoomButton::getDefaultSpacing() 28 | { 29 | return DEFAULT_CHOKING * 2; 30 | } 31 | 32 | void WaterZoomButton::setChokingProp(double p) 33 | { 34 | choking = min(width(), height()) * p; 35 | choking_prop = p; 36 | } 37 | 38 | void WaterZoomButton::setRadiusZoom(int radius) 39 | { 40 | radius_zoom = radius; 41 | } 42 | 43 | void WaterZoomButton::setRadius(int x, int x2) 44 | { 45 | // 注意:最终绘制中只计算 x 的半径,无视 y 的半径 46 | InteractiveButtonBase::setRadius(x); 47 | radius_zoom = x2; 48 | } 49 | 50 | QPainterPath WaterZoomButton::getBgPainterPath() 51 | { 52 | QPainterPath path; 53 | int c; 54 | int r; 55 | if (!hover_progress) 56 | { 57 | c = choking; 58 | r = radius_x; 59 | } 60 | else 61 | { 62 | c = choking * (1 - getNolinearProg(hover_progress, hovering?FastSlower:SlowFaster)); 63 | r = radius_zoom < 0 ? radius_x : 64 | radius_x + (radius_zoom-radius_x) * hover_progress / 100; 65 | } 66 | 67 | if (r) 68 | path.addRoundedRect(QRect(c,c,size().width()-c*2,size().height()-c*2), r, r); 69 | else 70 | path.addRect(QRect(c,c,size().width()-c*2,size().height()-c*2)); 71 | return path; 72 | } 73 | 74 | void WaterZoomButton::resizeEvent(QResizeEvent *event) 75 | { 76 | InteractiveButtonBase::resizeEvent(event); 77 | 78 | if (qAbs(choking_prop)>0.0001) 79 | { 80 | choking = min(width(), height()) * choking_prop; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /interactive_buttons/waterzoombutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERZOOMBUTTON_H 2 | #define WATERZOOMBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | const int DEFAULT_CHOKING = 5; 7 | 8 | class WaterZoomButton : public InteractiveButtonBase 9 | { 10 | public: 11 | WaterZoomButton(QString text, QWidget* parent = nullptr); 12 | WaterZoomButton(QWidget* parent = nullptr); 13 | 14 | void setChoking(int c); 15 | void setChokingProp(double p); 16 | void setRadiusZoom(int radius); 17 | void setRadius(int x, int x2); 18 | 19 | int getChokingSpacing(); 20 | static int getDefaultSpacing(); 21 | 22 | protected: 23 | QPainterPath getBgPainterPath() override; 24 | void resizeEvent(QResizeEvent *event) override; 25 | 26 | protected: 27 | int choking; 28 | double choking_prop; 29 | int radius_zoom; 30 | }; 31 | 32 | #endif // WATERZOOMBUTTON_H 33 | -------------------------------------------------------------------------------- /interactive_buttons/winclosebutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winclosebutton.h" 2 | 3 | WinCloseButton::WinCloseButton(QWidget *parent) 4 | : InteractiveButtonBase (parent), tr_radius(0) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinCloseButton::paintEvent(QPaintEvent *event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | int w = _w, h = _h; 16 | int l = _l+w/3, t = _t+h/3, r = w*2/3, b = h*2/3; 17 | int mx = _l+w/2+offset_pos.x(), my = _t+h/2+offset_pos.y(); 18 | 19 | if (click_ani_appearing || click_ani_disappearing) 20 | { 21 | double pro = click_ani_progress / 100.0; 22 | l -= l * pro; 23 | t -= t * pro; 24 | r += (w-r) * pro; 25 | b += (h-b) * pro; 26 | } 27 | 28 | QPainter painter(this); 29 | painter.setPen(QPen(icon_color)); 30 | painter.setRenderHint(QPainter::Antialiasing,true); 31 | if (offset_pos == QPoint(0,0)) 32 | { 33 | painter.drawLine(QPoint(l,t), QPoint(r,b)); 34 | painter.drawLine(QPoint(r,t), QPoint(l,b)); 35 | } 36 | else 37 | { 38 | QPainterPath path; 39 | path.moveTo(QPoint(l,t)); 40 | path.cubicTo(QPoint(l,t), QPoint(mx,my), QPoint(r,b)); 41 | path.moveTo(QPoint(r,t)); 42 | path.cubicTo(QPoint(r,t), QPoint(mx,my), QPoint(l,b)); 43 | 44 | painter.drawPath(path); 45 | } 46 | } 47 | 48 | /** 49 | * 针对圆角的设置 50 | */ 51 | void WinCloseButton::setTopRightRadius(int r) 52 | { 53 | tr_radius = r; 54 | } 55 | 56 | QPainterPath WinCloseButton::getBgPainterPath() 57 | { 58 | if (!tr_radius) 59 | return InteractiveButtonBase::getBgPainterPath(); 60 | 61 | QPainterPath path = InteractiveButtonBase::getBgPainterPath(); 62 | QPainterPath round_path; 63 | round_path.addEllipse(width() - tr_radius - tr_radius, 0, tr_radius*2, tr_radius*2); 64 | QPainterPath corner_path; 65 | corner_path.addRect(width() - tr_radius, 0, tr_radius, tr_radius); 66 | corner_path -= round_path; 67 | path -= corner_path; 68 | return path; 69 | } 70 | 71 | QPainterPath WinCloseButton::getWaterPainterPath(Water water) 72 | { 73 | return InteractiveButtonBase::getWaterPainterPath(water) & WinCloseButton::getBgPainterPath(); 74 | } 75 | -------------------------------------------------------------------------------- /interactive_buttons/winclosebutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINCLOSEBUTTON_H 2 | #define WINCLOSEBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | #include 7 | 8 | class WinCloseButton : public InteractiveButtonBase 9 | { 10 | public: 11 | WinCloseButton(QWidget* parent = nullptr); 12 | 13 | void setTopRightRadius(int r); 14 | 15 | protected: 16 | void paintEvent(QPaintEvent*event); 17 | 18 | QPainterPath getBgPainterPath(); 19 | QPainterPath getWaterPainterPath(Water water); 20 | 21 | private: 22 | int tr_radius; 23 | }; 24 | 25 | #endif // WINCLOSEBUTTON_H 26 | -------------------------------------------------------------------------------- /interactive_buttons/winmaxbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winmaxbutton.h" 2 | 3 | WinMaxButton::WinMaxButton(QWidget *parent) 4 | : InteractiveButtonBase (parent) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinMaxButton::paintEvent(QPaintEvent *event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | int w = _w, h = _h; 16 | int dx = offset_pos.x(), dy = offset_pos.y(); 17 | QRect r; 18 | if (click_ani_appearing || click_ani_disappearing) 19 | { 20 | double pro = click_ani_progress / 800.0; 21 | r = QRect( 22 | _l+(w/3+dx) - (w/3+dx)*pro, 23 | _t+(h/3+dy) - (h/3+dy)*pro, 24 | w/3 + (w*2/3)*pro, 25 | h/3 + (h*2/3)*pro 26 | ); 27 | } 28 | else 29 | { 30 | r = QRect(_l+w/3+dx, _t+h/3+dy, w/3, h/3); 31 | } 32 | 33 | 34 | QPainter painter(this); 35 | painter.setPen(QPen(icon_color)); 36 | painter.drawRect(r); 37 | } 38 | -------------------------------------------------------------------------------- /interactive_buttons/winmaxbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINMAXBUTTON_H 2 | #define WINMAXBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WinMaxButton : public InteractiveButtonBase 7 | { 8 | public: 9 | WinMaxButton(QWidget* parent = nullptr); 10 | 11 | protected: 12 | void paintEvent(QPaintEvent*event); 13 | }; 14 | 15 | #endif // WINMAXBUTTON_H 16 | -------------------------------------------------------------------------------- /interactive_buttons/winmenubutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winmenubutton.h" 2 | 3 | WinMenuButton::WinMenuButton(QWidget* parent) 4 | : InteractiveButtonBase (parent) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinMenuButton::paintEvent(QPaintEvent *event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | double w = _w, h = _h; 16 | double dx = offset_pos.x(), dy = offset_pos.y(); 17 | 18 | QPainter painter(this); 19 | painter.setPen(QPen(icon_color)); 20 | 21 | if (click_ani_appearing) 22 | { 23 | double pro = click_ani_progress / 100.0; 24 | if (!getState()) 25 | pro = 1 - pro; 26 | 27 | double l = _l+w/3, 28 | r = _l+w*2/3, 29 | t = _t+h/3 + pro*h/3; 30 | painter.drawLine(QPointF(l+dx/4,t+dy/4), QPointF(r+dx/4,t+dy/4)); 31 | 32 | l = _l+w/3+pro*w/24; 33 | r = _l+w*2/3-pro*w/24; 34 | t = _t+w/2+pro*h*5/18; 35 | painter.drawLine(QPointF(l+dx/2,t+dy/2), QPointF(r+dx/2,t+dy/2)); 36 | 37 | l = _l+w/3+pro*w/12; 38 | r = _l+w*2/3-pro*w/12; 39 | t = _t+w*2/3+pro*h*2/9; 40 | painter.drawLine(QPointF(l+dx,t+dy), QPointF(r+dx,t+dy)); 41 | } 42 | else if (getState()) 43 | { 44 | painter.drawLine(QLineF(_l+w/3+dx/4, _t+h*2/3+dy/4, w*2/3+dx/4,h*2/3+dy/4)); 45 | painter.drawLine(QLineF(_l+w/3+w/24+dx/2, _t+h*7/9+dy/2, w*2/3-w/24+dx/2, h*7/9+dy/2)); 46 | painter.drawLine(QLineF(_l+w/3+w/12+dx, _t+h*8/9+dy, w*2/3-w/12+dx, h*8/9+dy)); 47 | } 48 | else 49 | { 50 | painter.drawLine(QPointF(_l+w/3+dx/4,_t+h/3+dy/4), QPointF(_l+w*2/3+dx/4,_t+h/3+dy/4)); 51 | painter.drawLine(QPointF(_l+w/3+dx/2,_t+h/2+dy/2), QPointF(_l+w*2/3+dx/2,_t+h/2+dy/2)); 52 | painter.drawLine(QPointF(_l+w/3+dx,_t+h*2/3+dy), QPointF(_l+w*2/3+dx,_t+h*2/3+dy)); 53 | } 54 | } 55 | 56 | void WinMenuButton::slotClicked() 57 | { 58 | setState(!getState()); 59 | return InteractiveButtonBase::slotClicked(); 60 | } 61 | -------------------------------------------------------------------------------- /interactive_buttons/winmenubutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINMENUBUTTON_H 2 | #define WINMENUBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WinMenuButton : public InteractiveButtonBase 7 | { 8 | public: 9 | WinMenuButton(QWidget* parent = nullptr); 10 | 11 | protected: 12 | void paintEvent(QPaintEvent*event); 13 | void slotClicked(); 14 | }; 15 | 16 | #endif // WINMENUBUTTON_H 17 | -------------------------------------------------------------------------------- /interactive_buttons/winminbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winminbutton.h" 2 | 3 | WinMinButton::WinMinButton(QWidget* parent) 4 | : InteractiveButtonBase(parent) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinMinButton::paintEvent(QPaintEvent* event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | int w = _w, h = _h; 16 | QPoint left(_l+w/3, _t+h/2), right(_l+w*2/3, _t+h/2), 17 | mid(_l+w/2+offset_pos.x(), _t+h/2+offset_pos.y()); 18 | 19 | if (click_ani_appearing || click_ani_disappearing) 20 | { 21 | double pro = click_ani_progress / 800.0; 22 | left.setX(left.x()-left.x() * pro); 23 | right.setX(right.x()+(w-right.x()) * pro); 24 | } 25 | 26 | QPainter painter(this); 27 | QPainterPath path; 28 | path.moveTo(left); 29 | path.cubicTo(left, mid, right); 30 | painter.setPen(QPen(icon_color)); 31 | if (left.y() != mid.y()) 32 | painter.setRenderHint(QPainter::Antialiasing,true); 33 | painter.drawPath(path); 34 | } 35 | -------------------------------------------------------------------------------- /interactive_buttons/winminbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINMINBUTTON_H 2 | #define WINMINBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WinMinButton : public InteractiveButtonBase 7 | { 8 | Q_OBJECT 9 | public: 10 | WinMinButton(QWidget* parent = nullptr); 11 | 12 | void paintEvent(QPaintEvent* event) override; 13 | }; 14 | 15 | #endif // WINMINBUTTON_H -------------------------------------------------------------------------------- /interactive_buttons/winrestorebutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winrestorebutton.h" 2 | 3 | WinRestoreButton::WinRestoreButton(QWidget* parent) 4 | : InteractiveButtonBase(parent) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinRestoreButton::paintEvent(QPaintEvent* event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | // 画出现一角的矩形 16 | int w = _w, h = _h; 17 | int dx = offset_pos.x(), dy = offset_pos.y(); 18 | QRect br; 19 | if (click_ani_appearing || click_ani_disappearing) 20 | { 21 | double pro = click_ani_progress / 800.0; 22 | br = QRect( 23 | _l+(w/3+dx) - (w/3+dx)*pro, 24 | _t+(h/3+dy) - (h/3+dy)*pro, 25 | w/3 + (w*2/3)*pro, 26 | h/3 + (h*2/3)*pro 27 | ); 28 | } 29 | else 30 | { 31 | br = QRect(_l+w/3+dx, _t+h/3+dy, w/3, h/3); 32 | } 33 | 34 | // 画原来的矩形 35 | QPainter painter(this); 36 | painter.setPen(QPen(icon_color)); 37 | painter.drawRect(br); 38 | 39 | dx /= 2; dy /= 2; 40 | int l = _l+w*4/9+dx, t = _t+h*2/9+dy, r = _l+w*7/9+dx, b = _t+h*5/9+dy; 41 | if (click_ani_appearing || click_ani_disappearing) 42 | { 43 | double pro = click_ani_progress / 800.0; 44 | l -= l*pro; 45 | t -= t*pro; 46 | r += (w-r)*pro; 47 | b += (h-b)*pro; 48 | } 49 | QPoint topLeft(l, t), topRight(r, t), bottomLeft(l, b), bottomRight(r, b); 50 | QListpoints; 51 | 52 | /* 两个矩形一样大的,所以运行的时候,需要有三大类: 53 | * 1、完全重合(可以视为下一点任意之一) 54 | * 2、有一个点落在矩形内(4种情况) 55 | * 3、完全不重合 56 | * 根据3大类共6种进行判断 57 | */ 58 | if (br.topLeft() == topLeft) 59 | { 60 | points << topLeft << topRight << bottomRight << bottomLeft << topLeft; 61 | } 62 | else if (br.contains(topLeft)) // 左上角在矩形内 63 | { 64 | points << QPoint(br.right()+1, t) << topRight << bottomRight << bottomLeft << QPoint(l, br.bottom()+1); 65 | } 66 | else if (br.contains(topRight)) // 右上角在矩形内 67 | { 68 | points << QPoint(r, br.bottom()+1) << bottomRight << bottomLeft << topLeft << QPoint(br.left(), t); 69 | } 70 | else if (br.contains(bottomLeft)) // 左下角在矩形内(默认) 71 | { 72 | points << QPoint(l, br.top()) << topLeft << topRight << bottomRight << QPoint(br.right()+1, b); 73 | } 74 | else if (br.contains(bottomRight)) // 右下角在矩形内 75 | { 76 | points << QPoint(br.left(), b) << bottomLeft << topLeft << topRight << QPoint(r, br.top()); 77 | } 78 | else // 没有重合 79 | { 80 | points << topLeft << topRight << bottomRight << bottomLeft << topLeft; 81 | } 82 | 83 | if (points.size() > 1) 84 | { 85 | QPainterPath path; 86 | path.moveTo(points.at(0)); 87 | for (int i = 1; i < points.size(); ++i) 88 | path.lineTo(points.at(i)); 89 | QColor color(icon_color); 90 | color.setAlpha(color.alpha()*0.8); 91 | painter.setPen(QPen(color)); 92 | painter.drawPath(path); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /interactive_buttons/winrestorebutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINRESTOREBUTTON_H 2 | #define WINRESTOREBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WinRestoreButton : public InteractiveButtonBase 7 | { 8 | public: 9 | WinRestoreButton(QWidget* parent = nullptr); 10 | 11 | void paintEvent(QPaintEvent* event) override; 12 | }; 13 | 14 | #endif // WINRESTOREBUTTON_H 15 | -------------------------------------------------------------------------------- /interactive_buttons/winsidebarbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winsidebarbutton.h" 2 | 3 | WinSidebarButton::WinSidebarButton(QWidget* parent) 4 | : InteractiveButtonBase (parent), tl_radius(0) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinSidebarButton::paintEvent(QPaintEvent *event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | int dx = offset_pos.x(), dy = offset_pos.y(); 16 | int l = _l + _w/3+dx, t = _t + _h/3+dy, w = _w/3, h = _h/3; 17 | 18 | QPainter painter(this); 19 | painter.setPen(QPen(icon_color)); 20 | painter.setRenderHint(QPainter::Antialiasing,true); 21 | 22 | if (click_ani_appearing) 23 | { 24 | double pro = click_ani_progress / 100.0; 25 | if (getState()) 26 | pro = 1 - pro; 27 | painter.drawEllipse(l+w/2-w*pro/2, t+h/2-h*pro/2, w*pro, h*pro); 28 | 29 | if (getState()) 30 | pro = getSpringBackProgress(click_ani_progress, 50) / 100.0; 31 | else 32 | pro = 1 - click_ani_progress / 100.0; 33 | 34 | l = _l + _w/3+dx; 35 | t = _t + _h/3+dy; 36 | w = _w / 3; 37 | h = _h / 3; 38 | 39 | l += w/2.0 - w*pro/2; 40 | t += h/2.0 - h*pro/2; 41 | w *= pro; 42 | h *= pro; 43 | 44 | QPainterPath path; 45 | path.addEllipse(l, t, w, h); 46 | painter.fillPath(path, icon_color); 47 | } 48 | else if (getState()) 49 | { 50 | QPainterPath path; 51 | path.addEllipse(l, t, w, h); 52 | painter.fillPath(path, icon_color); 53 | } 54 | else 55 | { 56 | painter.drawEllipse(l, t, w, h); 57 | } 58 | } 59 | 60 | void WinSidebarButton::slotClicked() 61 | { 62 | if (getState()) 63 | setState(false); 64 | else 65 | setState(true); 66 | return InteractiveButtonBase::slotClicked(); 67 | } 68 | 69 | /** 70 | * 针对圆角的设置 71 | */ 72 | void WinSidebarButton::setTopLeftRadius(int r) 73 | { 74 | tl_radius = r; 75 | } 76 | 77 | QPainterPath WinSidebarButton::getBgPainterPath() 78 | { 79 | if (!tl_radius) 80 | return InteractiveButtonBase::getBgPainterPath(); 81 | 82 | QPainterPath path = InteractiveButtonBase::getBgPainterPath(); 83 | QPainterPath round_path; 84 | round_path.addEllipse(0, 0, tl_radius * 2, tl_radius * 2); 85 | QPainterPath corner_path; 86 | corner_path.addRect(0, 0, tl_radius, tl_radius); 87 | corner_path -= round_path; 88 | path -= corner_path; 89 | return path; 90 | } 91 | 92 | QPainterPath WinSidebarButton::getWaterPainterPath(Water water) 93 | { 94 | return InteractiveButtonBase::getWaterPainterPath(water) & WinSidebarButton::getBgPainterPath(); 95 | } -------------------------------------------------------------------------------- /interactive_buttons/winsidebarbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINSIDEBARBUTTON_H 2 | #define WINSIDEBARBUTTON_H 3 | 4 | #include 5 | #include 6 | #include "interactivebuttonbase.h" 7 | 8 | class WinSidebarButton : public InteractiveButtonBase 9 | { 10 | public: 11 | WinSidebarButton(QWidget *parent = nullptr); 12 | 13 | void setTopLeftRadius(int r); 14 | 15 | protected: 16 | void paintEvent(QPaintEvent*event); 17 | void slotClicked(); 18 | 19 | QPainterPath getBgPainterPath(); 20 | QPainterPath getWaterPainterPath(Water water); 21 | 22 | private: 23 | int tl_radius; 24 | }; 25 | 26 | #endif // WINSIDEBARBUTTON_H 27 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | MainWindow::MainWindow(QWidget *parent) : 4 | QMainWindow(parent) 5 | { 6 | setFixedSize(560, 500); 7 | QScreen* desktop = QApplication::screenAt(QCursor::pos()); 8 | QRect rect = desktop->availableGeometry(); 9 | move(rect.left()+(rect.width() - width())/2, (rect.height() - height())/2); 10 | 11 | setMouseTracking(true); 12 | 13 | InteractiveButtonBase* btn0 = new InteractiveButtonBase(this); 14 | btn0->setGeometry(0, 0, 120, 32); 15 | btn0->setIcon(QIcon(":/icons/point_menu")); 16 | 17 | InteractiveButtonBase* btn = new InteractiveButtonBase(this); 18 | btn->setGeometry(110, 100, 100, 100); 19 | btn->setBgColor(QColor(128, 0, 0, 100)); 20 | btn->setRadius(5); 21 | 22 | AppendButton* append_btn = new AppendButton(this); 23 | append_btn->setGeometry(304, 10, 32, 32); 24 | 25 | InfoButton* info_btn = new InfoButton(this); 26 | info_btn->setGeometry(336, 10, 32, 32); 27 | // info_btn->setGeometry(300, 300, 100, 100); 28 | 29 | WinMenuButton* menu_btn = new WinMenuButton(this); 30 | menu_btn->setGeometry(368, 10, 32, 32); 31 | 32 | WinMinButton *min_btn = new WinMinButton(this); 33 | min_btn->setGeometry(400, 10, 32, 32); 34 | 35 | WinMaxButton *max_btn = new WinMaxButton(this); 36 | max_btn->setGeometry(432, 10, 32, 32); 37 | 38 | WinRestoreButton* res_btn = new WinRestoreButton(this); 39 | res_btn->setGeometry(464, 10, 32, 32); 40 | 41 | WinCloseButton* close_btn = new WinCloseButton(this); 42 | close_btn->setGeometry(496, 10, 32, 32); 43 | close_btn->setBgColor(Qt::black, Qt::red); 44 | 45 | WaterCircleButton* cir_btn = new WaterCircleButton(this); 46 | cir_btn->setGeometry(528, 10, 32, 32); 47 | 48 | InteractiveButtonBase* text_btn = new InteractiveButtonBase("text", this); 49 | text_btn->setGeometry(0, 50, 100, 32); 50 | text_btn->setHoverAniDuration(5000); 51 | 52 | icon_btn = new WaterCircleButton(QIcon(":/icons/point_menu"), this); 53 | icon_btn->setGeometry(100, 50, 32, 32); 54 | icon_btn->setShowAni(true); 55 | icon_btn->setFixedForePos(true); // 和上面结合,即从中心开始出现和消失 56 | // icon_btn->setStyleSheet("qproperty-fore_enabled: false; "); 57 | 58 | InteractiveButtonBase* pixmap_btn = new InteractiveButtonBase(QPixmap(":/icons/point_menu"), this); 59 | pixmap_btn->setGeometry(132, 50, 32, 32); 60 | pixmap_btn->setDisabled(true); 61 | 62 | InteractiveButtonBase* float_btn = new WaterFloatButton("text", this); 63 | float_btn->setGeometry(164, 50, 100, 32); 64 | float_btn->setBgColor(QColor(102,51,204,192), QColor(102,51,204,255)); 65 | float_btn->setIconColor(QColor(102,51,204,192)); 66 | float_btn->setStyleSheet("qproperty-text: abcdefg; qproperty-water_animation: true; "); 67 | 68 | InteractiveButtonBase* par_btn = new InteractiveButtonBase("parent", this); 69 | par_btn->setGeometry(270, 50, 100, 32); 70 | par_btn->setParentEnabled(true); 71 | par_btn->setForeEnabled(false); 72 | par_btn->setStyleSheet("qproperty-text: asdasd; color: red; qproperty-press_color: green;"); 73 | 74 | QPushButton* push_btn = new QPushButton("parent", this); 75 | push_btn->setGeometry(370, 50, 100, 32); 76 | 77 | PointMenuButton* pm_btn = new PointMenuButton(this); 78 | pm_btn->setGeometry(0, 100, 100, 100); 79 | pm_btn->setStyleSheet("qproperty-background_color: red; qproperty-radius: 20; qproperty-border_color: green; qproperty-border_width:3;" 80 | "qproperty-icon_color: blue; "); 81 | 82 | ThreeDimenButton* tdb = new ThreeDimenButton(this); 83 | tdb->setGeometry(220, 100, 200, 100); 84 | tdb->setBgColor(Qt::gray); 85 | connect(tdb, &ThreeDimenButton::clicked, [=]{ qDebug() << "3D按钮clicked"; }); 86 | connect(tdb, &ThreeDimenButton::signalMouseEnter, [=]{ qDebug() << "mouseEnter"; }); 87 | connect(tdb, &ThreeDimenButton::signalMouseLeave, [=]{ qDebug() << "mouseLeave"; }); 88 | connect(tdb, &ThreeDimenButton::signalMouseEnterLater, [=]{ qDebug() << "mouseEnterLater"; }); 89 | connect(tdb, &ThreeDimenButton::signalMouseLeaveLater, [=]{ qDebug() << "mouseLeaveLater"; }); 90 | connect(tdb, &ThreeDimenButton::signalMousePress, [=](QMouseEvent*){ qDebug() << "mousePress"; }); 91 | connect(tdb, &ThreeDimenButton::signalMouseRelease, [=](QMouseEvent*){ qDebug() << "mouseRelease"; }); 92 | connect(tdb, &ThreeDimenButton::signalMousePressLater, [=](QMouseEvent*){ qDebug() << "mousePressLater"; }); 93 | connect(tdb, &ThreeDimenButton::signalMouseReleaseLater, [=](QMouseEvent*){ qDebug() << "mouseReleaseLater"; }); 94 | 95 | WaterZoomButton* zoom_btn1 = new WaterZoomButton("tttttttttt", this); 96 | zoom_btn1->setGeometry(300, 200, 200, 50); 97 | zoom_btn1->setBgColor(QColor(240,128,128)); 98 | zoom_btn1->setBgColor(Qt::transparent, QColor(0x88, 0x88, 0x88, 0x64)); 99 | zoom_btn1->setRadius(10, 5); 100 | zoom_btn1->setChokingProp(0.18); 101 | 102 | InteractiveButtonBase* double_btn = new InteractiveButtonBase(this); 103 | double_btn->setGeometry(510, 200, 50, 50); 104 | double_btn->setDoubleClicked(true); 105 | double_btn->setBgColor(QColor(102,51,204,192)); 106 | connect(double_btn, &InteractiveButtonBase::clicked, [=]{ 107 | qDebug() << "单击"; 108 | }); 109 | connect(double_btn, &InteractiveButtonBase::doubleClicked, [=]{ 110 | qDebug() << "双击"; 111 | }); 112 | 113 | InteractiveButtonBase* icon_text_btn = new InteractiveButtonBase(QIcon(":/icons/point_menu"), "菜单", this); 114 | icon_text_btn->setGeometry(100, 210, 100, 40); 115 | } 116 | 117 | void MainWindow::enterEvent(QEvent *event) 118 | { 119 | icon_btn->showForeground2(); 120 | 121 | return QMainWindow::enterEvent(event); 122 | } 123 | 124 | void MainWindow::leaveEvent(QEvent *event) 125 | { 126 | icon_btn->hideForeground(); 127 | 128 | return QMainWindow::leaveEvent(event); 129 | } 130 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "interactivebuttonbase.h" 9 | #include "winminbutton.h" 10 | #include "winmaxbutton.h" 11 | #include "winrestorebutton.h" 12 | #include "winclosebutton.h" 13 | #include "winmenubutton.h" 14 | #include "watercirclebutton.h" 15 | #include "waterfloatbutton.h" 16 | #include "pointmenubutton.h" 17 | #include "threedimenbutton.h" 18 | #include "waterzoombutton.h" 19 | #include "appendbutton.h" 20 | #include "infobutton.h" 21 | 22 | class MainWindow : public QMainWindow 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | MainWindow(QWidget *parent = nullptr); 28 | 29 | protected: 30 | void enterEvent(QEvent* event); 31 | void leaveEvent(QEvent* event); 32 | 33 | private: 34 | InteractiveButtonBase* icon_btn; 35 | 36 | }; 37 | 38 | #endif // MAINWINDOW_H 39 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | MainWindow 3 | 4 | 5 | 6 | 0 7 | 0 8 | 400 9 | 300 10 | 11 | 12 | 13 | MainWindow 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /pictures/picture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-InteractiveButtons/07d20315723a060939bdbcc03e684526a2c4eefe/pictures/picture.gif -------------------------------------------------------------------------------- /pictures/picture2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-InteractiveButtons/07d20315723a060939bdbcc03e684526a2c4eefe/pictures/picture2.gif -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | resources/icons/point_menu.png 4 | 5 | 6 | -------------------------------------------------------------------------------- /resources/icons/point_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-InteractiveButtons/07d20315723a060939bdbcc03e684526a2c4eefe/resources/icons/point_menu.png --------------------------------------------------------------------------------