├── .gitignore ├── Qt-SlideShow.pro ├── README.md ├── images ├── img1.jpg ├── img2.jpg ├── img3.jpg ├── img4.jpg ├── img5.jpg └── screenshot.gif ├── interactive_buttons ├── interactivebuttonbase.cpp └── interactivebuttonbase.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── resources.qrc └── slide_show ├── sidehidelabel.cpp ├── sidehidelabel.h ├── slideshow.cpp └── slideshow.h /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | -------------------------------------------------------------------------------- /Qt-SlideShow.pro: -------------------------------------------------------------------------------- 1 | QT += core gui 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | # You can also make your code fail to compile if it uses deprecated APIs. 14 | # In order to do so, uncomment the following line. 15 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 | 18 | INCLUDEPATH += \ 19 | slide_show/ 20 | 21 | SOURCES += \ 22 | interactive_buttons/interactivebuttonbase.cpp \ 23 | main.cpp \ 24 | mainwindow.cpp \ 25 | slide_show/sidehidelabel.cpp \ 26 | slide_show/slideshow.cpp 27 | 28 | HEADERS += \ 29 | interactive_buttons/interactivebuttonbase.h \ 30 | mainwindow.h \ 31 | slide_show/sidehidelabel.h \ 32 | slide_show/slideshow.h 33 | 34 | FORMS += \ 35 | mainwindow.ui 36 | 37 | # Default rules for deployment. 38 | qnx: target.path = /tmp/$${TARGET}/bin 39 | else: unix:!android: target.path = /opt/$${TARGET}/bin 40 | !isEmpty(target.path): INSTALLS += target 41 | 42 | RESOURCES += \ 43 | resources.qrc 44 | 45 | DISTFILES += \ 46 | README.md \ 47 | images/screenshot.gif 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 轮播图 2 | === 3 | 4 | 一个懒得取名的只为了好看的轮播图 5 | 6 | 特点: 7 | 8 | - 左右轮播(废话) 9 | - 鼠标悬浮切换,无需点击 10 | - 自动定时轮播 11 | - 自动裁剪和缩放不同尺寸图片 12 | - 任意添加、插入、删除 13 | - 单击事件,支持索引和自定义文本 14 | - 界面美观,圆角、阴影、卡片内偏移、非线性并行动画、渐变切换等 15 | 16 | 17 | 18 | 与其他的轮播图不同,在图片切换的同时,每张图片内部还有一丝丝的“**漂移**”效果。除了带来灵活的视觉效果外,还使得左右缩略图露出的部分可以很好的**显示偏向中心的内容**。 19 | 20 | 另外,在两图交替的那一瞬间,采用了很微妙的**渐变消失**特效,从而**不会产生瞬间变换的闪屏感觉**,这是其它轮播图都未能做到的。 21 | 22 | 23 | 24 | 25 | ![预览](images/screenshot.gif) 26 | -------------------------------------------------------------------------------- /images/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-SlideShow/7941af1657709923a2ce71f838ff656c7ab58fb6/images/img1.jpg -------------------------------------------------------------------------------- /images/img2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-SlideShow/7941af1657709923a2ce71f838ff656c7ab58fb6/images/img2.jpg -------------------------------------------------------------------------------- /images/img3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-SlideShow/7941af1657709923a2ce71f838ff656c7ab58fb6/images/img3.jpg -------------------------------------------------------------------------------- /images/img4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-SlideShow/7941af1657709923a2ce71f838ff656c7ab58fb6/images/img4.jpg -------------------------------------------------------------------------------- /images/img5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-SlideShow/7941af1657709923a2ce71f838ff656c7ab58fb6/images/img5.jpg -------------------------------------------------------------------------------- /images/screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-SlideShow/7941af1657709923a2ce71f838ff656c7ab58fb6/images/screenshot.gif -------------------------------------------------------------------------------- /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.25), 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 | model = PaintModel::Text; 99 | else if (model == PaintModel::PixmapMask) 100 | { 101 | if (pixmap.isNull()) 102 | model = PaintModel::Text; 103 | else 104 | model = PaintModel::PixmapText; 105 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 106 | QFontMetrics fm(this->font()); 107 | icon_text_size = fm.lineSpacing(); 108 | } 109 | else if (model == PaintModel::Icon) 110 | { 111 | if (icon.isNull()) 112 | model = PaintModel::Text; 113 | else 114 | model = PaintModel::IconText; 115 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 116 | QFontMetrics fm(this->font()); 117 | icon_text_size = fm.lineSpacing(); 118 | } 119 | 120 | if (parent_enabled) 121 | QPushButton::setText(text); 122 | 123 | // 根据字体调整大小 124 | if (text_dynamic_size) 125 | { 126 | if (font_size <= 0) 127 | { 128 | QFontMetrics fm(font()); 129 | setMinimumSize(fm.horizontalAdvance(text)+fore_paddings.left+fore_paddings.right, fm.lineSpacing()+fore_paddings.top+fore_paddings.bottom); 130 | } 131 | else 132 | { 133 | QFont font; 134 | font.setPointSize(font_size); 135 | QFontMetrics fm(font); 136 | setMinimumSize(fm.horizontalAdvance(text)+fore_paddings.left+fore_paddings.right, fm.lineSpacing()+fore_paddings.top+fore_paddings.bottom); 137 | } 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 | model = PaintModel::Icon; 168 | else if (model == PaintModel::Text) 169 | { 170 | if (text.isEmpty()) 171 | model = PaintModel::Icon; 172 | else 173 | model = PaintModel::IconText; 174 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 175 | QFontMetrics fm(this->font()); 176 | icon_text_size = fm.lineSpacing(); 177 | } 178 | else if (model == PaintModel::PixmapMask) 179 | { 180 | pixmap = QPixmap(); 181 | model = PaintModel::Icon; 182 | } 183 | else if (model == PaintModel::PixmapText) 184 | { 185 | pixmap = QPixmap(); 186 | if (text.isEmpty()) 187 | model = PaintModel::Icon; 188 | else 189 | model = PaintModel::IconText; 190 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 191 | QFontMetrics fm(this->font()); 192 | icon_text_size = fm.lineSpacing(); 193 | } 194 | this->icon = icon; 195 | if (parent_enabled) 196 | QPushButton::setIcon(icon); 197 | update(); 198 | } 199 | 200 | /** 201 | * 设置 Pixmap 202 | * @param pixmap [description] 203 | */ 204 | void InteractiveButtonBase::setPixmap(QPixmap pixmap) 205 | { 206 | if (model == PaintModel::None) 207 | model = PaintModel::PixmapMask; 208 | else if (model == PaintModel::Text) 209 | { 210 | if (text.isEmpty()) 211 | model = PaintModel::PixmapMask; 212 | else 213 | model = PaintModel::PixmapText; 214 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 215 | QFontMetrics fm(this->font()); 216 | icon_text_size = fm.lineSpacing(); 217 | } 218 | else if (model == PaintModel::Icon) 219 | { 220 | icon = QIcon(); 221 | model = PaintModel::PixmapMask; 222 | } 223 | else if (model == PaintModel::IconText) 224 | { 225 | icon = QIcon(); 226 | if (text.isEmpty()) 227 | model = PaintModel::PixmapMask; 228 | else 229 | model = PaintModel::PixmapText; 230 | setAlign(Qt::AlignLeft | Qt::AlignVCenter); 231 | QFontMetrics fm(this->font()); 232 | icon_text_size = fm.lineSpacing(); 233 | } 234 | this->pixmap = getMaskPixmap(pixmap, isEnabled()?icon_color:getOpacityColor(icon_color)); 235 | if (parent_enabled) 236 | QPushButton::setIcon(QIcon(pixmap)); 237 | update(); 238 | } 239 | 240 | /** 241 | * 设置额外的图标,例如角标 242 | * @param pixmap 图标 243 | * @param align 对齐方式 244 | * @param size 图标尺寸 245 | */ 246 | void InteractiveButtonBase::setPaintAddin(QPixmap pixmap, Qt::Alignment align, QSize size) 247 | { 248 | QBitmap mask = pixmap.mask(); 249 | pixmap.fill(icon_color); 250 | pixmap.setMask(mask); 251 | paint_addin = PaintAddin(pixmap, align, size); 252 | update(); 253 | } 254 | 255 | /** 256 | * 设置子类功能是否开启 257 | * 如果关闭,则相当于默认的 QPushButton 258 | * @param e 开关 259 | */ 260 | void InteractiveButtonBase::setSelfEnabled(bool e) 261 | { 262 | self_enabled = e; 263 | } 264 | 265 | /** 266 | * 设置父类(QPushButton)功能是否开启 267 | * 如果开启,则绘制父类背景、父类前景 268 | * @param e 开关 269 | */ 270 | void InteractiveButtonBase::setParentEnabled(bool e) 271 | { 272 | parent_enabled = e; 273 | 274 | // 传递子类内容到父类去,避免子类关掉后不显示 275 | if (model == PaintModel::Text || model == PaintModel::IconText || model == PaintModel::PixmapText) 276 | QPushButton::setText(text); 277 | if (model == PaintModel::Icon || model == PaintModel::IconText) 278 | QPushButton::setIcon(icon); 279 | if (model == PaintModel::PixmapMask || model == PaintModel::PixmapText) 280 | QPushButton::setIcon(QIcon(pixmap)); 281 | } 282 | 283 | /** 284 | * 设置是否绘制前景图标/文字 285 | * 关闭后则只绘制背景 286 | * @param e 开启 287 | */ 288 | void InteractiveButtonBase::setForeEnabled(bool e) 289 | { 290 | fore_enabled = e; 291 | } 292 | 293 | /** 294 | * 设置鼠标悬浮背景渐变的动画时长 295 | * @param d 动画时长(毫秒) 296 | */ 297 | void InteractiveButtonBase::setHoverAniDuration(int d) 298 | { 299 | this->hover_bg_duration = d; 300 | // hover_progress = 0; // 重置hover效果 301 | } 302 | 303 | /** 304 | * 设置鼠标按下渐变效果的动画时长 305 | * @param d 动画时长(毫秒) 306 | */ 307 | void InteractiveButtonBase::setPressAniDuration(int d) 308 | { 309 | this->press_bg_duration = d; 310 | } 311 | 312 | /** 313 | * 设置单击效果的动画时长 314 | * @param d 动画时长(毫秒) 315 | */ 316 | void InteractiveButtonBase::setClickAniDuration(int d) 317 | { 318 | this->click_ani_duration = d; 319 | } 320 | 321 | /** 322 | * 设置水波纹动画时长 323 | * @param press 按住时时长(时长毫秒) 324 | * @param release 松开后速度(时长毫秒) 325 | * @param finish 渐变消失速度(时长毫秒) 326 | */ 327 | void InteractiveButtonBase::setWaterAniDuration(int press, int release, int finish) 328 | { 329 | this->water_press_duration = press; 330 | this->water_release_duration = release; 331 | this->water_finish_duration = finish; 332 | } 333 | 334 | /** 335 | * 各种状态改变 336 | * 主要是监控 可用 状态,不可用时设置为半透明 337 | */ 338 | void InteractiveButtonBase::changeEvent(QEvent *event) 339 | { 340 | QPushButton::changeEvent(event); 341 | 342 | if (event->type() == QEvent::EnabledChange && model == PixmapMask) // 可用状态改变了 343 | { 344 | if (isEnabled()) // 恢复可用:透明度变回去 345 | { 346 | QColor color = icon_color; 347 | color.setAlpha(color.alpha() * 2); 348 | setIconColor(color); 349 | } 350 | else // 变成不可用:透明度减半 351 | { 352 | QColor color = icon_color; 353 | color.setAlpha(color.alpha() / 2); 354 | setIconColor(color); 355 | } 356 | } 357 | } 358 | 359 | /** 360 | * 设置水波纹动画是否开启 361 | * 关闭时,将使用渐变动画 362 | * @param enable 开关 363 | */ 364 | void InteractiveButtonBase::setWaterRipple(bool enable) 365 | { 366 | if (water_animation == enable) return ; 367 | water_animation = enable; 368 | } 369 | 370 | /** 371 | * 设置抖动效果是否开启 372 | * 鼠标拖拽移动的距离越长,抖动距离越长、次数越多 373 | * @param enable 开关 374 | */ 375 | void InteractiveButtonBase::setJitterAni(bool enable) 376 | { 377 | jitter_animation = enable; 378 | } 379 | 380 | /** 381 | * 设置是否使用统一图标绘制区域 382 | * 监听图标尺寸大小变化、中心点偏移,计算新的中心坐标位置 383 | * @param enable 开关 384 | */ 385 | void InteractiveButtonBase::setUnifyGeomerey(bool enable) 386 | { 387 | unified_geometry = enable; 388 | _l = _t = 0; _w = size().width(); _h = size().height(); 389 | } 390 | 391 | /** 392 | * 设置背景颜色 393 | * @param bg 背景颜色 394 | */ 395 | void InteractiveButtonBase::setBgColor(QColor bg) 396 | { 397 | setNormalColor(bg); 398 | update(); 399 | } 400 | 401 | /** 402 | * 设置事件背景颜色 403 | * @param hover 鼠标悬浮时的背景颜色 404 | * @param press 鼠标按下时的背景颜色 405 | */ 406 | void InteractiveButtonBase::setBgColor(QColor hover, QColor press) 407 | { 408 | if (hover != Qt::black) 409 | setHoverColor(hover); 410 | if (press != Qt::black) 411 | setPressColor(press); 412 | update(); 413 | } 414 | 415 | /** 416 | * 设置按钮背景颜色 417 | * @param color 背景颜色 418 | */ 419 | void InteractiveButtonBase::setNormalColor(QColor color) 420 | { 421 | normal_bg = color; 422 | } 423 | 424 | /** 425 | * 设置边框线条颜色 426 | * @param color 边框颜色 427 | */ 428 | void InteractiveButtonBase::setBorderColor(QColor color) 429 | { 430 | border_bg = color; 431 | } 432 | 433 | /** 434 | * 设置鼠标悬浮时的背景颜色 435 | * @param color 背景颜色 436 | */ 437 | void InteractiveButtonBase::setHoverColor(QColor color) 438 | { 439 | hover_bg = color; 440 | } 441 | 442 | /** 443 | * 设置鼠标按住时的背景颜色 444 | * @param color 背景颜色 445 | */ 446 | void InteractiveButtonBase::setPressColor(QColor color) 447 | { 448 | press_bg = color; 449 | } 450 | 451 | /** 452 | * 设置图标颜色(仅针对可变色的 pixmap 图标) 453 | * @param color 图标颜色 454 | */ 455 | void InteractiveButtonBase::setIconColor(QColor color) 456 | { 457 | icon_color = color; 458 | 459 | // 绘制图标(如果有) 460 | if (model == PaintModel::PixmapMask || model == PaintModel::PixmapText) 461 | { 462 | pixmap = getMaskPixmap(pixmap, isEnabled()?icon_color:getOpacityColor(icon_color)); 463 | } 464 | 465 | // 绘制额外角标(如果有的话) 466 | if (paint_addin.enable) 467 | { 468 | paint_addin.pixmap = getMaskPixmap(paint_addin.pixmap, isEnabled()?icon_color:getOpacityColor(icon_color)); 469 | } 470 | 471 | update(); 472 | } 473 | 474 | /** 475 | * 设置前景文字颜色 476 | * @param color 文字颜色 477 | */ 478 | void InteractiveButtonBase::setTextColor(QColor color) 479 | { 480 | text_color = color; 481 | update(); 482 | } 483 | 484 | /** 485 | * 设置获取焦点时的背景颜色(默认关闭) 486 | * @param color 背景颜色 487 | */ 488 | void InteractiveButtonBase::setFocusBg(QColor color) 489 | { 490 | setFocusPolicy(Qt::StrongFocus); 491 | focus_bg = color; 492 | } 493 | 494 | /** 495 | * 设置获取焦点时的边框颜色(默认关闭) 496 | * @param color 边框颜色 497 | */ 498 | void InteractiveButtonBase::setFocusBorder(QColor color) 499 | { 500 | setFocusPolicy(Qt::StrongFocus); 501 | focus_border = color; 502 | } 503 | 504 | /** 505 | * 设置文字大小(PointSize,覆盖 font() 字体大小) 506 | * @param f 文字大小 507 | */ 508 | void InteractiveButtonBase::setFontSize(int f) 509 | { 510 | if (!font_size) // 第一次设置字体大小,直接设置 511 | { 512 | font_size = f; 513 | QFont font(this->font()); 514 | font.setPointSize(f); 515 | setFont(font); 516 | update(); 517 | } 518 | else // 改变字体大小,使用字体缩放动画 519 | { 520 | QPropertyAnimation* ani = new QPropertyAnimation(this, "font_size"); 521 | ani->setStartValue(font_size); 522 | ani->setEndValue(f); 523 | ani->setDuration(click_ani_duration); 524 | connect(ani, &QPropertyAnimation::finished, [=]{ 525 | QFontMetrics fm(this->font()); 526 | icon_text_size = fm.lineSpacing(); 527 | ani->deleteLater(); 528 | }); 529 | ani->start(); 530 | } 531 | // 修改字体大小时调整按钮的最小尺寸,避免文字显示不全 532 | if (text_dynamic_size) 533 | { 534 | QFont font; 535 | font.setPointSize(f); 536 | QFontMetrics fms(font); 537 | setMinimumSize(fms.horizontalAdvance(text)+fore_paddings.left+fore_paddings.right, fms.lineSpacing()+fore_paddings.top+fore_paddings.bottom); 538 | } 539 | if (model != PaintModel::Text) 540 | { 541 | QFontMetrics fm(this->font()); 542 | icon_text_size = fm.lineSpacing(); 543 | } 544 | } 545 | 546 | /** 547 | * 获取字体大小 548 | * 用来作为字体动画的属性参数 549 | * @return 临时字体大小 550 | */ 551 | int InteractiveButtonBase::getFontSizeT() 552 | { 553 | return font_size; 554 | } 555 | 556 | /** 557 | * 设置动画中的临时字体大小 558 | * 用来作为字体动画的属性参数 559 | * @param f 临时字体大小 560 | */ 561 | void InteractiveButtonBase::setFontSizeT(int f) 562 | { 563 | this->font_size = f; 564 | QFont font(this->font()); 565 | font.setPointSize(f); 566 | setFont(font); 567 | update(); 568 | } 569 | 570 | /** 571 | * 如果点击失去焦点的话,即使鼠标移到上面,也不会出现背景 572 | * 可以用这个方法继续保持悬浮状态 573 | */ 574 | void InteractiveButtonBase::setHover() 575 | { 576 | if (!hovering && inArea(mapFromGlobal(QCursor::pos()))) 577 | InteractiveButtonBase::enterEvent(new QEvent(QEvent::Type::None)); 578 | } 579 | 580 | /** 581 | * 设置对齐方式 582 | * @param a 对齐方式 583 | */ 584 | void InteractiveButtonBase::setAlign(Qt::Alignment a) 585 | { 586 | align = a; 587 | update(); 588 | } 589 | 590 | /** 591 | * 设置四个角的半径 592 | * @param r 半径 593 | */ 594 | void InteractiveButtonBase::setRadius(int r) 595 | { 596 | radius_x = radius_y = r; 597 | } 598 | 599 | /** 600 | * 分开设置 X、Y 的半径 601 | * @param rx X半径 602 | * @param ry Y半径 603 | */ 604 | void InteractiveButtonBase::setRadius(int rx, int ry) 605 | { 606 | radius_x = rx; 607 | radius_y = ry; 608 | } 609 | 610 | /** 611 | * 设置边框线条的粗细 612 | * @param x 线条粗细 613 | */ 614 | void InteractiveButtonBase::setBorderWidth(int x) 615 | { 616 | border_width = x; 617 | } 618 | 619 | /** 620 | * 设置不可用情况(默认为假) 621 | * 区别于 setEnabled(bool),两个相反的,并不是覆盖方法 622 | * @param dis 不可用 623 | */ 624 | void InteractiveButtonBase::setDisabled(bool dis) 625 | { 626 | if (dis == !isEnabled()) // 相同的 627 | return ; 628 | 629 | setEnabled(!dis); 630 | 631 | if (parentWidget()!=nullptr) 632 | { 633 | setAttribute(Qt::WA_TransparentForMouseEvents, dis); // 点击穿透 634 | } 635 | 636 | if (model == PixmapMask || model == PixmapText) 637 | { 638 | pixmap = getMaskPixmap(pixmap, dis?getOpacityColor(icon_color):icon_color); 639 | } 640 | 641 | update(); // 修改透明度 642 | } 643 | 644 | /** 645 | * 设置前景和四条边的 paddings 646 | * @param l 左边空白 647 | * @param r 右边空白 648 | * @param t 顶边空白 649 | * @param b 底边空白 650 | */ 651 | void InteractiveButtonBase::setPaddings(int l, int r, int t, int b) 652 | { 653 | fore_paddings.left = l; 654 | fore_paddings.right = r; 655 | fore_paddings.top = t; 656 | fore_paddings.bottom = b; 657 | setFixedForeSize(); 658 | } 659 | 660 | /** 661 | * 统一设置方向的 paddings 662 | * @param h 横向 663 | * @param v 纵向 664 | */ 665 | void InteractiveButtonBase::setPaddings(int h, int v) 666 | { 667 | fore_paddings.left = fore_paddings.right = (h+1) / 2; 668 | fore_paddings.top = fore_paddings.bottom = (v+1) / 2; 669 | setFixedForeSize(); 670 | } 671 | 672 | /** 673 | * 统一设置前景和四条边的 paddings 674 | * @param x 一样大小的四边留白 675 | */ 676 | void InteractiveButtonBase::setPaddings(int x) 677 | { 678 | fore_paddings.left = x; 679 | fore_paddings.right = x; 680 | fore_paddings.top = x; 681 | fore_paddings.bottom = x; 682 | setFixedForeSize(); 683 | } 684 | 685 | /** 686 | * 设置Icon模式旁边空多少 687 | * @param x 0~1.0,越大越空 688 | */ 689 | void InteractiveButtonBase::setIconPaddingProper(double x) 690 | { 691 | icon_padding_proper = x; 692 | int short_side = min(geometry().width(), geometry().height()); // 短边 693 | // 非固定的情况,尺寸大小变了之后所有 padding 都要变 694 | int padding = short_side*icon_padding_proper; //static_cast(short_side * (1 - GOLDEN_RATIO) / 2); 695 | fore_paddings.left = fore_paddings.top = fore_paddings.right = fore_paddings.bottom = padding; 696 | update(); 697 | } 698 | 699 | /** 700 | * 设置字体大小时是否同步修改按钮的最小尺寸(避免按钮显示不全) 701 | * @param d 开关 702 | */ 703 | void InteractiveButtonBase::setTextDynamicSize(bool d) 704 | { 705 | text_dynamic_size = d; 706 | } 707 | 708 | /** 709 | * 见 setFixedForePos(bool f) 710 | */ 711 | void InteractiveButtonBase::setFixedTextPos(bool f) 712 | { 713 | fixed_fore_pos = f; 714 | } 715 | 716 | /** 717 | * 设置前景是否固定,而不移动 718 | * 将去除鼠标移入靠近、抖动效果,统一图标区域大小不变 719 | * 只包括:鼠标进入/点击,均表现为缩放效果(默认) 720 | * 不影响任何其他功能 721 | * @param f [description] 722 | */ 723 | void InteractiveButtonBase::setFixedForePos(bool f) 724 | { 725 | fixed_fore_pos = f; 726 | } 727 | 728 | /** 729 | * 固定按钮为适当尺寸,并且固定四周留白 730 | * 前景应为文字/图标对应尺寸的最小尺寸 731 | * @param f 是否固定前景 732 | * @param addin 留白的像素大小 733 | */ 734 | void InteractiveButtonBase::setFixedForeSize(bool f, int addin) 735 | { 736 | fixed_fore_size = f; 737 | 738 | if (!f) return ; 739 | if (model == PaintModel::Text || model == PaintModel::IconText || model == PaintModel::PixmapText) 740 | { 741 | QFont font = this->font(); 742 | if (font_size > 0) 743 | font.setPointSize(font_size); 744 | QFontMetrics fm(font); 745 | setMinimumSize( 746 | fm.horizontalAdvance(text)+fore_paddings.left+fore_paddings.right+addin, 747 | fm.lineSpacing()+fore_paddings.top+fore_paddings.bottom+addin 748 | ); 749 | } 750 | else if (model == PaintModel::Icon || model == PaintModel::PixmapMask) 751 | { 752 | int size = height(); 753 | setMinimumSize(size+addin, size+addin); 754 | } 755 | } 756 | 757 | void InteractiveButtonBase::setSquareSize() 758 | { 759 | setFixedWidth(height()); 760 | setMinimumWidth(height()); 761 | setMaximumWidth(height()); 762 | } 763 | 764 | /** 765 | * 设置鼠标单击松开后是否当做移开 766 | * 避免菜单、弹窗出现后,由于鼠标仍然留在按钮上面,导致依旧显示 hover 背景 767 | * @param l 开关 768 | */ 769 | void InteractiveButtonBase::setLeaveAfterClick(bool l) 770 | { 771 | leave_after_clicked = l; 772 | } 773 | 774 | /** 775 | * 响应双击事件 776 | * 注意:会先触发单击事件、再触发双击事件(其实就是懒得做) 777 | * 建议在 QListWidget 等地方使用! 778 | * @param e 开关 779 | */ 780 | void InteractiveButtonBase::setDoubleClicked(bool e) 781 | { 782 | double_clicked = e; 783 | 784 | if (double_timer == nullptr) 785 | { 786 | double_timer = new QTimer(this); 787 | double_timer->setInterval(DOUBLE_PRESS_INTERVAL); 788 | connect(double_timer, &QTimer::timeout, [=]{ 789 | double_timer->stop(); 790 | emit clicked(); // 手动触发单击事件 791 | }); 792 | } 793 | } 794 | 795 | /** 796 | * 动画时是否自动设置文字的颜色 797 | */ 798 | void InteractiveButtonBase::setAutoTextColor(bool a) 799 | { 800 | this->auto_text_color = a; 801 | } 802 | 803 | /** 804 | * 一开始没有聚焦时,假装获取焦点 805 | * 通过信号槽使其他控件(例如QLineEdit)按下enter键触发此按钮事件 806 | * 直到触发了焦点改变事件,此控件失去焦点(需要手动改变) 807 | */ 808 | void InteractiveButtonBase::setPretendFocus(bool f) 809 | { 810 | focusing = f; 811 | update(); 812 | } 813 | 814 | /** 815 | * 如果按钮被做成一个组合,在显示的时候开启动画 816 | * 一开始鼠标下的按钮一直在hover状态,移开也不会变 817 | * 开启后临时屏蔽,记得在动画结束后关闭 818 | */ 819 | void InteractiveButtonBase::setBlockHover(bool b) 820 | { 821 | _block_hover = b; 822 | if (b && hovering) 823 | leaveEvent(nullptr); 824 | } 825 | 826 | /** 827 | * 是否开启出现动画 828 | * 鼠标进入按钮区域,前景图标从对面方向缩放出现 829 | * @param enable 开关 830 | */ 831 | void InteractiveButtonBase::setShowAni(bool enable) 832 | { 833 | show_animation = enable; 834 | 835 | if (!show_animation) // 关闭隐藏前景 836 | { 837 | show_foreground = true; 838 | } 839 | else if (show_animation) // 开启隐藏前景 840 | { 841 | if (!hovering && !pressing) // 应该是隐藏状态 842 | { 843 | show_ani_appearing = show_ani_disappearing = show_foreground = false; 844 | show_ani_progress = 0; 845 | } 846 | else // 应该是显示状态 847 | { 848 | show_foreground = true; 849 | show_ani_appearing = show_ani_disappearing = false; 850 | show_ani_progress = 100; 851 | } 852 | } 853 | } 854 | 855 | /** 856 | * 按钮前景出现动画 857 | * 从中心点出现的缩放动画 858 | */ 859 | void InteractiveButtonBase::showForeground() 860 | { 861 | if (!show_animation) return ; 862 | waters.clear(); 863 | if (!anchor_timer->isActive()) 864 | anchor_timer->start(); 865 | if (show_ani_disappearing) 866 | show_ani_disappearing = false; 867 | show_ani_appearing = true; 868 | show_timestamp = getTimestamp(); 869 | show_foreground = true; 870 | show_ani_point = QPoint(0,0); 871 | } 872 | 873 | /** 874 | * 按钮前景出现动画2 875 | * 指定方向(笛卡尔坐标),从反方向至中心点 876 | * @param point 最开始出现的方向(大小不影响,只按 x、y 比例来) 877 | */ 878 | void InteractiveButtonBase::showForeground2(QPoint point) 879 | { 880 | showForeground(); 881 | if (point == QPoint(0,0)) 882 | point = mapFromGlobal(QCursor::pos()) - QPoint(size().width()/2, size().height()/2); // 相对于按钮中心 883 | show_ani_point = point; 884 | 885 | if (unified_geometry) // 统一出现动画 886 | updateUnifiedGeometry(); 887 | } 888 | 889 | /** 890 | * 隐藏前景 891 | * 为下一次的出现动画做准备 892 | */ 893 | void InteractiveButtonBase::hideForeground() 894 | { 895 | if (!show_animation) return ; 896 | if (!anchor_timer->isActive()) 897 | anchor_timer->start(); 898 | if (show_ani_appearing) 899 | show_ani_appearing = false; 900 | show_ani_disappearing = true; 901 | hide_timestamp = getTimestamp(); 902 | } 903 | 904 | /** 905 | * 延迟出现前景 906 | * 适用于多个按钮连续出现的一套效果 907 | * @param time 延迟时长(毫秒) 908 | * @param point 出现方向 909 | */ 910 | void InteractiveButtonBase::delayShowed(int time, QPoint point) 911 | { 912 | setShowAni(true); 913 | QTimer::singleShot(time, [=]{ 914 | showForeground2(point); 915 | connect(this, &InteractiveButtonBase::showAniFinished, [=]{ 916 | setShowAni(false); 917 | disconnect(this, SIGNAL(showAniFinished()), nullptr, nullptr); 918 | }); 919 | }); 920 | } 921 | 922 | /** 923 | * 获取文字 924 | */ 925 | QString InteractiveButtonBase::getText() 926 | { 927 | return text; 928 | } 929 | 930 | /** 931 | * 设置菜单 932 | * 并解决菜单无法监听到 release 的问题 933 | * @param menu 菜单对象 934 | */ 935 | void InteractiveButtonBase::setMenu(QMenu *menu) 936 | { 937 | // 默认设置了不获取焦点事件,所以如果设置了菜单的话,就不会有Release事件,水波纹动画会一直飘荡 938 | // 在 focusOut 事件中,模拟了 release 事件, 939 | this->setFocusPolicy(Qt::FocusPolicy::ClickFocus); 940 | 941 | QPushButton::setMenu(menu); 942 | } 943 | 944 | /** 945 | * 设置状态 946 | * 一个用来作为开关效果的属性 947 | * @param s 状态 948 | */ 949 | void InteractiveButtonBase::setState(bool s) 950 | { 951 | _state = s; 952 | update(); 953 | } 954 | 955 | /** 956 | * 获取状态 957 | * @return 状态 958 | */ 959 | bool InteractiveButtonBase::getState() 960 | { 961 | return _state; 962 | } 963 | 964 | /** 965 | * 模拟按下开关的效果,并改变状态 966 | * 如果不使用状态,则出现点击动画 967 | * @param s 目标状态(默认为false) 968 | * @param a 鼠标在区域内则点击无效(恐怕再次点击) 969 | */ 970 | void InteractiveButtonBase::simulateStatePress(bool s, bool a) 971 | { 972 | if (getState() == s) 973 | return ; 974 | 975 | // 鼠标悬浮在上方,有两种情况: 976 | // 1、点击按钮后触发,重复了 977 | // 2、需要假装触发,例如 Popup 类型,尽管悬浮在上面,但是无法点击到 978 | if (a && inArea(mapFromGlobal(QCursor::pos()))) // 点击当前按钮,不需要再模拟了 979 | return ; 980 | 981 | mousePressEvent(new QMouseEvent(QMouseEvent::Type::None, QPoint(size().width()/2,size().height()/2), Qt::LeftButton, Qt::NoButton, Qt::NoModifier)); 982 | 983 | mouseReleaseEvent(new QMouseEvent(QMouseEvent::Type::None, QPoint(size().width()/2,size().height()/2), Qt::LeftButton, Qt::NoButton, Qt::NoModifier)); 984 | 985 | // if (!inArea(mapFromGlobal(QCursor::pos()))) // 针对模拟release 后面 // 必定成立 986 | hovering = false; 987 | } 988 | 989 | /** 990 | * 模拟鼠标悬浮的效果 991 | * 适用于键盘操作时,模拟鼠标hover状态 992 | * 用 discardHoverPress 取消状态 993 | */ 994 | void InteractiveButtonBase::simulateHover() 995 | { 996 | if (!hovering) 997 | { 998 | if (_block_hover) 999 | setBlockHover(false); // 可能已经临时屏蔽掉鼠标 enter 事件,强制hover 1000 | enterEvent(nullptr); 1001 | } 1002 | } 1003 | 1004 | /** 1005 | * 强制丢弃hover、press状态 1006 | * 适用于悬浮/点击后,弹出模态浮窗 1007 | * 浮窗关闭后调用此方法 1008 | * @param force 如果鼠标仍在此按钮内,是否强制取消hover/press状态 1009 | */ 1010 | void InteractiveButtonBase::discardHoverPress(bool force) 1011 | { 1012 | if (!force && inArea(mapFromGlobal(QCursor::pos()))) // 鼠标还在这范围内 1013 | return ; 1014 | 1015 | if (hovering) 1016 | { 1017 | leaveEvent(nullptr); 1018 | } 1019 | 1020 | if (pressing) 1021 | { 1022 | mouseReleaseEvent(new QMouseEvent(QMouseEvent::Type::None, QPoint(size().width()/2,size().height()/2), Qt::LeftButton, Qt::NoButton, Qt::NoModifier)); 1023 | } 1024 | } 1025 | 1026 | /** 1027 | * 鼠标移入事件,触发 hover 时间戳 1028 | */ 1029 | void InteractiveButtonBase::enterEvent(QEvent *event) 1030 | { 1031 | if (_block_hover) // 临时屏蔽hover事件 1032 | { 1033 | if (event) 1034 | event->accept(); 1035 | return ; 1036 | } 1037 | 1038 | if (!anchor_timer->isActive()) 1039 | { 1040 | anchor_timer->start(); 1041 | } 1042 | hovering = true; 1043 | hover_timestamp = getTimestamp(); 1044 | leave_timestamp = 0; 1045 | if (mouse_pos == QPoint(-1,-1)) 1046 | mouse_pos = mapFromGlobal(QCursor::pos()); 1047 | emit signalMouseEnter(); 1048 | 1049 | return QPushButton::enterEvent(event); 1050 | } 1051 | 1052 | /** 1053 | * 鼠标移开事件,触发 leave 时间戳 1054 | */ 1055 | void InteractiveButtonBase::leaveEvent(QEvent *event) 1056 | { 1057 | hovering = false; 1058 | if (!pressing) 1059 | mouse_pos = QPoint(geometry().width()/2, geometry().height()/2); 1060 | emit signalMouseLeave(); 1061 | 1062 | return QPushButton::leaveEvent(event); 1063 | } 1064 | 1065 | /** 1066 | * 鼠标按下事件,触发 press 时间戳 1067 | * 添加水波纹动画 waters 队列 1068 | */ 1069 | void InteractiveButtonBase::mousePressEvent(QMouseEvent *event) 1070 | { 1071 | mouse_pos = event->pos(); 1072 | 1073 | if (event->button() == Qt::LeftButton) 1074 | { 1075 | if (!hovering) 1076 | InteractiveButtonBase::enterEvent(new QEvent(QEvent::Type::None)); 1077 | 1078 | pressing = true; 1079 | press_pos = mouse_pos; 1080 | // 判断双击事件 1081 | if (double_clicked) 1082 | { 1083 | qint64 last_press_timestamp = press_timestamp; 1084 | press_timestamp = getTimestamp(); 1085 | if (release_timestamp+DOUBLE_PRESS_INTERVAL>=press_timestamp 1086 | && last_press_timestamp+SINGLE_PRESS_INTERVAL>release_timestamp 1087 | && release_pos==press_pos) // 是双击(判断两次单击的间隔) 1088 | { 1089 | double_prevent = true; // 阻止本次的release识别为单击 1090 | press_timestamp = 0; // 避免很可能出现的三击、四击... 1091 | double_timer->stop(); // 取消延迟一小会儿的单击信号 1092 | emit doubleClicked(); 1093 | return ; 1094 | } 1095 | else 1096 | { 1097 | double_prevent = false; // 避免有额外的 bug 1098 | } 1099 | } 1100 | else 1101 | { 1102 | press_timestamp = getTimestamp(); 1103 | } 1104 | 1105 | if (water_animation) 1106 | { 1107 | if (waters.size() && waters.last().release_timestamp == 0) // 避免两个按键同时按下 1108 | waters.last().release_timestamp = getTimestamp(); 1109 | waters << Water(press_pos, press_timestamp); 1110 | } 1111 | else // 透明渐变 1112 | { 1113 | if (press_progress < press_start) 1114 | press_progress = press_start; // 直接设置为按下效果初始值(避免按下反应慢) 1115 | } 1116 | } 1117 | mouse_press_event = event; 1118 | emit signalMousePress(event); 1119 | 1120 | return QPushButton::mousePressEvent(event); 1121 | } 1122 | 1123 | /** 1124 | * 鼠标松开事件,触发 release 时间戳 1125 | * 添加抖动动画 jitters 队列 1126 | */ 1127 | void InteractiveButtonBase::mouseReleaseEvent(QMouseEvent* event) 1128 | { 1129 | if (pressing && event->button() == Qt::LeftButton) 1130 | { 1131 | if (!inArea(event->pos()) || leave_after_clicked) 1132 | { 1133 | hovering = false; 1134 | } 1135 | pressing = false; 1136 | release_pos = event->pos(); 1137 | release_timestamp = getTimestamp(); 1138 | 1139 | // 添加抖动效果 1140 | if (jitter_animation) 1141 | { 1142 | setJitter(); 1143 | } 1144 | 1145 | if (water_animation && waters.size()) 1146 | { 1147 | waters.last().release_timestamp = release_timestamp; 1148 | } 1149 | 1150 | if (double_clicked) 1151 | { 1152 | if (double_prevent) // 双击的当次release,不参与单击计算 1153 | { 1154 | double_prevent = false; 1155 | return ; 1156 | } 1157 | 1158 | // 应该不是双击的操作 1159 | if (release_pos != press_pos || release_timestamp - press_timestamp >= SINGLE_PRESS_INTERVAL) 1160 | { 1161 | 1162 | } 1163 | else // 可能是双击,准备 1164 | { 1165 | double_timer->start(); 1166 | return ; // 禁止单击事件 1167 | } 1168 | } 1169 | } 1170 | else if (leave_after_clicked && !pressing && double_clicked && double_prevent) // 双击,失去焦点了,pressing 丢失 1171 | { 1172 | return ; 1173 | } 1174 | else if (event->button() == Qt::RightButton && event->buttons() == Qt::NoButton) 1175 | { 1176 | if ((release_pos - press_pos).manhattanLength() < QApplication::startDragDistance()) 1177 | emit rightClicked(); 1178 | } 1179 | mouse_release_event = event; 1180 | emit signalMouseRelease(event); 1181 | 1182 | return QPushButton::mouseReleaseEvent(event); 1183 | } 1184 | 1185 | /** 1186 | * 鼠标移动事件 1187 | */ 1188 | void InteractiveButtonBase::mouseMoveEvent(QMouseEvent *event) 1189 | { 1190 | if (_block_hover) // 临时屏蔽hover事件 1191 | { 1192 | if (event) 1193 | event->accept(); 1194 | return ; 1195 | } 1196 | if (hovering == false) // 失去焦点又回来了 1197 | { 1198 | enterEvent(nullptr); 1199 | } 1200 | mouse_pos = mapFromGlobal(QCursor::pos()); 1201 | 1202 | return QPushButton::mouseMoveEvent(event); 1203 | } 1204 | 1205 | /** 1206 | * 尺寸大小改变事件 1207 | * 同步调整和尺寸有关的所有属性 1208 | */ 1209 | void InteractiveButtonBase::resizeEvent(QResizeEvent *event) 1210 | { 1211 | if (!pressing && !hovering) 1212 | { 1213 | mouse_pos = QPoint(geometry().width()/2, geometry().height()/2); 1214 | anchor_pos = mouse_pos; 1215 | } 1216 | water_radius = static_cast(max(geometry().width(), geometry().height()) * 1.42); // 长边 1217 | // 非固定的情况,尺寸大小变了之后所有 padding 都要变 1218 | if (model == PaintModel::Icon || model == PaintModel::PixmapMask) 1219 | { 1220 | int short_side = min(geometry().width(), geometry().height()); // 短边 1221 | int padding = short_side*icon_padding_proper; //static_cast(short_side * (1 - GOLDEN_RATIO) / 2); 1222 | fore_paddings.left = fore_paddings.top = fore_paddings.right = fore_paddings.bottom = padding; 1223 | } 1224 | _l = _t = 0; _w = size().width(); _h = size().height(); 1225 | 1226 | return QPushButton::resizeEvent(event); 1227 | } 1228 | 1229 | /** 1230 | * 获得焦点事件 1231 | * 已经取消按钮获取焦点,focusIn和focusOut事件都不会触发 1232 | */ 1233 | void InteractiveButtonBase::focusInEvent(QFocusEvent *event) 1234 | { 1235 | if (!hovering && inArea(mapFromGlobal(QCursor::pos()))) 1236 | InteractiveButtonBase::enterEvent(new QEvent(QEvent::Type::None)); 1237 | 1238 | focusing = true; 1239 | emit signalFocusIn(); 1240 | 1241 | return QPushButton::focusInEvent(event); 1242 | } 1243 | 1244 | /** 1245 | * 失去焦点事件 1246 | * 兼容按住时突然失去焦点(例如弹出菜单、被其他窗口抢走了) 1247 | */ 1248 | void InteractiveButtonBase::focusOutEvent(QFocusEvent *event) 1249 | { 1250 | if (hovering) 1251 | { 1252 | hovering = false; 1253 | } 1254 | if (pressing) // 鼠标一直按住,可能在click事件中移动了焦点 1255 | { 1256 | pressing = false; 1257 | release_pos = mapFromGlobal(QCursor::pos()); 1258 | release_timestamp = getTimestamp(); 1259 | 1260 | if (water_animation && waters.size()) 1261 | { 1262 | waters.last().release_timestamp = release_timestamp; 1263 | } 1264 | } 1265 | 1266 | focusing = false; 1267 | emit signalFocusOut(); 1268 | 1269 | return QPushButton::focusOutEvent(event); 1270 | } 1271 | 1272 | /** 1273 | * 重绘事件 1274 | * 绘制所有内容:背景、动画、前景、角标 1275 | */ 1276 | void InteractiveButtonBase::paintEvent(QPaintEvent* event) 1277 | { 1278 | if (parent_enabled) // 绘制父类(以便使用父类的QSS和各项属性) 1279 | QPushButton::paintEvent(event); 1280 | if (!self_enabled) // 不绘制自己 1281 | return ; 1282 | QPainter painter(this); 1283 | 1284 | // ==== 绘制背景 ==== 1285 | QPainterPath path_back = getBgPainterPath(); 1286 | painter.setRenderHint(QPainter::Antialiasing,true); 1287 | 1288 | if (normal_bg.alpha() != 0) // 默认背景 1289 | { 1290 | painter.fillPath(path_back, isEnabled()?normal_bg:getOpacityColor(normal_bg)); 1291 | } 1292 | if (focusing && focus_bg.alpha() != 0) // 焦点背景 1293 | { 1294 | painter.fillPath(path_back, focus_bg); 1295 | } 1296 | 1297 | if ((border_bg.alpha() != 0 || (focusing && focus_border.alpha() != 0)) && border_width > 0) 1298 | { 1299 | painter.save(); 1300 | QPen pen; 1301 | pen.setColor((focusing && focus_border.alpha()) ? focus_border : border_bg); 1302 | pen.setWidth(border_width); 1303 | painter.setPen(pen); 1304 | painter.drawPath(path_back); 1305 | painter.restore(); 1306 | } 1307 | 1308 | if (hover_progress) // 悬浮背景 1309 | { 1310 | painter.fillPath(path_back, getOpacityColor(hover_bg, hover_progress / 100.0)); 1311 | } 1312 | 1313 | if (press_progress && !water_animation) // 按下渐变淡化消失 1314 | { 1315 | painter.fillPath(path_back, getOpacityColor(press_bg, press_progress/100.0)); 1316 | } 1317 | else if (water_animation && waters.size()) // 水波纹,且至少有一个水波纹 1318 | { 1319 | paintWaterRipple(painter); 1320 | } 1321 | 1322 | // ==== 绘制前景 ==== 1323 | if (fore_enabled/*针对按钮设置*/ && show_foreground/*针对动画设置*/) 1324 | { 1325 | painter.setPen(isEnabled()?icon_color:getOpacityColor(icon_color)); 1326 | 1327 | // 绘制额外内容(可能被前景覆盖) 1328 | if (paint_addin.enable) 1329 | { 1330 | int l = fore_paddings.left, t = fore_paddings.top, r = size().width()-fore_paddings.right, b = size().height()-fore_paddings.bottom; 1331 | int small_edge = min(size().height(), size().width()); 1332 | int pw = paint_addin.size.width() ? paint_addin.size.width() : small_edge-fore_paddings.left-fore_paddings.right; 1333 | int ph = paint_addin.size.height() ? paint_addin.size.height() : small_edge-fore_paddings.top-fore_paddings.bottom; 1334 | if (paint_addin.align & Qt::AlignLeft) 1335 | r = l + pw; 1336 | else if (paint_addin.align & Qt::AlignRight) 1337 | l = r - pw; 1338 | else if (paint_addin.align & Qt::AlignHCenter) 1339 | { 1340 | l = size().width()/2-pw/2; 1341 | r = l+pw; 1342 | } 1343 | if (paint_addin.align & Qt::AlignTop) 1344 | b = t + ph; 1345 | else if (paint_addin.align & Qt::AlignBottom) 1346 | t = b - ph; 1347 | else if (paint_addin.align & Qt::AlignVCenter) 1348 | { 1349 | t = size().height()/2-ph/2; 1350 | b = t+ph; 1351 | } 1352 | painter.drawPixmap(QRect(l,t,r-l,b-t), paint_addin.pixmap); 1353 | } 1354 | 1355 | QRect& rect = paint_rect; 1356 | rect = QRect(fore_paddings.left+(fixed_fore_pos?0:offset_pos.x()), fore_paddings.top+(fixed_fore_pos?0:offset_pos.y()), // 原来的位置,不包含点击、出现效果 1357 | (size().width()-fore_paddings.left-fore_paddings.right), 1358 | size().height()-fore_paddings.top-fore_paddings.bottom); 1359 | 1360 | // 抖动出现动画 1361 | if ((show_ani_appearing || show_ani_disappearing) && show_ani_point != QPoint( 0, 0 ) && ! fixed_fore_pos) 1362 | { 1363 | //int w = size().width(), h = size().height(); 1364 | int pro = getSpringBackProgress(show_ani_progress, 50); 1365 | 1366 | // show_ani_point 是鼠标进入的点,那么起始方向应该是相反的 1367 | int x = show_ani_point.x(), y = show_ani_point.y(); 1368 | int gen = quick_sqrt(x*x + y*y); 1369 | x = water_radius * x / gen; // 动画起始中心点横坐标 反向 1370 | y = water_radius * y / gen; // 动画起始中心点纵坐标 反向 1371 | 1372 | rect = QRect( 1373 | rect.left() - x * (100-pro) / 100 + rect.width() * (100-pro) / 100, 1374 | rect.top() - y * (100-pro) / 100 + rect.height() * (100-pro) / 100, 1375 | rect.width() * pro / 100, 1376 | rect.height() * pro / 100 1377 | ); 1378 | 1379 | } 1380 | else if (align == Qt::AlignCenter && model != PaintModel::Text && !fixed_fore_size) // 默认的缩放动画 1381 | { 1382 | int delta_x = 0, delta_y = 0; 1383 | if (click_ani_progress != 0) // 图标缩放 1384 | { 1385 | delta_x = rect.width() * click_ani_progress / 400; 1386 | delta_y = rect.height() * click_ani_progress / 400; 1387 | } 1388 | else if (show_ani_appearing) 1389 | { 1390 | /*int pro; // 将动画进度转换为回弹动画进度 1391 | if (show_ani_progress <= 50) 1392 | pro = show_ani_progress * 2; 1393 | else if (show_ani_progress <= 75) 1394 | pro = (show_ani_progress-50)/2 + 100; 1395 | else 1396 | pro = 100 + (100-show_ani_progress)/2; 1397 | 1398 | delta_x = rect.width() * (100-pro) / 100; 1399 | delta_y = rect.height() * (100-pro) / 100;*/ 1400 | 1401 | double pro = getNolinearProg(show_ani_progress, SpringBack50); 1402 | delta_x = static_cast(rect.width() * (1-pro)); 1403 | delta_y = static_cast(rect.height() * (1-pro)); 1404 | } 1405 | else if (show_ani_disappearing) 1406 | { 1407 | double pro = 1 - getNolinearProg(show_ani_progress, SlowFaster); 1408 | delta_x = rect.width() * pro; // (100-show_ani_progress) / 100; 1409 | delta_y = rect.height() * pro; // (100-show_ani_progress) / 100; 1410 | } 1411 | if (delta_x || delta_y) 1412 | rect = QRect(rect.left()+delta_x, rect.top()+delta_y, 1413 | rect.width()-delta_x*2, rect.height()-delta_y*2); 1414 | } 1415 | 1416 | /*if (this->isEnabled()) 1417 | { 1418 | QColor color = icon_color; 1419 | color.setAlpha(color.alpha() / 2); 1420 | painter.setPen(color); 1421 | }*/ 1422 | 1423 | if (model == None) 1424 | { 1425 | // 子类自己的绘制内容 1426 | } 1427 | else if (model == Text) 1428 | { 1429 | // 绘制文字教程: https://blog.csdn.net/temetnosce/article/details/78068464 1430 | painter.setPen(isEnabled()?text_color:getOpacityColor(text_color)); 1431 | /*if (show_ani_appearing || show_ani_disappearing) 1432 | { 1433 | int pro = getSpringBackProgress(show_ani_progress, 50); 1434 | QFont font = painter.font(); 1435 | int ps = font.pointSize(); 1436 | ps = ps * show_ani_progress / 100; 1437 | font.setPointSize(ps); 1438 | painter.setFont(font); 1439 | }*/ 1440 | if (font_size > 0) 1441 | { 1442 | QFont font = painter.font(); 1443 | font.setPointSize(font_size); 1444 | painter.setFont(font); 1445 | } 1446 | painter.drawText(rect, static_cast(align), text); 1447 | } 1448 | else if (model == Icon) // 绘制图标 1449 | { 1450 | icon.paint(&painter, rect, align, getIconMode()); 1451 | } 1452 | else if (model == PixmapMask) 1453 | { 1454 | painter.setRenderHint(QPainter::SmoothPixmapTransform, true); // 可以让边缘看起来平滑一些 1455 | painter.drawPixmap(rect, pixmap); 1456 | } 1457 | else if (model == IconText || model == PixmapText) // 强制左对齐;左图标中文字 1458 | { 1459 | // 绘制图标 1460 | int& sz = icon_text_size; 1461 | QRect icon_rect(rect.left(), rect.top() + rect.height()/2 - sz / 2, sz, sz); 1462 | icon_rect.moveTo(icon_rect.left() - quick_sqrt(offset_pos.x()), icon_rect.top() - quick_sqrt(offset_pos.y())); 1463 | drawIconBeforeText(painter, icon_rect); 1464 | rect.setLeft(rect.left() + sz + icon_text_padding); 1465 | 1466 | // 绘制文字 1467 | // 扩展文字范围,确保文字可见 1468 | painter.setPen(isEnabled()?text_color:getOpacityColor(text_color)); 1469 | rect.setWidth(rect.width() + sz + icon_text_padding); 1470 | if (font_size > 0) 1471 | { 1472 | QFont font = painter.font(); 1473 | font.setPointSize(font_size); 1474 | painter.setFont(font); 1475 | } 1476 | painter.drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, text); 1477 | } 1478 | } 1479 | 1480 | // ==== 绘制鼠标位置 ==== 1481 | // painter.drawEllipse(QRect(anchor_pos.x()-5, anchor_pos.y()-5, 10, 10)); // 移动锚点 1482 | // painter.drawEllipse(QRect(effect_pos.x()-2, effect_pos.y()-2, 4, 4)); // 影响位置锚点 1483 | 1484 | // return QPushButton::paintEvent(event); // 不绘制父类背景了 1485 | } 1486 | 1487 | /** 1488 | * IconText/PixmapText模式下,绘制图标 1489 | * 可扩展到绘制图标背景色(模仿menu选中、禁用情况)等 1490 | */ 1491 | void InteractiveButtonBase::drawIconBeforeText(QPainter& painter, QRect icon_rect) 1492 | { 1493 | if (model == IconText) 1494 | icon.paint(&painter, icon_rect, align, getIconMode()); 1495 | else if (model == PixmapText) 1496 | painter.drawPixmap(icon_rect, pixmap); 1497 | } 1498 | 1499 | /** 1500 | * 判断坐标是否在按钮区域内 1501 | * 避免失去了焦点,但是依旧需要 hover 效果(非菜单和弹窗抢走焦点) 1502 | * 为子类异形按钮区域判断提供支持 1503 | * @param point 当前鼠标 1504 | * @return 是否在区域内 1505 | */ 1506 | bool InteractiveButtonBase::inArea(QPoint point) 1507 | { 1508 | return !(point.x() < 0 || point.y() < 0 || point.x() > size().width() || point.y() > size().height()); 1509 | } 1510 | 1511 | /** 1512 | * 获取按钮背景的绘制区域 1513 | * 为子类异形按钮提供支持 1514 | * @return [description] 1515 | */ 1516 | QPainterPath InteractiveButtonBase::getBgPainterPath() 1517 | { 1518 | QPainterPath path; 1519 | if (radius_x || radius_y) 1520 | path.addRoundedRect(QRect(0,0,size().width(),size().height()), radius_x, radius_y); 1521 | else 1522 | path.addRect(QRect(0,0,size().width(),size().height())); 1523 | return path; 1524 | } 1525 | 1526 | /** 1527 | * 获取水波纹绘制区域(圆形,但不规则区域) 1528 | * 圆形水面 & 按钮区域 1529 | * @param water 一面水波纹动画对象 1530 | * @return 绘制路径 1531 | */ 1532 | QPainterPath InteractiveButtonBase::getWaterPainterPath(InteractiveButtonBase::Water water) 1533 | { 1534 | double prog = getNolinearProg(water.progress, FastSlower); 1535 | int ra = water_radius*prog; 1536 | QRect circle(water.point.x() - ra, 1537 | water.point.y() - ra, 1538 | ra*2, 1539 | ra*2); 1540 | /*QRect circle(water.point.x() - water_radius*water.progress/100, 1541 | water.point.y() - water_radius*water.progress/100, 1542 | water_radius*water.progress/50, 1543 | water_radius*water.progress/50);*/ 1544 | QPainterPath path; 1545 | path.addEllipse(circle); 1546 | if (radius_x || radius_y) 1547 | return path & getBgPainterPath(); 1548 | return path; 1549 | } 1550 | 1551 | /** 1552 | * 获取统一的尺寸大小(已废弃) 1553 | * 兼容圆形按钮出现动画,半径使用水波纹(对角线) 1554 | * 可直接使用 protected 对象 1555 | * @return 前景绘制区域 1556 | */ 1557 | QRect InteractiveButtonBase::getUnifiedGeometry() 1558 | { 1559 | // 将动画进度转换为回弹动画进度 1560 | int pro = show_ani_appearing ? getSpringBackProgress(show_ani_progress,50) : show_ani_progress; 1561 | int ul = 0, ut = 0, uw = size().width(), uh = size().height(); 1562 | 1563 | // show_ani_point 是鼠标进入的点,那么起始方向应该是相反的 1564 | int x = show_ani_point.x(), y = show_ani_point.y(); 1565 | int gen = quick_sqrt(x*x + y*y); 1566 | x = - water_radius * x / gen; // 动画起始中心点横坐标 反向 1567 | y = - water_radius * y / gen; // 动画起始中心点纵坐标 反向 1568 | 1569 | ul = ul + x * (100-pro) / 100 + uw * (100-pro) / 200; 1570 | ut = ut + y * (100-pro) / 100 + uh * (100-pro) / 200; 1571 | uw = uw * pro / 100; 1572 | uh = uh * pro / 100; 1573 | 1574 | return QRect(ul, ut, uw, uh); 1575 | } 1576 | 1577 | /** 1578 | * 更新统一绘制区域 1579 | * 内部的 _l, _t, _w, _h 可直接使用 1580 | */ 1581 | void InteractiveButtonBase::updateUnifiedGeometry() 1582 | { 1583 | _l = 0; _t = 0; _w = geometry().width(); _h = geometry().height(); 1584 | if ((show_ani_appearing || show_ani_disappearing) && show_ani_point != QPoint( 0, 0 )) 1585 | { 1586 | int pro; // 将动画进度转换为回弹动画进度 1587 | pro = show_ani_appearing ? getSpringBackProgress(show_ani_progress,50) : show_ani_progress; 1588 | 1589 | // show_ani_point 是鼠标进入的点,那么起始方向应该是相反的 1590 | int x = show_ani_point.x(), y = show_ani_point.y(); 1591 | int gen = quick_sqrt(x*x + y*y); 1592 | x = - water_radius * x / gen; // 动画起始中心点横坐标 反向 1593 | y = - water_radius * y / gen; // 动画起始中心点纵坐标 反向 1594 | 1595 | _l = _l + x * (100-pro) / 100 + _w * (100-pro) / 200; 1596 | _t = _t + y * (100-pro) / 100 + _h * (100-pro) / 200; 1597 | _w = _w * pro / 100; 1598 | _h = _h * pro / 100; 1599 | } 1600 | } 1601 | 1602 | /** 1603 | * 绘制一个水波纹动画 1604 | * @param painter 绘制对象(即painter(this)对象) 1605 | */ 1606 | void InteractiveButtonBase::paintWaterRipple(QPainter& painter) 1607 | { 1608 | QColor water_finished_color(press_bg); 1609 | 1610 | for (int i = 0; i < waters.size(); i++) 1611 | { 1612 | Water water = waters.at(i); 1613 | if (water.finished) // 渐变消失 1614 | { 1615 | water_finished_color.setAlpha(press_bg.alpha() * water.progress / 100); 1616 | QPainterPath path_back = getBgPainterPath(); 1617 | // painter.setPen(water_finished_color); 1618 | painter.fillPath(path_back, QBrush(water_finished_color)); 1619 | } 1620 | else // 圆形出现 1621 | { 1622 | QPainterPath path = getWaterPainterPath(water); 1623 | painter.fillPath(path, QBrush(press_bg)); 1624 | } 1625 | } 1626 | } 1627 | 1628 | /** 1629 | * 鼠标松开的时候,计算所有抖动效果的路径和事件 1630 | * 在每次重绘界面的时候,依次遍历所有的路径 1631 | */ 1632 | void InteractiveButtonBase::setJitter() 1633 | { 1634 | jitters.clear(); 1635 | QPoint center_pos = geometry().center()-geometry().topLeft(); 1636 | int full_manh = (anchor_pos-center_pos).manhattanLength(); // 距离 1637 | // 是否达到需要抖动的距离 1638 | if (full_manh > (geometry().topLeft() - geometry().bottomRight()).manhattanLength()) // 距离超过外接圆半径,开启抖动 1639 | { 1640 | QPoint jitter_pos(effect_pos); 1641 | full_manh = (jitter_pos-center_pos).manhattanLength(); 1642 | int manh = full_manh; 1643 | int duration = jitter_duration; 1644 | qint64 timestamp = release_timestamp; 1645 | while (manh > elastic_coefficient) 1646 | { 1647 | jitters << Jitter(jitter_pos, timestamp); 1648 | jitter_pos = center_pos - (jitter_pos - center_pos) / elastic_coefficient; 1649 | duration = jitter_duration * manh / full_manh; 1650 | timestamp += duration; 1651 | manh = static_cast(manh / elastic_coefficient); 1652 | } 1653 | jitters << Jitter(center_pos, timestamp); 1654 | anchor_pos = mouse_pos = center_pos; 1655 | } 1656 | else if (!hovering) // 悬浮的时候依旧有效 1657 | { 1658 | // 未达到抖动距离,直接恢复 1659 | mouse_pos = center_pos; 1660 | } 1661 | } 1662 | 1663 | /** 1664 | * 速度极快的开方算法,效率未知,原理未知 1665 | * @param X 待开方的数字 1666 | * @return 平方根 1667 | */ 1668 | int InteractiveButtonBase::quick_sqrt(long X) const 1669 | { 1670 | bool fu = false; 1671 | if (X < 0) 1672 | { 1673 | fu = true; 1674 | X = -X; 1675 | } 1676 | #if !defined(Q_OS_WIN) 1677 | X = qSqrt(X); 1678 | return fu ? -X : X; 1679 | #endif 1680 | unsigned long M = static_cast(X); 1681 | unsigned int N, i; 1682 | unsigned long tmp, ttp; // 结果、循环计数 1683 | if (M == 0) // 被开方数,开方结果也为0 1684 | return 0; 1685 | N = 0; 1686 | tmp = (M >> 30); // 获取最高位:B[m-1] 1687 | M <<= 2; 1688 | if (tmp > 1) // 最高位为1 1689 | { 1690 | N ++; // 结果当前位为1,否则为默认的0 1691 | tmp -= N; 1692 | } 1693 | for (i = 15; i > 0; i--) // 求剩余的15位 1694 | { 1695 | N <<= 1; // 左移一位 1696 | tmp <<= 2; 1697 | tmp += (M >> 30); // 假设 1698 | ttp = N; 1699 | ttp = (ttp << 1) + 1; 1700 | M <<= 2; 1701 | if (tmp >= ttp) // 假设成立 1702 | { 1703 | tmp -= ttp; 1704 | N ++; 1705 | } 1706 | } 1707 | return (fu ? -1 : 1) * static_cast(N); // 不知道为什么计算出来的结果是反过来的 1708 | } 1709 | 1710 | /** 1711 | * 最大值 1712 | */ 1713 | int InteractiveButtonBase::max(int a, int b) const { return a > b ? a : b; } 1714 | 1715 | /** 1716 | * 最小值 1717 | */ 1718 | int InteractiveButtonBase::min(int a, int b) const { return a < b ? a : b; } 1719 | 1720 | /** 1721 | * 获取现行时间戳,13位,精确到毫秒 1722 | * @return 时间戳 1723 | */ 1724 | qint64 InteractiveButtonBase::getTimestamp() const 1725 | { 1726 | return QDateTime::currentDateTime().toMSecsSinceEpoch(); 1727 | } 1728 | 1729 | /** 1730 | * 是否为亮色颜色 1731 | * @param color 颜色 1732 | * @return 是否为亮色 1733 | */ 1734 | bool InteractiveButtonBase::isLightColor(QColor color) 1735 | { 1736 | return color.red()*0.299 + color.green()*0.578 + color.blue()*0.114 >= 192; 1737 | } 1738 | 1739 | /** 1740 | * 获取非线性动画在某一时间比例的动画进度 1741 | * 仅适用于弹过头效果的动画 1742 | * @param x 实际相对完整100%的动画进度 1743 | * @param max 前半部分动画进度上限 1744 | * @return 应当显示的动画进度 1745 | */ 1746 | int InteractiveButtonBase::getSpringBackProgress(int x, int max) 1747 | { 1748 | if (x <= max) 1749 | return x * 100 / max; 1750 | if (x <= max + (100-max)/2) 1751 | return (x-max)/2+100; 1752 | return 100 + (100-x)/2; 1753 | } 1754 | 1755 | /** 1756 | * 获取透明的颜色 1757 | * @param color 颜色 1758 | * @param level 比例 1759 | * @return 透明颜色 1760 | */ 1761 | QColor InteractiveButtonBase::getOpacityColor(QColor color, double level) 1762 | { 1763 | color.setAlpha(static_cast(color.alpha() * level)); 1764 | return color; 1765 | } 1766 | 1767 | /** 1768 | * 获取对应颜色的图标 pixmap 1769 | * @param p 图标 1770 | * @param c 颜色 1771 | * @return 对应颜色的图标 1772 | */ 1773 | QPixmap InteractiveButtonBase::getMaskPixmap(QPixmap p, QColor c) 1774 | { 1775 | QBitmap mask = p.mask(); 1776 | p.fill(c); 1777 | p.setMask(mask); 1778 | return p; 1779 | } 1780 | 1781 | double InteractiveButtonBase::getNolinearProg(int p, InteractiveButtonBase::NolinearType type) 1782 | { 1783 | if (p <= 0) 1784 | return 0.0; 1785 | if (p >= 100) 1786 | return 1.0; 1787 | 1788 | switch (type) 1789 | { 1790 | case Linear: 1791 | return p / 100.0; 1792 | case SlowFaster: 1793 | return p * p / 10000.0; 1794 | case FastSlower : 1795 | return quick_sqrt(p*100) / 100.0; 1796 | case SlowFastSlower: 1797 | if (p <= 50) 1798 | return p * p / 50.0; 1799 | else 1800 | return 0.5 + quick_sqrt(50*(p-50))/100.0; 1801 | case SpringBack20: 1802 | case SpringBack50: 1803 | if (p <= 50) 1804 | return p / 50.0; 1805 | else if (p < 75) 1806 | return 1.0 + (p-50) / 200.0; 1807 | else 1808 | return 1.0 + (100-p) / 200.0; 1809 | } 1810 | } 1811 | 1812 | QIcon::Mode InteractiveButtonBase::getIconMode() 1813 | { 1814 | return isEnabled() ? (getState() ? QIcon::Selected : (hovering||pressing ? QIcon::Active : QIcon::Normal)) : QIcon::Disabled; 1815 | } 1816 | 1817 | /** 1818 | * 锚点变成到鼠标位置的定时时钟 1819 | * 同步计算所有和时间或者帧数有关的动画和属性 1820 | */ 1821 | void InteractiveButtonBase::anchorTimeOut() 1822 | { 1823 | qint64 timestamp = getTimestamp(); 1824 | // ==== 背景色 ==== 1825 | /*if (hovering) // 在框内:加深 1826 | { 1827 | if (hover_progress < 100) // 先判断,再计算,可节约运算资源 1828 | hover_progress = min((timestamp - hover_timestamp) * 100 / press_bg_duration, 100); 1829 | } 1830 | else // 在框外:变浅 1831 | { 1832 | if (hover_progress > 0) 1833 | hover_progress = max((timestamp - leave_timestamp) * 100 / press_bg_duration, 0); 1834 | } 1835 | 1836 | if (pressing) 1837 | { 1838 | if (press_progress < 100) 1839 | press_progress = min(press_start + (timestamp - press_timestamp) * 100 / press_bg_duration, 100); 1840 | } 1841 | else 1842 | { 1843 | if (press_progress > 0) // 如果按下的效果还在,变浅 1844 | press_progress = max((timestamp - release_timestamp) * 100 / press_bg_duration, 0); 1845 | }*/ 1846 | 1847 | if (pressing) // 鼠标按下 1848 | { 1849 | if (press_progress < 100) // 透明渐变,且没有完成 1850 | { 1851 | press_progress += press_speed; 1852 | if (press_progress >= 100) 1853 | { 1854 | press_progress = 100; 1855 | if (mouse_press_event) 1856 | { 1857 | emit signalMousePressLater(mouse_press_event); 1858 | mouse_press_event = nullptr; 1859 | } 1860 | } 1861 | } 1862 | if (hovering && hover_progress < 100) 1863 | { 1864 | hover_progress += hover_speed; 1865 | if (hover_progress >= 100) 1866 | { 1867 | hover_progress = 100; 1868 | emit signalMouseEnterLater(); 1869 | } 1870 | } 1871 | } 1872 | else // 鼠标悬浮 1873 | { 1874 | if (press_progress>0) // 如果按下的效果还在,变浅 1875 | { 1876 | press_progress -= press_speed; 1877 | if (press_progress <= 0) 1878 | { 1879 | press_progress = 0; 1880 | if (mouse_release_event) 1881 | { 1882 | emit signalMouseReleaseLater(mouse_release_event); 1883 | mouse_release_event = nullptr; 1884 | } 1885 | } 1886 | } 1887 | 1888 | if (hovering) // 在框内:加深 1889 | { 1890 | if (hover_progress < 100) 1891 | { 1892 | hover_progress += hover_speed; 1893 | if (hover_progress >= 100) 1894 | { 1895 | hover_progress = 100; 1896 | emit signalMouseEnterLater(); 1897 | } 1898 | } 1899 | } 1900 | else // 在框外:变浅 1901 | { 1902 | if (hover_progress > 0) 1903 | { 1904 | hover_progress -= hover_speed; 1905 | if (hover_progress <= 0) 1906 | { 1907 | hover_progress = 0; 1908 | emit signalMouseLeaveLater(); 1909 | } 1910 | } 1911 | } 1912 | } 1913 | 1914 | // ==== 按下背景水波纹动画 ==== 1915 | if (water_animation) 1916 | { 1917 | for (int i = 0; i < waters.size(); i++) 1918 | { 1919 | Water& water = waters[i]; 1920 | if (water.finished) // 结束状态 1921 | { 1922 | water.progress = static_cast(100 - 100 * (timestamp-water.finish_timestamp) / water_finish_duration); 1923 | if (water.progress <= 0) 1924 | { 1925 | waters.removeAt(i--); 1926 | if (mouse_release_event) // 还没有发送按下延迟信号 1927 | { 1928 | emit signalMouseReleaseLater(mouse_release_event); 1929 | mouse_release_event = nullptr; 1930 | } 1931 | } 1932 | } 1933 | else // 正在出现状态 1934 | { 1935 | if (water.progress >= 100) // 满了 1936 | { 1937 | water.progress = 100; 1938 | if (water.release_timestamp) // 鼠标已经松开了 1939 | { 1940 | water.finished = true; // 准备结束 1941 | water.finish_timestamp = timestamp; 1942 | } 1943 | } 1944 | else // 动画中的 1945 | { 1946 | if (water.release_timestamp) // 鼠标已经松开了 1947 | { 1948 | water.progress = static_cast(100 * (water.release_timestamp - water.press_timestamp) / water_press_duration 1949 | + 100 * (timestamp - water.release_timestamp) / water_release_duration); 1950 | } 1951 | else // 鼠标一直按下 1952 | { 1953 | water.progress = static_cast(100 * (timestamp - water.press_timestamp) / water_press_duration); 1954 | } 1955 | if (water.progress >= 100) 1956 | { 1957 | water.progress = 100; 1958 | if (mouse_press_event) // 还没有发送按下延迟信号 1959 | { 1960 | emit signalMousePressLater(mouse_press_event); 1961 | mouse_press_event = nullptr; 1962 | } 1963 | } 1964 | } 1965 | } 1966 | } 1967 | } 1968 | 1969 | // ==== 出现动画 ==== 1970 | if (show_animation) 1971 | { 1972 | if (show_ani_appearing) // 出现 1973 | { 1974 | qint64 delta = getTimestamp() - show_timestamp; 1975 | if (show_ani_progress >= 100) // 出现结束 1976 | { 1977 | show_ani_appearing = false; 1978 | emit showAniFinished(); 1979 | } 1980 | else 1981 | { 1982 | show_ani_progress = static_cast(100 * delta / show_duration); 1983 | if (show_ani_progress > 100) 1984 | show_ani_progress = 100; 1985 | } 1986 | } 1987 | if (show_ani_disappearing) // 消失 1988 | { 1989 | qint64 delta = getTimestamp() - hide_timestamp; 1990 | if (show_ani_progress <= 0) // 消失结束 1991 | { 1992 | show_ani_disappearing = false; 1993 | show_foreground = false; 1994 | show_ani_point = QPoint(0,0); 1995 | emit hideAniFinished(); 1996 | } 1997 | else 1998 | { 1999 | show_ani_progress = static_cast(100 - 100 * delta / show_duration); 2000 | if (show_ani_progress < 0) 2001 | show_ani_progress = 0; 2002 | } 2003 | } 2004 | } 2005 | 2006 | // ==== 按下动画 ==== 2007 | if (click_ani_disappearing) // 点击动画效果消失 2008 | { 2009 | qint64 delta = getTimestamp()-release_timestamp-click_ani_duration; 2010 | if (delta <= 0) click_ani_progress = 100; 2011 | else click_ani_progress = static_cast(100 - delta*100 / click_ani_duration); 2012 | if (click_ani_progress < 0) 2013 | { 2014 | click_ani_progress = 0; 2015 | click_ani_disappearing = false; 2016 | emit pressAppearAniFinished(); 2017 | } 2018 | } 2019 | if (click_ani_appearing) // 点击动画效果 2020 | { 2021 | qint64 delta = getTimestamp()-release_timestamp; 2022 | if (delta <= 0) click_ani_progress = 0; 2023 | else click_ani_progress = static_cast(delta * 100 / click_ani_duration); 2024 | if (click_ani_progress > 100) 2025 | { 2026 | click_ani_progress = 100; // 保持100的状态,下次点击时回到0 2027 | click_ani_appearing = false; 2028 | click_ani_disappearing = true; 2029 | emit pressDisappearAniFinished(); 2030 | } 2031 | } 2032 | 2033 | // ==== 锚点移动 ==== 2034 | if (jitters.size() > 0) // 松开时的抖动效果 2035 | { 2036 | // 当前应该是处在最后一个点 2037 | Jitter cur = jitters.first(); 2038 | Jitter aim = jitters.at(1); 2039 | int del = static_cast(getTimestamp()-cur.timestamp); 2040 | int dur = static_cast(aim.timestamp - cur.timestamp); 2041 | effect_pos = cur.point + (aim.point-cur.point)*del/dur; 2042 | offset_pos = effect_pos- (geometry().center() - geometry().topLeft()); 2043 | 2044 | if (del >= dur) 2045 | jitters.removeFirst(); 2046 | 2047 | // 抖动结束 2048 | if (jitters.size() == 1) 2049 | { 2050 | jitters.clear(); 2051 | emit jitterAniFinished(); 2052 | } 2053 | } 2054 | else if (anchor_pos != mouse_pos) // 移动效果 2055 | { 2056 | int delta_x = anchor_pos.x() - mouse_pos.x(), 2057 | delta_y = anchor_pos.y() - mouse_pos.y(); 2058 | 2059 | anchor_pos.setX( anchor_pos.x() - quick_sqrt(delta_x) ); 2060 | anchor_pos.setY( anchor_pos.y() - quick_sqrt(delta_y) ); 2061 | 2062 | offset_pos.setX(quick_sqrt(static_cast(anchor_pos.x()-(geometry().width()>>1)))); 2063 | offset_pos.setY(quick_sqrt(static_cast(anchor_pos.y()-(geometry().height()>>1)))); 2064 | effect_pos.setX( (geometry().width() >>1) + offset_pos.x()); 2065 | effect_pos.setY( (geometry().height()>>1) + offset_pos.y()); 2066 | } 2067 | else if (!pressing && !hovering && !hover_progress && !press_progress 2068 | && !click_ani_appearing && !click_ani_disappearing && !jitters.size() && !waters.size() 2069 | && !show_ani_appearing && !show_ani_disappearing) // 没有需要加载的项,暂停(节约资源) 2070 | { 2071 | anchor_timer->stop(); 2072 | } 2073 | 2074 | // ==== 统一坐标的出现动画 ==== 2075 | if (unified_geometry) 2076 | { 2077 | updateUnifiedGeometry(); 2078 | } 2079 | 2080 | update(); 2081 | } 2082 | 2083 | /** 2084 | * 鼠标单击事件 2085 | * 实测按下后,在按钮区域弹起,不管移动多少距离都算是 clicked 2086 | */ 2087 | void InteractiveButtonBase::slotClicked() 2088 | { 2089 | click_ani_appearing = true; 2090 | click_ani_disappearing = false; 2091 | click_ani_progress = 0; 2092 | release_offset = offset_pos; 2093 | 2094 | jitters.clear(); // 清除抖动 2095 | } 2096 | 2097 | /** 2098 | * 强行关闭状态 2099 | * 以槽的形式,便与利用 2100 | */ 2101 | void InteractiveButtonBase::slotCloseState() 2102 | { 2103 | setState(false); 2104 | } 2105 | -------------------------------------------------------------------------------- /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 500 // /* 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.09.09 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(QPoint p, qint64 t) : point(p), timestamp(t) {} 109 | QPoint point; // 要运动到的目标坐标 110 | qint64 timestamp; // 运动到目标坐标应该的时间戳,结束后删除本次抖动路径对象 111 | }; 112 | 113 | /** 114 | * 鼠标按下/弹起水波纹动画 115 | * 鼠标按下时动画速度慢(压住),松开后动画速度骤然加快 116 | * 同样用队列记录所有的水波纹动画实体 117 | */ 118 | struct Water 119 | { 120 | Water(QPoint p, qint64 t) : point(p), progress(0), press_timestamp(t), 121 | release_timestamp(0), finish_timestamp(0), finished(false) {} 122 | QPoint 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 setState(bool s = true); 209 | bool getState(); 210 | virtual void simulateStatePress(bool s = true, bool a = false); 211 | bool isHovering() { return hovering; } 212 | bool isPressing() { return pressing; } 213 | void simulateHover(); 214 | void discardHoverPress(bool force = false); 215 | 216 | bool getSelfEnabled() { return self_enabled; } 217 | bool getParentEnabled() { return parent_enabled; } 218 | bool getForeEnabled() { return fore_enabled; } 219 | QColor getIconColor() { return icon_color; } 220 | QColor getTextColor() { return text_color; } 221 | QColor getNormalColor() { return normal_bg; } 222 | QColor getBorderColor() { return border_bg; } 223 | QColor getHoverColor() { return hover_bg; } 224 | QColor getPressColor() { return press_bg; } 225 | QString getIconPath() { return ""; } 226 | QString getPixmapPath() { return ""; } 227 | int getHoverAniDuration() { return hover_bg_duration; } 228 | int getPressAniDuration() { return press_bg_duration; } 229 | int getClickAniDuration() { return click_ani_duration; } 230 | double getIconPaddingProper() { return icon_padding_proper; } 231 | int getRadius() { return qMax(radius_x, radius_y); } 232 | int getBorderWidth() { return border_width; } 233 | bool getFixedTextPos() { return fixed_fore_pos; } 234 | bool getTextDynamicSize() { return text_dynamic_size; } 235 | bool getLeaveAfterClick() { return leave_after_clicked; } 236 | bool getShowAni() { return show_animation; } 237 | bool getWaterRipple() { return water_animation; } 238 | 239 | #if QT_DEPRECATED_SINCE(5, 11) 240 | QT_DEPRECATED_X("Use InteractiveButtonBase::setFixedForePos(bool fixed = true)") 241 | void setFixedTextPos(bool f = true); 242 | #endif 243 | 244 | protected: 245 | void enterEvent(QEvent *event) override; 246 | void leaveEvent(QEvent *event) override; 247 | void mousePressEvent(QMouseEvent *event) override; 248 | void mouseReleaseEvent(QMouseEvent *event) override; 249 | void mouseMoveEvent(QMouseEvent *event) override; 250 | void resizeEvent(QResizeEvent *event) override; 251 | void focusInEvent(QFocusEvent *event) override; 252 | void focusOutEvent(QFocusEvent *event) override; 253 | void changeEvent(QEvent *event) override; 254 | void paintEvent(QPaintEvent *event) override; 255 | 256 | virtual bool inArea(QPoint point); 257 | virtual QPainterPath getBgPainterPath(); 258 | virtual QPainterPath getWaterPainterPath(Water water); 259 | virtual void drawIconBeforeText(QPainter &painter, QRect icon_rect); 260 | 261 | QRect getUnifiedGeometry(); 262 | void updateUnifiedGeometry(); 263 | void paintWaterRipple(QPainter &painter); 264 | void setJitter(); 265 | 266 | int getFontSizeT(); 267 | void setFontSizeT(int f); 268 | 269 | int max(int a, int b) const; 270 | int min(int a, int b) const; 271 | int quick_sqrt(long X) const; 272 | qint64 getTimestamp() const; 273 | bool isLightColor(QColor color); 274 | int getSpringBackProgress(int x, int max); 275 | QColor getOpacityColor(QColor color, double level = 0.5); 276 | QPixmap getMaskPixmap(QPixmap p, QColor c); 277 | 278 | double getNolinearProg(int p, NolinearType type); 279 | QIcon::Mode getIconMode(); 280 | 281 | signals: 282 | void showAniFinished(); 283 | void hideAniFinished(); 284 | void pressAppearAniFinished(); 285 | void pressDisappearAniFinished(); 286 | void jitterAniFinished(); 287 | void doubleClicked(); 288 | void rightClicked(); 289 | void signalFocusIn(); 290 | void signalFocusOut(); 291 | 292 | void signalMouseEnter(); 293 | void signalMouseEnterLater(); // 进入后延迟信号(以渐变动画完成为准,相当于可手动设置) 294 | void signalMouseLeave(); 295 | void signalMouseLeaveLater(); // 离开后延迟的信号(直至渐变动画完成(要是划过一下子离开,这个也会变快)) 296 | void signalMousePress(QMouseEvent* event); 297 | void signalMousePressLater(QMouseEvent* event); 298 | void signalMouseRelease(QMouseEvent* event); 299 | void signalMouseReleaseLater(QMouseEvent* event); 300 | 301 | public slots: 302 | virtual void anchorTimeOut(); 303 | virtual void slotClicked(); 304 | void slotCloseState(); 305 | 306 | protected: 307 | PaintModel model; 308 | QIcon icon; 309 | QString text; 310 | QPixmap pixmap; 311 | PaintAddin paint_addin; 312 | EdgeVal fore_paddings; 313 | 314 | protected: 315 | // 总体开关 316 | bool self_enabled, parent_enabled, fore_enabled; // 是否启用子类、启动父类、绘制子类前景 317 | 318 | // 出现前景的动画 319 | bool show_animation, show_foreground; 320 | bool show_ani_appearing, show_ani_disappearing; 321 | int show_duration; 322 | qint64 show_timestamp, hide_timestamp; 323 | int show_ani_progress; 324 | QPoint show_ani_point; 325 | QRect paint_rect; 326 | 327 | // 鼠标开始悬浮、按下、松开、离开的坐标和时间戳 328 | // 鼠标锚点、目标锚点、当前锚点的坐标;当前XY的偏移量 329 | QPoint enter_pos, press_pos, release_pos, mouse_pos, anchor_pos /*目标锚点渐渐靠近鼠标*/; 330 | QPoint offset_pos /*当前偏移量*/, effect_pos, release_offset; // 相对中心、相对左上角、弹起时的平方根偏移 331 | bool hovering, pressing; // 是否悬浮和按下的状态机 332 | qint64 hover_timestamp, leave_timestamp, press_timestamp, release_timestamp; // 各种事件的时间戳 333 | int hover_bg_duration, press_bg_duration, click_ani_duration; // 各种动画时长 334 | 335 | // 定时刷新界面(保证动画持续) 336 | QTimer *anchor_timer; 337 | int move_speed; 338 | 339 | // 背景与前景 340 | QColor icon_color, text_color; // 前景颜色 341 | QColor normal_bg, hover_bg, press_bg, border_bg; // 各种背景颜色 342 | QColor focus_bg, focus_border; // 有焦点的颜色 343 | int hover_speed, press_start, press_speed; // 颜色渐变速度 344 | int hover_progress, press_progress; // 颜色渐变进度 345 | double icon_padding_proper; // 图标的大小比例 346 | int icon_text_padding, icon_text_size; // 图标+文字模式共存时,两者间隔、图标大小 347 | int border_width; 348 | int radius_x, radius_y; 349 | int font_size; 350 | bool fixed_fore_pos; // 鼠标进入时是否固定文字位置 351 | bool fixed_fore_size; // 鼠标进入/点击时是否固定前景大小 352 | bool text_dynamic_size; // 设置字体时自动调整最小宽高 353 | bool auto_text_color; // 动画时是否自动调整文字颜色 354 | bool focusing; // 是否获得了焦点 355 | 356 | // 鼠标单击动画 357 | bool click_ani_appearing, click_ani_disappearing; // 是否正在按下的动画效果中 358 | int click_ani_progress; // 按下的进度(使用时间差计算) 359 | QMouseEvent *mouse_press_event, *mouse_release_event; 360 | 361 | // 统一绘制图标的区域(从整个按钮变为中心三分之二,并且根据偏移计算) 362 | bool unified_geometry; // 上面用不到的话,这个也用不到…… 363 | int _l, _t, _w, _h; 364 | 365 | // 鼠标拖拽弹起来回抖动效果 366 | bool jitter_animation; // 是否开启鼠标松开时的抖动效果 367 | double elastic_coefficient; // 弹性系数 368 | QList jitters; 369 | int jitter_duration; // 抖动一次,多次效果叠加 370 | 371 | // 鼠标按下水波纹动画效果 372 | bool water_animation; // 是否开启水波纹动画 373 | QList waters; 374 | int water_press_duration, water_release_duration, water_finish_duration; 375 | int water_radius; 376 | 377 | // 其他效果 378 | Qt::Alignment align; // 文字/图标对其方向 379 | bool _state; // 一个记录状态的变量,比如是否持续 380 | bool leave_after_clicked; // 鼠标单击松开后取消悬浮效果(针对菜单、弹窗),按钮必定失去焦点 381 | bool _block_hover; // 如果有出现动画,临时屏蔽hovering效果 382 | 383 | // 双击 384 | bool double_clicked; // 开启双击 385 | QTimer *double_timer; // 双击时钟 386 | bool double_prevent; // 双击阻止单击release的flag 387 | }; 388 | 389 | #endif // INTERACTIVEBUTTONBASE_H 390 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | MainWindow w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | MainWindow::MainWindow(QWidget *parent) 5 | : QMainWindow(parent) 6 | , ui(new Ui::MainWindow) 7 | { 8 | ui->setupUi(this); 9 | 10 | ui->widget->addImage(QPixmap(":/test/images/img1.jpg")); 11 | ui->widget->addImage(QPixmap(":/test/images/img2.jpg")); 12 | ui->widget->addImage(QPixmap(":/test/images/img3.jpg")); 13 | ui->widget->addImage(QPixmap(":/test/images/img4.jpg")); 14 | ui->widget->addImage(QPixmap(":/test/images/img5.jpg")); 15 | ui->widget->setCurrentIndex(0); 16 | ui->widget->adjustSize(); 17 | ui->widget->setAutoSlide(5000); 18 | 19 | connect(ui->widget, &SlideShow::signalImageClicked, this, [=](int index){ 20 | qDebug() << "单击:" << index; 21 | }); 22 | } 23 | 24 | MainWindow::~MainWindow() 25 | { 26 | delete ui; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | QT_BEGIN_NAMESPACE 7 | namespace Ui { class MainWindow; } 8 | QT_END_NAMESPACE 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | MainWindow(QWidget *parent = nullptr); 16 | ~MainWindow(); 17 | 18 | private: 19 | Ui::MainWindow *ui; 20 | }; 21 | #endif // MAINWINDOW_H 22 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 480 10 | 300 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Qt::Vertical 25 | 26 | 27 | 28 | 20 29 | 40 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 0 40 | 0 41 | 480 42 | 21 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | SlideShow 51 | QWidget 52 |
slideshow.h
53 | 1 54 |
55 |
56 | 57 | 58 |
59 | -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | images/img1.jpg 4 | images/img2.jpg 5 | images/img3.jpg 6 | images/img4.jpg 7 | images/img5.jpg 8 | 9 | 10 | -------------------------------------------------------------------------------- /slide_show/sidehidelabel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "sidehidelabel.h" 4 | 5 | SideHideLabel::SideHideLabel(QWidget *parent) : QLabel(parent) 6 | { 7 | } 8 | 9 | SideHideLabel *SideHideLabel::copy(const SideHideLabel *other) 10 | { 11 | SideHideLabel* label = new SideHideLabel(other->parentWidget()); 12 | label->setGeometry(other->geometry()); 13 | label->sideOffset = other->sideOffset; 14 | label->startX = other->startX; 15 | label->radius = other->radius; 16 | label->pixmap = other->pixmap; 17 | return label; 18 | } 19 | 20 | void SideHideLabel::setPixmap(const QPixmap &pixmap, double sideMaxOffset) 21 | { 22 | // QLabel::setPixmap(pixmap); 23 | this->pixmap = pixmap; 24 | this->startX = -sideMaxOffset; // 单边的长度 25 | } 26 | 27 | double SideHideLabel::getMaxOffset() const 28 | { 29 | return -startX; 30 | } 31 | 32 | void SideHideLabel::paintEvent(QPaintEvent *e) 33 | { 34 | // QLabel::paintEvent(e); 35 | // return ; 36 | 37 | QPainter painter(this); 38 | painter.setRenderHint(QPainter::Antialiasing); 39 | painter.setRenderHint(QPainter::SmoothPixmapTransform); 40 | QPainterPath path; 41 | path.addRoundedRect(rect(), radius, radius); 42 | painter.setClipPath(path); 43 | 44 | QPixmap newPixmap = pixmap.scaledToHeight(this->height(), Qt::FastTransformation); 45 | 46 | painter.drawPixmap(int(startX + sideOffset), 0, 47 | newPixmap.width(), newPixmap.height(), 48 | newPixmap); 49 | } 50 | 51 | void SideHideLabel::setSideOffset(double of) 52 | { 53 | this->sideOffset = of; 54 | update(); 55 | } 56 | 57 | double SideHideLabel::getSideOffset() const 58 | { 59 | return sideOffset; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /slide_show/sidehidelabel.h: -------------------------------------------------------------------------------- 1 | #ifndef SIDEHIDELABEL_H 2 | #define SIDEHIDELABEL_H 3 | 4 | #include 5 | #include 6 | 7 | class SideHideLabel : public QLabel 8 | { 9 | Q_OBJECT 10 | Q_PROPERTY(double sideOffset READ getSideOffset WRITE setSideOffset) 11 | public: 12 | SideHideLabel(QWidget* parent = nullptr); 13 | 14 | static SideHideLabel* copy(const SideHideLabel* other); 15 | 16 | void setPixmap(const QPixmap& pixmap, double sideMaxOffset); 17 | double getMaxOffset() const; 18 | 19 | protected: 20 | void paintEvent(QPaintEvent *e) override; 21 | 22 | public: 23 | void setSideOffset(double of); 24 | double getSideOffset() const; 25 | 26 | QPixmap getScaledRoundedPixmap(QPixmap pixmap) const; 27 | 28 | private: 29 | double sideOffset = 0; 30 | double startX = 0; 31 | int radius = 10; 32 | QPixmap pixmap; 33 | }; 34 | 35 | #endif // SIDEHIDELABEL_H 36 | -------------------------------------------------------------------------------- /slide_show/slideshow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "slideshow.h" 3 | 4 | SlideShow::SlideShow(QWidget *parent) : QWidget(parent) 5 | { 6 | setPixmapSize(QSize(400, 150)); 7 | 8 | indicationLayout = new QHBoxLayout; 9 | QVBoxLayout* mainLayout = new QVBoxLayout(this); 10 | mainLayout->addStretch(1); 11 | mainLayout->addLayout(indicationLayout); 12 | indicationLayout->setAlignment(Qt::AlignCenter); 13 | 14 | autoSlideTimer = new QTimer(this); 15 | autoSlideTimer->setInterval(5000); 16 | connect(autoSlideTimer, &QTimer::timeout, this, &SlideShow::slideToRight); 17 | } 18 | 19 | void SlideShow::setPixmapSize(QSize size) 20 | { 21 | this->oneSize = size; 22 | this->setMinimumSize(int(oneSize.width() * (3 - backScale*2) + SHADOW_RADIUS), 23 | oneSize.height() + SHADOW_RADIUS); 24 | for (int i = 0; i < labels.size(); i++) 25 | { 26 | labels.at(i)->setPixmap(getScaledRoundedPixmap(pixmaps.at(i)), oneSize.width() * imgOffside); 27 | labels.at(i)->setMinimumSize(1, 1); 28 | labels.at(i)->setMaximumSize(oneSize); 29 | labels.at(i)->resize(oneSize); 30 | } 31 | } 32 | 33 | void SlideShow::setPixmapScale(bool scale) 34 | { 35 | this->scalePixmap = scale; 36 | for (int i = 0; i < labels.size(); i++) 37 | labels.at(i)->setPixmap(getScaledRoundedPixmap(pixmaps.at(i)), oneSize.width() * imgOffside); 38 | } 39 | 40 | void SlideShow::setAutoSlide(int interval) 41 | { 42 | if (interval) 43 | { 44 | autoSlideTimer->setInterval(interval); 45 | autoSlideTimer->start(); 46 | } 47 | else 48 | { 49 | autoSlideTimer->stop(); 50 | } 51 | } 52 | 53 | void SlideShow::addImage(const QPixmap &pixmap, QString text) 54 | { 55 | insertImage(labels.size(), pixmap, text); 56 | } 57 | 58 | void SlideShow::insertImage(int index, const QPixmap &pixmap, QString text) 59 | { 60 | // 图片 61 | SideHideLabel* label = new SideHideLabel(this); 62 | label->setScaledContents(true); 63 | labels.insert(index, label); 64 | texts.insert(index, text); 65 | 66 | pixmaps.insert(index, pixmap); // 添加原图 67 | label->setPixmap(getScaledRoundedPixmap(pixmap), oneSize.width() * imgOffside); 68 | label->setMinimumSize(1, 1); 69 | label->resize(oneSize); 70 | CREATE_SHADOW(label); 71 | label->show(); 72 | label->installEventFilter(this); 73 | 74 | // 指示球 75 | InteractiveButtonBase* btn = new InteractiveButtonBase(this); 76 | btn->setFixedSize(8, 8); 77 | btn->setRadius(4); 78 | btn->setNormalColor(normalColor); 79 | btn->setHoverColor(selectColor); 80 | indications.insert(index, btn); 81 | indicationLayout->insertWidget(index, btn); 82 | 83 | connect(btn, &InteractiveButtonBase::signalMouseEnter, this, [=]{ 84 | int index = indications.indexOf(btn); 85 | setCurrentIndex(index); 86 | }); 87 | 88 | for (int i = 0; i < indications.size(); i++) 89 | indications.at(i)->raise(); 90 | 91 | if (currentIndex > index) 92 | { 93 | currentIndex++; 94 | setCurrentIndex(currentIndex); 95 | } 96 | } 97 | 98 | void SlideShow::removeImage(int index) 99 | { 100 | labels.takeAt(index)->deleteLater(); 101 | pixmaps.removeAt(index); 102 | texts.removeAt(index); 103 | indications.takeAt(index)->deleteLater(); 104 | 105 | // 加了阴影,如果没有图片从而没有动画的话,阴影会残留下来 106 | if (!labels.size()) 107 | update(); 108 | else 109 | { 110 | if (currentIndex > index) 111 | currentIndex--; 112 | setCurrentIndex(currentIndex); 113 | } 114 | } 115 | 116 | void SlideShow::setCurrentIndex(int index) 117 | { 118 | int count = labels.size(); 119 | if (index >= count) 120 | index = count-1; 121 | if (index < 0) 122 | index = 0; 123 | if (index >= count) 124 | return ; 125 | if (autoSlideTimer->isActive()) 126 | autoSlideTimer->start(); 127 | 128 | bool leftToRight = currentIndex < index; 129 | if (currentIndex > 0 && currentIndex < count) 130 | indications.at(currentIndex)->setNormalColor(normalColor); 131 | 132 | // 设置即将离开的label 133 | SideHideLabel* leavingLabel = nullptr; 134 | if (currentIndex >= 0 && currentIndex < count) 135 | { 136 | leavingLabel = labels.at(currentIndex); 137 | } 138 | 139 | // 开始切换 140 | currentIndex = index; 141 | adjustLabels(leavingLabel); 142 | 143 | // raise界面 144 | if (leftToRight) // 从左到右,左边在上 145 | labels.at((index + 1) % count)->raise(); 146 | labels.at((index + count - 1) % count)->raise(); 147 | if (!leftToRight) // 从右到左,右边在上 148 | labels.at((index + 1) % count)->raise(); 149 | labels.at(index)->raise(); 150 | if (hidingLabel) 151 | hidingLabel->raise(); 152 | 153 | for (int i = 0; i < indications.size(); i++) 154 | { 155 | indications.at(i)->raise(); 156 | indications.at(i)->setNormalColor(normalColor); 157 | } 158 | indications.at(index)->setNormalColor(selectColor); 159 | } 160 | 161 | int SlideShow::getCurrentIndex() const 162 | { 163 | return currentIndex; 164 | } 165 | 166 | void SlideShow::slideToLeft() 167 | { 168 | if (!labels.size()) 169 | return ; 170 | setCurrentIndex((currentIndex + labels.size() - 1) % labels.size()); 171 | } 172 | 173 | void SlideShow::slideToRight() 174 | { 175 | if (!labels.size()) 176 | return ; 177 | setCurrentIndex((currentIndex + 1) % labels.size()); 178 | } 179 | 180 | QPixmap SlideShow::getScaledRoundedPixmap(QPixmap pixmap) const 181 | { 182 | // 缩放 183 | int needWidth = int(oneSize.width() * (1 + imgOffside * 2)); 184 | pixmap = pixmap.scaled(needWidth, oneSize.height(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); 185 | 186 | // 裁剪 187 | if (/* DISABLES CODE */ (true) || !scalePixmap) 188 | { 189 | if (pixmap.width() > needWidth) 190 | { 191 | pixmap = pixmap.copy(pixmap.width()/2 - needWidth/2, 0, 192 | needWidth, oneSize.height()); 193 | } 194 | else if (pixmap.height() > oneSize.height()) 195 | { 196 | pixmap = pixmap.copy(0, pixmap.height()/2 - oneSize.height()/2, 197 | needWidth, oneSize.height()); 198 | } 199 | } 200 | 201 | return pixmap; 202 | 203 | // 圆角 204 | /* QPixmap dest(pixmap.size()); 205 | dest.fill(Qt::transparent); 206 | QPainter painter(&dest); 207 | painter.setRenderHint(QPainter::Antialiasing, true); 208 | painter.setRenderHint(QPainter::SmoothPixmapTransform, true); 209 | QRect rect = QRect(0, 0, pixmap.width(), pixmap.height()); 210 | QPainterPath path; 211 | path.addRoundedRect(rect, 10, 10); 212 | painter.setClipPath(path); 213 | painter.drawPixmap(rect, pixmap); 214 | return dest; */ 215 | } 216 | 217 | void SlideShow::adjustLabels(SideHideLabel *leavingLabel) 218 | { 219 | if (!labels.size()) 220 | return ; 221 | 222 | // 计算分布 223 | int sw = width(), sh = height(); 224 | int w = oneSize.width(), h = oneSize.height(); 225 | double scale = backScale; 226 | int marginTop = sh/2 - h / 2 - SHADOW_RADIUS/2; 227 | 228 | // 计算尺寸 229 | centerRect = QRect(sw/2 - w/2, marginTop, w, h); 230 | leftRect = QRect(int(sw/2 - w*(1-(1-scale)*sideOffside)*3/4), 231 | marginTop + int(h * (1 - scale) / 2), 232 | int(w*scale), 233 | int(h*scale)); 234 | rightRect = QRect(int(sw/2 + w*(1-(1-scale)*sideOffside)*3/4 - w*scale), 235 | marginTop + int(h * (1 - scale) / 2), 236 | int(w*scale), 237 | int(h*scale)); 238 | backRect = QRect(int(sw/2 - w*scale/2), 239 | marginTop + int(h * (1 - scale) / 2), 240 | int(w*scale), 241 | int(h*scale)); 242 | 243 | int count = labels.size(); 244 | for (int i = currentIndex; i < count + currentIndex; i++) 245 | { 246 | QRect rect = backRect; 247 | if (i == currentIndex) 248 | rect = centerRect; 249 | else if (i == currentIndex + 1) 250 | rect = rightRect; 251 | else if ((i + 1) % count == currentIndex) 252 | rect = leftRect; 253 | auto label = labels.at(i % count); 254 | moveTo(label, rect); 255 | 256 | // 是否同样进行一个透明度渐变的拷贝动作 257 | if (leavingLabel && label == leavingLabel) 258 | { 259 | hidingLabel = SideHideLabel::copy(leavingLabel); 260 | auto opaLabel = hidingLabel; 261 | 262 | moveTo(opaLabel, rect); 263 | opaLabel->show(); 264 | 265 | // 设置透明度 266 | QGraphicsOpacityEffect* effect = new QGraphicsOpacityEffect(opaLabel); 267 | effect->setOpacity(1); 268 | opaLabel->setGraphicsEffect(effect); 269 | 270 | // 透明度动画 271 | QPropertyAnimation* ani = new QPropertyAnimation(effect, "opacity"); 272 | ani->setStartValue(1); 273 | ani->setEndValue(0); 274 | ani->setEasingCurve(QEasingCurve::OutQuad); 275 | ani->setDuration(200); 276 | connect(ani, &QPropertyAnimation::finished, this, [=]{ 277 | if (hidingLabel == opaLabel) 278 | hidingLabel = nullptr; 279 | ani->deleteLater(); 280 | opaLabel->deleteLater(); 281 | effect->deleteLater(); 282 | }); 283 | ani->start(); 284 | } 285 | } 286 | } 287 | 288 | void SlideShow::moveTo(SideHideLabel *label, QRect geometry) 289 | { 290 | if (label->geometry() == geometry) 291 | return ; 292 | 293 | QPropertyAnimation* ani = new QPropertyAnimation(label, "geometry"); 294 | ani->setStartValue(label->geometry()); 295 | ani->setEndValue(geometry); 296 | ani->setDuration(300); 297 | ani->setEasingCurve(QEasingCurve::OutQuad); 298 | connect(ani, SIGNAL(finished()), ani, SLOT(deleteLater())); 299 | ani->start(); 300 | 301 | ani = new QPropertyAnimation(label, "sideOffset"); 302 | ani->setStartValue(label->getSideOffset()); 303 | double offset = 0; 304 | if (geometry.x() == leftRect.x()) 305 | offset = -label->getMaxOffset(); 306 | else if (geometry.x() == rightRect.x()) 307 | offset = label->getMaxOffset(); 308 | ani->setEndValue(offset); 309 | ani->setDuration(500); 310 | ani->setEasingCurve(QEasingCurve::OutQuad); 311 | connect(ani, SIGNAL(finished()), ani, SLOT(deleteLater())); 312 | ani->start(); 313 | } 314 | 315 | void SlideShow::resizeEvent(QResizeEvent *event) 316 | { 317 | QWidget::resizeEvent(event); 318 | 319 | // 调整已有图片的大小 320 | adjustLabels(); 321 | } 322 | 323 | bool SlideShow::eventFilter(QObject *obj, QEvent *event) 324 | { 325 | if(event->type() == QEvent::MouseButtonRelease) 326 | { 327 | if (labels.contains(static_cast(obj))) 328 | { 329 | // 图片被单击 330 | int index = labels.indexOf(static_cast(obj)); 331 | if (currentIndex == index) // 正面图片被单击 332 | { 333 | emit signalImageClicked(index); 334 | emit signalTextActivated(texts.at(index)); 335 | } 336 | else // 不是当前图片,可能是动画或者两侧的 337 | setCurrentIndex(index); 338 | } 339 | } 340 | 341 | return QWidget::eventFilter(obj, event); 342 | } 343 | 344 | -------------------------------------------------------------------------------- /slide_show/slideshow.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIDESHOW_H 2 | #define SLIDESHOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "interactive_buttons/interactivebuttonbase.h" 12 | #include "sidehidelabel.h" 13 | 14 | #define SHADOW_RADIUS 12 15 | 16 | #define CREATE_SHADOW(x) \ 17 | do { \ 18 | QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect(x); \ 19 | effect->setOffset(3, 3); \ 20 | effect->setBlurRadius(SHADOW_RADIUS); \ 21 | effect->setColor(QColor(64, 64, 64, 64)); \ 22 | x->setGraphicsEffect(effect); \ 23 | } while (0) 24 | 25 | class SlideShow : public QWidget 26 | { 27 | Q_OBJECT 28 | public: 29 | explicit SlideShow(QWidget *parent = nullptr); 30 | 31 | void setPixmapSize(QSize size); 32 | void setPixmapScale(bool scale); // 已废弃 33 | void setAutoSlide(int interval = 5000); // 设置自动轮播,interval=0时停止 34 | int getCurrentIndex() const; 35 | void adjustLabels(SideHideLabel* hidingLabel = nullptr); 36 | 37 | public slots: 38 | void addImage(const QPixmap& pixmap, QString text = ""); 39 | void insertImage(int index, const QPixmap& pixmap, QString text = ""); 40 | void removeImage(int index); 41 | 42 | void setCurrentIndex(int index); 43 | void slideToLeft(); 44 | void slideToRight(); 45 | 46 | private: 47 | QPixmap getScaledRoundedPixmap(QPixmap pixmap) const; 48 | void moveTo(SideHideLabel *label, QRect geometry); 49 | 50 | protected: 51 | void resizeEvent(QResizeEvent *event) override; 52 | bool eventFilter(QObject * obj, QEvent * event) override; 53 | 54 | signals: 55 | void signalImageClicked(int index); 56 | void signalTextActivated(QString text); 57 | 58 | private: 59 | QList labels; 60 | QList pixmaps; 61 | QList texts; 62 | QList indications; 63 | int currentIndex = -1; 64 | QTimer* autoSlideTimer; 65 | SideHideLabel* hidingLabel = nullptr; 66 | 67 | QColor normalColor = QColor(172, 128, 58); 68 | QColor selectColor = Qt::yellow; 69 | 70 | QSize oneSize; // 默认图片大小 71 | double backScale = 0.8; // 两侧卡片的缩放程度 72 | double sideOffside = 0.5; // 两侧卡片的位置偏移,越大越靠近中间 73 | double imgOffside = 0.15; // 图片内偏移比例 74 | bool scalePixmap = false; // 图片居中裁剪还是大小缩放(现在只能为false了) 75 | QHBoxLayout* indicationLayout; 76 | 77 | QRect centerRect, leftRect, rightRect, backRect; 78 | }; 79 | 80 | #endif // SLIDESHOW_H 81 | --------------------------------------------------------------------------------